diff --git a/.jenkins/Jenkinsfile b/.jenkins/Jenkinsfile index 6c2d68ca..6ee488ce 100644 --- a/.jenkins/Jenkinsfile +++ b/.jenkins/Jenkinsfile @@ -54,6 +54,9 @@ def ACCContainerTest(String label, String version) { // Run the OE tests from the git repository with the currently // generated az-dcap-client deb package installed def task = """ + sudo apt-get purge az-dcap-client -y + sudo apt-get update + sudo apt-get install sudo libcurl4-openssl-dev wget -y cd ${WORKSPACE}/src/Linux dpkg-buildpackage -us -uc sudo dpkg -i ${WORKSPACE}/src/az-dcap-client_*_amd64.deb @@ -63,7 +66,7 @@ def ACCContainerTest(String label, String version) { ninja -v ctest --output-on-failure """ - oe.ContainerRun("${DOCKER_REGISTRY}/az-dcap-tools-${version}", 'clang-7', task, '--cap-add=SYS_PTRACE --device /dev/sgx:/dev/sgx') + oe.ContainerRun("${DOCKER_REGISTRY}/oetools-full-${version}:latest", "clang-7", task, "--cap-add=SYS_PTRACE --device /dev/sgx:/dev/sgx") } } } @@ -79,6 +82,9 @@ def ACCTestOeRelease(String label, String version) { // Run the OE samples bundled with the published OE package, having // the currently generated az-dcap-client deb package installed def task = """ + sudo apt-get purge az-dcap-client -y + sudo apt-get update + sudo apt-get install sudo libcurl4-openssl-dev wget -y cd ${WORKSPACE}/src/Linux dpkg-buildpackage -us -uc sudo dpkg -i ${WORKSPACE}/src/az-dcap-client_*_amd64.deb @@ -92,7 +98,7 @@ def ACCTestOeRelease(String label, String version) { make run done """ - oe.ContainerRun("${DOCKER_REGISTRY}/az-dcap-tools-${version}", 'clang-7', task, '--cap-add=SYS_PTRACE --device /dev/sgx:/dev/sgx') + oe.ContainerRun("${DOCKER_REGISTRY}/oetools-full-${version}:latest", "clang-7", task, "--cap-add=SYS_PTRACE --device /dev/sgx:/dev/sgx") } } } @@ -138,5 +144,7 @@ parallel "ACC1604 SGX1-FLC Container RelWithDebInfo" : { ACCContainerTest('ACC-1 "ACC1804 SGX1-FLC gcc RelWithDebInfo" : { ACCTest('ACC-1804', '18.04', 'gcc', 'RelWithDebInfo') }, "ACC1604 OpenEnclave Release Test" : { ACCTestOeRelease('ACC-1604','16.04') }, "ACC1804 OpenEnclave Release Test" : { ACCTestOeRelease('ACC-1804','18.04') }, - "ACCWin DCAP Debug Test" : { DCAPBuildTest('SGXFLC-Windows', 'Debug') }, - "ACCWin DCAP Release Test" : { DCAPBuildTest('SGXFLC-Windows', 'Release') } + "ACCWin 2016 DCAP Debug Test" : { DCAPBuildTest('SGXFLC-Windows-2016-DCAP', 'Debug') }, + "ACCWin 2016 DCAP Release Test" : { DCAPBuildTest('SGXFLC-Windows-2016-DCAP', 'Release') }, + "ACCWin 2019 DCAP Debug Test" : { DCAPBuildTest('SGXFLC-Windows-2019-DCAP', 'Debug') }, + "ACCWin 2019 DCAP Release Test" : { DCAPBuildTest('SGXFLC-Windows-2019-DCAP', 'Release') } diff --git a/src/Linux/local_cache.cpp b/src/Linux/local_cache.cpp index 2e5e3d2a..ea17cd26 100644 --- a/src/Linux/local_cache.cpp +++ b/src/Linux/local_cache.cpp @@ -21,7 +21,11 @@ constexpr uint16_t CACHE_V1 = 1; constexpr locale_t NULL_LOCALE = reinterpret_cast(0); -static std::string g_cache_dirname; +static std::string g_cache_dirname = ""; +static std::mutex cache_directory_lock; + +static constexpr size_t CACHE_LOCATIONS = 5; +static const char *cache_locations[CACHE_LOCATIONS]; // // Various exception helpers @@ -180,41 +184,57 @@ static void make_dir(const std::string& dirname, mode_t mode) } } +static void load_cache_locations() +{ + cache_locations[0] = ::getenv("AZDCAP_CACHE"); + cache_locations[1] = ::getenv("XDG_CACHE_HOME"); + cache_locations[2] = ::getenv("HOME"); + cache_locations[3] = ::getenv("TMPDIR"); + + // The fallback location isn't an environment variable + cache_locations[4] = "/tmp/"; +} + static void init_callback() { - const char * env_home = ::getenv("HOME"); - const char * env_azdcap_cache = ::getenv("AZDCAP_CACHE"); + load_cache_locations(); const std::string application_name("/.az-dcap-client/"); - std::string dirname; + std::string all_locations; - if (env_azdcap_cache != 0 && (strcmp(env_azdcap_cache,"") != 0)) - { - dirname = env_azdcap_cache; - } - else if (env_home != 0 && (strcmp(env_home,"") != 0)) + // Try the cache locations in order + for (auto &cache_location : cache_locations) { - dirname = std::string(env_home); + if (cache_location != 0 && strcmp(cache_location, "") != 0) + { + dirname = cache_location + application_name; + make_dir(dirname, 0777); + g_cache_dirname = dirname; + return; + } } - else + + // Collect all of the environment variables for the error message + std::string environment_variable_list; + for (size_t i = 0; i < CACHE_LOCATIONS - 1; ++i) { - // Throwing exception if the expected HOME - // environment variable is not defined. - - throw std::runtime_error("HOME and AZDCAPCACHE environment variables not defined"); + environment_variable_list += cache_locations[i]; + if (i != CACHE_LOCATIONS - 2) + { + environment_variable_list += ","; + } } - dirname += application_name; - - make_dir(dirname, 0700); - - g_cache_dirname = dirname; + throw std::runtime_error("No cache location was found. Please define one of the following environment variables to enable caching: " + environment_variable_list); } static void init() { - static std::once_flag init_flag; - std::call_once(init_flag, init_callback); + std::lock_guard lock(cache_directory_lock); + if (g_cache_dirname == "") + { + init_callback(); + } } static std::string sha256(size_t data_size, const void* data) @@ -244,6 +264,7 @@ static std::string sha256(const std::string& input) static std::string get_file_name(const std::string& id) { + std::lock_guard lock(cache_directory_lock); return g_cache_dirname + "/" + sha256(id); } @@ -288,6 +309,7 @@ void local_cache_clear() { init(); + std::lock_guard lock(cache_directory_lock); constexpr int MAX_FDS = 4; int rc = nftw(g_cache_dirname.c_str(), delete_path, MAX_FDS, FTW_DEPTH); if (rc != 0) @@ -314,7 +336,7 @@ void local_cache_add( file cache_entry; cache_entry.throw_on_error(); - cache_entry.open(get_file_name(id), O_CREAT | O_WRONLY, 0600); + cache_entry.open(get_file_name(id), O_CREAT | O_WRONLY, 0666); cache_entry.truncate(); cache_entry.write(&header, sizeof(header)); cache_entry.write(data, data_size); diff --git a/src/UnitTests/test_quote_prov.cpp b/src/UnitTests/test_quote_prov.cpp index 4ffe007e..464fbe22 100644 --- a/src/UnitTests/test_quote_prov.cpp +++ b/src/UnitTests/test_quote_prov.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #if defined(__LINUX__) #include @@ -21,6 +22,12 @@ #include #endif +#if defined __LINUX__ +typedef void * libary_type_t; +#else +typedef HINSTANCE libary_type_t; +#endif + typedef quote3_error_t (*sgx_ql_get_quote_config_t)( const sgx_ql_pck_cert_id_t* p_pck_cert_id, sgx_ql_config_t** pp_quote_config); @@ -73,6 +80,22 @@ static sgx_ql_get_root_ca_crl_t sgx_ql_get_root_ca_crl; // Test FMSPC static constexpr uint8_t TEST_FMSPC[] = {0x00, 0x90, 0x6E, 0xA1, 0x00, 0x00}; +// Test input (choose an arbitrary Azure server) +static uint8_t qe_id[16] = { + 0x00, 0xfb, 0xe6, 0x73, 0x33, 0x36, 0xea, 0xf7, + 0xa4, 0xe3, 0xd8, 0xb9, 0x66, 0xa8, 0x2e, 0x64 + }; + +static sgx_cpu_svn_t cpusvn = { + 0x04, 0x04, 0x02, 0x04, 0xff, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + +static sgx_isv_svn_t pcesvn = 6; + +static sgx_ql_pck_cert_id_t id = {qe_id, sizeof(qe_id), &cpusvn, &pcesvn, 0}; + + static void Log(sgx_ql_log_level_t level, const char* message) { char const* levelText = "ERROR"; @@ -193,27 +216,13 @@ static void GetCertsTest() { TEST_START(); - // Setup the input (choose an arbitrary Azure server) - uint8_t qe_id[16] = { - 0x00, 0xfb, 0xe6, 0x73, 0x33, 0x36, 0xea, 0xf7, - 0xa4, 0xe3, 0xd8, 0xb9, 0x66, 0xa8, 0x2e, 0x64 - }; - sgx_cpu_svn_t cpusvn = { - 0x04, 0x04, 0x02, 0x04, 0xff, 0x80, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - sgx_isv_svn_t pcesvn = 6; - - sgx_ql_pck_cert_id_t id = {qe_id, sizeof(qe_id), &cpusvn, &pcesvn, 0}; - + sgx_ql_config_t* config = nullptr; // Get the cert data - sgx_ql_config_t* config; Log(SGX_QL_LOG_INFO , "Calling sgx_ql_get_quote_config"); assert(SGX_QL_SUCCESS == sgx_ql_get_quote_config(&id, &config)); Log(SGX_QL_LOG_INFO , "sgx_ql_get_quote_config returned"); assert(nullptr != config); - + // Just sanity check a few fields. Parsing the certs would require a big // dependency like OpenSSL that we don't necessarily want. constexpr sgx_cpu_svn_t CPU_SVN_MAPPED = { @@ -359,7 +368,7 @@ constexpr auto CURL_TOLERANCE = 0.002; constexpr auto CURL_TOLERANCE = 0.04; #endif -void RunQuoteProviderTests() +void RunQuoteProviderTests(bool caching_enabled=true) { std::clock_t start; double duration_curl; @@ -386,12 +395,63 @@ void RunQuoteProviderTests() GetRootCACrlTest(); GetVerificationCollateralTest(); - // Ensure that there is a signficiant enough difference between the cert - // fetch to the end point and cert fetch to local cache and that local cache - // call is fast enough - assert(fabs(duration_curl - duration_local) > CURL_TOLERANCE); - assert(duration_local < CURL_TOLERANCE); + if (caching_enabled) + { + // Ensure that there is a signficiant enough difference between the cert + // fetch to the end point and cert fetch to local cache and that local cache + // call is fast enough + assert(fabs(duration_curl - duration_local) > CURL_TOLERANCE); + assert(duration_local < CURL_TOLERANCE); + } +} + +#if defined __LINUX__ +void ReloadLibrary(libary_type_t *library) +{ + + dlclose(*library); + *library = LoadFunctions(); + assert(SGX_PLAT_ERROR_OK == sgx_ql_set_logging_function(Log)); +} + +void RunCachePermissionTests(libary_type_t *library) +{ + TEST_START(); + + auto permissions = {0700, 0400, 0200, 0000}; + auto permission_folder = "./test_permissions"; + #if defined __LINUX__ + setenv("AZDCAP_CACHE", permission_folder, 1); + #endif + + // Create the parent folder before the library runs + for (auto permission : permissions) + { + ReloadLibrary(library); + assert(0 == mkdir(permission_folder, permission)); + + RunQuoteProviderTests(permission == 0700); + assert(0 == chmod(permission_folder, 0700)); + assert(0 == system("rm -rf ./test_permissions")); + } + + // Change the permissions on the parent folder after the + // library has used it + for (auto permission : permissions) + { + ReloadLibrary(library); + assert(0 == mkdir(permission_folder, 0700)); + RunQuoteProviderTests(true); + + assert(0 == chmod(permission_folder, permission)); + RunQuoteProviderTests(false); + assert(0 == chmod(permission_folder, 0700)); + assert(0 == system("rm -rf ./test_permissions")); + } + + TEST_PASSED(); } +#endif void SetupEnvironment(std::string version) { @@ -419,32 +479,36 @@ void SetupEnvironment(std::string version) extern void QuoteProvTests() { -#if defined __LINUX__ - void* library = LoadFunctions(); -#else - HINSTANCE library = LoadFunctions(); -#endif + libary_type_t library = LoadFunctions(); assert(SGX_PLAT_ERROR_OK == sgx_ql_set_logging_function(Log)); // - // First pass: Get the data from the service + // Get the data from the service // SetupEnvironment(""); RunQuoteProviderTests(); // - // Second pass: Get the V1 collateral specifically + // Get the V1 collateral specifically // SetupEnvironment("v1"); RunQuoteProviderTests(); // - // Second pass: Get the V2 data from the service + // Get the V2 data from the service // SetupEnvironment("v2"); RunQuoteProviderTests(); GetQveIdentityTest(); + + #if defined __LINUX__ + // + // Run tests to make sure libray can operate + // even if access to filesystem is restricted + // + RunCachePermissionTests(&library); + #endif #if defined __LINUX__ dlclose(library); diff --git a/src/Windows/GeneratePackage/Azure.DCAP.Windows.nuspec b/src/Windows/GeneratePackage/Azure.DCAP.Windows.nuspec index deb8f6c2..d717259a 100644 --- a/src/Windows/GeneratePackage/Azure.DCAP.Windows.nuspec +++ b/src/Windows/GeneratePackage/Azure.DCAP.Windows.nuspec @@ -2,7 +2,7 @@ Microsoft.Azure.DCAP - 1.3.0 + 1.4.0 Microsoft Microsoft diff --git a/src/Windows/README.MD b/src/Windows/README.MD index 08e92846..82534a33 100644 --- a/src/Windows/README.MD +++ b/src/Windows/README.MD @@ -19,12 +19,11 @@ cd src\Windows > If you get an error like the script cannot be loaded because the execution of scripts is disabled on this system, run `Set-ExecutionPolicy bypass` and accept the changes. # Build the Installer -To build the installer Visual Studio 2017 and the WIX toolset are required. +To build the installer Visual Studio 2017 or 2019 and the WIX toolset are required. 1. Downlad the WIX Toolset from https://wixtoolset.org/releases/ and install the recommended Toolset -1. Install the Wix Toolset Visual Studio 2017 Extension. From Visual Studio select Tools->Extensions and Updates and search for "Wix Toolset Visual Studio 2017 Extension" +1. Install the Wix Toolset Visual Studio Extension. From Visual Studio search for "Wix Toolset Visual Studio Extension" 1. Reload the project or re-open `src/Windows/dcap_provider.sln` - # Packaging 1. Run the build script in Release mode 1. Run `nuget pack` in the GeneratePackage directory. If you need to bump the version number diff --git a/src/Windows/dcap_provider_tests/easy_curl_tests.cpp b/src/Windows/dcap_provider_tests/easy_curl_tests.cpp index 14abe909..cde9c629 100644 --- a/src/Windows/dcap_provider_tests/easy_curl_tests.cpp +++ b/src/Windows/dcap_provider_tests/easy_curl_tests.cpp @@ -34,28 +34,31 @@ namespace dcap_provider_tests TEST_METHOD(TestCreateCurlEasy) { - auto curl = curl_easy::create("http://www.microsoft.com"); + auto curl = curl_easy::create("http://www.microsoft.com", nullptr); Assert::IsTrue(static_cast(curl), L"Create curl_easy object."); } TEST_METHOD(TestSimpleNetworkExchanges) { { - auto curl = curl_easy::create("https://www.microsoft.com"); + auto curl = + curl_easy::create("https://www.microsoft.com", nullptr); Assert::IsTrue( static_cast(curl), L"Create curl_easy object."); curl->perform(); } { - auto curl = curl_easy::create("https://www.example.com"); + auto curl = + curl_easy::create("https://www.example.com", nullptr); Assert::IsTrue( static_cast(curl), L"Create curl_easy object."); curl->perform(); } { - auto curl = curl_easy::create("https://www.nonexistanthost.com"); + auto curl = curl_easy::create( + "https://www.nonexistanthost.com", nullptr); Assert::IsTrue( static_cast(curl), L"Create curl_easy object."); @@ -68,7 +71,7 @@ namespace dcap_provider_tests TEST_METHOD(GetHeader) { - auto curl = curl_easy::create("https://www.microsoft.com"); + auto curl = curl_easy::create("https://www.microsoft.com", nullptr); Assert::IsTrue( static_cast(curl), L"Create curl_easy object."); diff --git a/src/Windows/dll/dcap_provider.rc b/src/Windows/dll/dcap_provider.rc index 077ed86d..663c59c2 100644 Binary files a/src/Windows/dll/dcap_provider.rc and b/src/Windows/dll/dcap_provider.rc differ diff --git a/src/Windows/local_cache.cpp b/src/Windows/local_cache.cpp index f51ebe1c..a17cfcae 100644 --- a/src/Windows/local_cache.cpp +++ b/src/Windows/local_cache.cpp @@ -70,7 +70,7 @@ static void init_callback() } else if (wenv_home != L"" && wenv_home[0] != 0) { - dirname = wenv_home; + dirname = wenv_home.append(L"..\\..\\LocalLow"); } else { diff --git a/src/dcap_provider.cpp b/src/dcap_provider.cpp index ea7ebd86..c701d410 100644 --- a/src/dcap_provider.cpp +++ b/src/dcap_provider.cpp @@ -52,7 +52,8 @@ constexpr char REQUEST_ID[] = "Request-ID"; constexpr char CACHE_CONTROL[] = "Cache-Control"; static const std::map default_values = { - {"Content-Type", "application/json"}}; + {"Content-Type", "application/json"} +}; }; // namespace headers @@ -67,8 +68,8 @@ static std::string cert_base_url = DEFAULT_CERT_URL; static char DEFAULT_CLIENT_ID[] = "production_client"; static std::string prod_client_id = DEFAULT_CLIENT_ID; -static char DEFAULT_COLLATARAL_VERSION[] = "v1"; -static std::string default_collateral_version = DEFAULT_COLLATARAL_VERSION; +static char DEFAULT_COLLATERAL_VERSION[] = "v2"; +static std::string default_collateral_version = DEFAULT_COLLATERAL_VERSION; static char CRL_CA_PROCESSOR[] = "processor"; static char CRL_CA_PLATFORM[] = "platform"; @@ -755,6 +756,20 @@ static std::string build_eppid_json(const sgx_ql_pck_cert_id_t& pck_cert_id) return json.str(); } +static std::unique_ptr> try_cache_get( + const std::string& cert_url) +{ + try + { + return local_cache_get(cert_url); + } + catch (std::runtime_error& error) + { + log(SGX_QL_LOG_WARNING, "Unable to access cache: %s", error.what()); + return nullptr; + } +} + extern "C" quote3_error_t sgx_ql_get_quote_config( const sgx_ql_pck_cert_id_t* p_pck_cert_id, sgx_ql_config_t** pp_quote_config) @@ -764,8 +779,12 @@ extern "C" quote3_error_t sgx_ql_get_quote_config( try { const std::string cert_url = build_pck_cert_url(*p_pck_cert_id); - if (auto cache_hit = local_cache_get(cert_url)) + if (auto cache_hit = try_cache_get(cert_url)) { + log(SGX_QL_LOG_INFO, + "Fetching quote config from cache: '%s'.", + cert_url.c_str()); + *pp_quote_config = (sgx_ql_config_t*)(new uint8_t[cache_hit->size()]); memcpy(*pp_quote_config, cache_hit->data(), cache_hit->size()); @@ -862,6 +881,15 @@ extern "C" quote3_error_t sgx_ql_get_quote_config( ? SGX_QL_NO_PLATFORM_CERT_DATA : SGX_QL_ERROR_UNEXPECTED; } + catch (std::runtime_error& error) + { + log(SGX_QL_LOG_ERROR, + "Runtime exception thrown, error: %s", + error.what()); + // Swallow adding file to cache. Library can + // operate without caching + // return SGX_QL_ERROR_UNEXPECTED; + } catch (std::exception& error) { log(SGX_QL_LOG_ERROR,