Our code generation allows developers to call functions that live in either VTL1 or
VTL0 in a natural way without having to interact with the CallEnclave API directly, nor deal with the unstructured void *
contract when passing data into/out-of an enclave. The generated code will package up your function arguments, copy them safely
across the VTL boundary, then forward them to your ultimate function implementation. This allows developers to focus only
on their business logic, instead of the logic around the trust boundary.
- Create an .edl file like example edl. See: Edl format for more current support.
- Add the
Microsoft.Windows.VbsEnclave.CodeGeneratornuget package to both yourenclaveandhostAppprojects. - Add properties to your project as listed in README.md.
- Build your projects.
Generated enclave project artifacts:
VbsEnclave/
|
|--- Enclave/
|--- Abi
|--- AbiTypes.h
|--- Definitions.h
|--- Exports.<Edl-Filename>.cpp
|--- FlatbufferTypes.fbs
|--- FlatbufferTypes.h
|--- LinkerPragmas.<Edl-Filename>.cpp
|--- TypeMetadata.h
|--- Implementation
|--- Trusted.h
|--- Types.h
|--- Stubs
|--- Untrusted.h
Generated hostApp project artifacts:
VbsEnclave/
|
|--- HostApp/
|--- Abi
|--- AbiTypes.h
|--- Definitions.h
|--- FlatbufferTypes.fbs
|--- FlatbufferTypes.h
|--- TypeMetadata.h
|--- Implementation
|--- Types.h
|--- Untrusted.h
|--- Stubs
|--- Trusted.h
Note
- The default output directory is
$(ProjectDir)\Generated Files - If you're using the nuget package the files will automatically be added to the projects build so there is no need to explicitly include them.
Note
The developer is only expected to interact with files generated in the Implementation and the Stubs directories.
| File | Description |
|---|---|
Abi\Abitypes.h |
Defines data structures for packaging function parameters for use with the VBS enclave codegen ABI. |
Abi\Definitions.h |
Contains methods used to forward and return parameters to and from the vbs enclave codegen ABI. This is the glue code between the hostApp and the enclave. |
Abi\Exports.<Edl-Filename>.cpp |
Contains the generated functions that are exported by the enclave. These functions call into a sibling functions generated in Abi\Definitions.h. |
Abi\FlatbufferTypes.h |
Defines a Flatbuffer type for each type defined in Abi\AbiType.h and Implementation\Types.h. |
Abi\FlatbufferTypes.fbs |
Defines a Flatbuffer schema that generates the types found in Abi\FlatbufferTypes.h. |
Abi\LinkerPragmas.<Edl-Filename>.cpp |
Contains a #pragma comment(linker, /include) for each generated function in Abi\Exports.<Edl-Filename>.cpp. This ensures that functions generated in a developer's static library are exported from the enclave dll. |
Abi\TypeMetadata.h |
Contains data needed to perform the conversion between Flatbuffer types and the types found in Abi\AbiType.h and Implementation\Types.h. |
| File | Description |
|---|---|
Implementation\Trusted.h |
Contains all of the function declarations that the developer outlined in the trusted scope of the .edl file. The developer must implement these. |
Implementation\Types.h |
Defines C++ parameter types the developer can pass into .edl-specified functions. This is the parameter currency of the interfacing layer that the developer's app logic & enclave logic uses to speak to codegen'd functions. |
Stubs\Untrusted.h |
Contains stubs functions that the developer will call from inside the enclave to invoke the implementation in the hostApp. |
| File | Description |
|---|---|
Implementation\Untrusted.h |
Contains all of the function declarations that the developer outlined in the untrusted scope of the .edl file. The developer must implement these. |
Implementation\Types.h |
Defines C++ parameter types developer can pass into .edl-specified functions. This is the parameter currency of the interfacing layer that the developer's app logic & enclave logic uses to speak to codegen'd functions. |
Stubs\Trusted.h |
Contains a class that is constructed with a void* to an enclave instance, and includes stub functions for invoking the implementation in the enclave from the hostApp. |
The Microsoft.Windows.VbsEnclave.CodeGenerator nuget package exports 7 non-generated .h files that your codegen'd layer (above) relies on. You typically won't ever need to interact with these files explicitly.
#include <VbsEnclaveABI\Shared\VbsEnclaveAbiBase.h>
#include <VbsEnclaveABI\Shared\ConversionHelpers.h>
#include <VbsEnclaveABI\Enclave\EnclaveHelpers.h>
#include <VbsEnclaveABI\Enclave\Vtl0Pointers.h>
#include <VbsEnclaveABI\Enclave\MemoryAllocation.h>
#include <VbsEnclaveABI\Enclave\MemoryChecks.h>
#include <VbsEnclaveABI\Host\HostHelpers.h>VbsEnclaveAbiBase.h- Defines the core structures, macros and helper functions needed to support parameter packing and unpacking.ConversionHelpers.h- defines Helper functions for translatingFlatbuffertypes to and from EDL code-generated types.
-
EnclaveHelpers.h- Provides helper functions used by the generated functions to facilitate marshaling data into and out of theenclave. These functions leverage the EnclaveCopyIntoEnclave and EnclaveCopyOutOfEnclave Win32 APIs. -
Vtl0Pointers.h- Contains smart pointers that are used when dealing withhostAppmemory inside theenclave. -
MemoryAllocation.h- Contains functions to allocate and retrievehostAppmemory from inside anenclave. -
MemoryChecks.h- Contains checks to verify that data is either inhostAppmemory orenclavememory.
HostHelpers.h- Provides helper functions used by the generated functions to facilitate marshaling data into and out of thehostApp.
For the following example we will assume the developer used the following .edl file content and the edlcodegen
executable generated code based on it.
// example edl
enclave {
struct ExampleStruct
{
vector<string> vec_str;
wstring arr_str[5];
optional<int32_t> opt_int32;
};
trusted {
string TrustedExample(
[out] vector<int8_t> int8_vec,
optional<int64_t> opt_int64, // no attribute so by default will be _In_ attribute.
[in, out] ExampleStruct ex_struct
);
};
untrusted {
HRESULT UntrustedExample(
[in, out] string some_string,
[out] wstring some_wstring
);
};
};Imagine the developer does the following:
- Uses the string
MyEnclavein theVbsEnclaveVtl0ClassNameproperty in theirhostApp.vcxproj file - Uses the string
VbsEnclaveas theVbsEnclaveNamespaceproperty in both theirhostAppandenclave.vcxproj files.
The following sections describe what will be generated and how the developer will interact with the generated code.
The generated class contains a stub function for every function the developer declares in the trusted scope of the .edl file.
The expectation is for a developer to use the generated class in the following way:
// This is in the hostApp, in a developer function where they want to call their enclave trusted function.
// We have omitted the code to create, initialize and load the enclave's void* instance, but imagine we have a void* variable we
// called enclave_void_star after we created it.
#include <VbsEnclave\HostApp\Stubs\Trusted.h> // generated file.
auto generated_class = VbsEnclave::Trusted::Stubs::MyEnclave(enclave_void_star);
THROW_IF_FAILED(generated_class.RegisterVtl0Callbacks()); // call RegisterVtl0Callbacks at least once.
std::vector<int8_t> int8_vec {};
std::optional<int64_t> some_int64 = 200;
ExampleStruct ex_struct{};
ex_struct.vec_str.push_back("string 1");
ex_struct.arr_str[0] = L"wstring 1";
std::string enclave_str = generated_class.TrustedExample(int8_vec, some_int64, ex_struct);
for (size_t i = 0; i < int8_vec.size(); i++)
{
// Should print 0, 1, 2, 3, 4 ... up to 9
std::cout << int8_vec[i] << std::endl;
}
for (auto& value : ex_struct.vec_str)
{
// Should print "string 1", "string 2" , "string 3" etc
std::cout << value << std::endl;
}
for (auto& value : ex_struct.arr_str)
{
// Should print "wstring 1", "wstring 2" , "wstring 3" etc
std::wcout << value << std::endl;
}
std::cout << *ex_struct.opt_int32 << std::endl; // should print 20
std::cout << enclave_str << std::endl; // should print "String from enclave!"Note
RegisterVtl0Callbacks is always generated, regardless of whether trusted or untrusted
functions are defined in the .edl file. It registers memory allocation callbacks needed for
parameter passing between hostApp and enclave, and must be called at least once before using
any of the class's methods.
Back in the enclave a declaration for the enclave function would have been generated in the
Implementation\Trusted.h file. The developer is expected to create a definition for this declaration.
Using the example .edl above the following declaration would be generated.
// The generated enclave function declarations are encapsulated
// in the <Namespace provided>::Trusted::Implementation namespace.
namespace VbsEnclave
{
namespace Trusted::Implementation
{
std::string TrustedExample(
_Out_ std::vector<int8_t>& int8_vec,
_In_ const std::optional<int64_t>& opt_int64,
_Inout_ ExampleStruct& ex_struct );
}
}Note
In the C++ case when the out annotation is used in tandem with a pointer,
the code generator will generate the parameter as a reference to a unique_ptr.
Continuing on, the developer might implement the declaration like this:
#include <VbsEnclave\Enclave\Implementation\Trusted.h> // generated file.
using namespace VbsEnclave;
std::string Trusted::Implementation::TrustedExample(
_Out_ std::vector<int8_t>& int8_vec,
_In_ const std::optional<int64_t>& opt_int64,
_Inout_ ExampleStruct& ex_struct)
{
int8_vec.resize(10);
std::iota(int8_vec.begin(), int8_vec.end(), 0);
// Check that opt_int64 has the expected value. It should!
THROW_HR_IF(E_INVALIDARG, !opt_int64.has_value());
THROW_HR_IF(E_INVALIDARG, *opt_int64 != 200);
ex_struct.opt_int32 = 20;
for (int i = 2; i < 6; i++)
{
ex_struct.vec_str.push_back("string "+ std::to_string(i));
}
for (int i = 2; i < ex_struct.arr_str.size(); i++)
{
ex_struct.arr_str.push_back(L"wstring "+ std::to_wstring(i));
}
return "String from enclave!";
}Note
From this demonstration we can see that the developers business logic
never directly uses the CallEnclave API, creates a module .def file,
or calls the exported enclave functions directly. This is all handled by
the CodeGen layer.
In the untrusted scenario, the function declarations are generated
in Implementation\Untrusted.h. These are implemented in the hostApp by the developer
and the developer calls them via the generated stub functions inside the enclave.
This is how the developer might interact with it:
// This is inside the enclave, in the developers business logic where they want
// to call the hostApp's implementation they defined.
#include <VbsEnclave\Enclave\Stubs\Untrusted.h> // generated file.
std::string str1 = "The quick brown";
std::wstring wstr1{};
HRESULT result = VbsEnclave::Untrusted::Stubs::UntrustedExample(str1, wstr1);
// Verifying expected str1 and wstr1 values post call
THROW_IF_FAILED(result);
THROW_HR_IF(INVALIDARG, str1 != "The quick brown fox jumps over the lazy dog");
THROW_HR_IF(INVALIDARG, wstr1 != L"HELLO WORLD FROM VTL0");
In the hostApp there will be a generated function declaration for this
stub function in Implementation\Untrusted.h. This would look like the following:
namespace VbsEnclave
{
namespace Untrusted::Implementation
{
static HRESULT UntrustedExample(
_Inout_ std::string& some_string,
_Out_ std::wstring& some_wstring);
};
}The developer must implement this declaration. One way would be the following:
#include <VbsEnclave\HostApp\Implementation\Untrusted.h> // generated file.
using namespace VbsEnclave;
HRESULT Untrusted::Implementation::UntrustedExample(
_Inout_ std::string& some_string,
_Out_ std::wstring& some_wstring)
{
// Verifying that the initial values for the some_string and some_wstring
// parameters are what we expect.
RETURN_HR_IF(INVALIDARG, some_string != "The quick brown");
RETURN_HR_IF(INVALIDARG, some_wstring != L"");
some_string += " fox jumps over the lazy dog";
some_wstring = L"HELLO WORLD FROM VTL0";
return S_OK;
}Note
The developer only needs to worry about their business logic. They do not
need to worry about copying parameters into and out of the enclave or
using the CallEnclave Win32 API directly.