Intel Software Guard Extensions (SGX): Re-encryption in PARSEC

INTRO

This article follows on from the one presenting SGX technology. We will look at the technical application of SGX for a proof of concept (POC) carried out on the Parsec application.

PARSEC IN AES

Parsec uses the PyNacl library (Python module based on Libsodium written in C) to encrypt information. As this library Libsodium is not officially compatible with SGX enclaves, due to the very limited number of instructions in an enclave, we had to modify the encryption part of Parsec. We decided to use AES with the Galois Counter Mode (GCM).

def encrypt(self, data):
        time_stamp = struct.pack(">Q", int(time.time()))
        iv = os.urandom(12)
        cipher = Cipher(algorithms.AES(self), modes.GCM(iv))
        encryptor = cipher.encryptor()
        ciphered = encryptor.update(data) + encryptor.finalize()
        tag = encryptor.tag
        version = b"\x80"
        token = version + time_stamp + iv + tag + ciphered
        return token
    def decrypt(self, token):
        version = token[0:1]
        time_stamp = token[1:9]
        iv = token[9:21]
        tag = token[21:37]
        ciphered = token[37:]
        if version != b"\x80":
            raise CryptoError("Invalid token")
        if time_stamp > struct.pack(">Q", int(time.time())):
            raise CryptoError("Invalid token")
        try:
            cipher = Cipher(algorithms.AES(self), modes.GCM(iv, tag))
            decryptor = cipher.decryptor()
            decrypted_message = decryptor.update(ciphered) + decryptor.finalize()
            return decrypted_message
        except (InvalidTag, ValueError) as exc:
            raise CryptoError(str(exc)) from exc

The encryption function above encrypts the data and creates a token composed of different information:

  • 1 byte: the version by convention \x80
  • 8 bytes: the time_stamp of encryption
  • 12 bytes: the vecteur d'initialisation(IV)
  • the tag used by AES and the encrypted message ciphered written in multiples of 128 bytes.

The purpose of the token is to facilitate retrieval of the various data for decryption from the enclave. Initially, data encryption and decryption are carried out locally, from the client, for performance reasons.

RECHIFFREMENT

During the re-encryption process, the encrypted blocks (in the form of token) are sent from Parsec’s backend to the SGX enclave, using SGX library functions. The old and new encryption keys are also sent to the enclave. To ensure that the old and new keys are not compromised, they are also encrypted from the client of the administrator who initiated the re-encryption. Only the enclave has the decryption key.

async def reencrypt_data(self, encrypted_old_key, encrypted_new_key, token: bytes):
        ecall_reencryptText = LibSgx.ecall_reencryptText
        ecall_reencryptText.restype = POINTER(c_char_p)
        token_len = len(token)
        status_code = c_int()
        reencrypted_token_ptr = ecall_reencryptText(
            byref(status_code), encrypted_old_key, len(encrypted_old_key), encrypted_new_key, len(encrypted_new_key), token, token_len
        )
        if status_code.value != SgxStatus.SGX_SUCCESS.value:
            print(
                "\nReencryption went wrong, status_code = ",
                SgxStatus(status_code.value).name,
                "\n\n",
            )
            return b""
        else:
            reencrypted_token = cast(reencrypted_token_ptr, POINTER(c_uint8 * token_len))
            reencrypted_token = bytes(list(reencrypted_token.contents))
            print("Reencryption SUCCESS")
            return reencrypted_token

To use the SGX library, we use the ctypeswhich enables calls to be made from Python to functions written in C. The status_code is returned directly from the function called from the library, to indicate any errors during re-encryption in the enclave.

SGX POC

SGX is divided into two parts:

  • The application part is converted into a dynamic library (.dll or .so) so that functions can be called from Python. As this library is executed fromOS, it can potentially be compromised. The functions used will be described as Untrusted.
  • The enclave part, which uses a private area of memory. We call these functions Trusted.

The application part enables calls to be made to enclave functions, via E-CALL.

The principle of data re-encryption with the enclave is therefore to transmit encrypted data and encryption/decryption keys via E-CALL. This makes it possible to perform re-encryption in the Trusted zone. It is also possible to call library functions from the enclave via methods called O-CALL, for example, to display text from the enclave. We had no use for this in POC.

Library

Below, we can see the library’s Untrusted function, which we use for data re-encryption. The function takes as arguments status_code, the old encrypted key, the new encrypted key and the encrypted token with their respective sizes.

uint8_t *ecall_reencryptText(int *status_code, const uint8_t *encrypted_old_key, size_t encrypted_old_key_len, const uint8_t *encrypted_new_key, size_t encrypted_new_key_len,  const uint8_t *input_token, size_t token_len)
{


    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

	/*Debug Variables*/
	char debug[50];
	uint8_t debug_size = 50;

    ret = reencryptText(global_eid, encrypted_old_key, encrypted_old_key_len, encrypted_new_key, encrypted_new_key_len, (char *)input_token, token_len, debug, debug_size);
    printf("DEBUG = %s\n", debug);
    if (strcmp(debug, "SUCCESS") != 0) {
        if (ret == SGX_SUCCESS)
            ret = SGX_ERROR_UNEXPECTED;
        *status_code = ret;
        printf("status code end of app = %i\n", *status_code);
        return NULL;
	}
	if (ret != SGX_SUCCESS)
    {
        *status_code = ret;
        return NULL;
    }

    return (uint8_t *)input_token;
}

The line of interest here is :

ret = reencryptText(global_eid, encrypted_old_key, encrypted_old_key_len, encrypted_new_key, encrypted_new_key_len, (char *)input_token, token_len, debug, debug_size);

This is theE-CALL that calls the enclave’s re-encryption function. This E-CALL returns a status SGX_STATUS indicating whether the operation was successful or whether there are any potential errors.

.edl files

Before delving deeper into the enclave’s re-encryption function, it’s important to understand how data is exchanged between the library and the enclave. The enclave’s memory is always private and independent from that of the library, which is executed fromOS. There is no data transfer as such, but a copy of the variables inserted in the function call.

To indicate in which direction the variable copy must operate, it is important to understand the files of type .edl:

enclave {

    from "sgx_tstdc.edl" import sgx_oc_cpuidex;
    include "sgx_tcrypto.h"

    untrusted {
        [cdecl] void emit_debug([string,in] const char *str);
    };
    trusted {
        public void reencryptText([in,size=encrypted_old_key_len] const uint8_t *p_encrypted_old_key, size_t encrypted_old_key_len,
        [in,size=encrypted_new_key_len] const uint8_t *p_encrypted_new_key, size_t encrypted_new_key_len, [in, out, size=token_len] char *token, size_t
        token_len, [out,size=debug_size]char *debug, uint8_t debug_size);
    };

};

The file Libc.edl in the enclave section above tells the library how to proceed for each variable during a E-CALL. Let’s take the example of the line corresponding toE-CALL for re-encryption in the trusted section:

The direction of copying is indicated in square brackets before variables are declared. in indicates that the variable is copied from the library to the enclave (before execution of the function in the enclave), out from the enclave to the library (at the end of execution of the function in the enclave). in, out copies in both directions. In this case, the token will be copied from the library function to the enclave, and the re-encrypted token will be copied from the enclave to the library function. The pointer size must also be specified.

When the enclave is compiled, the automatic tool supplied with SGX (edger8r) converts the methods defined in the various edl files into C functions.

Enclave

We’re only interested in the enclave function called for re-encryption:

void reencryptText(const uint8_t *p_encrypted_old_key, size_t encrypted_old_key_len, const uint8_t *p_encrypted_new_key, size_t encrypted_new_key_len, char *token, size_t token_len, char *debug, uint8_t debug_size)
{
	sgx_status_t status;
	char *error_msg = "SUCCESS";

    sgx_aes_gcm_128bit_key_t    old_key;
    sgx_aes_gcm_128bit_key_t    new_key;

	uint8_t version[SIZE_VERSION];
	uint8_t time_stamp[SIZE_TIMESTAMP];
	uint8_t iv[SGX_AESGCM_IV_SIZE];
	sgx_aes_gcm_128bit_tag_t tag[SIZE_TAG];

    ...
}

The function takes the same arguments as the library function, with the exception of the status_code directly returned by the enclave and a variable for debugging purposes. The first step is to decompose the token received:

    ...
    unsigned long long len_ciphered = encrypted_old_key_len - (SIZE_VERSION + SIZE_TIMESTAMP + SGX_AESGCM_IV_SIZE + SIZE_TAG);
	uint8_t ciphered_old_key[len_ciphered];

	memcpy(version, p_encrypted_old_key, SIZE_VERSION);
	memcpy(time_stamp, &(p_encrypted_old_key[SIZE_VERSION]), SIZE_TIMESTAMP);
	memcpy(iv, &p_encrypted_old_key[SIZE_VERSION + SIZE_TIMESTAMP], SGX_AESGCM_IV_SIZE);
	memcpy(tag, &p_encrypted_old_key[SIZE_VERSION + SIZE_TIMESTAMP + SGX_AESGCM_IV_SIZE], SIZE_TAG);
	memcpy(ciphered_old_key, &p_encrypted_old_key[SIZE_VERSION + SIZE_TIMESTAMP + SGX_AESGCM_IV_SIZE + SIZE_TAG], len_ciphered);
    ...

We then check the :

	...
    // Checking version
	if (memcmp((const void *)version, "\x80", 1))
	{
		error_msg = "Bad token";
		memcpy(debug, error_msg, strlen(error_msg));
		return;
	}
    ...

For this POC we don’t check time_stamp but for a deployed version, it would be interesting to check that date_time is less than the current date. As the old and new encryption keys are themselves encrypted, we first need to put them in clear in the enclave:

	...
    status = sgx_rijndael128GCM_decrypt(
		&aes_key,
		ciphered_old_key,
		len_ciphered,
		old_key,
		aes_iv,
		SGX_AESGCM_IV_SIZE,
		NULL,
		0,
		tag
	);
	if (status != SGX_SUCCESS) {
		printf("status = %i\n", status);
		error_msg = "Problem with data decryption";
		memcpy(debug, error_msg, strlen(error_msg));
		return;
	}
    ...

For the example POC, the key is stored directly in the enclave and encrypted symmetrically using AES. An SGX-specific process called data_sealing would enable this key to be kept between different instances of an enclave. A future enhancement would be to use asymmetric encryption, with an encryption key in the Parsec client and a unique decryption key known by the enclave. RSA encryption is available in the enclave and could enable this asymmetric encryption.

Since we’re also using AES for the encrypted token, the process is identical to that for decrypting the keys. We decompose token and decrypt it using the sgx_rijndael128GCM_decrypt(&old_key, ...) function. We encrypt token with the new key in the same way, using the sgx_rijndael128GCM_encrypt(&new_key, ...) function.

	...
    // Creating token again : version || time_stamp || iv || tag || ciphered
	memcpy(token, "\x80", SIZE_VERSION);
	memcpy(&(token[SIZE_VERSION]), time_stamp, SIZE_TIMESTAMP);
	memcpy(&(token[SIZE_VERSION + SIZE_TIMESTAMP]), iv, SGX_AESGCM_IV_SIZE);
	memcpy(&(token[SIZE_VERSION + SIZE_TIMESTAMP +SGX_AESGCM_IV_SIZE]), tag, SIZE_TAG);
	memcpy(&(token[SIZE_VERSION + SIZE_TIMESTAMP + SGX_AESGCM_IV_SIZE + SIZE_TAG]), ciphered, len_ciphered);
    ...

The token is created again and sent back to the library function with the new time_stamp, the new IV, the new tag and the re-encrypted message. The version remains the same, \x80. The token is then sent back to Parsec’s backend.

CONCLUSION

In this article, we’ve looked at the various steps involved in re-encrypting data from a Parsec workspace in an SGX enclave. On the Parsec side, we modified the encryption part to use AES with GCM mode, compatible with the instructions available in an SGX enclave. We then added the reencryption API from the backend, which calls on an SGX library.

We have created a re-encryption function in the SGX library and the corresponding function in the enclave. A number of possible improvements have been discussed, including asymmetric encryption of reencryption keys and data_sealing, which would enable decryption keys unique to the enclave to be stored.

REFERENCES

[1] SGX [2] parsec-sgx-poc [3] use-aes-in-parsec-poc [4] ctypes [5] data_sealing [6] AES [7] GCM

By Malo BOUCE (SCILLE/PARSEC intern) and Antoine DUPRE (SCILLE/PARSEC engineer)

You may also like these articles

Image titre

The NIS2 Directive: A Pillar for Cybersecurity in Europe

Introduction With the adoption of the NIS2 Directive, the European Union is strengthening the cybersecurity of its member states, and kicking off the digital security and digital autonomy of European organizations. The NIS2 Directive builds on the foundations of its

Secure public data management with Parsec

Public administrations manage a massive volume of sensitive data, from tax information to medical records and administrative data relating to citizens. The increasing digitization of public services calls for secure solutions to protect this information against cyber attacks. Administrations are

Looking for other items?

Chiffrement Zéro Trust

Collaboratif

Anti ransomware

Stockage

Intégrateurs

Banque et assurance

Industrie

Expert comptable

Santé et Structures hospitalières

Grand Groupe

Administration

Startup

Certification CSPN

Hébergement cloud

Zero Trust encryption

Collaborative

Anti ransomware

Storage

Integrators

Banking & Insurance

Industry

Chartered Accountant

Health and hospital structures

Large Group

Administration

Startup

CSPN certification

Cloud hosting