📜 ⬆️ ⬇️

Intel Software Guard Extensions tutorial. Part 5, development of the enclave

In the fifth installment of the Intel Software Guard Extensions (Intel SGX) tutorial series, we will complete the development of the Tutorial Password Manager enclave application. In the fourth part of this series, we created a DLL library used as the interface level between the enclave bridge functions and the C ++ / CLI program core, and also defined the enclave interface. These components are ready, so now you can go to the enclave itself.

Along with this part of the series, the source code is provided: a finished application with an enclave. In this version, the code branch is hardcoded using Intel SGX.

Enclave components


To determine which components should be implemented inside the enclave, let's return to the class diagram of the application kernel, which we first described in the third part - it is shown in Figure. 1. As before, the objects in the enclave are shaded green, and the untrusted components are blue.


Figure 1. Class diagram in Tutorial Password Manager with Intel Software Guard Extensions.
')
According to this scheme, you can define four classes that should be transferred to the enclave:


However, before starting work you will need to make a decision about the application device. Our application should work on systems with both Intel SGX support and without Intel SGX. This means that it is not possible to simply convert existing classes so that they work inside the enclave. You need to create two versions of each class: one for use in enclaves, another for use in untrusted memory. The question is how to implement this double support?

Option 1. Conditional compilation


The first option is to implement the functionality for the enclave and for untrusted memory in the same source module and use preprocessing definitions and #ifdef instructions to compile the necessary code depending on the context. The advantage of this approach is that for each class you need only one source code file, there is no need to apply each change in two places. The disadvantage is that such code is less clear, especially if there are several or significant changes between versions. In addition, the structure of the project is complicated. The two Visual Studio * projects, Enclave and PasswordManagerCore , will have common source code files, and each will need to set a preprocessing symbol to ensure that the correct version of the source code is compiled.

Option 2. Separate classes


The second option is to duplicate each source code file to be placed in the enclave. The advantages of this approach are that the enclave will have its own copy of source code files that can be changed directly, thereby simplifying the structure of the project and viewing the code. But there is a drawback: if you need to make changes to the classes, you will have to make these changes in two places, even if the changes are the same for the version in the enclave and for the version in the untrusted memory.

Option 3. Inheritance


The third option involves the use of class inheritance, available in C ++. Functions that are common to both versions of the class are implemented in the base class, and derived classes will implement methods related to each of the branches of the code. An important advantage of this approach is that this is a very natural and elegant solution to the problem: we use the possibility of the language, designed specifically for such situations. The disadvantage is the increased complexity of the project structure and the code itself.

There are no hard and fast rules here; it is not necessary to use any decision made always and everywhere. The general recommendation is this: option 1 is best suited for modules where there are few changes or where they can be easily identified; Options 2 and 3 are better suited for cases where the changes are significant enough or the resulting source code is too complex in terms of reading and maintenance. If, however, reduce the choice to the level of style and preferences, then any of the listed approaches is quite efficient.

We will use the second option, since it provides an opportunity to compare versions of the source code files intended for the enclave and for untrusted memory. In future releases of this series, we may move on to the third option to make the code more compact.

Enclave classes


Each class has its own problems and difficulties in adapting to work in an enclave, but there is one general rule that applies to all classes: it is no longer necessary to fill the memory with zeros before it is released. As you may remember from part three, this is the recommended action when processing secure data in untrusted memory. The memory of the enclave is encrypted by the processor using an encryption key that is not available for any of the hardware levels, so the contents of the released memory will look like random data for other applications. This means that you can delete all calls to SecureZeroMemory from within the enclave.

Vault class


The Vault class is our interface to the password store. All bridge functions operate through one or more methods in the Vault class. His announcement from Vault.h is shown below.

class PASSWORDMANAGERCORE_API Vault { Crypto crypto; char m_pw_salt[8]; char db_key_nonce[12]; char db_key_tag[16]; char db_key_enc[16]; char db_key_obs[16]; char db_key_xor[16]; UINT16 db_version; UINT32 db_size; // Use get_db_size() to fetch this value so it gets updated as needed char db_data_nonce[12]; char db_data_tag[16]; char *db_data; UINT32 state; // Cache the number of defined accounts so that the GUI doesn't have to fetch // "empty" account info unnecessarily. UINT32 naccounts; AccountRecord accounts[MAX_ACCOUNTS]; void clear(); void clear_account_info(); void update_db_size(); void get_db_key(char key[16]); void set_db_key(const char key[16]); public: Vault(); ~Vault(); int initialize(); int initialize(const unsigned char *header, UINT16 size); int load_vault(const unsigned char *edata); int get_header(unsigned char *header, UINT16 *size); int get_vault(unsigned char *edata, UINT32 *size); UINT32 get_db_size(); void lock(); int unlock(const char *password); int set_master_password(const char *password); int change_master_password(const char *oldpass, const char *newpass); int accounts_get_count(UINT32 *count); int accounts_get_info_sizes(UINT32 idx, UINT16 *mbname_sz, UINT16 *mblogin_sz, UINT16 *mburl_sz); int accounts_get_info(UINT32 idx, char *mbname, UINT16 mbname_sz, char *mblogin, UINT16 mblogin_sz, char *mburl, UINT16 mburl_sz); int accounts_get_password_size(UINT32 idx, UINT16 *mbpass_sz); int accounts_get_password(UINT32 idx, char *mbpass, UINT16 mbpass_sz); int accounts_set_info(UINT32 idx, const char *mbname, UINT16 mbname_len, const char *mblogin, UINT16 mblogin_len, const char *mburl, UINT16 mburl_len); int accounts_set_password(UINT32 idx, const char *mbpass, UINT16 mbpass_len); int accounts_generate_password(UINT16 length, UINT16 pwflags, char *cpass); int is_valid() { return _VST_IS_VALID(state); } int is_locked() { return ((state&_VST_LOCKED) == _VST_LOCKED) ? 1 : 0; } }; 

The declaration of the version of this class for the enclave, which we will call E_Vault for clarity, will be identical, with the exception of one important change.

In the untrusted code branch, the Vault object must store the decrypted database key in memory. Every time we make any change to the password store, we need to encrypt the updated store data and write it to disk. This means that the key must be at our disposal. Before us are four paths:

  1. To prompt the user to enter the master password with each change in order to generate a database key on demand.
  2. Cache the user's master password to generate an on-demand database key without user intervention.
  3. Encrypt, encode or hide the database key in memory.
  4. Keep the key unencrypted.

None of these solutions is satisfactory. The lack of more convenient solutions again underlines the relevance of technologies such as the Intel SGX. The first solution can - with reservations - be considered more secure, but no user will want to use an application that will behave in a detailed manner. The second solution is feasible with the .NET * SecureString class, but it will still be vulnerable to getting the key through a debugger. In addition, for generating a key, certain computational resources will be required, due to which performance may decline to an unacceptable level for users. The third option, in fact, is as unsafe as the second, but without sacrificing performance. The fourth option is the worst of all.

In our application, the Tutorial Password Manager uses the third option: the database key is encoded using XOR with a 128-bit value, which is formed arbitrarily when opening a storage file and stored in memory only in this form after processing with XOR. This is, in fact, a scheme with a one-time encryption key. The key is available to anyone who can run the debugger, but the time during which the database key is kept in memory in an unencrypted form is limited.

 void Vault::set_db_key(const char db_key[16]) { UINT i, j; for (i = 0; i < 4; ++i) for (j = 0; j < 4; ++j) db_key_obs[4 * i + j] = db_key[4 * i + j] ^ db_key_xor[4 * i + j]; } void Vault::get_db_key(char db_key[16]) { UINT i, j; for (i = 0; i < 4; ++i) for (j = 0; j < 4; ++j) db_key[4 * i + j] = db_key_obs[4 * i + j] ^ db_key_xor[4 * i + j]; } 

This approach obviously belongs to the “security through obscurity” model, but since we publish the source code, there is no need to speak about any particular obscurity. It would be possible to choose a better algorithm or make more efforts to hide the database key and one-time key (including ways of storing them in memory), but the method we chose will still be vulnerable to view using the debugger, and the algorithm for hiding everything equal will be published and available to all.

However, inside the enclave, this problem disappears. The memory is protected by hardware-based encryption, so even after decrypting the database key, it is not available for anyone to view, even for processes with extended rights. Therefore, we no longer need the following members and methods of the class:

 char db_key_obs[16]; char db_key_xor[16]; void get_db_key(char key[16]); void set_db_key(const char key[16]); 

You can replace them with just one member of the class: the char array to store the database key.

 char db_key[16]; 

Class AccountInfo


Account data is stored in a fixed-size array of AccountInfo objects as a member of a Vault object. The AccountInfo declaration is also in Vault.h, shown below:

 class PASSWORDMANAGERCORE_API AccountRecord { char nonce[12]; char tag[16]; // Store these in their multibyte form. There's no sense in translating // them back to wchar_t since they have to be passed in and out as // char * anyway. char *name; char *login; char *url; char *epass; UINT16 epass_len; // Can't rely on NULL termination! It's an encrypted string. int set_field(char **field, const char *value, UINT16 len); void zero_free_field(char *field, UINT16 len); public: AccountRecord(); ~AccountRecord(); void set_nonce(const char *in) { memcpy(nonce, in, 12); } void set_tag(const char *in) { memcpy(tag, in, 16); } int set_enc_pass(const char *in, UINT16 len); int set_name(const char *in, UINT16 len) { return set_field(&name, in, len); } int set_login(const char *in, UINT16 len) { return set_field(&login, in, len); } int set_url(const char *in, UINT16 len) { return set_field(&url, in, len); } const char *get_epass() { return (epass == NULL)? "" : (const char *)epass; } const char *get_name() { return (name == NULL) ? "" : (const char *)name; } const char *get_login() { return (login == NULL) ? "" : (const char *)login; } const char *get_url() { return (url == NULL) ? "" : (const char *)url; } const char *get_nonce() { return (const char *)nonce; } const char *get_tag() { return (const char *)tag; } UINT16 get_name_len() { return (name == NULL) ? 0 : (UINT16)strlen(name); } UINT16 get_login_len() { return (login == NULL) ? 0 : (UINT16)strlen(login); } UINT16 get_url_len() { return (url == NULL) ? 0 : (UINT16)strlen(url); } UINT16 get_epass_len() { return (epass == NULL) ? 0 : epass_len; } void clear(); }; 

For this class there is no need to do anything so that it can work inside the enclave. It is enough to remove unnecessary calls to SecureZeroFree , and the work can be considered finished. Nevertheless, we will change it anyway to demonstrate an important point: inside the enclave we gain additional flexibility that we did not have before.

Let us return to the third part of this series: one of the rules for protecting data in an untrusted memory space concerned not using container classes that manage their own memory, in particular, the class std :: string from the standard template library. Inside the enclave, this problem disappears. For the very same reason that we do not need to fill the memory with zeros before it is released, we don’t need to worry about how the standard template library (STL) containers manage their memory. The memory of the enclave is encrypted, so even if fragments of protected data remain there after container operations, this data will not be available for other processes.

In addition, there is a strong argument in favor of using the class std :: string inside an enclave: the code for STL containers has been thoroughly studied by developers for several years, so it can be argued that this code is safer than our own high-level string functions (if you have a choice). For simple code, such as in the class AccountInfo , this is not too important, but in more complex programs this can be a very useful advantage. However, the size of the DLL library increases due to the additional STL code.

The declaration of the new class, which we will call E_AccountInfo , is shown below:

 #define TRY_ASSIGN(x) try{x.assign(in,len);} catch(...){return 0;} return 1 class E_AccountRecord { char nonce[12]; char tag[16]; // Store these in their multibyte form. There's no sense in translating // them back to wchar_t since they have to be passed in and out as // char * anyway. string name, login, url, epass; public: E_AccountRecord(); ~E_AccountRecord(); void set_nonce(const char *in) { memcpy(nonce, in, 12); } void set_tag(const char *in) { memcpy(tag, in, 16); } int set_enc_pass(const char *in, uint16_t len) { TRY_ASSIGN(epass); } int set_name(const char *in, uint16_t len) { TRY_ASSIGN(name); } int set_login(const char *in, uint16_t len) { TRY_ASSIGN(login); } int set_url(const char *in, uint16_t len) { TRY_ASSIGN(url); } const char *get_epass() { return epass.c_str(); } const char *get_name() { return name.c_str(); } const char *get_login() { return login.c_str(); } const char *get_url() { return url.c_str(); } const char *get_nonce() { return (const char *)nonce; } const char *get_tag() { return (const char *)tag; } uint16_t get_name_len() { return (uint16_t) name.length(); } uint16_t get_login_len() { return (uint16_t) login.length(); } uint16_t get_url_len() { return (uint16_t) url.length(); } uint16_t get_epass_len() { return (uint16_t) epass.length(); } void clear(); }; 

The tag and nonce members are still stored as char arrays. The password is encrypted using the AES algorithm in GCM mode with a 128-bit key, a 96-bit random number and a 128-bit authentication tag. Since the fixed size of the random number and tag is used, there is no need to store them in any structures more complex than simple arrays of char.

Please note that this approach based on std :: string allows us to almost completely define the class in the header file.

Class crypto


The Crypto class provides encryption functions. This class declaration is shown below.

 class PASSWORDMANAGERCORE_API Crypto { DRNG drng; crypto_status_t aes_init (BCRYPT_ALG_HANDLE *halgo, LPCWSTR algo_id, PBYTE chaining_mode, DWORD chaining_mode_len, BCRYPT_KEY_HANDLE *hkey, PBYTE key, ULONG key_len); void aes_close (BCRYPT_ALG_HANDLE *halgo, BCRYPT_KEY_HANDLE *hkey); crypto_status_t aes_128_gcm_encrypt(PBYTE key, PBYTE nonce, ULONG nonce_len, PBYTE pt, DWORD pt_len, PBYTE ct, DWORD ct_sz, PBYTE tag, DWORD tag_len); crypto_status_t aes_128_gcm_decrypt(PBYTE key, PBYTE nonce, ULONG nonce_len, PBYTE ct, DWORD ct_len, PBYTE pt, DWORD pt_sz, PBYTE tag, DWORD tag_len); crypto_status_t sha256_multi (PBYTE *messages, ULONG *lengths, BYTE hash[32]); public: Crypto(void); ~Crypto(void); crypto_status_t generate_database_key (BYTE key_out[16], GenerateDatabaseKeyCallback callback); crypto_status_t generate_salt (BYTE salt[8]); crypto_status_t generate_salt_ex (PBYTE salt, ULONG salt_len); crypto_status_t generate_nonce_gcm (BYTE nonce[12]); crypto_status_t unlock_vault(PBYTE passphrase, ULONG passphrase_len, BYTE salt[8], BYTE db_key_ct[16], BYTE db_key_iv[12], BYTE db_key_tag[16], BYTE db_key_pt[16]); crypto_status_t derive_master_key (PBYTE passphrase, ULONG passphrase_len, BYTE salt[8], BYTE mkey[16]); crypto_status_t derive_master_key_ex (PBYTE passphrase, ULONG passphrase_len, PBYTE salt, ULONG salt_len, ULONG iterations, BYTE mkey[16]); crypto_status_t validate_passphrase(PBYTE passphrase, ULONG passphrase_len, BYTE salt[8], BYTE db_key[16], BYTE db_iv[12], BYTE db_tag[16]); crypto_status_t validate_passphrase_ex(PBYTE passphrase, ULONG passphrase_len, PBYTE salt, ULONG salt_len, ULONG iterations, BYTE db_key[16], BYTE db_iv[12], BYTE db_tag[16]); crypto_status_t encrypt_database_key (BYTE master_key[16], BYTE db_key_pt[16], BYTE db_key_ct[16], BYTE iv[12], BYTE tag[16], DWORD flags= 0); crypto_status_t decrypt_database_key (BYTE master_key[16], BYTE db_key_ct[16], BYTE iv[12], BYTE tag[16], BYTE db_key_pt[16]); crypto_status_t encrypt_account_password (BYTE db_key[16], PBYTE password_pt, ULONG password_len, PBYTE password_ct, BYTE iv[12], BYTE tag[16], DWORD flags= 0); crypto_status_t decrypt_account_password (BYTE db_key[16], PBYTE password_ct, ULONG password_len, BYTE iv[12], BYTE tag[16], PBYTE password); crypto_status_t encrypt_database (BYTE db_key[16], PBYTE db_serialized, ULONG db_size, PBYTE db_ct, BYTE iv[12], BYTE tag[16], DWORD flags= 0); crypto_status_t decrypt_database (BYTE db_key[16], PBYTE db_ct, ULONG db_size, BYTE iv[12], BYTE tag[16], PBYTE db_serialized); crypto_status_t generate_password(PBYTE buffer, USHORT buffer_len, USHORT flags); }; 

Public methods in this class are modified to perform various high-level operations with the storage: unlock_vault , derive_master_key , validate_passphrase , encrypt_database , etc. Each of these methods invokes one or more encryption algorithms to perform its task. For example, the unlock_vault method gets the password phrase provided by the user, passes it through the key generation function based on the SHA-256 algorithm, then uses the received key to decrypt the database key using the AES-128 algorithm in GCM mode.

However, these high-level methods do not cause encryption primitives directly. They call the middle level at which each encryption algorithm is implemented as an independent function.


Figure 2. Encryption library dependencies.

The private methods that make up our middle layer are built on the basis of encryption primitives and support the functions provided by the basic encryption library, as shown in Fig. 2. An implementation that does not use Intel SGX relies on the Microsoft Cryptography API: Next Generation (CNG), but the same library cannot be used inside the enclave, since the enclave cannot depend on external DLL libraries. To create a version of this class for Intel SGX, you need to replace these basic functions with functions from the trusted encryption library that is distributed with the Intel SGX SDK. (As you probably remember from the second part, we very carefully selected the encryption functions common to the CNG and to the trusted encryption library of the Intel SGX, for this very reason.)

To create an Crypto class with enclave support, which we will call E_Crypto , you need to change the following private methods:

 crypto_status_t aes_128_gcm_encrypt(PBYTE key, PBYTE nonce, ULONG nonce_len, PBYTE pt, DWORD pt_len, PBYTE ct, DWORD ct_sz, PBYTE tag, DWORD tag_len); crypto_status_t aes_128_gcm_decrypt(PBYTE key, PBYTE nonce, ULONG nonce_len, PBYTE ct, DWORD ct_len, PBYTE pt, DWORD pt_sz, PBYTE tag, DWORD tag_len); crypto_status_t sha256_multi (PBYTE *messages, ULONG *lengths, BYTE hash[32]); 

A description of each method, as well as the primitives and supporting functions of the CNG, on the basis of which they are built, is given in Table 1.
MethodAlgorithmCNG primitives and supporting functions
aes_128_gcm_encryptAES encryption in GCM mode:
• 128-bit key
• 128-bit authentication tag
• No additional validated data (AAD)
BCryptOpenAlgorithmProvider
BCryptSetProperty
BCryptGenerateSymmetricKey
BCryptEncrypt
BCryptCloseAlgorithmProvider
BCryptDestroyKey
aes_128_gcm_decryptAES encryption in GCM mode:
• 128-bit key
• 128-bit authentication tag
• Lack of AAD
BCryptOpenAlgorithmProvider
BCryptSetProperty
BCryptGenerateSymmetricKey
BCryptDecrypt
BCryptCloseAlgorithmProvider
BCryptDestroyKey
sha256_multiHash SHA-256 (extra)BCryptOpenAlgorithmProvider
BCryptGetProperty
BCryptCreateHash
BCryptHashData
BCryptFinishHash
BCryptDestroyHash
BCryptCloseAlgorithmProvider
Table 1. Comparison of Crypto class methods with API Cryptography: Next Generation (CNG) functions.

CNG provides very precise control over encryption algorithms, as well as optimization options for improved performance. Our class Crypto cannot boast of excessive efficiency: each time when calling one of these algorithms, it initializes the basic primitives from scratch, and then completely closes them. This is not a serious problem for Password Manager, which works on the basis of the user interface and encrypts small amounts of data at the same time. A more powerful server application, such as a web server or a database server, would require a more efficient approach.

The API for the trusted encryption library distributed with the Intel SGX SDK looks more like our average level than the CNG. At the same time, there is less support for precise control of basic primitives, but the creation of the E_Crypto class becomes much easier. Table 2 shows the new mapping between the middle tier and the underlying provider.
MethodAlgorithmPrimitives and supporting functions in the Intel SGX Trusted Cryptography Library
aes_128_gcm_encryptAES encryption in GCM mode:
• 128-bit key
• 128-bit authentication tag
• No additional validated data (AAD)
sgx_rijndael128GCM_encrypt
aes_128_gcm_decryptAES encryption in GCM mode:
• 128-bit key
• 128-bit authentication tag
• Lack of AAD
sgx_rijndael128GCM_decrypt
sha256_multiHash SHA-256 (extra)sgx_sha256_init
sgx_sha256_update
sgx_sha256_get_hash
sgx_sha256_close
Table 2. Comparison of Crypto class methods with Intel SGX Trusted Cryptography Library functions.

DRNG class


The DRNG class is an interface to a hardware digital random number generator, which is available thanks to support for Intel Secure Key technology. For homogeneity with previous actions, the version of this class intended for the enclave will be called E_DRNG .

We will make two changes in this class to adapt it to the enclave. Both changes are internal to the methods of this class. The class declaration remains the same.

CPUID instruction


One of the requirements of our application is that the CPU must support Intel Secure Key Technology. Intel SGX technology is newer than Secure Key, but there is no guarantee that all future generations of all possible CPUs with Intel SGX support will also support the Intel Secure Key. Now it is difficult to foresee such a situation, but in practice it is better not to rely on the relationship between the components, one of which may not exist. If a set of components has independent detection mechanisms, then you must assume that these components do not depend on each other, so you need to check their presence separately. In practice, this means the following: however much we would like to hope that the CPU supporting Intel SGX will also support the Intel Secure Key, this should not be done in any way.

The situation is further complicated by the fact that the Intel Secure Key consists of two separate components, the presence of each of them also needs to be checked separately. Our application should determine support for RDRAND and RDSEED instructions. For more information about Intel Secure Key Technology, see the Intel Random Number Generation Software (DRNG) Implementation Guide .

The constructor in the DRNG class is responsible for the checks needed to locate the RDRAND and RDSEED components . It makes the necessary calls to the CPUID instruction using the built-in compiler functions __cpuid and __cpuidex and sets a static global variable with the results.

 static int _drng_support= DRNG_SUPPORT_UNKNOWN; static int _drng_support= DRNG_SUPPORT_UNKNOWN; DRNG::DRNG(void) { int info[4]; if (_drng_support != DRNG_SUPPORT_UNKNOWN) return; _drng_support= DRNG_SUPPORT_NONE; // Check our feature support __cpuid(info, 0); if ( memcmp(&(info[1]), "Genu", 4) || memcmp(&(info[3]), "ineI", 4) || memcmp(&(info[2]), "ntel", 4) ) return; __cpuidex(info, 1, 0); if ( ((UINT) info[2]) & (1<<30) ) _drng_support|= DRNG_SUPPORT_RDRAND; #ifdef COMPILER_HAS_RDSEED_SUPPORT __cpuidex(info, 7, 0); if ( ((UINT) info[1]) & (1<<18) ) _drng_support|= DRNG_SUPPORT_RDSEED; #endif } 

The problem for the E_DRNG class is that the CPUID is not a valid instruction inside the enclave. To call the CPUID, you need to use OCALL to exit the enclave, and then call the CPUID in an untrusted code. Fortunately, the developers of the Intel SGX SDK have created two convenient functions that greatly simplify this task: sgx_cpuid and sgx_cpuidex . These functions automatically perform OCALL , and the creation of OCALL occurs automatically. The only requirement is that the EDL file must import the sgx_tstdc.edl header:

 enclave { /* Needed for the call to sgx_cpuidex */ from "sgx_tstdc.edl" import *; trusted { /* define ECALLs here. */ public int ve_initialize (); public int ve_initialize_from_header ([in, count=len] unsigned char *header, uint16_t len); /* Our other ECALLs have been omitted for brevity */ }; untrusted { }; }; 

The detection code for system components in the E_DRNG constructor becomes:

 static int _drng_support= DRNG_SUPPORT_UNKNOWN; E_DRNG::E_DRNG(void) { int info[4]; sgx_status_t status; if (_drng_support != DRNG_SUPPORT_UNKNOWN) return; _drng_support = DRNG_SUPPORT_NONE; // Check our feature support status= sgx_cpuid(info, 0); if (status != SGX_SUCCESS) return; if (memcmp(&(info[1]), "Genu", 4) || memcmp(&(info[3]), "ineI", 4) || memcmp(&(info[2]), "ntel", 4)) return; status= sgx_cpuidex(info, 1, 0); if (status != SGX_SUCCESS) return; if (info[2]) & (1 << 30)) _drng_support |= DRNG_SUPPORT_RDRAND; #ifdef COMPILER_HAS_RDSEED_SUPPORT status= __cpuidex(info, 7, 0); if (status != SGX_SUCCESS) return; if (info[1]) & (1 << 18)) _drng_support |= DRNG_SUPPORT_RDSEED; #endif } 

Since calls to the CPUID instruction are made in untrusted memory, the CPUID results cannot be trusted! This warning is valid in all cases when you run the CPUID yourself or use SGX functions for this. The Intel SGX SDK offers the following advice: “the code must check the results and evaluate the threat in order to determine the effect on the trusted code in case of fake results”.

In our training password manager, there are three options:

  1. The RDRAND and / or RDSEED instructions were not detected, but a positive result for one of them was tampered with. This will lead to an error due to invalid instructions at runtime, the program will crash.
  2. The RDRAND instruction was found, but a negative result was forged. This will result in a runtime error; the program will shut down in a regular manner, since the required component was not found.
  3. The RDSEED instruction has been detected, but a negative result has been tampered with. In this case, the program will return to using the RDRAND instruction to obtain initial random values, which will slightly affect performance. .

, , , ().


RDRAND


RDSEED, RDRAND , , RDSEED ( ). (DRNG) Intel RDRAND, : 512 128- CBC-MAC AES 128- . , , .

Intel SGX seed_from_rdrand CNG . Intel SGX CNG, , Intel SGX SDK. 3.
AlgorithmCNGIntel SGX Trusted Cryptography Library
aes-cmacBCryptOpenAlgorithmProvider
BCryptGenerateSymmetricKey
BCryptSetProperty
BCryptEncrypt
BCryptDestroyKey
BCryptCloseAlgorithmProvider
sgx_cmac128_init
sgx_cmac128_update
sgx_cmac128_final
sgx_cmac128_close
3. seed_from_rdrand E_DRNG.

DRNG , Crypto ? , . DRNG , DRNG Crypto ( Crypto DRNG ). , Crypto , , API .

sgx_read_rand?


Intel SGX SDK sgx_read_rand , . :

  1. Intel SGX SDK, « C , , rand , srand ., ». sgx_read_rand RDRAND, , , srand rand , C. , C, . , - , , , , CPUID, , .
  2. Intel SGX SDK RDSEED. , . RDRAND sgx_read_rand , , .
  3. , sgx_read_rand , .

Summarizing


. , , , .
As mentioned above, this section provides sample code for download.

Tutorial Password Manager, -. , ; , Intel SGX.

In further releases


, , Intel SGX. Follow the news!

Intel Software Guard Extensions Tutorial Series .

Source: https://habr.com/ru/post/320030/


All Articles