diff --git a/Makefile b/Makefile index 76ed3451..344d08ec 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,9 @@ CUR_MKFILE:= $(lastword $(MAKEFILE_LIST)) -.PHONY: all clean rebuild QuoteGeneration QuoteVerification PCKCertSelection PCKRetrievalTool SGXPlatformRegistration +.PHONY: all clean rebuild QuoteGeneration QuoteVerification PCKCertSelection PCKRetrievalTool SGXPlatformRegistration WinPle WinPleIntel -all: QuoteGeneration QuoteVerification PCKCertSelection PCKRetrievalTool SGXPlatformRegistration +all: QuoteGeneration QuoteVerification PCKCertSelection PCKRetrievalTool SGXPlatformRegistration WinPle WinPleIntel QuoteGeneration: QuoteVerification $(MAKE) -C QuoteGeneration @@ -50,12 +50,20 @@ PCKRetrievalTool: QuoteGeneration SGXPlatformRegistration: $(MAKE) -C tools/SGXPlatformRegistration +WinPle: + $(MAKE) -C driver/win/PLE + +WinPleIntel: + $(MAKE) -C driver/win/PLE INTEL_SIGNED=1 + clean: $(MAKE) -C QuoteGeneration clean $(MAKE) -C QuoteVerification clean $(MAKE) -C tools/PCKCertSelection clean $(MAKE) -C tools/PCKRetrievalTool clean $(MAKE) -C tools/SGXPlatformRegistration clean + $(MAKE) -C driver/win/PLE clean + $(MAKE) -C driver/win/PLE INTEL_SIGNED=1 clean rebuild: $(MAKE) -f $(CUR_MKFILE) clean diff --git a/QuoteGeneration/Makefile b/QuoteGeneration/Makefile index 4c725b54..0310d028 100644 --- a/QuoteGeneration/Makefile +++ b/QuoteGeneration/Makefile @@ -117,11 +117,13 @@ tdx_qgs deb_sgx_tdx_qgs_pkg: tdx_attest deb_sgx_tdx_attest_pkg: echo "Skip tdx_attest in ubuntu 18.04" else +qgs_msg_lib: + $(MAKE) -C quote_wrapper/qgs_msg_lib/linux tdx_logic: pce_logic $(MAKE) -C quote_wrapper/tdx_quote/linux -tdx_qgs: tdx_logic +tdx_qgs: tdx_logic qgs_msg_lib $(MAKE) -C quote_wrapper/qgs -tdx_attest: +tdx_attest: qgs_msg_lib $(MAKE) -C quote_wrapper/tdx_attest/linux deb_sgx_ae_tdqe_pkg: $(CHECK_OPT) ./installer/linux/deb/libsgx-ae-tdqe/build.sh diff --git a/QuoteGeneration/README.md b/QuoteGeneration/README.md index 9947b0c3..eff846d6 100644 --- a/QuoteGeneration/README.md +++ b/QuoteGeneration/README.md @@ -39,7 +39,7 @@ For Windows* OS **NOTE**:`sgx_dcap_dev.inf` is for Windows* Server 2016 LTSC and `sgx_dcap.inf` is for Windows* Server 2019 LTSC. ## How to install - Refer to the *"Installation Instructions"* section in the [Intel(R) Software Guard Extensions: Data Center Attestation Primitives Installation Guide For Windows* OS](https://download.01.org/intel-sgx/sgx-dcap/1.15/windows/docs/Intel_SGX_DCAP_Windows_SW_Installation_Guide.pdf) to install the right packages on your platform. + Refer to the *"Installation Instructions"* section in the [Intel(R) Software Guard Extensions: Data Center Attestation Primitives Installation Guide For Windows* OS](https://download.01.org/intel-sgx/sgx-dcap/1.16/windows/docs/Intel_SGX_DCAP_Windows_SW_Installation_Guide.pdf) to install the right packages on your platform. For Linux* OS diff --git a/QuoteGeneration/common/inc/internal/se_version.h b/QuoteGeneration/common/inc/internal/se_version.h index a2812cc6..865a12a1 100644 --- a/QuoteGeneration/common/inc/internal/se_version.h +++ b/QuoteGeneration/common/inc/internal/se_version.h @@ -28,21 +28,21 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#define STRFILEVER "1.15.100.3" -#define COPYRIGHT "Copyright (C) 2022 Intel Corporation" -#define FILEVER 1,15,100,3 -#define PRODUCTVER 1,15,100,3 -#define STRPRODUCTVER "1.15.100.3" +#define STRFILEVER "1.16.100.2" +#define COPYRIGHT "Copyright (C) 2023 Intel Corporation" +#define FILEVER 1,16,100,2 +#define PRODUCTVER 1,16,100,2 +#define STRPRODUCTVER "1.16.100.2" #define COMPANYNAME "Intel Corporation" #define PRODUCTNAME "IntelĀ® Software Guard Extensions" -#define DEFAULT_QPL_VERSION "1.13.102.3" -#define QUOTE_VERIFIER_VERSION "1.12.102.3" -#define QUOTE_LOADER_VERSION "1.11.105.3" -#define TDQE_WRAPPER_VERSION "1.14.102.3" -#define PCE_WRAPPER_VERSION "1.14.102.3" +#define DEFAULT_QPL_VERSION "1.13.103.2" +#define QUOTE_VERIFIER_VERSION "1.12.103.2" +#define QUOTE_LOADER_VERSION "1.11.106.2" +#define TDQE_WRAPPER_VERSION "1.14.103.2" +#define PCE_WRAPPER_VERSION "1.14.103.2" -#define QE3_VERSION "1.15.100.1" -#define QVE_VERSION "1.15.100.1" -#define IDE_VERSION "1.14.100.2" -#define TDQE_VERSION "1.14.100.2" +#define QE3_VERSION "1.16.100.1" +#define QVE_VERSION "1.16.100.1" +#define IDE_VERSION "1.16.100.1" +#define TDQE_VERSION "1.16.100.1" diff --git a/QuoteGeneration/common/src/se_thread.c b/QuoteGeneration/common/src/se_thread.c index f4904e0a..caba85b5 100644 --- a/QuoteGeneration/common/src/se_thread.c +++ b/QuoteGeneration/common/src/se_thread.c @@ -30,7 +30,7 @@ */ #include "se_thread.h" #include "se_types.h" - +#include "se_memcpy.h" #if defined(_MSC_VER) @@ -68,7 +68,7 @@ void se_mutex_init(se_mutex_t* mutex) #endif /* C doesn't allow `*mutex = PTHREAD_..._INITIALIZER'.*/ - memcpy(mutex, &tmp, sizeof(tmp)); + memcpy_s(mutex, sizeof(tmp), &tmp, sizeof(tmp)); } int se_mutex_lock(se_mutex_t* mutex) { return (0 == pthread_mutex_lock(mutex)); } @@ -78,7 +78,7 @@ int se_mutex_destroy(se_mutex_t* mutex) { return (0 == pthread_mutex_destroy(mut void se_thread_cond_init(se_cond_t* cond) { se_cond_t tmp = PTHREAD_COND_INITIALIZER; - memcpy(cond, &tmp, sizeof(tmp)); + memcpy_s(cond, sizeof(tmp), &tmp, sizeof(tmp)); } int se_thread_cond_wait(se_cond_t *cond, se_mutex_t *mutex){return (0 == pthread_cond_wait(cond, mutex));} diff --git a/QuoteGeneration/download_prebuilt.bat b/QuoteGeneration/download_prebuilt.bat index ca6ef00b..68f9f875 100644 --- a/QuoteGeneration/download_prebuilt.bat +++ b/QuoteGeneration/download_prebuilt.bat @@ -29,9 +29,9 @@ @echo off -set ae_file_name=prebuilt_windows_dcap_1.15.zip -set checksum_file=SHA256SUM_prebuilt_windows_dcap_1.15.cfg -set server_url_path=https://download.01.org/intel-sgx/sgx-dcap/1.15/windows/ +set ae_file_name=prebuilt_windows_dcap_1.16.zip +set checksum_file=SHA256SUM_prebuilt_windows_dcap_1.16.cfg +set server_url_path=https://download.01.org/intel-sgx/sgx-dcap/1.16/windows/ set server_ae_url=%server_url_path%/%ae_file_name% set server_checksum_url=%server_url_path%/%checksum_file% diff --git a/QuoteGeneration/download_prebuilt.sh b/QuoteGeneration/download_prebuilt.sh index ac547d65..c6cd7efb 100755 --- a/QuoteGeneration/download_prebuilt.sh +++ b/QuoteGeneration/download_prebuilt.sh @@ -32,9 +32,9 @@ top_dir=`dirname $0` out_dir=$top_dir -ae_file_name=prebuilt_dcap_1.15.tar.gz -checksum_file=SHA256SUM_prebuilt_dcap_1.15.cfg -server_url_path=https://download.01.org/intel-sgx/sgx-dcap/1.15/linux/ +ae_file_name=prebuilt_dcap_1.16.tar.gz +checksum_file=SHA256SUM_prebuilt_dcap_1.16.cfg +server_url_path=https://download.01.org/intel-sgx/sgx-dcap/1.16/linux/ server_ae_url=$server_url_path/$ae_file_name server_checksum_url=$server_url_path/$checksum_file diff --git a/QuoteGeneration/installer/linux/deb/libsgx-dcap-ql/libsgx-dcap-ql-1.0/debian/control b/QuoteGeneration/installer/linux/deb/libsgx-dcap-ql/libsgx-dcap-ql-1.0/debian/control index 0ed7758c..41595d13 100644 --- a/QuoteGeneration/installer/linux/deb/libsgx-dcap-ql/libsgx-dcap-ql-1.0/debian/control +++ b/QuoteGeneration/installer/linux/deb/libsgx-dcap-ql/libsgx-dcap-ql-1.0/debian/control @@ -9,11 +9,11 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: libsgx-dcap-ql Architecture: amd64 Depends: libsgx-qe3-logic(>= @dep_version@), libsgx-pce-logic(>= @dep_version@), ${shlibs:Depends}, ${misc:Depends} -Recommends: libsgx-dcap-quote-verify(>= @dep_version@), libsgx-quote-ex(>= 2.18) +Recommends: libsgx-dcap-quote-verify(>= @dep_version@), libsgx-quote-ex(>= 2.19) Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives Package: libsgx-dcap-ql-dev Section: devel Architecture: amd64 -Depends: libsgx-dcap-ql (= @dep_version@), libsgx-headers (>= 2.18) +Depends: libsgx-dcap-ql (= @dep_version@), libsgx-headers (>= 2.19) Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives For Developers diff --git a/QuoteGeneration/installer/linux/deb/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify-1.0/debian/control b/QuoteGeneration/installer/linux/deb/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify-1.0/debian/control index 2e92777d..c61d1582 100644 --- a/QuoteGeneration/installer/linux/deb/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify-1.0/debian/control +++ b/QuoteGeneration/installer/linux/deb/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify-1.0/debian/control @@ -9,11 +9,11 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: libsgx-dcap-quote-verify Architecture: amd64 Depends: ${shlibs:Depends}, ${misc:Depends} -Recommends: libsgx-ae-qve (>= @dep_version@), libsgx-urts (>= 2.18) +Recommends: libsgx-ae-qve (>= @dep_version@), libsgx-urts (>= 2.19) Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives Package: libsgx-dcap-quote-verify-dev Section: devel Architecture: amd64 -Depends: libsgx-dcap-quote-verify (= @dep_version@), libsgx-headers (>= 2.18) +Depends: libsgx-dcap-quote-verify (= @dep_version@), libsgx-headers (>= 2.19) Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives For Developers diff --git a/QuoteGeneration/installer/linux/deb/libsgx-pce-logic/libsgx-pce-logic-1.0/debian/control b/QuoteGeneration/installer/linux/deb/libsgx-pce-logic/libsgx-pce-logic-1.0/debian/control index 149999f0..ff897bed 100644 --- a/QuoteGeneration/installer/linux/deb/libsgx-pce-logic/libsgx-pce-logic-1.0/debian/control +++ b/QuoteGeneration/installer/linux/deb/libsgx-pce-logic/libsgx-pce-logic-1.0/debian/control @@ -8,5 +8,5 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: libsgx-pce-logic Architecture: amd64 -Depends: libsgx-urts (>= 2.18), libsgx-ae-pce(>= 2.18), ${shlibs:Depends}, ${misc:Depends} +Depends: libsgx-urts (>= 2.19), libsgx-ae-pce(>= 2.19), ${shlibs:Depends}, ${misc:Depends} Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives diff --git a/QuoteGeneration/installer/linux/deb/libsgx-qe3-logic/libsgx-qe3-logic-1.0/debian/control b/QuoteGeneration/installer/linux/deb/libsgx-qe3-logic/libsgx-qe3-logic-1.0/debian/control index 70c4a03e..bff97919 100644 --- a/QuoteGeneration/installer/linux/deb/libsgx-qe3-logic/libsgx-qe3-logic-1.0/debian/control +++ b/QuoteGeneration/installer/linux/deb/libsgx-qe3-logic/libsgx-qe3-logic-1.0/debian/control @@ -8,5 +8,5 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: libsgx-qe3-logic Architecture: amd64 -Depends: libsgx-urts (>= 2.18), libsgx-ae-qe3(>= @dep_version@), libsgx-ae-id-enclave(>= @dep_version@), ${shlibs:Depends}, ${misc:Depends} +Depends: libsgx-urts (>= 2.19), libsgx-ae-qe3(>= @dep_version@), libsgx-ae-id-enclave(>= @dep_version@), ${shlibs:Depends}, ${misc:Depends} Description: Intel(R) Software Guard Extensions Data Center Attestation Primitives diff --git a/QuoteGeneration/installer/linux/deb/libsgx-tdx-logic/libsgx-tdx-logic-1.0/debian/control b/QuoteGeneration/installer/linux/deb/libsgx-tdx-logic/libsgx-tdx-logic-1.0/debian/control index 78b29d33..62b3e499 100644 --- a/QuoteGeneration/installer/linux/deb/libsgx-tdx-logic/libsgx-tdx-logic-1.0/debian/control +++ b/QuoteGeneration/installer/linux/deb/libsgx-tdx-logic/libsgx-tdx-logic-1.0/debian/control @@ -8,11 +8,11 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: libsgx-tdx-logic Architecture: amd64 -Depends: libsgx-urts (>= 2.18), libsgx-pce-logic(>= @dep_version@), libsgx-ae-tdqe(>= @dep_version@), libsgx-ae-id-enclave(>= @dep_version@), ${shlibs:Depends}, ${misc:Depends} +Depends: libsgx-urts (>= 2.19), libsgx-pce-logic(>= @dep_version@), libsgx-ae-tdqe(>= @dep_version@), libsgx-ae-id-enclave(>= @dep_version@), ${shlibs:Depends}, ${misc:Depends} Description: Intel(R) Trust Domain Extensions QE logic library Package: libsgx-tdx-logic-dev Section: devel Architecture: amd64 -Depends: libsgx-tdx-logic (= @dep_version@) +Depends: libsgx-tdx-logic (= @dep_version@), libsgx-headers (>= 2.19) Description: Intel(R) Trust Domain Extensions QE logic library For Developers diff --git a/QuoteGeneration/installer/linux/rpm/libsgx-dcap-ql/libsgx-dcap-ql.spec b/QuoteGeneration/installer/linux/rpm/libsgx-dcap-ql/libsgx-dcap-ql.spec index e6a23036..5eb5bdc8 100644 --- a/QuoteGeneration/installer/linux/rpm/libsgx-dcap-ql/libsgx-dcap-ql.spec +++ b/QuoteGeneration/installer/linux/rpm/libsgx-dcap-ql/libsgx-dcap-ql.spec @@ -37,7 +37,7 @@ Release: 1%{?dist} Summary: Intel(R) Software Guard Extensions Data Center Attestation Primitives Group: Development/Libraries Requires: libsgx-qe3-logic >= %{version}-%{release} libsgx-pce-logic >= %{version}-%{release} -Recommends: libsgx-dcap-quote-verify >= %{version}-%{release} libsgx-quote-ex >= 2.18 +Recommends: libsgx-dcap-quote-verify >= %{version}-%{release} libsgx-quote-ex >= 2.19 License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives @@ -49,7 +49,7 @@ Intel(R) Software Guard Extensions Data Center Attestation Primitives %package devel Summary: Intel(R) Software Guard Extensions Data Center Attestation Primitives for Developers Group: Development/Libraries -Requires: %{name} = %{version}-%{release} libsgx-headers >= 2.18 +Requires: %{name} = %{version}-%{release} libsgx-headers >= 2.19 %description devel Intel(R) Software Guard Extensions Data Center Attestation Primitives for Developers diff --git a/QuoteGeneration/installer/linux/rpm/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify.spec b/QuoteGeneration/installer/linux/rpm/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify.spec index a89d4e07..15f62775 100644 --- a/QuoteGeneration/installer/linux/rpm/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify.spec +++ b/QuoteGeneration/installer/linux/rpm/libsgx-dcap-quote-verify/libsgx-dcap-quote-verify.spec @@ -36,7 +36,7 @@ Version: @version@ Release: 1%{?dist} Summary: Intel(R) Software Guard Extensions Data Center Attestation Primitives Group: Development/Libraries -Recommends: libsgx-ae-qve >= %{version}-%{release} libsgx-urts >= 2.18 +Recommends: libsgx-ae-qve >= %{version}-%{release} libsgx-urts >= 2.19 License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives @@ -48,7 +48,7 @@ Intel(R) Software Guard Extensions Data Center Attestation Primitives %package devel Summary: Intel(R) Software Guard Extensions Data Center Attestation Primitives for Developers Group: Development/Libraries -Requires: %{name} = %{version}-%{release} libsgx-headers >= 2.18 +Requires: %{name} = %{version}-%{release} libsgx-headers >= 2.19 %description devel Intel(R) Software Guard Extensions Data Center Attestation Primitives for Developers diff --git a/QuoteGeneration/installer/linux/rpm/libsgx-pce-logic/libsgx-pce-logic.spec b/QuoteGeneration/installer/linux/rpm/libsgx-pce-logic/libsgx-pce-logic.spec index 91a29eac..e5e9dff0 100644 --- a/QuoteGeneration/installer/linux/rpm/libsgx-pce-logic/libsgx-pce-logic.spec +++ b/QuoteGeneration/installer/linux/rpm/libsgx-pce-logic/libsgx-pce-logic.spec @@ -36,7 +36,7 @@ Version: @version@ Release: 1%{?dist} Summary: Intel(R) Software Guard Extensions PCE logic Group: Development/Libraries -Requires: libsgx-urts >= 2.18 libsgx-ae-pce >= 2.18 +Requires: libsgx-urts >= 2.19 libsgx-ae-pce >= 2.19 License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives diff --git a/QuoteGeneration/installer/linux/rpm/libsgx-qe3-logic/libsgx-qe3-logic.spec b/QuoteGeneration/installer/linux/rpm/libsgx-qe3-logic/libsgx-qe3-logic.spec index 993e5e96..6cef823e 100644 --- a/QuoteGeneration/installer/linux/rpm/libsgx-qe3-logic/libsgx-qe3-logic.spec +++ b/QuoteGeneration/installer/linux/rpm/libsgx-qe3-logic/libsgx-qe3-logic.spec @@ -36,7 +36,7 @@ Version: @version@ Release: 1%{?dist} Summary: Intel(R) Software Guard Extensions QE3 logic Group: Development/Libraries -Requires: libsgx-urts >= 2.18 libsgx-ae-qe3 >= %{version}-%{release} libsgx-ae-id-enclave >= %{version}-%{release} +Requires: libsgx-urts >= 2.19 libsgx-ae-qe3 >= %{version}-%{release} libsgx-ae-id-enclave >= %{version}-%{release} License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives diff --git a/QuoteGeneration/installer/linux/rpm/libsgx-tdx-logic/libsgx-tdx-logic.spec b/QuoteGeneration/installer/linux/rpm/libsgx-tdx-logic/libsgx-tdx-logic.spec index 93d79c36..39da1c0c 100644 --- a/QuoteGeneration/installer/linux/rpm/libsgx-tdx-logic/libsgx-tdx-logic.spec +++ b/QuoteGeneration/installer/linux/rpm/libsgx-tdx-logic/libsgx-tdx-logic.spec @@ -36,7 +36,7 @@ Version: @version@ Release: 1%{?dist} Summary: Intel(R) Trust Domain Extensions QE logic library Group: Development/Libraries -Requires: libsgx-urts >= 2.18 libsgx-ae-tdqe >= %{version}-%{release} libsgx-ae-id-enclave >= %{version}-%{release} libsgx-pce-logic >= %{version}-%{release} +Requires: libsgx-urts >= 2.19 libsgx-ae-tdqe >= %{version}-%{release} libsgx-ae-id-enclave >= %{version}-%{release} libsgx-pce-logic >= %{version}-%{release} License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives @@ -49,7 +49,7 @@ Intel(R) Trust Domain Extensions QE logic library %package devel Summary: Intel(R) Trust Domain Extensions QE logic library For Developers Group: Development/Libraries -Requires: %{name} = %{version}-%{release} +Requires: %{name} = %{version}-%{release} libsgx-headers >= 2.19 %description devel Intel(R) Trust Domain Extensions QE logic library For Developers %prep diff --git a/QuoteGeneration/installer/win/DCAP_Components.bat b/QuoteGeneration/installer/win/DCAP_Components.bat index 98f6c945..f43f478a 100644 --- a/QuoteGeneration/installer/win/DCAP_Components.bat +++ b/QuoteGeneration/installer/win/DCAP_Components.bat @@ -5,7 +5,7 @@ set QGFOLDER="..\..\" set QVFOLDER="%TOPFOLDER%\QuoteVerification" set DEBUGFILEFOLDER="..\..\..\x64\Debug\" set RELEASEFILEFOLDER="..\..\..\x64\Release\" -set PACKAGETNAME=DCAP_Components.1.14.100.0 +set PACKAGETNAME=DCAP_Components.1.16.100.0 set pwd=%~dp0DCAP_Components pushd "%~dp0" diff --git a/QuoteGeneration/installer/win/DCAP_Components/DCAP_Components.nuspec b/QuoteGeneration/installer/win/DCAP_Components/DCAP_Components.nuspec index ed4ad2d5..3cc12d20 100644 --- a/QuoteGeneration/installer/win/DCAP_Components/DCAP_Components.nuspec +++ b/QuoteGeneration/installer/win/DCAP_Components/DCAP_Components.nuspec @@ -2,15 +2,15 @@ DCAP_Components - 1.15.100.3 + 1.16.100.2 DCAP Components Intel(R) SGX Intel false DCAP Components - Copyright (C) 2022 Intel Corporation + Copyright (C) 2023 Intel Corporation - + diff --git a/QuoteGeneration/pccs/config/default.json b/QuoteGeneration/pccs/config/default.json index 77b05f99..7f65bfbd 100644 --- a/QuoteGeneration/pccs/config/default.json +++ b/QuoteGeneration/pccs/config/default.json @@ -36,6 +36,7 @@ "password" : "mypass", "options" : { "host": "localhost", + "port": "3306", "dialect": "mysql", "pool": { "max": 5, diff --git a/QuoteGeneration/pccs/container/Dockerfile b/QuoteGeneration/pccs/container/Dockerfile index 463e5e84..e6dfe4f9 100644 --- a/QuoteGeneration/pccs/container/Dockerfile +++ b/QuoteGeneration/pccs/container/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:20.04 AS builder # DCAP version (github repo branch, tag or commit hash) -ARG DCAP_VERSION=DCAP_1.15 +ARG DCAP_VERSION=DCAP_1.16 # update and install packages RUN DEBIAN_FRONTEND=noninteractive \ diff --git a/QuoteGeneration/pccs/controllers/crlController.js b/QuoteGeneration/pccs/controllers/crlController.js index b234d713..1ecc51cc 100644 --- a/QuoteGeneration/pccs/controllers/crlController.js +++ b/QuoteGeneration/pccs/controllers/crlController.js @@ -43,6 +43,13 @@ export async function getCrl(req, res, next) { throw new PccsError(PccsStatus.PCCS_STATUS_INVALID_REQ); } + // validate uri + let found_root = uri.match(/https:\/\/([a-zA-Z0-9-]*certificates\.trustedservices\.intel\.com|certprx\.adsdcsp\.com)\/IntelSGXRootCA\..*/); + let found_intermediate = uri.match(/https:\/\/([a-zA-Z0-9-]*\.?api\.trustedservices\.intel\.com|[a-zA-Z0-9-]+\.az\.sgx(prod|np)\.adsdcsp\.com)\/sgx\/certification\/v([1-9][0-9]*)\/pckcrl\?.*/); + if (!found_root && !found_intermediate) { + throw new PccsError(PccsStatus.PCCS_STATUS_INVALID_REQ); + } + // call service let crl = await crlService.getCrl(uri); diff --git a/QuoteGeneration/pccs/dao/models/index.js b/QuoteGeneration/pccs/dao/models/index.js index e7cad870..56754f6e 100644 --- a/QuoteGeneration/pccs/dao/models/index.js +++ b/QuoteGeneration/pccs/dao/models/index.js @@ -44,6 +44,7 @@ import PlatformsRegistered from './platforms_registered.js'; import Platforms from './platforms.js'; import EnclaveIdentities from './enclave_identities.js'; import CrlCache from './crl_cache.js'; +import mysqlPromise from 'mysql2/promise.js'; const pccs_namespace = clshooked.createNamespace('pccs-namespace'); Sequelize.useCLS(pccs_namespace); @@ -62,6 +63,33 @@ const sequelize = new Sequelize( db_opt ); +try { + // Test connection + await sequelize.authenticate(); +} catch (err) { + if (Config.get('DB_CONFIG') == 'mysql') { + logger.error('Failed to connect DB. Try to create it ...'); + try { + // For MySQL, maybe the database doesn't exist. Try to create it + const connection = await mysqlPromise.createConnection({ + host: db_opt.host, + port: db_opt.port, + user: db_conf.username, + password: db_conf.password, + }); + await connection.query( + `CREATE DATABASE IF NOT EXISTS \`${db_conf.database}\` CHARACTER SET utf8 COLLATE utf8_general_ci;` + ); + } catch (err2) { + logger.error(err2); + process.exit(1); + } + } else { + logger.error(err); + process.exit(1); + } +} + FmspcTcbs.init(sequelize); PckCert.init(sequelize); PckCertchain.init(sequelize); diff --git a/QuoteGeneration/pccs/utils/apputil.js b/QuoteGeneration/pccs/utils/apputil.js index e2e4ed81..c5c2b2d6 100644 --- a/QuoteGeneration/pccs/utils/apputil.js +++ b/QuoteGeneration/pccs/utils/apputil.js @@ -71,14 +71,6 @@ export function startup_check() { return true; } -async function test_connection() { - try { - return await sequelize.authenticate(); - } catch (err) { - throw new Error('Failed to connect to the database.'); - } -} - async function test_db_status() { const sql = 'select * from pck_crl'; try { @@ -143,8 +135,6 @@ async function db_migration() { export async function database_check() { try { - await test_connection(); - if (Config.has('init_db') && Config.get('init_db') == false) { return true; } diff --git a/QuoteGeneration/pce_wrapper/pce_wrapper.cpp b/QuoteGeneration/pce_wrapper/pce_wrapper.cpp index 492dbaa9..e84b4a16 100644 --- a/QuoteGeneration/pce_wrapper/pce_wrapper.cpp +++ b/QuoteGeneration/pce_wrapper/pce_wrapper.cpp @@ -59,6 +59,7 @@ struct PCE_status { sgx_ql_request_policy_t m_pce_enclave_load_policy; sgx_enclave_id_t m_pce_eid; sgx_misc_attribute_t m_pce_attributes; + metadata_t m_pce_metadata; char pce_path[MAX_PATH]; PCE_status() : @@ -67,6 +68,7 @@ struct PCE_status { { se_mutex_init(&m_pce_mutex); memset(&m_pce_attributes, 0, sizeof(m_pce_attributes)); + memset(&m_pce_metadata, 0, sizeof(m_pce_metadata)); memset(pce_path, 0, sizeof(pce_path)); } ~PCE_status() { @@ -150,17 +152,16 @@ bool get_pce_path( static sgx_pce_error_t load_pce(sgx_enclave_id_t *p_pce_eid, sgx_misc_attribute_t *p_pce_attributes, - sgx_launch_token_t *p_launch_token) + metadata_t *p_metadata) { sgx_status_t sgx_status = SGX_SUCCESS; + sgx_pce_error_t ret = SGX_PCE_INTERFACE_UNAVAILABLE; int enclave_lost_retry_time = 1; - int launch_token_updated = 0; #if defined(_MSC_VER) TCHAR pce_enclave_path[MAX_PATH] = _T(""); #else char pce_enclave_path[MAX_PATH] = ""; #endif - memset(p_launch_token, 0, sizeof(*p_launch_token)); int rc = se_mutex_lock(&g_pce_status.m_pce_mutex); if (rc != 1) @@ -169,54 +170,61 @@ static sgx_pce_error_t load_pce(sgx_enclave_id_t *p_pce_eid, return SGX_PCE_INTERFACE_UNAVAILABLE; } - // Load the PCE - if (g_pce_status.m_pce_eid == 0) - { - if (!get_pce_path(pce_enclave_path, MAX_PATH)) - return SGX_PCE_INTERFACE_UNAVAILABLE; - do + do { + // Load the PCE + if (g_pce_status.m_pce_eid == 0) { - SE_TRACE(SE_TRACE_DEBUG, "Call sgx_create_enclave for PCE. %s\n", pce_enclave_path); - sgx_status = sgx_create_enclave(pce_enclave_path, - 0, - p_launch_token, - &launch_token_updated, - p_pce_eid, - p_pce_attributes); - if (SGX_SUCCESS != sgx_status) - { - SE_PROD_LOG("Error, call sgx_create_enclave for PCE fail [%s], SGXError:%04x.\n", __FUNCTION__, sgx_status); - } + if (!get_pce_path(pce_enclave_path, MAX_PATH)) + break; + if (SGX_SUCCESS != sgx_get_metadata(pce_enclave_path, &g_pce_status.m_pce_metadata)) + break; - // Retry in case there was a power transition that resulted is losing the enclave. - } while (SGX_ERROR_ENCLAVE_LOST == sgx_status && enclave_lost_retry_time--); - if (sgx_status != SGX_SUCCESS) - { - rc = se_mutex_unlock(&g_pce_status.m_pce_mutex); - if (rc != 1) + do + { + sgx_launch_token_t launch_token = { 0 }; + int launch_token_updated; + SE_TRACE(SE_TRACE_DEBUG, "Call sgx_create_enclave for PCE. %s\n", pce_enclave_path); + sgx_status = sgx_create_enclave(pce_enclave_path, + 0, + &launch_token, + &launch_token_updated, + p_pce_eid, + p_pce_attributes); + if (SGX_SUCCESS != sgx_status) + { + SE_PROD_LOG("Error, call sgx_create_enclave for PCE fail [%s], SGXError:%04x.\n", __FUNCTION__, sgx_status); + } + + // Retry in case there was a power transition that resulted is losing the enclave. + } while (SGX_ERROR_ENCLAVE_LOST == sgx_status && enclave_lost_retry_time--); + if (sgx_status != SGX_SUCCESS) { - SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex"); - return SGX_PCE_INTERFACE_UNAVAILABLE; + if (sgx_status == SGX_ERROR_OUT_OF_EPC) + ret = SGX_PCE_OUT_OF_EPC; + else + ret = SGX_PCE_INTERFACE_UNAVAILABLE; + break; } - if (sgx_status == SGX_ERROR_OUT_OF_EPC) - return SGX_PCE_OUT_OF_EPC; - else - return SGX_PCE_INTERFACE_UNAVAILABLE; + g_pce_status.m_pce_eid = *p_pce_eid; + memcpy_s(&g_pce_status.m_pce_attributes, sizeof(sgx_misc_attribute_t), p_pce_attributes, sizeof(sgx_misc_attribute_t)); } - g_pce_status.m_pce_eid = *p_pce_eid; - memcpy_s(&g_pce_status.m_pce_attributes, sizeof(sgx_misc_attribute_t), p_pce_attributes, sizeof(sgx_misc_attribute_t)); - } - else { - *p_pce_eid = g_pce_status.m_pce_eid; - memcpy_s(p_pce_attributes, sizeof(sgx_misc_attribute_t), &g_pce_status.m_pce_attributes, sizeof(sgx_misc_attribute_t)); - } + else { + *p_pce_eid = g_pce_status.m_pce_eid; + memcpy_s(p_pce_attributes, sizeof(sgx_misc_attribute_t), &g_pce_status.m_pce_attributes, sizeof(sgx_misc_attribute_t)); + } + if (p_metadata) + memcpy_s(p_metadata, sizeof(metadata_t), &g_pce_status.m_pce_metadata, sizeof(metadata_t)); + + ret = SGX_PCE_SUCCESS; + } while(0); + rc = se_mutex_unlock(&g_pce_status.m_pce_mutex); if (rc != 1) { SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex"); return SGX_PCE_INTERFACE_UNAVAILABLE; } - return SGX_PCE_SUCCESS; + return ret; } static void unload_pce(bool force = false) @@ -260,31 +268,17 @@ sgx_pce_error_t sgx_pce_get_target(sgx_target_info_t *p_target, { sgx_misc_attribute_t pce_attributes; sgx_enclave_id_t pce_eid = 0; - sgx_launch_token_t launch_token = { 0 }; metadata_t metadata; -#if defined(_MSC_VER) - TCHAR pce_enclave_path[MAX_PATH] = _T(""); -#else - char pce_enclave_path[MAX_PATH] = ""; -#endif if ((NULL == p_target) || (NULL == p_isvsvn)) { return(SGX_PCE_INVALID_PARAMETER); } - if (!get_pce_path(pce_enclave_path, MAX_PATH)) - return SGX_PCE_INTERFACE_UNAVAILABLE; - - if (SGX_SUCCESS != sgx_get_metadata(pce_enclave_path, &metadata)) - { - return SGX_PCE_INTERFACE_UNAVAILABLE; - } - // Load the PCE enclave sgx_pce_error_t pce_status = load_pce(&pce_eid, &pce_attributes, - &launch_token); + &metadata); if (SGX_PCE_SUCCESS != pce_status) { return pce_status; @@ -319,7 +313,6 @@ sgx_pce_error_t sgx_get_pce_info(const sgx_report_t *p_report, sgx_enclave_id_t pce_eid = 0; sgx_status_t sgx_status = SGX_SUCCESS; sgx_misc_attribute_t pce_attributes; - sgx_launch_token_t launch_token = { 0 }; uint32_t ae_error; uint32_t enclave_lost_retry_time = 1; pce_info_t pce_info; @@ -339,7 +332,7 @@ sgx_pce_error_t sgx_get_pce_info(const sgx_report_t *p_report, // Load the PCE enclave pce_status = load_pce(&pce_eid, &pce_attributes, - &launch_token); + NULL); if (SGX_PCE_SUCCESS != pce_status) { return pce_status; @@ -420,7 +413,6 @@ sgx_pce_error_t sgx_get_pce_info_without_ppid(sgx_isv_svn_t* p_pce_isvsvn, uint1 sgx_enclave_id_t pce_eid = 0; sgx_status_t sgx_status = SGX_SUCCESS; sgx_misc_attribute_t pce_attributes; - sgx_launch_token_t launch_token = { 0 }; uint32_t ae_error; uint32_t enclave_lost_retry_time = 1; pce_info_t pce_info; @@ -435,7 +427,7 @@ sgx_pce_error_t sgx_get_pce_info_without_ppid(sgx_isv_svn_t* p_pce_isvsvn, uint1 // Load the PCE enclave pce_status = load_pce(&pce_eid, &pce_attributes, - &launch_token); + NULL); if (SGX_PCE_SUCCESS != pce_status) { return pce_status; @@ -499,7 +491,6 @@ sgx_pce_error_t sgx_pce_sign_report(const sgx_isv_svn_t *p_isv_svn, sgx_enclave_id_t pce_eid = 0; sgx_status_t sgx_status = SGX_SUCCESS; sgx_misc_attribute_t pce_attributes; - sgx_launch_token_t launch_token = { 0 }; uint32_t ae_error; uint32_t enclave_lost_retry_time = 1; psvn_t psvn; @@ -520,7 +511,7 @@ sgx_pce_error_t sgx_pce_sign_report(const sgx_isv_svn_t *p_isv_svn, // Load the PCE enclave pce_status = load_pce(&pce_eid, &pce_attributes, - &launch_token); + NULL); if (SGX_PCE_SUCCESS != pce_status) { return pce_status; diff --git a/QuoteGeneration/psw/ae/data/prebuilt/README.md b/QuoteGeneration/psw/ae/data/prebuilt/README.md index e2ed3547..80579f4d 100644 --- a/QuoteGeneration/psw/ae/data/prebuilt/README.md +++ b/QuoteGeneration/psw/ae/data/prebuilt/README.md @@ -1,14 +1,14 @@ # PCE source code -The PCE is part of Intel(R) Software Guard Extensions for Linux\* OS which is published in [linux-sgx](https://github.com/intel/linux-sgx/) Github repository. The libsgx_pce.signed.so in prebuilt package is built by [pce](https://github.com/intel/linux-sgx/tree/master/psw/ae/pce) with branch [sgx_2.17_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.17_reproducible) and signed by Intel. +The PCE is part of Intel(R) Software Guard Extensions for Linux\* OS which is published in [linux-sgx](https://github.com/intel/linux-sgx/) Github repository. The libsgx_pce.signed.so in prebuilt package is built by [pce](https://github.com/intel/linux-sgx/tree/master/psw/ae/pce) with branch [sgx_2.19_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.19_reproducible) and signed by Intel. # QE3 source code -The QE3 is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_qe3.signed.so in prebuilt package is built by [qe3](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/quote/enclave) with branch [sgx_2.18_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.18_reproducible) and signed by Intel. +The QE3 is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_qe3.signed.so in prebuilt package is built by [qe3](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/quote/enclave) with branch [sgx_2.19_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.19_reproducible) and signed by Intel. # QVE source code -The QVE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_qve.signed.so in prebuilt package is built by [qve](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteVerification/QvE/Enclave) with branch [sgx_2.18_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.18_reproducible)and signed by Intel. +The QVE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_qve.signed.so in prebuilt package is built by [qve](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteVerification/QvE/Enclave) with branch [sgx_2.19_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.19_reproducible)and signed by Intel. # IDE source code -The IDE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_id_enclave.signed.so in prebuilt package is built by [id_enclave](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/quote/id_enclave) with branch [sgx_2.17_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.17_reproducible) and signed by Intel. +The IDE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_id_enclave.signed.so in prebuilt package is built by [id_enclave](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/quote/id_enclave) with branch [sgx_2.19_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.19_reproducible) and signed by Intel. # TDQE source code -The TDQE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_tdqe.signed.so in prebuilt package is built by [tdqe](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/tdx_quote/enclave) with branch [sgx_2.17_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.17_reproducible) and signed by Intel. +The TDQE is part of [Intel(R) Software Guard Extensions Data Center Attestation Primitives](https://github.com/intel/SGXDataCenterAttestationPrimitives/) Github repository. The libsgx_tdqe.signed.so in prebuilt package is built by [tdqe](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/QuoteGeneration/quote_wrapper/tdx_quote/enclave) with branch [sgx_2.19_reproducible](https://github.com/intel/linux-sgx/tree/sgx_2.19_reproducible) and signed by Intel. diff --git a/QuoteGeneration/qcnl/linux/sgx_default_qcnl.conf b/QuoteGeneration/qcnl/linux/sgx_default_qcnl.conf index 72b2f62e..3ed97fa2 100644 --- a/QuoteGeneration/qcnl/linux/sgx_default_qcnl.conf +++ b/QuoteGeneration/qcnl/linux/sgx_default_qcnl.conf @@ -2,42 +2,50 @@ // *** ATTENTION : This file is in JSON format so the keys are case sensitive. Don't change them. //PCCS server address - "pccs_url": "https://localhost:8081/sgx/certification/v4/", + "pccs_url": "https://localhost:8081/sgx/certification/v4/" // To accept insecure HTTPS certificate, set this option to false - "use_secure_cert": true, + ,"use_secure_cert": true // You can use the Intel PCS or another PCCS to get quote verification collateral. Retrieval of PCK - // Certificates will always use the PCCS described in PCCS_URL. When COLLATERAL_SERVICE is not defined, both - // PCK Certs and verification collateral will be retrieved using PCCS_URL - //"collateral_service": "https://api.trustedservices.intel.com/sgx/certification/v4/", + // Certificates will always use the PCCS described in pccs_url. When collateral_service is not defined, both + // PCK Certs and verification collateral will be retrieved using pccs_url + //,"collateral_service": "https://api.trustedservices.intel.com/sgx/certification/v4/" // If you use a PCCS service to get the quote verification collateral, you can specify which PCCS API version is to be used. // The legacy 3.0 API will return CRLs in HEX encoded DER format and the sgx_ql_qve_collateral_t.version will be set to 3.0, while - // the new 3.1 API will return raw DER format and the sgx_ql_qve_collateral_t.version will be set to 3.1. The PCCS_API_VERSION - // setting is ignored if COLLATERAL_SERVICE is set to the Intel PCS. In this case, the PCCS_API_VERSION is forced to be 3.1 + // the new 3.1 API will return raw DER format and the sgx_ql_qve_collateral_t.version will be set to 3.1. The pccs_api_version + // setting is ignored if collateral_service is set to the Intel PCS. In this case, the pccs_api_version is forced to be 3.1 // internally. Currently, only values of 3.0 and 3.1 are valid. Note, if you set this to 3.1, the PCCS use to retrieve // verification collateral must support the new 3.1 APIs. - //"pccs_api_version": "3.1", + //,"pccs_api_version": "3.1" // Maximum retry times for QCNL. If RETRY is not defined or set to 0, no retry will be performed. // It will first wait one second and then for all forthcoming retries it will double the waiting time. - // By using RETRY_DELAY you disable this exponential backoff algorithm - "retry_times": 6, + // By using retry_delay you disable this exponential backoff algorithm + ,"retry_times": 6 // Sleep this amount of seconds before each retry when a transfer has failed with a transient error - "retry_delay": 10, - - // If LOCAL_PCK_URL is defined, the QCNL will try to retrieve PCK cert chain from LOCAL_PCK_URL first, - // and failover to PCCS_URL as in legacy mode. - //"local_pck_url": "http://localhost:8081/sgx/certification/v4/", - - // If LOCAL_PCK_URL is not defined, the QCNL will cache PCK certificates in memory by default. - // The cached PCK certificates will expire after PCK_CACHE_EXPIRE_HOURS hours. - "pck_cache_expire_hours": 168, + ,"retry_delay": 10 + + // If local_pck_url is defined, the QCNL will try to retrieve PCK cert chain from local_pck_url first, + // and failover to pccs_url as in legacy mode. + //,"local_pck_url": "http://localhost:8081/sgx/certification/v4/" + + // If local_pck_url is not defined, set pck_cache_expire_hours to a none-zero value will enable local cache. + // The PCK certificates will be cached in memory and then to the disk drive. + // ===== Important: Once the local cache files are created, currently there is no other way to clean them other + // than to delete them manually, or wait for them to expire after "pck_cache_expire_hours" hours. + // To delete the cache files manually, go to these foders: + // Linux : $AZDCAP_CACHE, $XDG_CACHE_HOME, $HOME, $TMPDIR, /tmp/ + // Windows : $AZDCAP_CACHE, $LOCALAPPDATA\..\..\LocalLow + // If there is a folder called .dcap-qcnl, delete it. Restart the service after all cache + // folders were deleted. The same method applies to "verify_collateral_cache_expire_hours" + ,"pck_cache_expire_hours": 168 // To set cache expire time for quote verification collateral in hours - "verify_collateral_cache_expire_hours": 168 + // See the above comment for pck_cache_expire_hours for more information on the local cache. + ,"verify_collateral_cache_expire_hours": 168 // You can add custom request headers and parameters to the get certificate API. // But the default PCCS implementation just ignores them. diff --git a/QuoteGeneration/qpl/sgx_default_quote_provider.cpp b/QuoteGeneration/qpl/sgx_default_quote_provider.cpp index 1f1a68ea..486ecd1f 100644 --- a/QuoteGeneration/qpl/sgx_default_quote_provider.cpp +++ b/QuoteGeneration/qpl/sgx_default_quote_provider.cpp @@ -416,7 +416,7 @@ quote3_error_t tdx_ql_get_quote_verification_collateral(const uint8_t *fmspc, ui 0, pp_quote_collateral); if (ret == SGX_QL_SUCCESS) { - (*pp_quote_collateral)->tee_type = 0x0; // SGX + (*pp_quote_collateral)->tee_type = 0x81; // TDX } else { qpl_log(SGX_QL_LOG_ERROR, "[QPL] Failed to get SGX quote verification collateral : %d\n", ret); } diff --git a/QuoteGeneration/quote_wrapper/common/inc/sgx_ql_lib_common.h b/QuoteGeneration/quote_wrapper/common/inc/sgx_ql_lib_common.h index 5faa0fa2..2e089270 100644 --- a/QuoteGeneration/quote_wrapper/common/inc/sgx_ql_lib_common.h +++ b/QuoteGeneration/quote_wrapper/common/inc/sgx_ql_lib_common.h @@ -139,6 +139,8 @@ typedef enum _quote3_error_t { SGX_QL_ROOT_CA_UNTRUSTED = SGX_QL_MK_ERROR(0x0065), ///< The certificate used to establish SSL session is untrusted + SGX_QL_TCB_NOT_SUPPORTED = SGX_QL_MK_ERROR(0x0066), ///< Current TCB level cannot be found in platform/enclave TCB info + SGX_QL_ERROR_MAX = SGX_QL_MK_ERROR(0x00FF), ///< Indicate max error to allow better translation. } quote3_error_t; @@ -237,10 +239,6 @@ typedef enum _sgx_ql_log_level_t typedef void (*sgx_ql_logging_callback_t)(sgx_ql_log_level_t level, const char* message); -#ifndef tdx_ql_qve_collateral_t -typedef sgx_ql_qve_collateral_t tdx_ql_qve_collateral_t; -#endif - typedef enum _sgx_prod_type_t { SGX_PROD_TYPE_SGX = 0, SGX_PROD_TYPE_TDX = 1, diff --git a/QuoteGeneration/quote_wrapper/qgs/Makefile b/QuoteGeneration/quote_wrapper/qgs/Makefile index 762f567c..6c2fc636 100644 --- a/QuoteGeneration/quote_wrapper/qgs/Makefile +++ b/QuoteGeneration/quote_wrapper/qgs/Makefile @@ -33,17 +33,16 @@ TOP_DIR = ../.. include $(TOP_DIR)/buildenv.mk -QGS_SRCS = se_trace.c server_main.cpp qgs_server.cpp qgs_log.cpp qgs.message.pb.cc +QGS_SRCS = se_trace.c server_main.cpp qgs_server.cpp qgs_log.cpp qgs_ql_logic.cpp QGS_OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(patsubst %.cpp,%.o,$(QGS_SRCS)))) -PROTOBUF_CFLAGS = `pkg-config --cflags protobuf-lite` - QGS_INC = -I$(SGX_SDK)/include \ -I$(COMMON_DIR)/inc/internal \ -I$(TOP_DIR)/qpl/inc \ - -I$(TOP_DIR)/quote_wrapper/tdx_quote/inc -QGS_CFLAGS = -g -MMD $(CFLAGS) $(QGS_INC) $(PROTOBUF_CFLAGS) -QGS_CXXFLAGS = -g -MMD $(CXXFLAGS) $(QGS_INC) $(PROTOBUF_CFLAGS) + -I$(TOP_DIR)/quote_wrapper/tdx_quote/inc \ + -I$(TOP_DIR)/quote_wrapper/qgs_msg_lib/inc +QGS_CFLAGS = -g -MMD $(CFLAGS) $(QGS_INC) +QGS_CXXFLAGS = -g -MMD $(CXXFLAGS) $(QGS_INC) ifeq ($(CC_NO_LESS_THAN_8), 1) QGS_CFLAGS += -fcf-protection=none QGS_CXXFLAGS += -fcf-protection=none @@ -51,13 +50,13 @@ endif DEPENDS = ${QGS_OBJS:.o=.d} -# SGX related libraries +# SGX related libraries QGS_LFLAGS = -L$(TOP_DIR)/build/linux -lsgx_tdx_logic -lsgx_pce_logic -ldl \ -L$(SGX_SDK)/lib64 -lsgx_urts -g -# add protobuf for link -QGS_LFLAGS += `pkg-config --libs protobuf-lite` # add boost_system for link QGS_LFLAGS += -lboost_system -lboost_thread -lpthread +# add qgs_msg_lib for link +QGS_LFLAGS += -L../qgs_msg_lib/linux -lqgs_msg vpath %.c $(COMMON_DIR)/src @@ -66,21 +65,19 @@ vpath %.c $(COMMON_DIR)/src all: qgs qgs: $(QGS_OBJS) + make -C ../qgs_msg_lib/linux + make -C ../tdx_quote/linux $(CXX) -o $@ $^ $(QGS_LFLAGS) -qgs.message.pb.o: qgs.message.proto - protoc --cpp_out=. $^ - $(CXX) -c qgs.message.pb.cc $(QGS_CXXFLAGS) - -%.o: %.cpp qgs.message.pb.o +%.o: %.cpp $(CXX) -c $(QGS_CXXFLAGS) $< %.o: %.c $(CC) -c $(QGS_CFLAGS) $< clean: - rm -f *.pb.* - rm -f *_pb2.* + make -C ../qgs_msg_lib/linux clean + make -C ../tdx_quote/linux clean rm -f *.d - rm -f *.o + rm -f *.o rm -f qgs diff --git a/QuoteGeneration/quote_wrapper/qgs/qgs.message.proto b/QuoteGeneration/quote_wrapper/qgs/qgs.message.proto deleted file mode 100644 index c39192d8..00000000 --- a/QuoteGeneration/quote_wrapper/qgs/qgs.message.proto +++ /dev/null @@ -1,33 +0,0 @@ -syntax = "proto2"; -package qgs.message; -option optimize_for = LITE_RUNTIME; - -message UUID{ - required bytes value = 1; -} - -message Request{ - message GetQuoteRequest{ - required bytes report = 1; - repeated UUID id_list = 2; - } - - required uint32 type = 1; - oneof msg { - GetQuoteRequest getQuoteRequest = 2; - } -} - - -message Response{ - message GetQuoteResponse{ - required uint32 error_code = 1 [default = 1]; - optional UUID selected_id = 2; - optional bytes quote = 3; - } - - required uint32 type = 1; - oneof msg { - GetQuoteResponse getQuoteResponse = 2; - } -} diff --git a/QuoteGeneration/quote_wrapper/qgs/qgs_msg_wrapper.h b/QuoteGeneration/quote_wrapper/qgs/qgs_msg_wrapper.h deleted file mode 100644 index d19a5578..00000000 --- a/QuoteGeneration/quote_wrapper/qgs/qgs_msg_wrapper.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef QGS_MSG_WRAPPER_H -#define QGS_MSG_WRAPPER_H - -#include -#include -#include -#include -#include //for uint8_t - - -using data_buffer = std::vector; - -const unsigned HEADER_SIZE = 4; - -template -class QgsMsgWrapper -{ -public: - typedef boost::shared_ptr MessagePointer; - - explicit QgsMsgWrapper(MessagePointer msg = MessagePointer()) - : m_msg(msg) - {} - - void set_msg(MessagePointer msg) { - m_msg = msg; - } - - MessagePointer get_msg() { - return m_msg; - } - - bool pack(data_buffer& buf) const { - if (!m_msg) { - return false; - } - - unsigned msg_size = m_msg->ByteSize(); - buf.resize(HEADER_SIZE + msg_size); - encode_header(buf, msg_size); - return m_msg->SerializeToArray(&buf[HEADER_SIZE], msg_size); - } - - unsigned decode_header(const data_buffer& buf) const { - if (buf.size() < HEADER_SIZE) { - return 0; - } - unsigned msg_size = 0; - for (unsigned i = 0; i < HEADER_SIZE; ++i) { - msg_size = msg_size * 256 + (static_cast(buf[i]) & 0xFF); - } - return msg_size; - } - - bool unpack(const data_buffer& buf) { - return m_msg->ParseFromArray(&buf[HEADER_SIZE], - static_cast(buf.size() - HEADER_SIZE)); - } -private: - void encode_header(data_buffer& buf, unsigned size) const { - assert(buf.size() >= HEADER_SIZE); - buf[0] = static_cast((size >> 24) & 0xFF); - buf[1] = static_cast((size >> 16) & 0xFF); - buf[2] = static_cast((size >> 8) & 0xFF); - buf[3] = static_cast(size & 0xFF); - } - - MessagePointer m_msg; -}; - -#endif /* QGS_MSG_WRAPPER_H */ diff --git a/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.cpp b/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.cpp new file mode 100644 index 00000000..c93264c8 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.cpp @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + +#include "qgs_ql_logic.h" +#include "qgs_log.h" +#include "qgs_msg_lib.h" +#include "se_trace.h" +#include "sgx_ql_lib_common.h" +#include "td_ql_wrapper.h" +#include +#include +#include +#include + +typedef quote3_error_t (*get_collateral_func)(const uint8_t *fmspc, + uint16_t fmspc_size, const char *pck_ca, + tdx_ql_qve_collateral_t **pp_quote_collateral); +typedef quote3_error_t (*free_collateral_func)(tdx_ql_qve_collateral_t *p_quote_collateral); +extern "C" tee_att_error_t tee_att_get_qpl_handle(const tee_att_config_t *p_context, void **pp_qpl_handle); + +void cleanup(tee_att_config_t *p_ctx) { + QGS_LOG_INFO("About to delete ctx in cleanup\n"); + tee_att_free_context(p_ctx); + return; +} + +boost::thread_specific_ptr ptr(cleanup); + +namespace intel { namespace sgx { namespace dcap { namespace qgs { + + data_buffer get_resp(uint8_t *p_req, uint32_t req_size) { + + tee_att_error_t tee_att_ret = TEE_ATT_SUCCESS; + qgs_msg_error_t qgs_msg_error_ret = QGS_MSG_SUCCESS; + uint8_t *p_resp = NULL; + uint32_t resp_size = 0; + uint32_t resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + + QGS_LOG_INFO("enter prepare_response\n"); + if (ptr.get() == 0) { + tee_att_error_t ret = TEE_ATT_SUCCESS; + tee_att_config_t *p_ctx = NULL; + QGS_LOG_INFO("call tee_att_create_context\n"); + ret = tee_att_create_context(NULL, NULL, &p_ctx); + if (TEE_ATT_SUCCESS == ret) { + std::ostringstream oss; + oss << boost::this_thread::get_id(); + QGS_LOG_INFO("create context in thread[%s]\n", + oss.str().c_str()); + ptr.reset(p_ctx); + } else { + QGS_LOG_ERROR("Cannot create context\n"); + return {}; + } + } + + uint32_t req_type = QGS_MSG_TYPE_MAX; + if (QGS_MSG_SUCCESS != qgs_msg_get_type(p_req, req_size, &req_type)) { + QGS_LOG_ERROR("Cannot get msg type\n"); + return {}; + } + switch (req_type) { + case GET_QUOTE_REQ: { + uint32_t size = 0; + + const uint8_t *p_report; + uint32_t report_size; + const uint8_t *p_id_list; + uint32_t id_list_size; + + data_buffer quote_buf; + + qgs_msg_error_ret = qgs_msg_inflate_get_quote_req(p_req, + req_size, + &p_report, &report_size, + &p_id_list, &id_list_size); + if (QGS_MSG_SUCCESS != qgs_msg_error_ret) { + // TODO: need to define the error code list for R3AAL + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("qgs_msg_inflate_get_quote_req return error\n"); + } else { + + int retry = 1; + + do { + sgx_target_info_t qe_target_info; + uint8_t hash[32] = {0}; + size_t hash_size = sizeof(hash); + QGS_LOG_INFO("call tee_att_init_quote\n"); + tee_att_ret = tee_att_init_quote(ptr.get(), &qe_target_info, false, + &hash_size, + hash); + if (TEE_ATT_SUCCESS != tee_att_ret) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("tee_att_init_quote return 0x%x\n", tee_att_ret); + } else if (TEE_ATT_SUCCESS != (tee_att_ret = tee_att_get_quote_size(ptr.get(), &size))) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("tee_att_get_quote_size return 0x%x\n", tee_att_ret); + } else { + quote_buf.resize(size); + tee_att_ret = tee_att_get_quote(ptr.get(), + p_report, + report_size, + NULL, + quote_buf.data(), + size); + if (TEE_ATT_SUCCESS != tee_att_ret) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("tee_att_get_quote return 0x%x\n", tee_att_ret); + } else { + resp_error_code = QGS_MSG_SUCCESS; + QGS_LOG_INFO("tee_att_get_quote return Success\n"); + } + } + // Only retry once when the return code is TEE_ATT_ATT_KEY_NOT_INITIALIZED + } while (TEE_ATT_ATT_KEY_NOT_INITIALIZED == tee_att_ret && retry--); + } + if (resp_error_code == QGS_MSG_SUCCESS) { + qgs_msg_error_ret = qgs_msg_gen_get_quote_resp(NULL, 0, quote_buf.data(), size, &p_resp, &resp_size); + } else { + qgs_msg_error_ret = qgs_msg_gen_error_resp(resp_error_code, GET_QUOTE_RESP, &p_resp, &resp_size); + } + if (QGS_MSG_SUCCESS != qgs_msg_error_ret) { + QGS_LOG_ERROR("call qgs_msg_gen function failed\n"); + qgs_msg_free(p_resp); + return {}; + } + break; + } + case GET_COLLATERAL_REQ: { + const uint8_t *p_fsmpc; + uint32_t fsmpc_size; + const uint8_t *p_pckca; + uint32_t pckca_size; + tdx_ql_qve_collateral_t *p_collateral = NULL; + free_collateral_func free_func = NULL; + + qgs_msg_error_ret = qgs_msg_inflate_get_collateral_req(p_req, + req_size, + &p_fsmpc, &fsmpc_size, + &p_pckca, &pckca_size); + if (QGS_MSG_SUCCESS != qgs_msg_error_ret || fsmpc_size >= UINT16_MAX) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("qgs_msg_inflate_get_collateral_req return error\n"); + } else { + do { + extern tee_att_error_t tee_att_get_qpl_handle(const tee_att_config_t *p_context, + void **pp_qpl_handle); + + char *error1 = NULL; + char *error2 = NULL; + void *p_handle = NULL; + quote3_error_t quote3_ret = SGX_QL_SUCCESS; + tee_att_ret = ::tee_att_get_qpl_handle(ptr.get(), &p_handle); + if (TEE_ATT_SUCCESS != tee_att_ret || NULL == p_handle) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("tee_att_get_qpl_handle return 0x%x\n", tee_att_ret); + break; + } + + auto get_func = (get_collateral_func)dlsym(p_handle, "tdx_ql_get_quote_verification_collateral"); + error1 = dlerror(); + free_func = (free_collateral_func)dlsym(p_handle, "tdx_ql_free_quote_verification_collateral"); + error2 = dlerror(); + if ((NULL == error1) && (NULL != get_func) && (NULL == error2) && (NULL != free_func)) { + SE_PROD_LOG("Found tdx quote verification functions.\n"); + quote3_ret = get_func(p_fsmpc, (uint16_t)fsmpc_size, (const char *)p_pckca, &p_collateral); + if (SGX_QL_SUCCESS != quote3_ret) { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("tdx_ql_get_quote_verification_collateral return %d\n", quote3_ret); + break; + } else { + resp_error_code = QGS_MSG_SUCCESS; + QGS_LOG_INFO("tdx_ql_get_quote_verification_collateral return SUCCESS\n"); + break; + } + } else { + resp_error_code = QGS_MSG_ERROR_UNEXPECTED; + QGS_LOG_ERROR("Cannot find tdx quote verification functions.\n"); + break; + } + } while (0); + } + if (resp_error_code == QGS_MSG_SUCCESS) { + qgs_msg_error_ret = qgs_msg_gen_get_collateral_resp(p_collateral->major_version, p_collateral->minor_version, + (const uint8_t *)p_collateral->pck_crl_issuer_chain, p_collateral->pck_crl_issuer_chain_size, + (const uint8_t *)p_collateral->root_ca_crl, p_collateral->root_ca_crl_size, + (const uint8_t *)p_collateral->pck_crl, p_collateral->pck_crl_size, + (const uint8_t *)p_collateral->tcb_info_issuer_chain, p_collateral->tcb_info_issuer_chain_size, + (const uint8_t *)p_collateral->tcb_info, p_collateral->tcb_info_size, + (const uint8_t *)p_collateral->qe_identity_issuer_chain, p_collateral->qe_identity_issuer_chain_size, + (const uint8_t *)p_collateral->qe_identity, p_collateral->qe_identity_size, + &p_resp, &resp_size); + free_func(p_collateral); + } else { + qgs_msg_error_ret = qgs_msg_gen_error_resp(resp_error_code, GET_COLLATERAL_RESP, &p_resp, &resp_size); + } + if (QGS_MSG_SUCCESS != qgs_msg_error_ret) { + QGS_LOG_ERROR("call qgs_msg_gen function failed\n"); + qgs_msg_free(p_resp); + return {}; + } + break; + } + default: + QGS_LOG_ERROR("Whoops, bad request!"); + return {}; + } + + QGS_LOG_INFO("Return from get_resp\n"); + data_buffer resp(p_resp, p_resp + resp_size); + qgs_msg_free(p_resp); + return resp; + } +} +} // namespace dcap +} // namespace sgx +} // namespace intel diff --git a/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.h b/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.h new file mode 100644 index 00000000..c32f7d83 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/qgs/qgs_ql_logic.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QGS_QL_LOGIC_H +#define QGS_QL_LOGIC_H + +#include +#include + +namespace intel { namespace sgx { namespace dcap { namespace qgs { + + using data_buffer = std::vector; + data_buffer get_resp(uint8_t *p_req, uint32_t req_size); +} +} +} +} +#endif /* QGS_QL_LOGIC_H */ diff --git a/QuoteGeneration/quote_wrapper/qgs/qgs_server.cpp b/QuoteGeneration/quote_wrapper/qgs/qgs_server.cpp index fec30a8d..e2813d4c 100644 --- a/QuoteGeneration/quote_wrapper/qgs/qgs_server.cpp +++ b/QuoteGeneration/quote_wrapper/qgs/qgs_server.cpp @@ -30,23 +30,22 @@ */ #include "qgs_server.h" -#include "qgs.message.pb.h" #include "qgs_log.h" -#include "qgs_msg_wrapper.h" +#include "qgs_ql_logic.h" #include "se_trace.h" -#include "td_ql_wrapper.h" #include #include #include #include #include +#include #include #include #include #include -#include #include #include +#include #include #include #include @@ -54,23 +53,35 @@ using namespace std; using boost::uint8_t; -using namespace qgs::message; static const int QGS_TIMEOUT = 30; + namespace intel { namespace sgx { namespace dcap { namespace qgs { -void cleanup(tee_att_config_t *p_ctx) { - QGS_LOG_INFO("About to delete ctx in cleanup\n"); - tee_att_free_context(p_ctx); - return; +const unsigned HEADER_SIZE = 4; + +uint32_t decode_header(const data_buffer &buf) { + if (buf.size() < HEADER_SIZE) { + return 0; + } + uint32_t msg_size = 0; + for (uint32_t i = 0; i < HEADER_SIZE; ++i) { + msg_size = msg_size * 256 + (static_cast(buf[i]) & 0xFF); + } + return msg_size; +} + +void encode_header(data_buffer &buf, uint32_t size) { + assert(buf.size() >= HEADER_SIZE); + buf[0] = static_cast((size >> 24) & 0xFF); + buf[1] = static_cast((size >> 16) & 0xFF); + buf[2] = static_cast((size >> 8) & 0xFF); + buf[3] = static_cast(size & 0xFF); } -boost::thread_specific_ptr ptr(cleanup); class QgsConnection : public boost::enable_shared_from_this { public: using Pointer = boost::shared_ptr; - using RequestPointer = boost::shared_ptr; - using ResponsePointer = boost::shared_ptr; using ConnectionSet = boost::unordered_set; static Pointer create(boost::mutex &connection_mtx, ConnectionSet &connections, @@ -113,8 +124,7 @@ class QgsConnection : public boost::enable_shared_from_this { asio::thread_pool &m_pool; gs::socket m_socket; asio::deadline_timer m_timer; - vector m_readbuf; - QgsMsgWrapper m_packed_request; + data_buffer m_readbuf; const boost::posix_time::time_duration timeout = boost::posix_time::seconds(QGS_TIMEOUT); @@ -127,8 +137,7 @@ class QgsConnection : public boost::enable_shared_from_this { m_connections(connections), m_pool(pool), m_socket(io_service), - m_timer(io_service), - m_packed_request(boost::shared_ptr(new Request())) { + m_timer(io_service) { } void handle_read_header(const boost::system::error_code &ec) { @@ -138,9 +147,15 @@ class QgsConnection : public boost::enable_shared_from_this { oss.str().c_str()); if (!ec) { QGS_LOG_INFO("Got header!\n"); - unsigned msg_len = m_packed_request.decode_header(m_readbuf); + unsigned msg_len = decode_header(m_readbuf); QGS_LOG_INFO("body should be [%d] bytes!\n", msg_len); - start_read_body(msg_len); + if (!msg_len) { + QGS_LOG_INFO("Failed to decode heaer, stop\n"); + m_timer.cancel(); + stop(); + } else { + start_read_body(msg_len); + } } } @@ -156,28 +171,32 @@ class QgsConnection : public boost::enable_shared_from_this { } void handle_request() { - if (m_packed_request.unpack(m_readbuf)) { - std::ostringstream oss; - oss << boost::this_thread::get_id(); - QGS_LOG_INFO("unpack message successfully in thread [%s]\n", - oss.str().c_str()); - RequestPointer req = m_packed_request.get_msg(); - asio::post(m_pool, [this, self = shared_from_this(), req] { - boost::system::error_code ec; - ResponsePointer resp = prepare_response(req); - - vector writebuf; - QgsMsgWrapper resp_msg(resp); - resp_msg.pack(writebuf); - std::ostringstream oss1; - oss1 << boost::this_thread::get_id(); - QGS_LOG_INFO("About to write response in thread [%s]\n", - oss1.str().c_str()); - asio::write(m_socket, asio::buffer(writebuf), ec); + std::ostringstream oss; + oss << boost::this_thread::get_id(); + QGS_LOG_INFO("unpack message successfully in thread [%s]\n", + oss.str().c_str()); + asio::post(m_pool, [this, self = shared_from_this()] { + boost::system::error_code ec; + + data_buffer resp = prepare_response(const_cast(m_readbuf)); + + uint32_t resp_size = (uint32_t)resp.size(); + if (!resp_size) { m_timer.cancel(); stop(); - }); - } + } + data_buffer writebuf; + writebuf.resize(HEADER_SIZE + resp_size); + encode_header(writebuf, resp_size); + std::copy(resp.begin(), resp.end(), writebuf.begin() + HEADER_SIZE); + std::ostringstream oss1; + oss1 << boost::this_thread::get_id(); + QGS_LOG_INFO("About to write response in thread [%s]\n", + oss1.str().c_str()); + asio::write(m_socket, asio::buffer(writebuf), ec); + m_timer.cancel(); + stop(); + }); } void start_read_header() { @@ -198,79 +217,8 @@ class QgsConnection : public boost::enable_shared_from_this { asio::placeholders::error)); } - ResponsePointer prepare_response(RequestPointer req) { - ResponsePointer resp(new Response); - tee_att_error_t tee_att_ret = TEE_ATT_SUCCESS; - - QGS_LOG_INFO("enter prepare_response\n"); - if (ptr.get() == 0) { - tee_att_error_t ret = TEE_ATT_SUCCESS; - tee_att_config_t *p_ctx = NULL; - QGS_LOG_INFO("call tee_att_create_context\n"); - ret = tee_att_create_context(NULL, NULL, &p_ctx); - if (TEE_ATT_SUCCESS == ret) { - std::ostringstream oss; - oss << boost::this_thread::get_id(); - QGS_LOG_INFO("create context in thread[%s]\n", - oss.str().c_str()); - ptr.reset(p_ctx); - } else { - QGS_LOG_ERROR("Cannot create context\n"); - } - } - - switch (req->type()) { - case Request::MsgCase::kGetQuoteRequest: { - uint32_t size = 0; - vector quote_buf; - auto get_quote_resp = new Response::GetQuoteResponse(); - resp->set_type(Response::kGetQuoteResponse); - - sgx_target_info_t qe_target_info; - uint8_t hash[32] = {0}; - size_t hash_size = sizeof(hash); - int retry = 1; - - do { - QGS_LOG_INFO("call tee_att_init_quote\n"); - tee_att_ret = tee_att_init_quote(ptr.get(), &qe_target_info, false, - &hash_size, - hash); - if (TEE_ATT_SUCCESS != tee_att_ret) { - get_quote_resp->set_error_code(1); - QGS_LOG_ERROR("tee_att_init_quote return 0x%x\n", tee_att_ret); - } else if (TEE_ATT_SUCCESS != (tee_att_ret = tee_att_get_quote_size(ptr.get(), &size))) { - get_quote_resp->set_error_code(1); - QGS_LOG_ERROR("tee_att_get_quote_size return 0x%x\n", tee_att_ret); - } else { - quote_buf.resize(size); - tee_att_ret = tee_att_get_quote(ptr.get(), - (uint8_t *)req->getquoterequest().report().c_str(), - (uint32_t)req->getquoterequest().report().length(), - NULL, - quote_buf.data(), - size); - if (TEE_ATT_SUCCESS != tee_att_ret) { - get_quote_resp->set_error_code(1); - QGS_LOG_ERROR("tee_att_get_quote return 0x%x\n", tee_att_ret); - } else { - get_quote_resp->set_error_code(0); - get_quote_resp->set_quote(quote_buf.data(), size); - QGS_LOG_INFO("tee_att_get_quote return Success\n"); - } - } - // Only return once when the return code is TEE_ATT_ATT_KEY_NOT_INITIALIZED - } while (TEE_ATT_ATT_KEY_NOT_INITIALIZED == tee_att_ret && retry--); - resp->set_allocated_getquoteresponse(get_quote_resp); - QGS_LOG_INFO("byte length is: %d\n", resp->ByteSize()); - break; - } - default: - QGS_LOG_ERROR("Whoops, bad request!"); - break; - } - - return resp; + data_buffer prepare_response(data_buffer const &req) { + return get_resp((uint8_t *)&req[HEADER_SIZE], (uint32_t)req.size() - HEADER_SIZE); } }; diff --git a/QuoteGeneration/quote_wrapper/qgs_msg_lib/inc/qgs_msg_lib.h b/QuoteGeneration/quote_wrapper/qgs_msg_lib/inc/qgs_msg_lib.h new file mode 100644 index 00000000..7e55b93a --- /dev/null +++ b/QuoteGeneration/quote_wrapper/qgs_msg_lib/inc/qgs_msg_lib.h @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + +/** + * File: qgs_msg_lib.h + * + * Description: Message and API definitions for TDX QGS messages + * + */ +#ifndef _QGS_MSG_DEF_H_ +#define _QGS_MSG_DEF_H_ +#include + +#ifndef QGS_MSG_MK_ERROR +#define QGS_MSG_MK_ERROR(x) (0x00012000 | (x)) +#endif + +#pragma pack(push, 1) + +/** Possible errors generated by the qgs message library. */ +typedef enum _qgs_msg_error_t { + QGS_MSG_SUCCESS = 0x0000, ///< Success + QGS_MSG_ERROR_UNEXPECTED = QGS_MSG_MK_ERROR(0x0001), ///< Unexpected error + QGS_MSG_ERROR_OUT_OF_MEMORY = QGS_MSG_MK_ERROR(0x0002), ///< Not enough memory is available to complete this operation + QGS_MSG_ERROR_INVALID_PARAMETER = QGS_MSG_MK_ERROR(0x0003), ///< The parameter is incorrect + QGS_MSG_ERROR_INVALID_VERSION = QGS_MSG_MK_ERROR(0x0004), ///< Unrecognized version of serialized data + QGS_MSG_ERROR_INVALID_TYPE = QGS_MSG_MK_ERROR(0x0005), ///< Invalid message type found + QGS_MSG_ERROR_INVALID_SIZE = QGS_MSG_MK_ERROR(0x0006), ///< Invalid message size found + QGS_MSG_ERROR_INVALID_CODE = QGS_MSG_MK_ERROR(0x0007), ///< Invalid error code + + QGS_MSG_ERROR_MAX, ///< Indicate max error to allow better translation. +} qgs_msg_error_t; + +typedef enum _qgs_msg_type_t { + GET_QUOTE_REQ = 0, + GET_QUOTE_RESP = 1, + GET_COLLATERAL_REQ = 2, + GET_COLLATERAL_RESP = 3, + QGS_MSG_TYPE_MAX +} qgs_msg_type_t; + +typedef struct _qgs_msg_header_t { + uint16_t major_version; + uint16_t minor_version; + uint32_t type; + uint32_t size; // size of the whole message, include this header, in byte + uint32_t error_code; // used in response only +} qgs_msg_header_t; + +typedef struct _qgs_msg_get_quote_req_t { + qgs_msg_header_t header; // header.type = GET_QUOTE_REQ + uint32_t report_size; // cannot be 0 + uint32_t id_list_size; // length of id_list, in byte, can be 0 + uint8_t report_id_list[]; // report followed by id list +} qgs_msg_get_quote_req_t; + +typedef struct _qgs_msg_get_quote_resp_s { + qgs_msg_header_t header; // header.type = GET_QUOTE_RESP + uint32_t selected_id_size; // can be 0 in case only one id is sent in request + uint32_t quote_size; // length of quote_data, in byte + uint8_t id_quote[]; // selected id followed by quote +} qgs_msg_get_quote_resp_t; + +typedef struct _qgs_msg_get_collateral_req_t { + qgs_msg_header_t header; // header.type = GET_COLLATERAL_REQ + uint32_t fsmpc_size; // length of fsmpc, in byte + uint32_t pckca_size; // length of pckca, in byte + uint8_t fsmpc_pckca[]; // fsmpc followed by pckca +} qgs_msg_get_collateral_req_t; + +typedef struct _qgs_msg_get_collateral_resp_s { + qgs_msg_header_t header; // header.type = GET_COLLATERAL_RESP + uint16_t major_version; + uint16_t minor_version; + uint32_t pck_crl_issuer_chain_size; + uint32_t root_ca_crl_size; + uint32_t pck_crl_size; + uint32_t tcb_info_issuer_chain_size; + uint32_t tcb_info_size; + uint32_t qe_identity_issuer_chain_size; + uint32_t qe_identity_size; + uint8_t collaterals[]; // payload filled in same order as upper sizes parameters +} qgs_msg_get_collateral_resp_t; + +#pragma pack(pop) + +#if defined(__cplusplus) +extern "C" { +#endif +void qgs_msg_free(void *buf); + +qgs_msg_error_t qgs_msg_gen_get_quote_req( + const uint8_t *p_report, uint32_t report_size, + const uint8_t *p_id_list, uint32_t id_list_size, + uint8_t **pp_req, uint32_t *p_req_size); +qgs_msg_error_t qgs_msg_gen_get_collateral_req( + const uint8_t *p_fsmpc, uint32_t fsmpc_size, + const uint8_t *p_pckca, uint32_t pckca_size, + uint8_t **pp_req, uint32_t *p_req_size); + +qgs_msg_error_t qgs_msg_inflate_get_quote_req( + const uint8_t *p_serialized_req, uint32_t size, + const uint8_t **pp_report, uint32_t *p_report_size, + const uint8_t **pp_id_list, uint32_t *p_id_list_size); +qgs_msg_error_t qgs_msg_inflate_get_collateral_req( + const uint8_t *p_serialized_req, uint32_t size, + const uint8_t **pp_fsmpc, uint32_t *p_fsmpc_size, + const uint8_t **pp_pckca, uint32_t *p_pckca_size); + +qgs_msg_error_t qgs_msg_gen_error_resp( + uint32_t error_code, uint32_t type, + uint8_t **pp_resp, uint32_t *p_resp_size); + +qgs_msg_error_t qgs_msg_gen_get_quote_resp( + const uint8_t *p_selected_id, uint32_t id_size, + const uint8_t *p_quote, uint32_t quote_size, + uint8_t **pp_resp, uint32_t *p_resp_size); +qgs_msg_error_t qgs_msg_gen_get_collateral_resp( + uint16_t major_version, uint16_t minor_version, + const uint8_t *p_pck_crl_issuer_chain, uint32_t pck_crl_issuer_chain_size, + const uint8_t *p_root_ca_crl, uint32_t root_ca_crl_size, + const uint8_t *p_pck_crl, uint32_t pck_crl_size, + const uint8_t *p_tcb_info_issuer_chain, uint32_t tcb_info_issuer_chain_size, + const uint8_t *p_tcb_info, uint32_t tcb_info_size, + const uint8_t *p_qe_identity_issuer_chain, uint32_t qe_identity_issuer_chain_size, + const uint8_t *p_qe_identity, uint32_t qe_identity_size, + uint8_t **pp_resp, uint32_t *p_resp_size); + +qgs_msg_error_t qgs_msg_inflate_get_quote_resp( + const uint8_t *p_serialized_resp, uint32_t size, + const uint8_t **pp_selected_id, uint32_t *p_id_size, + const uint8_t **pp_quote, uint32_t *p_quote_size); +qgs_msg_error_t qgs_msg_inflate_get_collateral_resp( + const uint8_t *p_serialized_resp, uint32_t size, + uint16_t *p_major_version, uint16_t *p_minor_version, + const uint8_t **pp_pck_crl_issuer_chain, uint32_t *p_pck_crl_issuer_chain_size, + const uint8_t **pp_root_ca_crl, uint32_t *p_root_ca_crl_size, + const uint8_t **pp_pck_crl, uint32_t *p_pck_crl_size, + const uint8_t **pp_tcb_info_issuer_chain, uint32_t *p_tcb_info_issuer_chain_size, + const uint8_t **pp_tcb_info, uint32_t *p_tcb_info_size, + const uint8_t **pp_qe_identity_issuer_chain, uint32_t *p_qe_identity_issuer_chain_size, + const uint8_t **pp_qe_identity, uint32_t *p_qe_identity_size); +uint32_t qgs_msg_get_type(const uint8_t *p_serialized_msg, uint32_t size, uint32_t *p_type); + +#if defined(__cplusplus) +} +#endif + +#endif \ No newline at end of file diff --git a/QuoteGeneration/quote_wrapper/qgs_msg_lib/linux/Makefile b/QuoteGeneration/quote_wrapper/qgs_msg_lib/linux/Makefile new file mode 100644 index 00000000..02c6d1c5 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/qgs_msg_lib/linux/Makefile @@ -0,0 +1,62 @@ +# +# Copyright (C) 2011-2021 Intel Corporation. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# + +######## SGX SDK Settings ######## +TOP_DIR = ../../.. +include $(TOP_DIR)/buildenv.mk + + +######## LIBQGS_MSG ######## +SRC := $(wildcard ../*.cpp) +OBJS := $(sort $(SRC:.cpp=.o)) +LIBQGS_MSG_DEP := $(sort $(SRC:.cpp=.d)) +INCLUDES := -I../inc/ + +LIBQGS_MSG := libqgs_msg.a +-include $(LIBQGS_MSG_DEP) + +.PHONY: all +all: $(LIBQGS_MSG) + +$(LIBQGS_MSG): $(OBJS) + $(AR) rcsD $@ $^ + +$(OBJS): %.o:%.cpp + $(CXX) -c $(CXXFLAGS) -g -MMD -fPIC -Wno-attributes $(INCLUDES) $< -o $@ + +.PHONY: clean +clean: + @$(RM) $(OBJS) $(LIBQGS_MSG) $(LIBQGS_MSG_DEP) + +.PHONY: rebuild +rebuild: + $(MAKE) clean + $(MAKE) all diff --git a/QuoteGeneration/quote_wrapper/qgs_msg_lib/qgs_msg_lib.cpp b/QuoteGeneration/quote_wrapper/qgs_msg_lib/qgs_msg_lib.cpp new file mode 100644 index 00000000..3156a56e --- /dev/null +++ b/QuoteGeneration/quote_wrapper/qgs_msg_lib/qgs_msg_lib.cpp @@ -0,0 +1,822 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "qgs_msg_lib.h" + +#include +#include + +const uint32_t QGS_MSG_LIB_MAJOR_VER = 1; +const uint32_t QGS_MSG_LIB_MINOR_VER = 0; + +void qgs_msg_free(void *p_buf) { + free(p_buf); +} + +/** + * @brief Generate serialized get_quote request + * + * @param p_report Cannot be NULL + * @param report_size Cannot be 0 + * @param p_id_list Can be NULL + * @param id_list_size Can be 0 + * @param pp_req returned serialized buffer, valid only if the return code is QGS_MSG_SUCCESS + * @param p_req_size return size of the serialized buffer, valid only if the return code is QGS_MSG_SUCCESS + * @return qgs_msg_error_t + */ +qgs_msg_error_t qgs_msg_gen_get_quote_req( + const uint8_t *p_report, uint32_t report_size, + const uint8_t *p_id_list, uint32_t id_list_size, + uint8_t **pp_req, uint32_t *p_req_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_quote_req_t *p_req = NULL; + uint32_t buf_size = 0; + uint64_t temp = 0; + + if (!p_report || !report_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if ((!p_id_list && id_list_size) || (p_id_list && !id_list_size)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_req || !p_req_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + temp = sizeof(*p_req); + temp += report_size; + temp += id_list_size; + if (temp < UINT32_MAX) { + buf_size = temp & UINT32_MAX; + } else { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + p_req = (qgs_msg_get_quote_req_t *)calloc(buf_size, sizeof(uint8_t)); + if (!p_req) { + ret = QGS_MSG_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + + p_req->header.major_version = QGS_MSG_LIB_MAJOR_VER; + p_req->header.minor_version = QGS_MSG_LIB_MINOR_VER; + p_req->header.type = GET_QUOTE_REQ; + p_req->header.size = buf_size; + p_req->header.error_code = 0; + + p_req->report_size = report_size; + p_req->id_list_size = id_list_size; + memcpy(p_req->report_id_list, p_report, report_size); + if (id_list_size) { + memcpy(p_req->report_id_list + report_size, p_id_list, id_list_size); + } + *pp_req = (uint8_t *)p_req; + *p_req_size = buf_size; + ret = QGS_MSG_SUCCESS; + +ret_point : + return ret; +} + +/** + * @brief Generate serialized get_collateral request + * + * @param p_fsmpc Cannot be NULL + * @param fsmpc_size Cannot be 0 + * @param p_pckca Cannot be NULL + * @param pckca_size Cannot be 0 + * @param pp_req returned serialized buffer, valid only if the return code is QGS_MSG_SUCCESS + * @param p_req_size return size of the serialized buffer, valid only if the return code is QGS_MSG_SUCCESS + * @return qgs_msg_error_t + */ +qgs_msg_error_t qgs_msg_gen_get_collateral_req( + const uint8_t *p_fsmpc, uint32_t fsmpc_size, + const uint8_t *p_pckca, uint32_t pckca_size, + uint8_t **pp_req, uint32_t *p_req_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_collateral_req_t *p_req = NULL; + uint32_t buf_size = 0; + uint64_t temp = 0; + + if (!p_fsmpc || !fsmpc_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!p_pckca || !pckca_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_req || !p_req_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + temp = sizeof(*p_req); + temp += fsmpc_size; + temp += pckca_size; + if (temp < UINT32_MAX) { + buf_size = temp & UINT32_MAX; + } else { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + p_req = (qgs_msg_get_collateral_req_t *)calloc(buf_size, sizeof(uint8_t)); + if (!p_req) { + ret = QGS_MSG_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + + p_req->header.major_version = QGS_MSG_LIB_MAJOR_VER; + p_req->header.minor_version = QGS_MSG_LIB_MINOR_VER; + p_req->header.type = GET_COLLATERAL_REQ; + p_req->header.size = buf_size; + p_req->header.error_code = 0; + + p_req->fsmpc_size = fsmpc_size; + p_req->pckca_size = pckca_size; + memcpy(p_req->fsmpc_pckca, p_fsmpc, fsmpc_size); + memcpy(p_req->fsmpc_pckca + fsmpc_size, p_pckca, pckca_size); + + *pp_req = (uint8_t *)p_req; + *p_req_size = buf_size; + ret = QGS_MSG_SUCCESS; + +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_inflate_get_quote_req( + const uint8_t *p_serialized_req, uint32_t size, + const uint8_t **pp_report, uint32_t *p_report_size, + const uint8_t **pp_id_list, uint32_t *p_id_list_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_quote_req_t *p_req = NULL; + uint64_t temp = 0; + + if (!p_serialized_req || !size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_report || !p_report_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_id_list || !p_id_list_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + // sanity check, the size shouldn't smaller than qgs_msg_get_quote_req_t + if (size < sizeof(qgs_msg_get_quote_req_t)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + p_req = (qgs_msg_get_quote_req_t *)p_serialized_req; + // Only major version is checked, minor change is deemed as compatible. + if (p_req->header.major_version != QGS_MSG_LIB_MAJOR_VER) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + + if (p_req->header.type != GET_QUOTE_REQ) { + ret = QGS_MSG_ERROR_INVALID_TYPE; + goto ret_point; + } + + if (p_req->header.size != size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + if (p_req->header.error_code != 0) { + ret = QGS_MSG_ERROR_INVALID_CODE; + goto ret_point; + } + + if (!p_req->report_size) { + ret = QGS_MSG_ERROR_INVALID_CODE; + goto ret_point; + } + + temp = sizeof(qgs_msg_get_quote_req_t); + temp += p_req->report_size; + temp += p_req->id_list_size; + if (temp >= UINT32_MAX) { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + if (p_req->header.size != temp) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + *pp_report = p_req->report_id_list; + if (p_req->id_list_size) { + *pp_id_list = p_req->report_id_list + p_req->report_size; + } else { + *pp_id_list = NULL; + } + + *p_report_size = p_req->report_size; + *p_id_list_size = p_req->id_list_size; + +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_inflate_get_collateral_req( + const uint8_t *p_serialized_req, uint32_t size, + const uint8_t **pp_fsmpc, uint32_t *p_fsmpc_size, + const uint8_t **pp_pckca, uint32_t *p_pckca_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_collateral_req_t *p_req = NULL; + uint64_t temp = 0; + + if (!p_serialized_req || !size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_fsmpc || !p_fsmpc_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_pckca || !p_pckca_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + // sanity check, the size shouldn't smaller than qgs_msg_get_quote_req_t + if (size < sizeof(qgs_msg_get_collateral_req_t)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + p_req = (qgs_msg_get_collateral_req_t *)p_serialized_req; + // Only major version is checked, minor change is deemed as compatible. + if (p_req->header.major_version != QGS_MSG_LIB_MAJOR_VER) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + + if (p_req->header.type != GET_COLLATERAL_REQ) { + ret = QGS_MSG_ERROR_INVALID_TYPE; + goto ret_point; + } + + if (p_req->header.size != size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + if (p_req->header.error_code != 0) { + ret = QGS_MSG_ERROR_INVALID_CODE; + goto ret_point; + } + + if (!p_req->fsmpc_size || !p_req->pckca_size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + temp = sizeof(qgs_msg_get_collateral_req_t); + temp += p_req->fsmpc_size; + temp += p_req->pckca_size; + if (temp >= UINT32_MAX) { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + if (p_req->header.size != temp) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + *pp_fsmpc = p_req->fsmpc_pckca; + *pp_pckca = p_req->fsmpc_pckca + p_req->fsmpc_size; + + *p_fsmpc_size = p_req->fsmpc_size; + *p_pckca_size = p_req->pckca_size; + +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_gen_error_resp( + uint32_t error_code, uint32_t type, + uint8_t **pp_resp, uint32_t *p_resp_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + uint32_t buf_size = 0; + qgs_msg_header_t *p_resp = NULL; + if (error_code == QGS_MSG_SUCCESS) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_resp || !p_resp_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + switch (type) { + case GET_QUOTE_RESP: + buf_size = sizeof(qgs_msg_get_quote_resp_t); + break; + case GET_COLLATERAL_RESP: + buf_size = sizeof(qgs_msg_get_collateral_resp_t); + break; + default: + ret = QGS_MSG_ERROR_INVALID_TYPE; + goto ret_point; + } + p_resp = (qgs_msg_header_t *)calloc(buf_size, sizeof(uint8_t)); + if (!p_resp) { + ret = QGS_MSG_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + + p_resp->major_version = QGS_MSG_LIB_MAJOR_VER; + p_resp->minor_version = QGS_MSG_LIB_MINOR_VER; + p_resp->type = type; + p_resp->size = buf_size; + p_resp->error_code = error_code; + + *pp_resp = (uint8_t *)p_resp; + *p_resp_size = buf_size; + ret = QGS_MSG_SUCCESS; + +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_gen_get_quote_resp( + const uint8_t *p_selected_id, uint32_t id_size, + const uint8_t *p_quote, uint32_t quote_size, + uint8_t **pp_resp, uint32_t *p_resp_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_quote_resp_t *p_resp = NULL; + uint32_t buf_size = 0; + uint64_t temp = 0; + + if (!pp_resp || !p_resp_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if ((!p_selected_id && id_size) || (p_selected_id && !id_size)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!p_quote || !quote_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + temp = sizeof(*p_resp); + temp += id_size; + temp += quote_size; + if (temp < UINT32_MAX) { + buf_size = temp & UINT32_MAX; + } else { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + p_resp = (qgs_msg_get_quote_resp_t *)calloc(buf_size, sizeof(uint8_t)); + if (!p_resp) { + ret = QGS_MSG_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + + p_resp->header.major_version = QGS_MSG_LIB_MAJOR_VER; + p_resp->header.minor_version = QGS_MSG_LIB_MINOR_VER; + p_resp->header.type = GET_QUOTE_RESP; + p_resp->header.size = buf_size; + p_resp->header.error_code = QGS_MSG_SUCCESS; + + p_resp->selected_id_size = id_size; + p_resp->quote_size = quote_size; + if (id_size) { + memcpy(p_resp->id_quote, p_selected_id, id_size); + } + if (quote_size) { + memcpy(p_resp->id_quote + id_size, p_quote, quote_size); + } + *pp_resp = (uint8_t *)p_resp; + *p_resp_size = buf_size; + ret = QGS_MSG_SUCCESS; + +ret_point : + return ret; +} + +qgs_msg_error_t qgs_msg_gen_get_collateral_resp( + uint16_t major_version, uint16_t minor_version, + const uint8_t *p_pck_crl_issuer_chain, uint32_t pck_crl_issuer_chain_size, + const uint8_t *p_root_ca_crl, uint32_t root_ca_crl_size, + const uint8_t *p_pck_crl, uint32_t pck_crl_size, + const uint8_t *p_tcb_info_issuer_chain, uint32_t tcb_info_issuer_chain_size, + const uint8_t *p_tcb_info, uint32_t tcb_info_size, + const uint8_t *p_qe_identity_issuer_chain, uint32_t qe_identity_issuer_chain_size, + const uint8_t *p_qe_identity, uint32_t qe_identity_size, + uint8_t **pp_resp, uint32_t *p_resp_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_collateral_resp_t *p_resp = NULL; + uint8_t *p_ptr = NULL; + uint32_t buf_size = 0; + uint64_t temp = 0; + + if (!pp_resp || !p_resp_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + //TODO major_version and minor_version are ignored here, is 0.0 a valid version? + if (!p_pck_crl_issuer_chain || !pck_crl_issuer_chain_size + || !p_root_ca_crl || !root_ca_crl_size + || !p_pck_crl || !pck_crl_size + || !p_tcb_info_issuer_chain || !tcb_info_issuer_chain_size + || !p_tcb_info || !tcb_info_size + || !p_qe_identity_issuer_chain || !qe_identity_issuer_chain_size + || !p_qe_identity || !qe_identity_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + temp = sizeof(major_version) + sizeof(minor_version) + sizeof(*p_resp); + temp += pck_crl_issuer_chain_size; + temp += root_ca_crl_size; + temp += pck_crl_size; + temp += tcb_info_issuer_chain_size; + temp += tcb_info_size; + temp += qe_identity_issuer_chain_size; + temp += qe_identity_size; + + if (temp < UINT32_MAX) { + buf_size = temp & UINT32_MAX; + } else { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + p_resp = (qgs_msg_get_collateral_resp_t *)calloc(buf_size, sizeof(uint8_t)); + if (!p_resp) { + ret = QGS_MSG_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + + p_resp->header.major_version = QGS_MSG_LIB_MAJOR_VER; + p_resp->header.minor_version = QGS_MSG_LIB_MINOR_VER; + p_resp->header.type = GET_COLLATERAL_RESP; + p_resp->header.size = buf_size; + p_resp->header.error_code = QGS_MSG_SUCCESS; + + p_resp->major_version = major_version; + p_resp->minor_version = minor_version; + p_ptr = p_resp->collaterals; + if (pck_crl_issuer_chain_size) { + p_resp->pck_crl_issuer_chain_size = pck_crl_issuer_chain_size; + memcpy(p_ptr, p_pck_crl_issuer_chain, pck_crl_issuer_chain_size); + p_ptr += pck_crl_issuer_chain_size; + } + + if (root_ca_crl_size) { + p_resp->root_ca_crl_size = root_ca_crl_size; + memcpy(p_ptr, p_root_ca_crl, root_ca_crl_size); + p_ptr += root_ca_crl_size; + } + + if (pck_crl_size) { + p_resp->pck_crl_size = pck_crl_size; + memcpy(p_ptr, p_pck_crl, pck_crl_size); + p_ptr += pck_crl_size; + } + + if (tcb_info_issuer_chain_size) { + p_resp->tcb_info_issuer_chain_size = tcb_info_issuer_chain_size; + memcpy(p_ptr, p_tcb_info_issuer_chain, tcb_info_issuer_chain_size); + p_ptr += tcb_info_issuer_chain_size; + } + + if (tcb_info_size) { + p_resp->tcb_info_size = tcb_info_size; + memcpy(p_ptr, p_tcb_info, tcb_info_size); + p_ptr += tcb_info_size; + } + + if (qe_identity_issuer_chain_size) { + p_resp->qe_identity_issuer_chain_size = qe_identity_issuer_chain_size; + memcpy(p_ptr, p_qe_identity_issuer_chain, qe_identity_issuer_chain_size); + p_ptr += qe_identity_issuer_chain_size; + } + + if (root_ca_crl_size) { + p_resp->qe_identity_size = qe_identity_size; + memcpy(p_ptr, p_qe_identity, qe_identity_size); + } + + *pp_resp = (uint8_t *)p_resp; + *p_resp_size = buf_size; + ret = QGS_MSG_SUCCESS; + +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_inflate_get_quote_resp( + const uint8_t *p_serialized_resp, uint32_t size, + const uint8_t **pp_selected_id, uint32_t *p_id_size, + const uint8_t **pp_quote, uint32_t *p_quote_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_quote_resp_t *p_resp = NULL; + uint64_t temp = 0; + + if (!p_serialized_resp || !size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_selected_id || !p_id_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!pp_quote || !p_quote_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + // sanity check, the size shouldn't smaller than qgs_msg_get_quote_req_t + if (size < sizeof(qgs_msg_get_quote_resp_t)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + p_resp = (qgs_msg_get_quote_resp_t *)p_serialized_resp; + // Only major version is checked, minor change is deemed as compatible. + if (p_resp->header.major_version != QGS_MSG_LIB_MAJOR_VER) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + + if (p_resp->header.type != GET_QUOTE_RESP) { + ret = QGS_MSG_ERROR_INVALID_TYPE; + goto ret_point; + } + + if (p_resp->header.size != size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + temp = sizeof(qgs_msg_get_quote_resp_t); + temp += p_resp->selected_id_size; + temp += p_resp->quote_size; + if (temp >= UINT32_MAX) { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + if (p_resp->header.size != temp) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + if (p_resp->header.error_code == QGS_MSG_SUCCESS) { + if (!p_resp->quote_size) { + // It makes no sense to return success and an empty quote + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + if (p_resp->selected_id_size) { + *pp_selected_id = p_resp->id_quote; + *p_id_size = p_resp->selected_id_size; + } else { + *pp_selected_id = NULL; + *p_id_size = 0; + } + *pp_quote = p_resp->id_quote + p_resp->selected_id_size; + *p_quote_size = p_resp->quote_size; + } else if (p_resp->header.error_code < QGS_MSG_ERROR_MAX) { + if (p_resp->selected_id_size || p_resp->quote_size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + *pp_selected_id = NULL; + *p_id_size = 0; + *pp_quote = NULL; + *p_quote_size = 0; + } else { + ret = QGS_MSG_ERROR_INVALID_CODE; + goto ret_point; + } + + ret = QGS_MSG_SUCCESS; +ret_point: + return ret; +} + +qgs_msg_error_t qgs_msg_inflate_get_collateral_resp( + const uint8_t *p_serialized_resp, uint32_t size, + uint16_t *p_major_version, uint16_t *p_minor_version, + const uint8_t **pp_pck_crl_issuer_chain, uint32_t *p_pck_crl_issuer_chain_size, + const uint8_t **pp_root_ca_crl, uint32_t *p_root_ca_crl_size, + const uint8_t **pp_pck_crl, uint32_t *p_pck_crl_size, + const uint8_t **pp_tcb_info_issuer_chain, uint32_t *p_tcb_info_issuer_chain_size, + const uint8_t **pp_tcb_info, uint32_t *p_tcb_info_size, + const uint8_t **pp_qe_identity_issuer_chain, uint32_t *p_qe_identity_issuer_chain_size, + const uint8_t **pp_qe_identity, uint32_t *p_qe_identity_size) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + qgs_msg_get_collateral_resp_t *p_resp = NULL; + uint64_t temp = 0; + + if (!p_serialized_resp || !size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (!p_major_version || !p_minor_version + || !pp_pck_crl_issuer_chain || !p_pck_crl_issuer_chain_size + || !pp_root_ca_crl || !p_root_ca_crl_size + || !pp_pck_crl || !p_pck_crl_size + || !pp_tcb_info_issuer_chain || !p_tcb_info_issuer_chain_size + || !pp_tcb_info || !p_tcb_info_size + || !pp_qe_identity_issuer_chain || !p_qe_identity_issuer_chain_size + || !pp_qe_identity || !p_qe_identity_size) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + // sanity check, the size shouldn't smaller than qgs_msg_get_quote_req_t + if (size < sizeof(qgs_msg_get_collateral_resp_t)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + p_resp = (qgs_msg_get_collateral_resp_t *)p_serialized_resp; + // Only major version is checked, minor change is deemed as compatible. + if (p_resp->header.major_version != QGS_MSG_LIB_MAJOR_VER) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + + if (p_resp->header.type != GET_COLLATERAL_RESP) { + ret = QGS_MSG_ERROR_INVALID_TYPE; + goto ret_point; + } + + if (p_resp->header.size != size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + temp = sizeof(p_resp->major_version) + sizeof(p_resp->minor_version) + sizeof(*p_resp); + temp += p_resp->pck_crl_issuer_chain_size; + temp += p_resp->root_ca_crl_size; + temp += p_resp->pck_crl_size; + temp += p_resp->tcb_info_issuer_chain_size; + temp += p_resp->tcb_info_size; + temp += p_resp->qe_identity_issuer_chain_size; + temp += p_resp->qe_identity_size; + + if (temp >= UINT32_MAX) { + ret = QGS_MSG_ERROR_UNEXPECTED; + goto ret_point; + } + if (p_resp->header.size != temp) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + + if (p_resp->header.error_code == QGS_MSG_SUCCESS) { + // It makes no sense to return success and empty collaterals + if (!p_resp->pck_crl_issuer_chain_size + || !p_resp->root_ca_crl_size + || !p_resp->pck_crl_size + || !p_resp->tcb_info_issuer_chain_size + || !p_resp->tcb_info_size + || !p_resp->qe_identity_issuer_chain_size + || !p_resp->qe_identity_size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + *p_major_version = p_resp->major_version; + *p_minor_version = p_resp->minor_version; + + *pp_pck_crl_issuer_chain = p_resp->collaterals; + *p_pck_crl_issuer_chain_size = p_resp->pck_crl_issuer_chain_size; + + *pp_root_ca_crl = *pp_pck_crl_issuer_chain + p_resp->pck_crl_issuer_chain_size; + *p_root_ca_crl_size = p_resp->root_ca_crl_size; + + *pp_pck_crl = *pp_root_ca_crl + p_resp->root_ca_crl_size; + *p_pck_crl_size = p_resp->pck_crl_size; + + *pp_tcb_info_issuer_chain = *pp_pck_crl + p_resp->pck_crl_size; + *p_tcb_info_issuer_chain_size = p_resp->tcb_info_issuer_chain_size; + + *pp_tcb_info = *pp_tcb_info_issuer_chain + p_resp->tcb_info_issuer_chain_size; + *p_tcb_info_size = p_resp->tcb_info_size; + + *pp_qe_identity_issuer_chain = *pp_tcb_info + p_resp->tcb_info_size; + *p_qe_identity_issuer_chain_size = p_resp->qe_identity_issuer_chain_size; + + *pp_qe_identity = *pp_qe_identity_issuer_chain + p_resp->qe_identity_issuer_chain_size; + *p_qe_identity_size = p_resp->qe_identity_size; + + } else if (p_resp->header.error_code < QGS_MSG_ERROR_MAX) { + if (p_resp->pck_crl_issuer_chain_size + || p_resp->root_ca_crl_size + || p_resp->pck_crl_size + || p_resp->tcb_info_issuer_chain_size + || p_resp->tcb_info_size + || p_resp->qe_identity_issuer_chain_size + || p_resp->qe_identity_size) { + ret = QGS_MSG_ERROR_INVALID_SIZE; + goto ret_point; + } + *p_major_version = 0; + *p_minor_version = 0; + + *pp_pck_crl_issuer_chain = NULL; + *p_pck_crl_issuer_chain_size = 0; + *pp_root_ca_crl = NULL; + *p_root_ca_crl_size = 0; + *pp_pck_crl = NULL; + *p_pck_crl_size = 0; + *pp_tcb_info_issuer_chain = NULL; + *p_tcb_info_issuer_chain_size = 0; + *pp_tcb_info = NULL; + *p_tcb_info_size = 0; + *pp_qe_identity_issuer_chain = NULL; + *p_qe_identity_issuer_chain_size = 0; + *pp_qe_identity = NULL; + *p_qe_identity_size = 0; + } else { + ret = QGS_MSG_ERROR_INVALID_CODE; + goto ret_point; + } + + ret = QGS_MSG_SUCCESS; +ret_point: + return ret; +} + +uint32_t qgs_msg_get_type(const uint8_t *p_serialized_msg, uint32_t size, uint32_t *p_type) { + qgs_msg_error_t ret = QGS_MSG_SUCCESS; + const qgs_msg_header_t *p_header = (const qgs_msg_header_t *)p_serialized_msg; + + if (size < sizeof(qgs_msg_header_t)) { + ret = QGS_MSG_ERROR_INVALID_PARAMETER; + goto ret_point; + } + + if (p_header->major_version != QGS_MSG_LIB_MAJOR_VER) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + if (p_header->type >= QGS_MSG_TYPE_MAX) { + ret = QGS_MSG_ERROR_INVALID_VERSION; + goto ret_point; + } + *p_type = p_header->type; + ret = QGS_MSG_SUCCESS; +ret_point: + return ret; +} \ No newline at end of file diff --git a/QuoteGeneration/quote_wrapper/quote/id_enclave/id_enclave.cpp b/QuoteGeneration/quote_wrapper/quote/id_enclave/id_enclave.cpp index 529077ff..4b5fee46 100644 --- a/QuoteGeneration/quote_wrapper/quote/id_enclave/id_enclave.cpp +++ b/QuoteGeneration/quote_wrapper/quote/id_enclave/id_enclave.cpp @@ -182,7 +182,7 @@ sgx_status_t ide_get_id(sgx_key_128bit_t *p_id) * PPID_RSA3072_ENCRYPTED. * * @param key_size [In] The size in bytes of the supplied p_public_key buffer. Currently, it must be equal to the size - * of an RSA3072 public key. 4 byte 'e' and 256 byte 'n'. + * of an RSA3072 public key. 4 bytes 'e' and 384 bytes 'n'. * @param p_public_key * [In, Out] Pointer to the buffer that will contain the public key used to encrypt the PPID. It must * not be NULL and the buffer must reside within the enclave's memory space. diff --git a/QuoteGeneration/quote_wrapper/quote/id_enclave/win/config.xml b/QuoteGeneration/quote_wrapper/quote/id_enclave/win/config.xml index 45e81438..67b4fbab 100644 --- a/QuoteGeneration/quote_wrapper/quote/id_enclave/win/config.xml +++ b/QuoteGeneration/quote_wrapper/quote/id_enclave/win/config.xml @@ -6,7 +6,7 @@ 1 1 0 - 0x1000 + 0x2000 0x1000 1 diff --git a/QuoteGeneration/quote_wrapper/quote/qe_logic.cpp b/QuoteGeneration/quote_wrapper/quote/qe_logic.cpp index 658ecc45..74a637c7 100644 --- a/QuoteGeneration/quote_wrapper/quote/qe_logic.cpp +++ b/QuoteGeneration/quote_wrapper/quote/qe_logic.cpp @@ -907,7 +907,7 @@ static quote3_error_t getencryptedppid(sgx_target_info_t& pce_target_info, uint8 } if (encrypted_ppid_ret_size != REF_RSA_OAEP_3072_MOD_SIZE) { - SE_TRACE(SE_TRACE_ERROR, "PCE returned unexpected returned encrypted PPID size.\n"); + SE_TRACE(SE_TRACE_ERROR, "PCE returned incorrect encrypted PPID size.\n"); return SGX_QL_ERROR_UNEXPECTED; } diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/.gitignore b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/.gitignore new file mode 100644 index 00000000..07a1e397 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock + diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/Cargo.toml b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/Cargo.toml new file mode 100644 index 00000000..d688f343 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "sgx-dcap-ql-rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +"sgx-dcap-ql-sys" = { version = "0.1.0", path = "../sgx-dcap-ql-sys" } diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/src/lib.rs b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/src/lib.rs new file mode 100644 index 00000000..a2fd9363 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs/src/lib.rs @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011-2022 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +//! This is the Intel SGX DCAP Quote Library for Rust. +#![allow(non_camel_case_types)] + +pub use sgx_dcap_ql_sys::quote3_error_t; +pub use sgx_dcap_ql_sys::sgx_target_info_t; +pub use sgx_dcap_ql_sys::sgx_report_t; + +/// Request rarget info of Quote enclave. +/// +/// # Param +/// - **sgx_target_info_t**\ +/// contain the QE's target information. This is used by the application's enclave to generate a REPORT verifiable by the QE. +/// # Return +/// - ***SGX_QL_SUCCESS***\ +/// Successfully retrieved the target information.\ +/// - ***SGX_QL_OUT_OF_EPC***\ +/// There is not enough EPC memory to load one of the Architecture Enclaves needed to complete this operation.\ +/// - ***SGX_QL_ERROR_OUT_OF_MEMORY***\ +/// Heap memory allocation error in library or enclave.\ +/// - ***SGX_QL_ERROR_UNEXPECTED***\ +/// An unexpected internal error occurred.\ +/// +/// # Examples +/// ``` +/// use sgx_dcap_ql_rs::*; +/// +/// let mut target_info: sgx_target_info_t = Default::default(); +/// let result = sgx_qe_get_target_info(&mut target_info); +/// ``` +pub fn sgx_qe_get_target_info( + qe_target_info: &mut sgx_target_info_t, +) -> quote3_error_t { + unsafe { + sgx_dcap_ql_sys::sgx_qe_get_target_info(qe_target_info) + } +} + +/// Request a Quote of the calling TD. +/// +/// # Param +/// - **app_report**\ +/// A set of data that the caller/TD wants to cryptographically bind to the Quote, typically a hash. May be all zeros for the Report data. +/// # Return +/// - ***SGX_QL_SUCCESS***\ +/// Successfully generated the Quote.\ +/// - ***SGX_QL_ERROR_INVALID_PARAMETER***\ +/// The parameter is incorrect.\ +/// - ***SGX_QL_ATT_KEY_NOT_INITIALIZED***\ +/// The platform quoting infrastructure does not have available attestation key to generate quotes.\ +/// - ***SGX_QL_ATT_KEY_CERT_DATA_INVALID***\ +/// The data returned by the platform library's sgx_ql_get_quote_config() is invalid.\ +/// - ***SGX_QL_OUT_OF_EPC***\ +/// There is not enough EPC memory to load one of the Architecture Enclaves needed to complete this operation.\ +/// - ***SGX_QL_ERROR_OUT_OF_MEMORY***\ +/// Heap memory allocation error in library or enclave.\ +/// - ***SGX_QL_ERROR_UNEXPECTED***\ +/// An unexpected internal error occurred.\ +/// +/// # Examples +/// ``` +/// use sgx_dcap_ql_rs::*; +/// +/// let sgx_report:sgx_report_t = Default::default(); +/// let (result, quote) = sgx_qe_get_quote(&sgx_report); +/// ``` +pub fn sgx_qe_get_quote( + app_report: &sgx_report_t, +) -> (quote3_error_t, Option>) { + let mut buf_len = 0; + unsafe { + let result = sgx_dcap_ql_sys::sgx_qe_get_quote_size(&mut buf_len); + match result { + quote3_error_t::SGX_QL_SUCCESS => { + let mut quote = vec![0u8; buf_len as usize]; + let result = sgx_dcap_ql_sys::sgx_qe_get_quote(app_report, buf_len, quote.as_mut_ptr()); + match result { + quote3_error_t::SGX_QL_SUCCESS => { + return (result, Some(quote)) + }, + _ => return (result, None), + } + }, + _ => return (result, None), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sgx_qe_get_target_info() { + let mut target_info: sgx_target_info_t = Default::default(); + let result = sgx_qe_get_target_info(&mut target_info); + assert!(result == quote3_error_t::SGX_QL_INTERFACE_UNAVAILABLE + || result == quote3_error_t::SGX_QL_SUCCESS); + } + + #[test] + fn test_sgx_qe_get_quote() { + let sgx_report:sgx_report_t = Default::default(); + let (result, quote) = sgx_qe_get_quote(&sgx_report); + match quote { + q => println!("quote {:?}", q), + } + assert!(result == quote3_error_t::SGX_QL_INTERFACE_UNAVAILABLE + || result == quote3_error_t::SGX_QL_INVALID_REPORT); + } +} diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/.gitignore b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/Cargo.toml b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/Cargo.toml new file mode 100644 index 00000000..101625b4 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sgx-dcap-ql-sys" +version = "0.1.0" +edition = "2021" +links = "sgx_dcap_ql" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[build-dependencies] +bindgen = "0.59.1" diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/bindings.h b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/bindings.h new file mode 100644 index 00000000..a73f1a97 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/bindings.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "sgx_dcap_ql_wrapper.h" diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/build.rs b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/build.rs new file mode 100644 index 00000000..4bdf1fa1 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/build.rs @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011-2022 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +extern crate bindgen; + +use std::env; +use std::path::PathBuf; + +fn main() { + // Tell cargo to tell rustc to link the system sgx_dcap_ql + // shared library. + println!("cargo:rustc-link-lib=sgx_dcap_ql"); + + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=bindings.h"); + + // Set sdk to search path if SGX_SDK is in environment variable + let mut sdk_inc = String::from(""); + match env::var("SGX_SDK") { + Ok(val) => { + sdk_inc.push_str("-I"); + sdk_inc.push_str(&val); + sdk_inc.push_str("/include/"); + }, + _ => (), + } + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("bindings.h") + // Include search path + .clang_arg(sdk_inc) + // Convert C enum to Rust enum + .rustified_enum("_quote3_error_t") + // Disable debug trait for packed C structures + .derive_debug(false) + .derive_default(true) + + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/src/lib.rs b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/src/lib.rs new file mode 100644 index 00000000..243061a4 --- /dev/null +++ b/QuoteGeneration/quote_wrapper/sgx-dcap-ql-sys/src/lib.rs @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011-2022 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +//! Intel(R) Software Guard Extensions Data Center Attestation Primitives (Intel(R) SGX DCAP) +//! Rust raw FFI bindings for SGX DCAP Quote Library +//! ================================================ +//! +//! Please install the following prerequisite: +//! * Intel(R) SGX DCAP Driver +//! * Intel(R) SGX SDK +//! * Intel(R) SGX DCAP Packages +//! * Intel(R) SGX DCAP PCCS (Provisioning Certificate Caching Service) +//! +//! *Please refer to [SGX DCAP Linux installation guide]( +//! https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf) +//! to install above dependencies.* + +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); \ No newline at end of file diff --git a/QuoteGeneration/quote_wrapper/tdx-attest-rs/Cargo.toml b/QuoteGeneration/quote_wrapper/tdx-attest-rs/Cargo.toml index 2c98ed44..b101b785 100644 --- a/QuoteGeneration/quote_wrapper/tdx-attest-rs/Cargo.toml +++ b/QuoteGeneration/quote_wrapper/tdx-attest-rs/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "tdx-attest-rs" -version = "0.1.0" +version = "0.1.2" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tdx-attest-sys = { version = "0.1.0", path = "../tdx-attest-sys" } \ No newline at end of file +tdx-attest-sys = { version = "0.1.0", path = "../tdx-attest-sys" } diff --git a/QuoteGeneration/quote_wrapper/tdx-attest-rs/src/lib.rs b/QuoteGeneration/quote_wrapper/tdx-attest-rs/src/lib.rs index 9d5fb3e2..5b54955e 100644 --- a/QuoteGeneration/quote_wrapper/tdx-attest-rs/src/lib.rs +++ b/QuoteGeneration/quote_wrapper/tdx-attest-rs/src/lib.rs @@ -55,6 +55,8 @@ pub use tdx_attest_sys::tdx_uuid_t; /// # Return /// - ***TDX_ATTEST_SUCCESS***\ /// Successfully generated the Quote.\ +/// - ***TDX_ATTEST_ERROR_UNSUPPORTED_ATT_KEY_ID***\ +/// The platform Quoting infrastructure does not support any of the keys.\ /// - ***TDX_ATT_ERROR_INVALID_PARAMETER***\ /// The parameter is incorrect.\ /// - ***TDX_ATTEST_ERROR_DEVICE_FAILURE***\ @@ -178,6 +180,8 @@ pub fn tdx_att_get_report( /// The parameter is incorrect. /// - ***TDX_ATTEST_ERROR_DEVICE_FAILURE***\ /// Failed to acess tdx attest device.\ +/// - ***TDX_ATTEST_ERROR_INVALID_RTMR_INDEX***\ +/// Only supported RTMR index is 2 and 3.\ /// - ***TDX_ATTEST_ERROR_EXTEND_FAILURE***\ /// Failed to extend data.\ /// - ***TDX_ATTEST_ERROR_NOT_SUPPORTED***\ @@ -225,19 +229,13 @@ pub fn tdx_att_extend( /// ``` pub fn tdx_att_get_supported_att_key_ids( ) -> (tdx_attest_error_t, Option>){ - const MAX_ATT_KEY_ID_COUNT: usize = 20; let mut list_count = 0; unsafe { let result = tdx_attest_sys::tdx_att_get_supported_att_key_ids(std::ptr::null_mut() as *mut tdx_uuid_t, &mut list_count); match result { tdx_attest_error_t::TDX_ATTEST_SUCCESS => { - if list_count > MAX_ATT_KEY_ID_COUNT as u32 { - return (tdx_attest_error_t::TDX_ATTEST_ERROR_UNEXPECTED, None); - } - let box_buf: Box<[tdx_uuid_t; MAX_ATT_KEY_ID_COUNT]> = Box::new([tdx_uuid_t{d: [0; 16usize],}; MAX_ATT_KEY_ID_COUNT]); - let p_buf = Box::into_raw(box_buf); - let result = tdx_attest_sys::tdx_att_get_supported_att_key_ids(p_buf as *mut tdx_uuid_t, &mut list_count); - let att_key_id_list = Box::from_raw(p_buf)[..list_count as usize].to_vec(); + let mut att_key_id_list = vec![tdx_uuid_t{d: [0; 16usize],}; list_count as usize]; + let result = tdx_attest_sys::tdx_att_get_supported_att_key_ids(att_key_id_list.as_mut_ptr(), &mut list_count); match result { tdx_attest_error_t::TDX_ATTEST_SUCCESS => { return (result, Some(att_key_id_list)) diff --git a/QuoteGeneration/quote_wrapper/tdx-attest-sys/src/lib.rs b/QuoteGeneration/quote_wrapper/tdx-attest-sys/src/lib.rs index 0004d995..df146645 100644 --- a/QuoteGeneration/quote_wrapper/tdx-attest-sys/src/lib.rs +++ b/QuoteGeneration/quote_wrapper/tdx-attest-sys/src/lib.rs @@ -39,11 +39,8 @@ //! * Intel(R) SGX DCAP PCCS (Provisioning Certificate Caching Service) //! //! *Please refer to [SGX DCAP Linux installation guide]( -//! https://download.01.org/intel-sgx/sgx-dcap/#version#/linux/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf) +//! https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf) //! to install above dependencies.* -//! -//! *Note that you need to change **\#version\#** to actual version number in URL, such as 1.4.*\ -//! *Note that you need to install **libtdx-attest-dev** and **clang** for this package.* #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] diff --git a/QuoteGeneration/quote_wrapper/tdx_attest/Makefile.sample b/QuoteGeneration/quote_wrapper/tdx_attest/Makefile.sample index 6d40b0f4..1c8bc138 100644 --- a/QuoteGeneration/quote_wrapper/tdx_attest/Makefile.sample +++ b/QuoteGeneration/quote_wrapper/tdx_attest/Makefile.sample @@ -1,5 +1,5 @@ # -# Copyright (C) 2011-2019 Intel Corporation. All rights reserved. +# Copyright (C) 2011-2022 Intel Corporation. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -35,8 +35,7 @@ else endif test_tdx_attest: test_tdx_attest.c - $(CC) $(CFLAGS) $^ -l:libtdx_attest.so.1 -o $@ + $(CC) $(CFLAGS) $^ -ltdx_attest -o $@ clean: rm -rf ./test_tdx_attest - diff --git a/QuoteGeneration/quote_wrapper/tdx_attest/linux/Makefile b/QuoteGeneration/quote_wrapper/tdx_attest/linux/Makefile index b06d426f..fd239c27 100644 --- a/QuoteGeneration/quote_wrapper/tdx_attest/linux/Makefile +++ b/QuoteGeneration/quote_wrapper/tdx_attest/linux/Makefile @@ -34,12 +34,11 @@ TOP_DIR = ../../.. include $(TOP_DIR)/buildenv.mk ######## Tdx_Attest Settings ######## -Protobuf_CFLAGS = `pkg-config --cflags libprotobuf-c` -Protobuf_LFLAGS = `pkg-config --libs libprotobuf-c` +QGS_MSG_LFLAGS = -L../../qgs_msg_lib/linux -lqgs_msg Tdx_Attest_C_Files := ../tdx_attest.c -Tdx_Attest_Include_Paths := -I./ +Tdx_Attest_Include_Paths := -I./ -I../../qgs_msg_lib/inc Tdx_Attest_C_Flags := $(CFLAGS) -g -MMD -fPIC -Wno-attributes $(Tdx_Attest_Include_Paths) @@ -60,17 +59,13 @@ install_lib: $(Tdx_Attest_Name) | $(BUILD_DIR) @$(CP) $(Tdx_Attest_Name) $| ######## Tdx_Attest Objects ######## -$(Tdx_Attest_Name): qgs.message.pb-c.o $(Tdx_Attest_C_Objects) - $(CC) $^ -shared -shared -Wl,-soname=$@.$(SGX_MAJOR_VER) $(Protobuf_LFLAGS) $(LDUFLAGS) -o $@ +$(Tdx_Attest_Name): $(Tdx_Attest_C_Objects) + $(CC) $^ -shared -shared -Wl,-soname=$@.$(SGX_MAJOR_VER) $(QGS_MSG_LFLAGS) $(LDUFLAGS) -o $@ @echo "LINK => $@" -$(Tdx_Attest_C_Objects): %.o: qgs.message.pb-c.o %.c +$(Tdx_Attest_C_Objects): %.o: %.c $(CC) $(Tdx_Attest_C_Flags) -c $(Tdx_Attest_C_Files) -o $@ -qgs.message.pb-c.o: ../../qgs/qgs.message.proto - protoc --c_out=. --proto_path=../../ ../../qgs/qgs.message.proto - $(CC) -c qgs/qgs.message.pb-c.c -I. $(Protobuf_CFLAGS) $(Tdx_Attest_C_Flags) - test_app: $(Tdx_Attest_Name) ../test_tdx_attest.c ../tdx_attest.h $(CC) -I. -L./linux ../test_tdx_attest.c -L. -ltdx_attest -g -o $@ @@ -80,6 +75,4 @@ $(BUILD_DIR): .PHONY: clean clean: - @rm -rf $(Tdx_Attest_Name) $(Tdx_Attest_C_Objects) $(Tdx_Attest_C_Depends) qgs.message.pb-c.o ./qgs test_app - - + @rm -rf $(Tdx_Attest_Name) $(Tdx_Attest_C_Objects) $(Tdx_Attest_C_Depends) ./qgs test_app diff --git a/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.c b/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.c index 7c0565ff..b506efb9 100644 --- a/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.c +++ b/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.c @@ -29,10 +29,10 @@ * */ -#include "qgs/qgs.message.pb-c.h" #include #include #include "tdx_attest.h" +#include "qgs_msg_lib.h" #include #include @@ -46,14 +46,22 @@ #include #include #include +#include -#define TDX_ATTEST_DEV_PATH "/dev/tdx-attest" +#define TDX_ATTEST_DEV_PATH "/dev/tdx-guest" #define CFG_FILE_PATH "/etc/tdx-attest.conf" -//TODO: Should include kernel header -#define TDX_CMD_GET_TDREPORT _IOWR('T', 0x01, __u64) -#define TDX_CMD_EXTEND_RTMR _IOR('T', 0x04, __u64) -#define TDX_CMD_GET_EXTEND_SIZE _IOR('T', 0x05, __u64) -#define TDX_CMD_GEN_QUOTE _IOR('T', 0x02, __u64) +// TODO: Should include kernel header, but the header file are included by +// different package in differnt distro, and installed in different locations. +// So add these defines here. Need to remove them later when kernel header +// became stable. +#define TDX_CMD_GET_REPORT _IOWR('T', 0x01, __u64) +#define TDX_CMD_GET_QUOTE _IOR('T', 0x02, __u64) + +/* TD Quote status codes */ +#define GET_QUOTE_SUCCESS 0 +#define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff +#define GET_QUOTE_ERROR 0x8000000000000000 +#define GET_QUOTE_SERVICE_UNAVAILABLE 0x8000000000000001 #ifdef DEBUG #define TDX_TRACE \ @@ -65,28 +73,34 @@ #define TDX_TRACE #endif -#pragma pack(push, 1) -// It's a 4*4K byte structure -typedef struct _get_quote_blob_t -{ - // Following fields are used for transport layer, LE - uint64_t version; - uint64_t status; - uint32_t in_len; - uint32_t out_len; - // Following fields are consumed by QGS and this library - uint8_t trans_len[4]; // BE - uint8_t p_buf[4 * 4 * 1024 - 28]; -} get_quote_blob_t; - -typedef struct _get_quote_ioctl_arg_t -{ - void *p_blob; - size_t len; -} get_quote_ioctl_arg_t; -#pragma pack(pop) +struct tdx_report_req { + __u8 subtype; + __u64 reportdata; + __u32 rpd_len; + __u64 tdreport; + __u32 tdr_len; +}; + +struct tdx_quote_hdr { + /* Quote version, filled by TD */ + __u64 version; + /* Status code of Quote request, filled by VMM */ + __u64 status; + /* Length of TDREPORT, filled by TD */ + __u32 in_len; + /* Length of Quote, filled by VMM */ + __u32 out_len; + /* Actual Quote data or TDREPORT on input */ + __u64 data[0]; +}; + +struct tdx_quote_req { + __u64 buf; + __u64 len; +}; static const unsigned HEADER_SIZE = 4; +static const size_t REQ_BUF_SIZE = 4 * 4 * 1024; // 4 pages static const tdx_uuid_t g_intel_tdqe_uuid = {TDX_SGX_ECDSA_ATTESTATION_ID}; static unsigned int get_vsock_port(void) @@ -136,7 +150,7 @@ static unsigned int get_vsock_port(void) // range is ok, so we can convert to int port = (unsigned int)long_num & 0xFFFFFFFF; - #ifdef DBUG + #ifdef DEBUG fprintf(stdout, "\nGet the vsock port number [%u]\n", port); #endif break; @@ -163,12 +177,16 @@ static tdx_attest_error_t get_tdx_report( return TDX_ATTEST_ERROR_INVALID_PARAMETER; } + struct tdx_report_req req; uint8_t tdx_report[TDX_REPORT_SIZE] = {0}; - if (p_tdx_report_data) { - memcpy(tdx_report, p_tdx_report_data->d, sizeof(p_tdx_report_data->d)); - } - if (-1 == ioctl(devfd, TDX_CMD_GET_TDREPORT, tdx_report)) { + req.subtype = 0; + req.reportdata = (__u64)p_tdx_report_data->d; + req.rpd_len = TDX_REPORT_DATA_SIZE; + req.tdreport = (__u64)tdx_report; + req.tdr_len = TDX_REPORT_SIZE; + + if (-1 == ioctl(devfd, TDX_CMD_GET_REPORT, &req)) { TDX_TRACE; return TDX_ATTEST_ERROR_REPORT_FAILURE; } @@ -193,13 +211,17 @@ tdx_attest_error_t tdx_att_get_quote( uint32_t in_msg_size = 0; unsigned int vsock_port = 0; tdx_attest_error_t ret = TDX_ATTEST_ERROR_UNEXPECTED; - get_quote_blob_t *p_get_quote_blob = NULL; + struct tdx_quote_hdr *p_get_quote_blob = NULL; + uint8_t *p_blob_payload = NULL; tdx_report_t tdx_report; uint32_t msg_size = 0; - Qgs__Message__Response *resp = NULL; - Qgs__Message__Request request = QGS__MESSAGE__REQUEST__INIT; - Qgs__Message__Request__GetQuoteRequest get_quote_request = - QGS__MESSAGE__REQUEST__GET_QUOTE_REQUEST__INIT; + + qgs_msg_error_t qgs_msg_ret = QGS_MSG_SUCCESS; + qgs_msg_header_t *p_header = NULL; + uint8_t *p_req = NULL; + const uint8_t *p_quote = NULL; + const uint8_t *p_selected_id = NULL; + uint32_t id_size = 0; if ((!p_att_key_id_list && list_size) || (p_att_key_id_list && !list_size)) { @@ -222,11 +244,11 @@ tdx_attest_error_t tdx_att_get_quote( } if (p_att_key_id_list && memcmp(p_att_key_id_list, &g_intel_tdqe_uuid, sizeof(g_intel_tdqe_uuid))) { - ret = TDX_ATTEST_ERROR_INVALID_PARAMETER; + ret = TDX_ATTEST_ERROR_UNSUPPORTED_ATT_KEY_ID; } *pp_quote = NULL; memset(&tdx_report, 0, sizeof(tdx_report)); - p_get_quote_blob = (get_quote_blob_t *)malloc(sizeof(get_quote_blob_t)); + p_get_quote_blob = (struct tdx_quote_hdr *)malloc(REQ_BUF_SIZE); if (!p_get_quote_blob) { ret = TDX_ATTEST_ERROR_OUT_OF_MEMORY; goto ret_point; @@ -244,21 +266,31 @@ tdx_attest_error_t tdx_att_get_quote( goto ret_point; } - request.type = QGS__MESSAGE__REQUEST__MSG_GET_QUOTE_REQUEST; - get_quote_request.report.len = sizeof(tdx_report.d); - get_quote_request.report.data = tdx_report.d; - request.msg_case = QGS__MESSAGE__REQUEST__MSG_GET_QUOTE_REQUEST; - request.getquoterequest = &get_quote_request; + qgs_msg_ret = qgs_msg_gen_get_quote_req(tdx_report.d, sizeof(tdx_report.d), + NULL, 0, &p_req, &msg_size); + if (QGS_MSG_SUCCESS != qgs_msg_ret) { + #ifdef DEBUG + fprintf(stdout, "\nqgs_msg_gen_get_quote_req return 0x%x\n", qgs_msg_ret); + #endif + ret = TDX_ATTEST_ERROR_UNEXPECTED; + goto ret_point; + } + + if (msg_size > REQ_BUF_SIZE - sizeof(struct tdx_quote_hdr) - HEADER_SIZE) { +#ifdef DEBUG + fprintf(stdout, "\nqmsg_size[%d] is too big\n", msg_size); + #endif + ret = TDX_ATTEST_ERROR_NOT_SUPPORTED; + goto ret_point; + } - // Add the size header - msg_size = (uint32_t)qgs__message__request__get_packed_size(&request); - p_get_quote_blob->trans_len[0] = (uint8_t)((msg_size >> 24) & 0xFF); - p_get_quote_blob->trans_len[1] = (uint8_t)((msg_size >> 16) & 0xFF); - p_get_quote_blob->trans_len[2] = (uint8_t)((msg_size >> 8) & 0xFF); - p_get_quote_blob->trans_len[3] = (uint8_t)(msg_size & 0xFF); + p_blob_payload = (uint8_t *)&p_get_quote_blob->data; + p_blob_payload[0] = (uint8_t)((msg_size >> 24) & 0xFF); + p_blob_payload[1] = (uint8_t)((msg_size >> 16) & 0xFF); + p_blob_payload[2] = (uint8_t)((msg_size >> 8) & 0xFF); + p_blob_payload[3] = (uint8_t)(msg_size & 0xFF); - // Serialization - qgs__message__request__pack(&request, p_get_quote_blob->p_buf); + memcpy(p_blob_payload + HEADER_SIZE, p_req, msg_size); do { vsock_port = get_vsock_port(); @@ -283,7 +315,7 @@ tdx_attest_error_t tdx_att_get_quote( } // Write to socket - if (HEADER_SIZE + msg_size != send(s, p_get_quote_blob->trans_len, + if (HEADER_SIZE + msg_size != send(s, p_blob_payload, HEADER_SIZE + msg_size, 0)) { TDX_TRACE; ret = TDX_ATTEST_ERROR_VSOCK_FAILURE; @@ -291,7 +323,7 @@ tdx_attest_error_t tdx_att_get_quote( } // Read the response size header - if (HEADER_SIZE != recv(s, p_get_quote_blob->trans_len, + if (HEADER_SIZE != recv(s, p_blob_payload, HEADER_SIZE, 0)) { TDX_TRACE; ret = TDX_ATTEST_ERROR_VSOCK_FAILURE; @@ -300,8 +332,7 @@ tdx_attest_error_t tdx_att_get_quote( // decode the size for (unsigned i = 0; i < HEADER_SIZE; ++i) { - in_msg_size = in_msg_size * 256 - + ((p_get_quote_blob->trans_len[i]) & 0xFF); + in_msg_size = in_msg_size * 256 + ((p_blob_payload[i]) & 0xFF); } // prepare the buffer and read the reply body @@ -309,8 +340,7 @@ tdx_attest_error_t tdx_att_get_quote( fprintf(stdout, "\nReply message body is %u bytes", in_msg_size); #endif - if (sizeof(p_get_quote_blob->p_buf) < in_msg_size) - { + if (REQ_BUF_SIZE - sizeof(struct tdx_quote_hdr) - HEADER_SIZE < in_msg_size) { #ifdef DEBUG fprintf(stdout, "\nReply message body is too big"); #endif @@ -318,7 +348,7 @@ tdx_attest_error_t tdx_att_get_quote( goto ret_point; } while( recieved_bytes < in_msg_size) { - int recv_ret = (int)recv(s, p_get_quote_blob->p_buf + recieved_bytes, + int recv_ret = (int)recv(s, p_blob_payload + HEADER_SIZE + recieved_bytes, in_msg_size - recieved_bytes, 0); if (recv_ret < 0) { ret = TDX_ATTEST_ERROR_VSOCK_FAILURE; @@ -334,15 +364,15 @@ tdx_attest_error_t tdx_att_get_quote( if (use_tdvmcall) { int ioctl_ret = 0; - get_quote_ioctl_arg_t arg; + struct tdx_quote_req arg; p_get_quote_blob->version = 1; p_get_quote_blob->status = 0; p_get_quote_blob->in_len = HEADER_SIZE + msg_size; - p_get_quote_blob->out_len = (uint32_t)(sizeof(*p_get_quote_blob) - 24); - arg.p_blob = p_get_quote_blob; - arg.len = sizeof(*p_get_quote_blob); + p_get_quote_blob->out_len = 0; + arg.buf = (__u64)p_get_quote_blob; + arg.len = REQ_BUF_SIZE; - ioctl_ret = ioctl(devfd, TDX_CMD_GEN_QUOTE, &arg); + ioctl_ret = ioctl(devfd, TDX_CMD_GET_QUOTE, &arg); if (EBUSY == ioctl_ret) { TDX_TRACE; ret = TDX_ATTEST_ERROR_BUSY; @@ -355,49 +385,64 @@ tdx_attest_error_t tdx_att_get_quote( if (p_get_quote_blob->status || p_get_quote_blob->out_len <= HEADER_SIZE) { TDX_TRACE; - ret = TDX_ATTEST_ERROR_UNEXPECTED; + if (GET_QUOTE_IN_FLIGHT == p_get_quote_blob->status) { + ret = TDX_ATTEST_ERROR_BUSY; + } else if (GET_QUOTE_SERVICE_UNAVAILABLE == p_get_quote_blob->status) { + ret = TDX_ATTEST_ERROR_NOT_SUPPORTED; + } else { + ret = TDX_ATTEST_ERROR_UNEXPECTED; + } goto ret_point; } - //in_msg_size is the size of serialized response, remove 4bytes header - //TODO: Decode the HEAD and compare it with out_len as defense-in-depth - in_msg_size = p_get_quote_blob->out_len - HEADER_SIZE; + //in_msg_size is the size of serialized response + for (unsigned i = 0; i < HEADER_SIZE; ++i) { + in_msg_size = in_msg_size * 256 + ((p_blob_payload[i]) & 0xFF); + } + if (in_msg_size != p_get_quote_blob->out_len - HEADER_SIZE) { + TDX_TRACE; + ret = TDX_ATTEST_ERROR_UNEXPECTED; + goto ret_point; + } #ifdef DEBUG fprintf(stdout, "\nGet %u bytes response from tdvmcall", in_msg_size); #endif } - resp = qgs__message__response__unpack( - NULL, in_msg_size, p_get_quote_blob->p_buf); - if (!resp) { + qgs_msg_ret = qgs_msg_inflate_get_quote_resp( + p_blob_payload + HEADER_SIZE, in_msg_size, + &p_selected_id, &id_size, + &p_quote, "e_size); + if (QGS_MSG_SUCCESS != qgs_msg_ret) { + #ifdef DEBUG + fprintf(stdout, "\nqgs_msg_inflate_get_quote_resp return 0x%x", qgs_msg_ret); + #endif ret = TDX_ATTEST_ERROR_UNEXPECTED; goto ret_point; } - switch (resp->type) - { - case QGS__MESSAGE__RESPONSE__MSG_GET_QUOTE_RESPONSE: - if (resp->getquoteresponse->error_code != 0) { - ret = TDX_ATTEST_ERROR_UNEXPECTED; - goto ret_point; - } - quote_size = (uint32_t)resp->getquoteresponse->quote.len; - *pp_quote = malloc(quote_size); - if (!*pp_quote) { - ret = TDX_ATTEST_ERROR_OUT_OF_MEMORY; - goto ret_point; - } - memcpy(*pp_quote, resp->getquoteresponse->quote.data, quote_size); - if (p_quote_size) { - *p_quote_size = quote_size; - } - if (p_att_key_id) { - *p_att_key_id = g_intel_tdqe_uuid; - } - break; - default: + // We've called qgs_msg_inflate_get_quote_resp, the message type should be GET_QUOTE_RESP + p_header = (qgs_msg_header_t *)(p_blob_payload + HEADER_SIZE); + if (p_header->error_code != 0) { + #ifdef DEBUG + fprintf(stdout, "\nerror code in resp msg is 0x%x", p_header->error_code); + #endif ret = TDX_ATTEST_ERROR_UNEXPECTED; + goto ret_point; } + *pp_quote = malloc(quote_size); + if (!*pp_quote) { + ret = TDX_ATTEST_ERROR_OUT_OF_MEMORY; + goto ret_point; + } + memcpy(*pp_quote, p_quote, quote_size); + if (p_quote_size) { + *p_quote_size = quote_size; + } + if (p_att_key_id) { + *p_att_key_id = g_intel_tdqe_uuid; + } + ret = TDX_ATTEST_SUCCESS; ret_point: if (s >= 0) { @@ -406,7 +451,7 @@ tdx_attest_error_t tdx_att_get_quote( if (-1 != devfd) { close(devfd); } - protobuf_c_message_free_unpacked((ProtobufCMessage *)resp, NULL); + qgs_msg_free(p_req); free(p_get_quote_blob); return ret; @@ -461,6 +506,7 @@ tdx_attest_error_t tdx_att_get_supported_att_key_ids( tdx_attest_error_t tdx_att_extend( const tdx_rtmr_event_t *p_rtmr_event) { +#ifdef TDX_CMD_EXTEND_RTMR int devfd = -1; uint64_t extend_data_size = 0; if (!p_rtmr_event || p_rtmr_event->version != 1) { @@ -492,4 +538,8 @@ tdx_attest_error_t tdx_att_extend( } close(devfd); return TDX_ATTEST_SUCCESS; +#else + (void)p_rtmr_event; + return TDX_ATTEST_ERROR_NOT_SUPPORTED; +#endif } diff --git a/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.h b/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.h index f4126c33..b7c5a540 100644 --- a/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.h +++ b/QuoteGeneration/quote_wrapper/tdx_attest/tdx_attest.h @@ -54,6 +54,7 @@ typedef enum _tdx_attest_error_t { TDX_ATTEST_ERROR_BUSY = 0x0009, ///< The device driver return busy TDX_ATTEST_ERROR_DEVICE_FAILURE = 0x000a, ///< Failed to acess tdx attest device TDX_ATTEST_ERROR_INVALID_RTMR_INDEX = 0x000b, ///< Only supported RTMR index is 2 and 3 + TDX_ATTEST_ERROR_UNSUPPORTED_ATT_KEY_ID = 0x000c, ///< The platform Quoting infrastructure does not support any of the keys described in att_key_id_list TDX_ATTEST_ERROR_MAX } tdx_attest_error_t; diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/enclave/linux/Makefile b/QuoteGeneration/quote_wrapper/tdx_quote/enclave/linux/Makefile index d9a60053..4896bfc3 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/enclave/linux/Makefile +++ b/QuoteGeneration/quote_wrapper/tdx_quote/enclave/linux/Makefile @@ -31,7 +31,6 @@ TOP_DIR := ../../../.. -MITIGATION-CVE-2020-0551 := LOAD include $(TOP_DIR)/ae/buildenv.mk AENAME := tdqe diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/inc/td_ql_wrapper.h b/QuoteGeneration/quote_wrapper/tdx_quote/inc/td_ql_wrapper.h index dd7d3a48..f7f8091c 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/inc/td_ql_wrapper.h +++ b/QuoteGeneration/quote_wrapper/tdx_quote/inc/td_ql_wrapper.h @@ -36,6 +36,8 @@ */ #ifndef _TD_QL_WRAPPER_H_ #define _TD_QL_WRAPPER_H_ +#include +#include #include "sgx_quote_4.h" #define TEE_ATT_MK_ERROR(x) (0x00011000|(x)) @@ -132,7 +134,7 @@ typedef enum _tee_att_error_t { } tee_att_error_t; -struct tee_att_config_t; +typedef struct tee_att_config_t tee_att_config_t; typedef enum { diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/linux/Makefile b/QuoteGeneration/quote_wrapper/tdx_quote/linux/Makefile index 0d36a4d2..2403a594 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/linux/Makefile +++ b/QuoteGeneration/quote_wrapper/tdx_quote/linux/Makefile @@ -123,7 +123,7 @@ force_look: clean: @rm -f .config_* $(Quote_Name) $(Quote_C_Objects) $(Quote_Cpp_Objects) \ - tdqe_u.* id_enclave_u.* + $(Quote_C_Depends) $(Quote_Cpp_Depends) tdqe_u.* id_enclave_u.* @make -C $(PCE_Library_Dir) clean @make -C $(TDQE_Dir) clean @make -C $(ID_ENCLAVE_Dir) clean diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/linux/td_ql_logic.lds b/QuoteGeneration/quote_wrapper/tdx_quote/linux/td_ql_logic.lds index 7ef0ca80..4d5382d7 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/linux/td_ql_logic.lds +++ b/QuoteGeneration/quote_wrapper/tdx_quote/linux/td_ql_logic.lds @@ -7,6 +7,7 @@ global: tee_att_get_quote; tee_att_get_keyid; tee_att_set_path; + tee_att_get_qpl_handle; local: *; }; diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.cpp b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.cpp index 6d758817..6670016c 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.cpp +++ b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.cpp @@ -704,7 +704,7 @@ tee_att_error_t tee_att_config_t::getencryptedppid(sgx_target_info_t& pce_target } if (encrypted_ppid_ret_size != REF_RSA_OAEP_3072_MOD_SIZE) { - SE_TRACE(SE_TRACE_ERROR, "PCE returned unexpected returned encrypted PPID size.\n"); + SE_TRACE(SE_TRACE_ERROR, "PCE returned incorrect encrypted PPID size.\n"); return TEE_ATT_ERROR_UNEXPECTED; } @@ -770,7 +770,7 @@ tee_att_error_t tee_att_config_t::write_persistent_data(const uint8_t *p_buf, SE_PROD_LOG("Error returned from the sgx_ql_write_persistent_data API. 0x%04x\n", ret_val); } } else { - SE_TRACE(SE_TRACE_WARNING, "Couldn't find 'sgx_ql_write_persistent_data()' in the platform library. %s\n", dlerror()); + SE_TRACE(SE_TRACE_WARNING, "Couldn't find 'sgx_ql_write_persistent_data()' in the platform library. %s\n", error); } } else { SE_PROD_LOG("Couldn't find the platform library. %s\n", dlerror()); @@ -847,7 +847,7 @@ tee_att_error_t tee_att_config_t::read_persistent_data(uint8_t *p_buf, SE_PROD_LOG("Error returned from the sgx_ql_read_persistent_data API. 0x%04x\n", ret_val); } } else { - SE_TRACE(SE_TRACE_WARNING, "Couldn't find 'sgx_ql_read_persistent_data()' in the platform library. %s\n", dlerror()); + SE_TRACE(SE_TRACE_WARNING, "Couldn't find 'sgx_ql_read_persistent_data()' in the platform library. %s\n", error); } } else { SE_PROD_LOG("Couldn't find the platform library. %s\n", dlerror()); diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.h b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.h index b3c094fb..bb6f128f 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.h +++ b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_logic.h @@ -139,14 +139,14 @@ struct tee_att_config_t { tee_att_error_t ecdsa_get_quote(const sgx_report2_t* p_app_report, sgx_quote4_t* p_quote, uint32_t quote_size); - -private: #ifndef _MSC_VER - void* + void * #else HMODULE #endif - get_qpl_handle(); + get_qpl_handle(); + + private: bool get_qe_path(tee_att_ae_type_t type, TCHAR* p_file_path, size_t buf_size); diff --git a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_wrapper.cpp b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_wrapper.cpp index 15899254..0a91b693 100644 --- a/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_wrapper.cpp +++ b/QuoteGeneration/quote_wrapper/tdx_quote/td_ql_wrapper.cpp @@ -133,7 +133,7 @@ tee_att_error_t tee_att_create_context(const tee_att_att_key_id_t* p_att_key_id, #ifndef _MSC_VER strncpy(p_context->tdqe_path, p_qe_path, MAX_PATH - 1); #else - MultiByteToWideChar(CP_ACP, 0, p_qe_path, (int)len, p_context->tdqe_path, MAX_PATH); + MultiByteToWideChar(CP_ACP, 0, p_qe_path, (int)len, p_context->tdqe_path, MAX_PATH); #endif p_context->tdqe_path[len] = '\0'; } @@ -479,8 +479,23 @@ tee_att_error_t tee_att_get_keyid(const tee_att_config_t* p_context, return TEE_ATT_SUCCESS; } - #ifndef _MSC_VER +extern "C" +tee_att_error_t tee_att_get_qpl_handle(const tee_att_config_t *p_context, + void **pp_qpl_handle) { + void *p_local_qpl_handle = NULL; + if (NULL == p_context || NULL == pp_qpl_handle) + return TEE_ATT_ERROR_INVALID_PARAMETER; + + p_local_qpl_handle = const_cast(p_context)->get_qpl_handle(); + if ( NULL == p_local_qpl_handle) { + return TEE_ATT_PLATFORM_LIB_UNAVAILABLE; + } + + *pp_qpl_handle = p_local_qpl_handle; + return TEE_ATT_SUCCESS; +} + static tee_att_error_t sgx_set_context_path(const tee_att_config_t* p_context, tee_att_ae_type_t type, const char* p_path) @@ -499,7 +514,7 @@ static tee_att_error_t sgx_set_context_path(const tee_att_config_t* p_context, break; case TEE_ATT_IDE: context_path = const_cast (p_context->ide_path); - break; + break; default: return TEE_ATT_ERROR_INVALID_PARAMETER; } diff --git a/QuoteVerification/QVL/README.md b/QuoteVerification/QVL/README.md index 0ef908ac..07abefdf 100644 --- a/QuoteVerification/QVL/README.md +++ b/QuoteVerification/QVL/README.md @@ -25,11 +25,12 @@ This library provides ra reference implementation for parsing ECDSA Attestation | BUILD_TESTS | Enable/Disable building of the unit and integration tests | ON | | BUILD_DOCS | Enable/Disable building of the doxygen based documentation | OFF | | BUILD_ENCLAVE | Enable/Disable building of test SGX enclave that uses Quote Verification Library as part of sample app (Linux only, requires Intel SGX SDK and Intel SGX SSL) | OFF | +| BUILD_LOGS | Enable/disable logging capabilities in Quote Verification Library. It is not supported inside enclave. | OFF | ### Linux Requirements: -* cmake version 3.10 or higher +* cmake version 3.18 or higher * make * clang++ with c++11 support (5.0.2 or higher is recommended) * doxygen version 1.8.14 if BUILD_DOCS is enabled @@ -42,6 +43,7 @@ Additional libraries will be downloaded and compiled during first build: * openssl v1.1.1 (AttestationLibrary dependency) * googletest (Tests dependency) +* spdlog (added only if building with logs capabilities) To build test SGX enclave that includes Quote Verification Library additional libraries are required and should be provided by user: @@ -121,6 +123,9 @@ Requirements: NOTE: Enclave build is currently not supported on Windows. +HINT: Windows build may fail if repository path is too long. + Consider changing HUNTER_ROOT location in CMakeLists.txt + #### Using Visual Studio with CMake support CMake project can be directly opened and built using Visual Studio with CMake support.. #### Using CMake, MSBuild and PowerShell diff --git a/QuoteVerification/QVL/Src/AttestationApp/CMakeLists.txt b/QuoteVerification/QVL/Src/AttestationApp/CMakeLists.txt index 5a7d88cc..1a508aac 100644 --- a/QuoteVerification/QVL/Src/AttestationApp/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/AttestationApp/CMakeLists.txt @@ -45,6 +45,10 @@ add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} PRIVATE AppCore) +if (BUILD_LOGS) + target_compile_definitions(${PROJECT_NAME} PUBLIC SGX_LOGS=true) + target_compile_definitions(AppCore PUBLIC SGX_LOGS=true) +endif() ######### QVL Enclave related settings ################################################################################# if (BUILD_ENCLAVE) diff --git a/QuoteVerification/QVL/Src/AttestationApp/src/main.cpp b/QuoteVerification/QVL/Src/AttestationApp/src/main.cpp index 8ee78cd7..12b01849 100644 --- a/QuoteVerification/QVL/Src/AttestationApp/src/main.cpp +++ b/QuoteVerification/QVL/Src/AttestationApp/src/main.cpp @@ -36,6 +36,7 @@ #include "AppCore/AppOptionsParser.h" #include "AppCore/AttestationLibraryAdapter.h" #include "AppCore/FileReader.h" +#include "SgxEcdsaAttestation/QuoteVerification.h" int main(int argc, char* argv[]) { @@ -45,7 +46,6 @@ int main(int argc, char* argv[]) std::stringstream logger; - intel::sgx::dcap::AppOptionsParser optionsParser; auto options = optionsParser.parse(argc, argv, logger); if(nullptr == options) @@ -54,6 +54,10 @@ int main(int argc, char* argv[]) return 0; } +#ifdef SGX_LOGS + sgxAttestationLoggerSetup("AttestationApp", "TRACE", "TRACE", "qvl.log", + ""); +#endif std::cout << "Running QVL version: " << app.version() << std::endl; bool result = app.runVerification(*options, logger); std::cout << "Verification results: " << std::boolalpha << result << std::noboolalpha << "\n\n"; diff --git a/QuoteVerification/QVL/Src/AttestationCommons/CMakeLists.txt b/QuoteVerification/QVL/Src/AttestationCommons/CMakeLists.txt index 4de68cc5..9a77f60f 100644 --- a/QuoteVerification/QVL/Src/AttestationCommons/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/AttestationCommons/CMakeLists.txt @@ -33,6 +33,8 @@ find_package(OpenSSL 1.1.1 EXACT REQUIRED) file(GLOB SOURCE_FILES src/*.cpp src/Utils/*.cpp + src/Utils/*.h + include/Utils/*.h ) add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) @@ -57,6 +59,21 @@ target_link_libraries(${PROJECT_NAME}Static PUBLIC OpenSSL::Crypto ) +if(BUILD_LOGS) + message("Logging enabled.") + hunter_add_package(spdlog) + find_package(spdlog CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC + spdlog::spdlog_header_only + ) + + target_link_libraries(${PROJECT_NAME}Static PUBLIC + spdlog::spdlog_header_only + ) + target_compile_definitions(${PROJECT_NAME} PUBLIC SGX_LOGS=true) + target_compile_definitions(${PROJECT_NAME}Static PUBLIC SGX_LOGS=true) +endif() + ######### QVL Enclave related settings ################################################################################# if (BUILD_ENCLAVE) diff --git a/QuoteVerification/QVL/Src/AttestationCommons/include/Utils/Logger.h b/QuoteVerification/QVL/Src/AttestationCommons/include/Utils/Logger.h new file mode 100644 index 00000000..3286fda3 --- /dev/null +++ b/QuoteVerification/QVL/Src/AttestationCommons/include/Utils/Logger.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef SGXECDSAATTESTATIONCOMMONS_LOGGER_H +#define SGXECDSAATTESTATIONCOMMONS_LOGGER_H + +#include +#include + +#ifdef SGX_LOGS +#define SPDLOG_LEVEL_NAMES \ + { \ + "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "OFF" \ + } +#include +#include "spdlog/pattern_formatter.h" +#include + +#define LOG(level, ...) spdlog::log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__) +#define LOG_TRACE(...) LOG(spdlog::level::trace, __VA_ARGS__) +#define LOG_DEBUG(...) LOG(spdlog::level::debug, __VA_ARGS__) +#define LOG_INFO(...) LOG(spdlog::level::info, __VA_ARGS__) +#define LOG_WARN(...) LOG(spdlog::level::warn, __VA_ARGS__) +#define LOG_ERROR(...) LOG(spdlog::level::err, __VA_ARGS__) +#define LOG_FATAL(...) LOG(spdlog::level::critical, __VA_ARGS__) + +#else +#define LOG_TRACE(...) +#define LOG_DEBUG(...) +#define LOG_INFO(...) +#define LOG_WARN(...) +#define LOG_ERROR(...) +#define LOG_FATAL(...) +#endif +#define LOG_AND_THROW(exp, msg) \ + LOG_ERROR(msg); \ + throw exp(msg); + +namespace intel { namespace sgx { namespace dcap { namespace logger { +#ifdef SGX_LOGS +static thread_local std::string scopedCustomFieldKey; +static thread_local std::string scopedCustomFieldValue; +#endif + +void init(const std::string &consoleLogLevel, const std::string &fileLogLevel, const std::string &fileName, + const std::string& name, const std::string &pattern); +void setCustomField(const std::string &key, const std::string &value); +std::string timeToString(const time_t time); + +#ifdef SGX_LOGS +class CustomFieldFormatter : public spdlog::custom_flag_formatter +{ +public: + void format(const spdlog::details::log_msg& /*msg*/, const std::tm &, spdlog::memory_buf_t &dest) override; + std::unique_ptr clone() const override; +}; +#endif +}}}} +#endif //SGXECDSAATTESTATIONCOMMONS_LOGGER_H diff --git a/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/Logger.cpp b/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/Logger.cpp new file mode 100644 index 00000000..ad0ec463 --- /dev/null +++ b/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/Logger.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#ifdef SGX_LOGS +#include +#include +#endif + +namespace intel { namespace sgx { namespace dcap { namespace logger { +const std::string DEFAULT_PATTERN = "[%Y-%m-%dT%H:%M:%S.%eZ] [%l] [%n %@] [pid:%P]%r %v"; + +void init(const std::string& name, const std::string &consoleLogLevel, const std::string &fileLogLevel, + const std::string &fileName, const std::string &pattern) +{ +#ifndef SGX_LOGS + // suppress unused variable warning when building enclave + (void)name; + (void)consoleLogLevel; + (void)fileLogLevel; + (void)fileName; + (void)pattern; +#endif +#ifdef SGX_LOGS + + auto loggerInstance = spdlog::get(name); + + if (!loggerInstance) { + std::vector sinks; + + auto consoleLogLevelParsed = spdlog::level::off; + if (!consoleLogLevel.empty()) { + consoleLogLevelParsed = spdlog::level::from_str(consoleLogLevel); + } + + if (consoleLogLevelParsed != spdlog::level::off) { + auto consoleSink = std::make_shared(); + consoleSink->set_level(consoleLogLevelParsed); + sinks.push_back(consoleSink); + } + + auto fileLogLevelParsed = spdlog::level::off; + if (!fileLogLevel.empty()) { + fileLogLevelParsed = spdlog::level::from_str(fileLogLevel); + } + + if (fileLogLevelParsed != spdlog::level::off && !fileName.empty()) { + auto fileSink = std::make_shared(fileName); + fileSink->set_level(fileLogLevelParsed); + sinks.push_back(fileSink); + } + + loggerInstance = std::make_shared(name, begin(sinks), end(sinks)); + + auto formatter = std::make_unique(); + formatter->add_flag('r'); + if (!pattern.empty()) { + formatter->set_pattern(pattern); + } else { + formatter->set_pattern(DEFAULT_PATTERN); + } + loggerInstance->set_formatter(std::move(formatter)); + + // Log level for whole logger is set to the lowest possible level, so we can set all possible levels on sinks + // If we leave it to default info level it won't be possible to set lower than info level on sinks. + loggerInstance->set_level(spdlog::level::trace); + + loggerInstance->flush_on(spdlog::level::info); + spdlog::flush_every(std::chrono::seconds(1)); + + spdlog::register_logger(loggerInstance); + spdlog::set_default_logger(loggerInstance); + LOG_INFO("QVL Logging enabled and configured"); + } +#endif +} + +void setCustomField(const std::string &key, const std::string &value) +{ +#ifndef SGX_LOGS + // suppress unused variable warning when building enclave + (void)key; + (void)value; +#endif +#ifdef SGX_LOGS + scopedCustomFieldKey = key; + scopedCustomFieldValue = value; +#endif +} + +#ifdef SGX_LOGS +void CustomFieldFormatter::format(const spdlog::details::log_msg &/*msg*/, const std::tm &, spdlog::memory_buf_t &dest) +{ + if (!scopedCustomFieldValue.empty()) + { + auto requestId = " [" + scopedCustomFieldKey + "=" + scopedCustomFieldValue + "]"; + dest.append(requestId.data(), requestId.data() + requestId.size()); + } +} + +std::unique_ptr CustomFieldFormatter::clone() const +{ + return spdlog::details::make_unique(); +} +#endif + +std::string timeToString(const time_t time) +{ +#ifdef SGX_LOGS + char dateStr[20]; + std::strftime(dateStr, sizeof(dateStr), "%Y-%m-%d %H:%M:%S", std::gmtime(&time)); + return dateStr; +#else + return std::to_string(time); +#endif +} + +}}}} \ No newline at end of file diff --git a/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/TimeUtils.cpp b/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/TimeUtils.cpp index 2e3ffeaf..c7f24599 100644 --- a/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/TimeUtils.cpp +++ b/QuoteVerification/QVL/Src/AttestationCommons/src/Utils/TimeUtils.cpp @@ -32,6 +32,7 @@ #include "TimeUtils.h" #include +#include #ifndef SGX_TRUSTED #include diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/CMakeLists.txt b/QuoteVerification/QVL/Src/AttestationLibrary/CMakeLists.txt index 49764ae2..54919dc5 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/AttestationLibrary/CMakeLists.txt @@ -52,6 +52,9 @@ endif() hunter_add_package(OpenSSL) find_package(OpenSSL 1.1.1 EXACT REQUIRED) +hunter_add_package(spdlog) +find_package(spdlog CONFIG REQUIRED) + file(GLOB SOURCE_FILES src/*.cpp src/OpensslHelpers/*.cpp src/PckParser/*.cpp @@ -59,6 +62,7 @@ file(GLOB SOURCE_FILES src/*.cpp src/QuoteVerification/*.cpp src/Verifiers/*.cpp src/Utils/*.cpp + include/SgxEcdsaAttestation/*.h ) add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) @@ -81,6 +85,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE rapidjson OpenSSL::SSL OpenSSL::Crypto + spdlog::spdlog_header_only ) target_link_libraries(${PROJECT_NAME}Static PRIVATE @@ -89,6 +94,7 @@ target_link_libraries(${PROJECT_NAME}Static PRIVATE rapidjson OpenSSL::SSL OpenSSL::Crypto + spdlog::spdlog_header_only ) if(MSVC) diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/include/SgxEcdsaAttestation/QuoteVerification.h b/QuoteVerification/QVL/Src/AttestationLibrary/include/SgxEcdsaAttestation/QuoteVerification.h index b1fd0bd7..ce525241 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/include/SgxEcdsaAttestation/QuoteVerification.h +++ b/QuoteVerification/QVL/Src/AttestationLibrary/include/SgxEcdsaAttestation/QuoteVerification.h @@ -427,8 +427,30 @@ QVL_API Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentitySt */ QVL_API Status sgxAttestationVerifyPCKRevocationList(const char *crl, const char *pemCACertChain, const char *pemTrustedRootCaCert); +/** + * This function allows user to setup logging in QVL. If fileLogLevel is empty or set to OFF or fileName is empty there + * will be no file logger created. + * @param name + * @param consoleLogLevel + * @param fileLogLevel + * @param fileName + * @param pattern + */ +QVL_API void sgxAttestationLoggerSetup(const char *name, const char *consoleLogLevel, const char *fileLogLevel, + const char *fileName, const char *pattern); + +/** + * This function allows user to set scoped custom field with specified key and value for current processing thread. + * This field will can be used in pattern using %r. + * @param key + * @param value + */ +QVL_API void sgxAttestationLoggerSetCustomField(const char *key, const char *value); + /** @}*/ + + #endif //SGX_ECDSA_QUOTE_VERIFICATION_H_ #ifdef __cplusplus diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/CertificateChain.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/CertificateChain.cpp index efe01043..96bf330e 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/CertificateChain.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/CertificateChain.cpp @@ -32,6 +32,7 @@ #include "CertificateChain.h" #include "X509Constants.h" +#include "Utils/Logger.h" #include @@ -57,11 +58,13 @@ Status CertificateChain::parse(const std::string& pemCertChain) } // any cert in chain has wrong format // then whole chain should be considered invalid - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("Cert Chain format error: {}, wrong certChain element: {}", + ex.what(), certPem); return STATUS_UNSUPPORTED_CERT_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { // Since cert order in the chain is not deterministic // we do not know which cert we failed to parse (subject is also an extension) @@ -69,18 +72,22 @@ Status CertificateChain::parse(const std::string& pemCertChain) // We will do some guessing... I mean some heuristics if (certs.empty()) // we failed parsing first cert, in most cases it will be a root CA { + LOG_ERROR("Error while parsing Root CA from cert chain: {}", ex.what()); return STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS; } if (certs.size() == 1) // second cert wll be probably an intermediate CA { if (certStrs.size() == 2) { + LOG_ERROR("Error while parsing TCB Signing cert from cert chain: {}", ex.what()); return STATUS_SGX_TCB_SIGNING_CERT_INVALID_EXTENSIONS; } + LOG_ERROR("Error while parsing intermediate CA from cert chain: {}", ex.what()); return STATUS_SGX_INTERMEDIATE_CA_INVALID_EXTENSIONS; } else // third cert wll be probably PCK CA { + LOG_ERROR("Error while parsing PCK Certificate from cert chain: {}", ex.what()); return STATUS_SGX_PCK_INVALID_EXTENSIONS; } } @@ -103,12 +110,14 @@ Status CertificateChain::parse(const std::string& pemCertChain) { pckCert = std::make_shared(dcap::parser::x509::PckCertificate(*cert)); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("PCK CertChain format error: {}", ex.what()); return STATUS_UNSUPPORTED_CERT_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { + LOG_ERROR("PCK CertChain invalid extension error: {}", ex.what()); return STATUS_SGX_PCK_INVALID_EXTENSIONS; } } @@ -116,6 +125,7 @@ Status CertificateChain::parse(const std::string& pemCertChain) if (length() == 0) { + LOG_ERROR("No certificate in CertChain has been supplied"); return STATUS_UNSUPPORTED_CERT_FORMAT; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/PckParser/CrlStore.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/PckParser/CrlStore.cpp index c17612e2..b7d918de 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/PckParser/CrlStore.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/PckParser/CrlStore.cpp @@ -32,6 +32,7 @@ #include "CrlStore.h" #include "FormatException.h" +#include "Utils/Logger.h" #include @@ -76,8 +77,9 @@ bool CrlStore::parse(const std::string& crlString) _signature = pckparser::getSignature(*_crl); _crlNum = pckparser::getCrlNum(*_crl); } - catch(const FormatException&) + catch(const FormatException& ex) { + LOG_ERROR("Error while parsing CRL: {}", ex.what()); return false; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification.cpp index ec1ef9a0..5be726f5 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification.cpp @@ -53,6 +53,10 @@ #include #include +#include + +// On Windows in one of headers STATUS_INVALID_PARAMETER macro is defined and it conflicts with our status name so undef it. +#undef STATUS_INVALID_PARAMETER static constexpr size_t EXPECTED_CERTIFICATE_COUNT_IN_PCK_CHAIN = 3; static constexpr size_t EXPECTED_CERTIFICATE_COUNT_IN_TCB_CHAIN = 2; @@ -84,6 +88,7 @@ Status sgxAttestationVerifyPCKCertificate(const char *pemCertChain, const char * } catch (const std::runtime_error&) { + LOG_ERROR("Can't get current time or it was not provided"); return STATUS_INVALID_PARAMETER; } @@ -93,6 +98,7 @@ Status sgxAttestationVerifyPCKCertificate(const char *pemCertChain, const char * !crls[0] || !crls[1]) { + LOG_ERROR("pemCertChain, pemRootCaCertificate, CRLs (RootCaCrl, IntermediateCaCrl) was not provided"); return STATUS_UNSUPPORTED_CERT_FORMAT; } @@ -101,17 +107,27 @@ Status sgxAttestationVerifyPCKCertificate(const char *pemCertChain, const char * if(status != STATUS_OK) { + LOG_ERROR("PCK Cert Chain parse error: {}", status); return status; } if(chain.length() != EXPECTED_CERTIFICATE_COUNT_IN_PCK_CHAIN) { + LOG_ERROR("PCK chain length is not correct. Expected: {}, actual: {}, cert chain: {}", + EXPECTED_CERTIFICATE_COUNT_IN_PCK_CHAIN, chain.length(), pemCertChain); return STATUS_UNSUPPORTED_CERT_FORMAT; } dcap::pckparser::CrlStore rootCaCrl, intermediateCrl; - if(!rootCaCrl.parse(crls[0]) || !intermediateCrl.parse(crls[1])) + if(!rootCaCrl.parse(crls[0])) + { + LOG_ERROR("rootCaCrl parsing failed. RootCaCrl: {}", crls[0]); + return STATUS_SGX_CRL_UNSUPPORTED_FORMAT; + } + + if(!intermediateCrl.parse(crls[1])) { + LOG_ERROR("IntermediateCaCrl parsing failed. IntermediateCaCrl: {}", crls[1]); return STATUS_SGX_CRL_UNSUPPORTED_FORMAT; } @@ -120,8 +136,9 @@ Status sgxAttestationVerifyPCKCertificate(const char *pemCertChain, const char * auto rootCa = dcap::parser::x509::Certificate::parse(pemRootCaCertificate); return dcap::PckCertVerifier{}.verify(chain, rootCaCrl, intermediateCrl, rootCa, currentTime); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("Trusted RootCA parsing failed: {}", ex.what()); return STATUS_TRUSTED_ROOT_CA_UNSUPPORTED_FORMAT; } } @@ -172,6 +189,7 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain } catch (const std::runtime_error&) { + LOG_ERROR("Can't get current time or it was not provided"); return STATUS_INVALID_PARAMETER; } @@ -180,6 +198,7 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain !stringRootCaCrl || !pemRootCaCertificate) { + LOG_ERROR("TcbInfo, pemCertChain, stringRootCaCrl, pemRootCaCertificate was not provided"); return STATUS_UNSUPPORTED_CERT_FORMAT; } @@ -188,12 +207,14 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain { tcbInfoJson = dcap::parser::json::TcbInfo::parse(tcbInfo); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("TcbInfo format error: {}, tcbInfo: {}", ex.what(), tcbInfo); return STATUS_SGX_TCB_INFO_UNSUPPORTED_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { + LOG_ERROR("TcbInfo invalid extension error: {}, tcbInfo: {}", ex.what(), tcbInfo); return STATUS_SGX_TCB_INFO_INVALID; } @@ -201,17 +222,21 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain const auto status = chain.parse(pemCertChain); if (status != STATUS_OK) { + LOG_ERROR("TCBInfo Signing chain parse error: {}", status); return status; } if(chain.length() != EXPECTED_CERTIFICATE_COUNT_IN_TCB_CHAIN) { + LOG_ERROR("TCBInfo Signing chain length is not correct. Expected: {}, actual: {}, cert chain: {}", + EXPECTED_CERTIFICATE_COUNT_IN_TCB_CHAIN, chain.length(), pemCertChain); return STATUS_UNSUPPORTED_CERT_FORMAT; } dcap::pckparser::CrlStore rootCaCrl; if(!rootCaCrl.parse(stringRootCaCrl)) { + LOG_ERROR("RootCA CRL parsing failed. CRL: {}", stringRootCaCrl); return STATUS_SGX_CRL_UNSUPPORTED_FORMAT; } @@ -220,12 +245,14 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain auto trustedRootCa = dcap::parser::x509::Certificate::parse(pemRootCaCertificate); return dcap::TCBInfoVerifier{}.verify(tcbInfoJson, chain, rootCaCrl, trustedRootCa, currentTime); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("Trusted RootCA parsing failed: {}", ex.what()); return STATUS_UNSUPPORTED_CERT_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { + LOG_ERROR("Trusted RootCA parsing failed: {}", ex.what()); return STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS; } } @@ -233,7 +260,6 @@ Status sgxAttestationVerifyTCBInfo(const char *tcbInfo, const char *pemCertChain Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentityString, const char *pemCertChain, const char *stringRootCaCrl, const char *pemRootCaCertificate, const time_t* expirationDate) { - time_t currentTime; try { @@ -241,6 +267,7 @@ Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentityString, co } catch (const std::runtime_error&) { + LOG_ERROR("Can't get current time or it was not provided"); return STATUS_INVALID_PARAMETER; } @@ -249,6 +276,7 @@ Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentityString, co !stringRootCaCrl || !pemRootCaCertificate) { + LOG_ERROR("enclaveIdentityString, pemCertChain, stringRootCaCrl, pemRootCaCertificate was not provided"); return STATUS_UNSUPPORTED_CERT_FORMAT; } @@ -267,17 +295,21 @@ Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentityString, co const auto status = chain.parse(pemCertChain); if(status != STATUS_OK) { + LOG_ERROR("TCBInfo Signing chain parse error: {}", status); return status; } if(chain.length() != EXPECTED_CERTIFICATE_COUNT_IN_TCB_CHAIN) { + LOG_ERROR("TCBInfo Signing chain length is not correct. Expected: {}, actual: {}, cert chain: {}", + EXPECTED_CERTIFICATE_COUNT_IN_TCB_CHAIN, chain.length(), pemCertChain); return STATUS_UNSUPPORTED_CERT_FORMAT; } dcap::pckparser::CrlStore rootCaCrl; if(!rootCaCrl.parse(stringRootCaCrl)) { + LOG_ERROR("RootCA CRL parsing failed. CRL: {}", stringRootCaCrl); return STATUS_SGX_CRL_UNSUPPORTED_FORMAT; } @@ -286,12 +318,14 @@ Status sgxAttestationVerifyEnclaveIdentity(const char *enclaveIdentityString, co auto trustedRootCa = dcap::parser::x509::Certificate::parse(pemRootCaCertificate); return dcap::EnclaveIdentityVerifier{}.verify(*enclaveIdentity, chain, rootCaCrl, trustedRootCa, currentTime); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("Trusted RootCA parsing failed: {}", ex.what()); return STATUS_UNSUPPORTED_CERT_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { + LOG_ERROR("Trusted RootCA parsing failed: {}", ex.what()); return STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS; } } @@ -305,6 +339,7 @@ Status sgxAttestationVerifyQuote(const uint8_t* rawQuote, uint32_t quoteSize, co !pckCrl || !tcbInfoJson) { + LOG_ERROR("rawQuote, pemPckCertificate, pckCrl, tcbInfoJson was not provided"); return STATUS_MISSING_PARAMETERS; } @@ -316,6 +351,7 @@ Status sgxAttestationVerifyQuote(const uint8_t* rawQuote, uint32_t quoteSize, co dcap::Quote quote; if(!quote.parse(vecQuote) || !quote.validate()) { + LOG_ERROR("Quote format verification failure"); return Status::STATUS_UNSUPPORTED_QUOTE_FORMAT; } @@ -323,6 +359,7 @@ Status sgxAttestationVerifyQuote(const uint8_t* rawQuote, uint32_t quoteSize, co dcap::pckparser::CrlStore pckCrlStore; if(!pckCrlStore.parse(pckCrl)) { + LOG_ERROR("PCK Revocation list is invalid. pckCrl: {}", pckCrl); return STATUS_UNSUPPORTED_PCK_RL_FORMAT; } @@ -332,12 +369,14 @@ Status sgxAttestationVerifyQuote(const uint8_t* rawQuote, uint32_t quoteSize, co { tcbInfo = dcap::parser::json::TcbInfo::parse(tcbInfoJson); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) { + LOG_ERROR("TcbInfo format error: {}", ex.what()); return STATUS_UNSUPPORTED_TCB_INFO_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) + catch (const dcap::parser::InvalidExtensionException& ex) { + LOG_ERROR("TcbInfo invalid extension error: {}", ex.what()); return STATUS_UNSUPPORTED_TCB_INFO_FORMAT; } @@ -348,23 +387,26 @@ Status sgxAttestationVerifyQuote(const uint8_t* rawQuote, uint32_t quoteSize, co try { enclaveIdentity = parser.parse(qeIdentityJson); } - catch (const dcap::ParserException&) + catch (const dcap::ParserException& ex) { + LOG_ERROR("Enclave Identity parsing error: {}", ex.what()); return STATUS_UNSUPPORTED_QE_IDENTITY_FORMAT; } } try { - auto pckCert = dcap::parser::x509::PckCertificate::parse(pemPckCertificate); /// 4.1.2.4.3 + auto pckCert = dcap::parser::x509::PckCertificate::parse(pemPckCertificate); return dcap::QuoteVerifier{}.verify(quote, pckCert, pckCrlStore, tcbInfo, enclaveIdentity.get(), dcap::EnclaveReportVerifier()); } - catch (const dcap::parser::FormatException&) + catch (const dcap::parser::FormatException& ex) /// 4.1.2.4.3 { + LOG_ERROR("PCK Certificate format error: {}", ex.what()); return STATUS_UNSUPPORTED_PCK_CERT_FORMAT; } - catch (const dcap::parser::InvalidExtensionException&) /// 4.1.2.4.4 + catch (const dcap::parser::InvalidExtensionException& ex) /// 4.1.2.4.4 { + LOG_ERROR("PCK Certificate invalid extension error: {}", ex.what()); return STATUS_INVALID_PCK_CERT; } } @@ -373,6 +415,7 @@ Status sgxAttestationVerifyEnclaveReport(const uint8_t* enclaveReport, const cha { if(!enclaveReport || !enclaveIdentity) { + LOG_ERROR("enclaveReport, enclaveIdentity was not provided"); return STATUS_SGX_ENCLAVE_REPORT_UNSUPPORTED_FORMAT; } @@ -386,11 +429,13 @@ Status sgxAttestationVerifyEnclaveReport(const uint8_t* enclaveReport, const cha dcap::constants::ENCLAVE_REPORT_BYTE_LEN, end)) { + LOG_ERROR("Enclave Report parsing as binary data representation failure"); return STATUS_SGX_ENCLAVE_REPORT_UNSUPPORTED_FORMAT; } if(from != end) { + LOG_ERROR("Enclave Report parsing as binary data representation failure. EnclaveReport could not be iterated through {} bytes", end - from); return STATUS_SGX_ENCLAVE_REPORT_UNSUPPORTED_FORMAT; } @@ -401,9 +446,10 @@ Status sgxAttestationVerifyEnclaveReport(const uint8_t* enclaveReport, const cha { enclaveIdentityParsed = parser.parse(enclaveIdentity); } - catch(const dcap::ParserException &e) + catch(const dcap::ParserException &ex) { - return e.getStatus(); + LOG_ERROR("Enclave identity parsing error: {}", ex.what()); + return ex.getStatus(); } return dcap::EnclaveReportVerifier{}.verify(enclaveIdentityParsed.get(), eReport); @@ -417,6 +463,7 @@ Status sgxAttestationGetQECertificationDataSize( if(!rawQuote || !qeCertificationDataSize) { + LOG_ERROR("Quote or output pointer for size was not provided."); return STATUS_MISSING_PARAMETERS; } @@ -427,6 +474,7 @@ Status sgxAttestationGetQECertificationDataSize( dcap::Quote quote; if(!quote.parse(vecQuote) || !quote.validate()) { + LOG_ERROR("Can't parse or validate quote"); return Status::STATUS_UNSUPPORTED_QUOTE_FORMAT; } @@ -446,6 +494,7 @@ Status sgxAttestationGetQECertificationData( !qeCertificationData|| !qeCertificationDataType) { + LOG_ERROR("Quote, certifation data or certification date type was not provided"); return STATUS_MISSING_PARAMETERS; } @@ -457,6 +506,7 @@ Status sgxAttestationGetQECertificationData( if(!quote.parse(vecQuote) || !quote.validate()) { + LOG_ERROR("Can't parse or validate quote"); return STATUS_UNSUPPORTED_QUOTE_FORMAT; } @@ -464,6 +514,7 @@ Status sgxAttestationGetQECertificationData( if(qeCertificationDataSize != quoteCertificationData.parsedDataSize) { + LOG_ERROR("Provided certification data size doesn't match one in quote"); return STATUS_INVALID_QE_CERTIFICATION_DATA_SIZE; } @@ -474,3 +525,32 @@ Status sgxAttestationGetQECertificationData( return STATUS_OK; } + + +void sgxAttestationLoggerSetup(const char *name, const char *consoleLogLevel, const char *fileLogLevel, + const char *fileName, const char *pattern) +{ +#ifndef SGX_LOGS + // suppress unused variable warning when building enclave + (void)name; + (void)consoleLogLevel; + (void)fileLogLevel; + (void)fileName; + (void)pattern; +#endif +#ifdef SGX_LOGS + logger::init(name, consoleLogLevel, fileLogLevel, fileName, pattern); +#endif +} + +void sgxAttestationLoggerSetCustomField(const char *key, const char *value) +{ +#ifndef SGX_LOGS + // suppress unused variable warning when building enclave + (void)key; + (void)value; +#endif +#ifdef SGX_LOGS + logger::setCustomField(key, value); +#endif +} \ No newline at end of file diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/Quote.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/Quote.cpp index f598f062..b3845066 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/Quote.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/Quote.cpp @@ -31,6 +31,7 @@ #include "Quote.h" #include "QuoteParsers.h" +#include "Utils/Logger.h" #include #include @@ -42,12 +43,14 @@ bool Quote::parse(const std::vector& rawQuote) { if(rawQuote.size() < QUOTE_MIN_BYTE_LEN) { + LOG_ERROR("Quote size {} is not at least {}.", rawQuote.size(), QUOTE_MIN_BYTE_LEN); return false; } auto from = rawQuote.cbegin(); Header localHeader{}; if (!copyAndAdvance(localHeader, from, HEADER_BYTE_LEN, rawQuote.cend())) { + LOG_ERROR("Can't read header from quote. Expected size: {}", HEADER_BYTE_LEN); return false; } @@ -59,6 +62,7 @@ bool Quote::parse(const std::vector& rawQuote) { if (!copyAndAdvance(localEnclaveReport, from, ENCLAVE_REPORT_BYTE_LEN, rawQuote.end())) { + LOG_ERROR("Can't read SGX enclave report from quote. Expected size: {}", ENCLAVE_REPORT_BYTE_LEN); return false; } } @@ -66,17 +70,20 @@ bool Quote::parse(const std::vector& rawQuote) { if (!copyAndAdvance(localTdReport, from, TD_REPORT_BYTE_LEN, rawQuote.end())) { + LOG_ERROR("Can't read TDX TD Report from quote. Expected size: {}", TD_REPORT_BYTE_LEN); return false; } } uint32_t localAuthDataSize = 0; if (!copyAndAdvance(localAuthDataSize, from, rawQuote.end())) { + LOG_ERROR("Can't read auth data size from quote."); return false; } const auto remainingDistance = std::distance(from, rawQuote.end()); if(localAuthDataSize != remainingDistance) { + LOG_ERROR("Declared auth data size {} doesn't match remaining quote size {}", localAuthDataSize, remainingDistance); return false; } @@ -86,6 +93,7 @@ bool Quote::parse(const std::vector& rawQuote) { if (!copyAndAdvance(localQuoteV3Auth, from, static_cast(localAuthDataSize), rawQuote.end())) { + LOG_ERROR("Can't read QUOTE v3 Auth data. Expected size: {}", localAuthDataSize); return false; } qeReportSignature = localQuoteV3Auth.qeReportSignature.signature; @@ -99,6 +107,7 @@ bool Quote::parse(const std::vector& rawQuote) { if (!copyAndAdvance(localQuoteV4Auth, from, static_cast(localAuthDataSize), rawQuote.end())) { + LOG_ERROR("Can't read QUOTE v4 Auth data. Expected size: {}", localAuthDataSize); return false; } @@ -122,6 +131,7 @@ bool Quote::parse(const std::vector& rawQuote) // and it means invalid format if(from != rawQuote.end()) { + LOG_ERROR("There is additional, not expected data in quote."); return false; } @@ -148,21 +158,25 @@ bool Quote::validate() const if(std::find(ALLOWED_QUOTE_VERSIONS.begin(), ALLOWED_QUOTE_VERSIONS.end(), header.version) == ALLOWED_QUOTE_VERSIONS.end()) { + LOG_ERROR("Quote version {} is not supported", header.version); return false; } if(std::find(ALLOWED_ATTESTATION_KEY_TYPES.begin(), ALLOWED_ATTESTATION_KEY_TYPES.end(), header.attestationKeyType) == ALLOWED_ATTESTATION_KEY_TYPES.end()) { + LOG_ERROR("Attestation Key type {} is not supported", header.attestationKeyType); return false; } if(std::find(ALLOWED_TEE_TYPES.begin(), ALLOWED_TEE_TYPES.end(), header.teeType) == ALLOWED_TEE_TYPES.end()) { + LOG_ERROR("TEE Type {} is not supported", header.teeType); return false; } if(header.qeVendorId != INTEL_QE_VENDOR_ID) { + LOG_ERROR("Wrong QE vendor ID. Found: {}, expected: {}", header.qeVendorId, INTEL_QE_VENDOR_ID); return false; } @@ -170,10 +184,13 @@ bool Quote::validate() const { if (header.teeType != TEE_TYPE_SGX) { + LOG_ERROR("Quote v3 supports only SGX tee type but found {}", header.teeType); return false; } if (authDataV3.certificationData.type < 1 || authDataV3.certificationData.type > 5) // QuoteV3 supports only 1-5 types { + LOG_ERROR("Quote v3 supports certification data types from 1 to 5 but found {}", + authDataV3.certificationData.type); return false; } } @@ -182,10 +199,14 @@ bool Quote::validate() const { if (authDataV4.certificationData.type != constants::PCK_ID_QE_REPORT_CERTIFICATION_DATA) { + LOG_ERROR("Quote v4 supports only {} certification data type but found {}", + constants::PCK_ID_QE_REPORT_CERTIFICATION_DATA, authDataV4.certificationData.type); return false; } if (certificationData.type < 1 || certificationData.type > 5) { + LOG_ERROR("Quote v4 supports QE Report Certification data types from 1 to 5 but found: {}", + certificationData.type); return false; } } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.cpp index 908774f5..6f947dbc 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/QuoteVerification/QuoteStructures.cpp @@ -1,6 +1,8 @@ #include "QuoteStructures.h" #include "QuoteParsers.h" +#include "Utils/Logger.h" + #include #include @@ -239,17 +241,27 @@ bool CertificationData::insert(std::vector::const_iterator& from, const bool QEReportCertificationData::insert(std::vector::const_iterator& from, const std::vector::const_iterator& end) { - if (!copyAndAdvance(qeReport, from, ENCLAVE_REPORT_BYTE_LEN, end)) { return false; } - if (!copyAndAdvance(qeReportSignature, from, ECDSA_SIGNATURE_BYTE_LEN, end)) { return false; } + if (!copyAndAdvance(qeReport, from, ENCLAVE_REPORT_BYTE_LEN, end)) + { + LOG_ERROR("Can't read enclave report. Expected size: {}", ENCLAVE_REPORT_BYTE_LEN); + return false; + } + if (!copyAndAdvance(qeReportSignature, from, ECDSA_SIGNATURE_BYTE_LEN, end)) + { + LOG_ERROR("Can't read report signature from quote. Expected size: {}", ECDSA_SIGNATURE_BYTE_LEN); + return false; + } uint16_t authSize = 0; if (!copyAndAdvance(authSize, from, end)) { + LOG_ERROR("Can't read auth data size from quote."); return false; } from = std::prev(from, sizeof(uint16_t)); if (!copyAndAdvance(qeAuthData, from, authSize + sizeof(uint16_t), end)) { + LOG_ERROR("Can't read auth data from quote. Expected size: {}", authSize + sizeof(uint16_t)); return false; } @@ -257,21 +269,25 @@ bool QEReportCertificationData::insert(std::vector::const_iterator& fro const auto available = std::distance(from, end); if (available < 0 || (unsigned) available < sizeof(uint16_t)) { + LOG_ERROR("Can't read QE Certification Data size from quote."); return false; } std::advance(from, sizeof(uint16_t)); // skip type if (!copyAndAdvance(qeCertSize, from, end)) { + LOG_ERROR("Can't read QE Certification Data size from quote."); return false; } from = std::prev(from, sizeof(uint32_t) + sizeof(uint16_t)); // go back to beg of struct data if (!copyAndAdvance(certificationData, from, qeCertSize + sizeof(uint16_t) + sizeof(uint32_t), end)) { + LOG_ERROR("Can't read QE Certification Data from quote. Expected size: {}", qeCertSize + sizeof(uint16_t) + sizeof(uint32_t)); return false; } if (from != end) { + LOG_ERROR("There is additional, not expected data in quote."); return false; // Inconsistent structure } return true; diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/CommonVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/CommonVerifier.cpp index 9c7253eb..75aee76e 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/CommonVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/CommonVerifier.cpp @@ -31,6 +31,7 @@ #include "CommonVerifier.h" +#include "Utils/Logger.h" #include #include @@ -44,6 +45,8 @@ bool CommonVerifier::checkStandardExtensions(const std::vector presentExtensions.size()) { + LOG_ERROR("Number of extensions in CRL is not what expected. Expected: {}, actual: {}", opensslExtensionNids.size(), + presentExtensions.size()); return false; } @@ -57,6 +60,7 @@ bool CommonVerifier::checkStandardExtensions(const std::vector #include "EnclaveIdentityParser.h" #include "EnclaveIdentityV2.h" +#include "Utils/Logger.h" #include #include @@ -43,11 +44,13 @@ namespace intel { namespace sgx { namespace dcap { { if (!jsonParser.parse(input)) { + LOG_ERROR("Enclave Identity format error. Enclave Identity: {}", input); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_FORMAT); } if (!jsonParser.getRoot()->IsObject()) { + LOG_ERROR("Invalid Enclave Identity. A valid root has not been supplied"); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_INVALID); } @@ -55,11 +58,20 @@ namespace intel { namespace sgx { namespace dcap { if (signature == nullptr) { + LOG_ERROR("Enclave Identity format error. Enclave Identity: {}", input); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_FORMAT); } - if(!signature->IsString() || signature->GetStringLength() != constants::ECDSA_P256_SIGNATURE_BYTE_LEN * 2) + if(!signature->IsString()) { + LOG_ERROR("Invalid Enclave Identity. A signature is not a string"); + throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_INVALID); + } + + if(signature->GetStringLength() != constants::ECDSA_P256_SIGNATURE_BYTE_LEN * 2) + { + LOG_ERROR("Invalid Enclave Identity. A signature: {} has wrong length. Expected: {}, actual: {}", + signature->GetString(), constants::ECDSA_P256_SIGNATURE_BYTE_LEN * 2, signature->GetStringLength()); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_INVALID); } @@ -72,12 +84,14 @@ namespace intel { namespace sgx { namespace dcap { if (identityField == nullptr || !identityField->IsObject()) { + LOG_ERROR("Invalid Enclave Identity. A valid identityField has not been supplied."); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_INVALID); } std::tie(version, status) = jsonParser.getIntFieldOf(*identityField, "version"); if (status != JsonParser::OK) { + LOG_ERROR("Invalid Enclave Identity. A version of IdentityField is not valid."); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_INVALID); } @@ -89,13 +103,17 @@ namespace intel { namespace sgx { namespace dcap { std::unique_ptr identity = std::unique_ptr(new EnclaveIdentityV2(*identityField)); // TODO make std::make_unique work in SGX enclave if (identity->getStatus() != STATUS_OK) { + LOG_ERROR("EnclaveIdentityV2 parsing error: {}", identity->getStatus()); throw ParserException(identity->getStatus()); } identity->setSignature(signatureBytes); return identity; } default: + { + LOG_ERROR("Enclave Identity version: {} is not supported", version); throw ParserException(STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_VERSION); + } } } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityV2.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityV2.cpp index d59f32bc..27bc84f2 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityV2.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityV2.cpp @@ -33,6 +33,7 @@ #include "CertVerification/X509Constants.h" #include "Utils/TimeUtils.h" #include "QuoteVerification/QuoteConstants.h" +#include "Utils/Logger.h" #include #include @@ -58,6 +59,7 @@ namespace intel { namespace sgx { namespace dcap { || !parseID(p_body) || !parseTcbEvaluationDataNumber(p_body) || !parseTcbLevels(p_body)) { + LOG_ERROR("Enclave Identity does not contain all the required fields in a correct format"); status = STATUS_SGX_ENCLAVE_IDENTITY_INVALID; return; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityVerifier.cpp index 5f834cc3..c8bf1390 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveIdentityVerifier.cpp @@ -32,6 +32,7 @@ #include "EnclaveIdentityVerifier.h" #include "EnclaveIdentityV2.h" #include "Utils/TimeUtils.h" +#include #include #include @@ -67,27 +68,44 @@ Status EnclaveIdentityVerifier::verify( if(!_commonVerifier->checkSha256EcdsaSignature( enclaveIdentity.getSignature(), enclaveIdentity.getBody(), tcbSigningCert->getPubKey())) { + LOG_ERROR("QE Identity signature verification failure."); return STATUS_SGX_ENCLAVE_IDENTITY_INVALID_SIGNATURE; } - if(expirationDate > tcbSigningCert->getValidity().getNotAfterTime()) + const auto validityDateTcbSigCert = tcbSigningCert->getValidity().getNotAfterTime(); + + if(expirationDate > validityDateTcbSigCert) { + LOG_ERROR("TCB Signing Certificate is expired. Expiration date: {}, validity: {}", + logger::timeToString(expirationDate), logger::timeToString(validityDateTcbSigCert)); return STATUS_SGX_SIGNING_CERT_CHAIN_EXPIRED; } const auto rootCA = chain.getRootCert(); - if(expirationDate > rootCA->getValidity().getNotAfterTime()) + const auto validityDateRootCA = rootCA->getValidity().getNotAfterTime(); + + if(expirationDate > validityDateRootCA) { + LOG_ERROR("TCB Signing Chain Root CA is expired. Expiration date: {}, validity: {}", + logger::timeToString(expirationDate), logger::timeToString(validityDateRootCA)); return STATUS_SGX_SIGNING_CERT_CHAIN_EXPIRED; } if(rootCaCrl.expired(expirationDate)) { + LOG_ERROR("ROOT CA CRL is expired. Expiration date: {}, validity date range - from: {} to: {}", + logger::timeToString(expirationDate), + logger::timeToString(rootCaCrl.getValidity().notBeforeTime), + logger::timeToString(rootCaCrl.getValidity().notAfterTime)); return STATUS_SGX_CRL_EXPIRED; } - if (expirationDate > enclaveIdentity.getNextUpdate()) + const auto enclaveIdentityNextUpdate = enclaveIdentity.getNextUpdate(); + + if (expirationDate > enclaveIdentityNextUpdate) { + LOG_ERROR("Enclave Identity is expired. Expiration date: {}, next update: {}", + logger::timeToString(expirationDate), logger::timeToString(enclaveIdentityNextUpdate)); return STATUS_SGX_ENCLAVE_IDENTITY_EXPIRED; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveReportVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveReportVerifier.cpp index b87e9420..4ed2e6de 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveReportVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/EnclaveReportVerifier.cpp @@ -38,7 +38,8 @@ #include #include #include - +#include +#include namespace intel { namespace sgx { namespace dcap { @@ -50,6 +51,8 @@ Status EnclaveReportVerifier::verify(const EnclaveIdentityV2 *enclaveIdentity, c /// 4.1.2.9.5 if((enclaveReport.miscSelect & miscselectMask) != miscselect) { + LOG_ERROR("MiscSelect value from Enclave Report: {} does not match miscSelect value from Enclave Identity: {}", + enclaveReport.miscSelect & miscselectMask, miscselect); return STATUS_SGX_ENCLAVE_REPORT_MISCSELECT_MISMATCH; } @@ -58,6 +61,7 @@ Status EnclaveReportVerifier::verify(const EnclaveIdentityV2 *enclaveIdentity, c std::vector attributes(attributesReport.begin(), attributesReport.end()); if(applyMask(attributes, enclaveIdentity->getAttributesMask()) != enclaveIdentity->getAttributes()) { + LOG_ERROR("Attributes value from Enclave Report does not match attributes from Enclave Identity"); return STATUS_SGX_ENCLAVE_REPORT_ATTRIBUTES_MISMATCH; } @@ -68,12 +72,16 @@ Status EnclaveReportVerifier::verify(const EnclaveIdentityV2 *enclaveIdentity, c if(!enclaveIdentityMrSigner.empty() && enclaveIdentityMrSigner != mrSigner) { + LOG_ERROR("Enclave Identity contains MRSIGNER field: {} which does not match MRSIGNER value from Enclave Report: {}", + bytesToHexString(enclaveIdentityMrSigner), bytesToHexString(mrSigner)); return STATUS_SGX_ENCLAVE_REPORT_MRSIGNER_MISMATCH; } /// 4.1.2.9.8 if(enclaveReport.isvProdID != enclaveIdentity->getIsvProdId()) { + LOG_ERROR("Enclave Identity contains IsvProdId field: {} which does not match IsvProdId value from Enclave Report: {}", + enclaveIdentity->getIsvProdId(), enclaveReport.isvProdID); return STATUS_SGX_ENCLAVE_REPORT_ISVPRODID_MISMATCH; } @@ -83,8 +91,12 @@ Status EnclaveReportVerifier::verify(const EnclaveIdentityV2 *enclaveIdentity, c { if (enclaveIdentityStatus == TcbStatus::Revoked) { + LOG_ERROR("Value of tcbStatus for the selected Enclave's Identity tcbLevel (isvSvn: {}) is \"Revoked\"", + enclaveReport.isvSvn); return STATUS_SGX_ENCLAVE_REPORT_ISVSVN_REVOKED; } + LOG_ERROR("Value of tcbStatus for the selected Enclave's Identity tcbLevel (isvSvn: {}) is \"OutOfDate\"", + enclaveReport.isvSvn); return STATUS_SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCertVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCertVerifier.cpp index 38071a56..840086a6 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCertVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCertVerifier.cpp @@ -30,6 +30,8 @@ */ #include "PckCertVerifier.h" +#include "Utils/Logger.h" +#include "Utils/TimeUtils.h" #include #include @@ -55,48 +57,74 @@ Status PckCertVerifier::verify(const CertificateChain &chain, const std::time_t& expirationDate) const { const auto x509InChainRootCa = chain.getRootCert(); - if(!x509InChainRootCa || !_baseVerifier.commonNameContains(x509InChainRootCa->getSubject(), constants::SGX_ROOT_CA_CN_PHRASE)) + if(!x509InChainRootCa) { + LOG_ERROR("ROOT CA is missing"); + return STATUS_SGX_ROOT_CA_MISSING; + } + + if(!_baseVerifier.commonNameContains(x509InChainRootCa->getSubject(), constants::SGX_ROOT_CA_CN_PHRASE)) + { + LOG_ERROR("RootCa from chain. CN in Subject field does not contain \"SGX Root CA\" phrase"); return STATUS_SGX_ROOT_CA_MISSING; } const auto x509InChainIntermediateCa = chain.getIntermediateCert(); - if(!x509InChainIntermediateCa || !_baseVerifier.commonNameContains(x509InChainIntermediateCa->getSubject(), constants::SGX_INTERMEDIATE_CN_PHRASE)) + if(!x509InChainIntermediateCa) + { + LOG_ERROR("Intermediate CA is missing"); + return STATUS_SGX_INTERMEDIATE_CA_MISSING; + } + + if(!_baseVerifier.commonNameContains(x509InChainIntermediateCa->getSubject(), constants::SGX_INTERMEDIATE_CN_PHRASE)) { + LOG_ERROR("IntermediateCa from chain. CN in Subject field does not contain \"CA\" phrase"); return STATUS_SGX_INTERMEDIATE_CA_MISSING; } const auto x509InChainPckCert = chain.getPckCert(); - if(!x509InChainPckCert || !_baseVerifier.commonNameContains(x509InChainPckCert->getSubject(), constants::SGX_PCK_CN_PHRASE)) + if(!x509InChainPckCert) { + LOG_ERROR("PCK cert is missing"); + return STATUS_SGX_PCK_MISSING; + } + + if(!_baseVerifier.commonNameContains(x509InChainPckCert->getSubject(), constants::SGX_PCK_CN_PHRASE)) + { + LOG_ERROR("PCK Cert from chain. CN in Subject field does not contain \"SGX PCK Certificate\" phrase"); return STATUS_SGX_PCK_MISSING; } const auto rootVerificationStatus = _commonVerifier->verifyRootCACert(*x509InChainRootCa); if(rootVerificationStatus != STATUS_OK) { + LOG_ERROR("Root CA verification failed: {}", rootVerificationStatus); return rootVerificationStatus; } const auto intermediateVerificationStatus = _commonVerifier->verifyIntermediate(*x509InChainIntermediateCa, *x509InChainRootCa); if(intermediateVerificationStatus != STATUS_OK) { + LOG_ERROR("Intermediate CA verification failed: {}", intermediateVerificationStatus); return intermediateVerificationStatus; } const auto pckVerificationStatus = verifyPCKCert(*x509InChainPckCert, *x509InChainIntermediateCa); if(pckVerificationStatus != STATUS_OK) { + LOG_ERROR("PCK Certificate verification failed: {}", pckVerificationStatus); return pckVerificationStatus; } if(rootCa.getSubject() != rootCa.getIssuer()) { + LOG_ERROR("PCK RootCA is not self signed"); return STATUS_TRUSTED_ROOT_CA_INVALID; } if(x509InChainRootCa->getSignature().getRawDer() != rootCa.getSignature().getRawDer()) { + LOG_ERROR("Signature of trusted root doesn't match signature of root cert from PCK Cert Chain. Chain is not trusted."); return STATUS_SGX_PCK_CERT_CHAIN_UNTRUSTED; } @@ -106,47 +134,68 @@ Status PckCertVerifier::verify(const CertificateChain &chain, const auto checkRootCaCrlCorrectness = _crlVerifier->verify(rootCaCrl, *x509InChainRootCa); if(checkRootCaCrlCorrectness != STATUS_OK) { + LOG_ERROR("PCK Revocation lists - RootCaCrl verification failed: {}", checkRootCaCrlCorrectness); return checkRootCaCrlCorrectness; } const auto checkIntermediateCrlCorrectness = _crlVerifier->verify(intermediateCrl, *x509InChainIntermediateCa); if(checkIntermediateCrlCorrectness != STATUS_OK) { + LOG_ERROR("PCK Revocation lists - IntermediateCaCrl verification failed: {}", checkIntermediateCrlCorrectness); return checkIntermediateCrlCorrectness; } if(rootCaCrl.isRevoked(*x509InChainIntermediateCa)) { + LOG_ERROR("Intermediate CA Cert is revoked by Root CA"); return STATUS_SGX_INTERMEDIATE_CA_REVOKED; } if(intermediateCrl.isRevoked(*x509InChainPckCert)) { + LOG_ERROR("PCK Cert is revoked by Intermediate CA"); return STATUS_SGX_PCK_REVOKED; } - if(expirationDate > x509InChainRootCa->getValidity().getNotAfterTime()) + const auto validityDateRootCA = x509InChainRootCa->getValidity().getNotAfterTime(); + if(expirationDate > validityDateRootCA) { + LOG_ERROR("PCK Cert Chain Root CA is expired. Expiration date: {}, validity: {}", + logger::timeToString(expirationDate), logger::timeToString(validityDateRootCA)); return STATUS_SGX_PCK_CERT_CHAIN_EXPIRED; } - if(expirationDate > x509InChainIntermediateCa->getValidity().getNotAfterTime()) + auto const validityDateIntermediateCA = x509InChainIntermediateCa->getValidity().getNotAfterTime(); + if(expirationDate > validityDateIntermediateCA) { + LOG_ERROR("PCK Cert Chain Intermediate CA is expired. Expiration date: {}, validity: {}", + logger::timeToString(expirationDate), logger::timeToString(validityDateIntermediateCA)); return STATUS_SGX_PCK_CERT_CHAIN_EXPIRED; } - if(expirationDate > x509InChainPckCert->getValidity().getNotAfterTime()) + auto const validityDatePCK = x509InChainPckCert->getValidity().getNotAfterTime(); + if(expirationDate > validityDatePCK) { + LOG_ERROR("PCK Cert Chain PCK Cert is expired. Expiration date: {}, validity: {}", + logger::timeToString(expirationDate), logger::timeToString(validityDatePCK)); return STATUS_SGX_PCK_CERT_CHAIN_EXPIRED; } if(rootCaCrl.expired(expirationDate)) { + LOG_ERROR("ROOT CA CRL is expired. Expiration date: {}, validity date range - from: {} to: {}", + logger::timeToString(expirationDate), + logger::timeToString(rootCaCrl.getValidity().notBeforeTime), + logger::timeToString(rootCaCrl.getValidity().notAfterTime)); return STATUS_SGX_CRL_EXPIRED; } if(intermediateCrl.expired(expirationDate)) { + LOG_ERROR("Intermediate CA CRL is expired. Expiration date: {}, validity date range - from: {} to: {}", + logger::timeToString(expirationDate), + logger::timeToString(rootCaCrl.getValidity().notBeforeTime), + logger::timeToString(rootCaCrl.getValidity().notAfterTime)); return STATUS_SGX_CRL_EXPIRED; } @@ -156,9 +205,15 @@ Status PckCertVerifier::verify(const CertificateChain &chain, Status PckCertVerifier::verifyPCKCert(const dcap::parser::x509::PckCertificate &pckCert, const dcap::parser::x509::Certificate &intermediate) const { - if(pckCert.getIssuer() != intermediate.getSubject() - || !_commonVerifier->checkSignature(pckCert, intermediate)) + if(pckCert.getIssuer() != intermediate.getSubject()) + { + LOG_ERROR("PCK Cert is not signed by Intermediate CA"); + return STATUS_SGX_PCK_INVALID_ISSUER; + } + + if(!_commonVerifier->checkSignature(pckCert, intermediate)) { + LOG_ERROR("PCK Cert signature is invalid"); return STATUS_SGX_PCK_INVALID_ISSUER; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCrlVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCrlVerifier.cpp index a1f99854..271eb87c 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCrlVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/PckCrlVerifier.cpp @@ -30,6 +30,7 @@ */ #include "PckCrlVerifier.h" +#include "Utils/Logger.h" #include #include @@ -57,16 +58,19 @@ Status PckCrlVerifier::verify(const pckparser::CrlStore &crl, const dcap::parser crlIssuer.locationName != certSubject.getLocationName() || crlIssuer.stateName != certSubject.getStateName()) { + LOG_ERROR("CRL has unknown issuer"); return STATUS_SGX_CRL_UNKNOWN_ISSUER; } if(!_commonVerifier->checkStandardExtensions(crl.getExtensions(), dcap::constants::CRL_REQUIRED_EXTENSIONS)) { + LOG_ERROR("CRL does not contain all the required, valid extensions"); return STATUS_SGX_CRL_INVALID_EXTENSIONS; } if(!_commonVerifier->checkSignature(crl, cert)) { + LOG_ERROR("CRL signature verification failure"); return STATUS_SGX_CRL_INVALID_SIGNATURE; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp index 06d96f82..369cfec2 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/QuoteVerifier.cpp @@ -41,7 +41,9 @@ #include #include #include +#include #include +#include namespace intel { namespace sgx { namespace dcap { @@ -111,6 +113,7 @@ const parser::json::TcbLevel& getMatchingTcbLevel(const dcap::parser::json::TcbI } /// 4.1.2.4.17.3 + LOG_ERROR("TCB Level has not been selected"); throw RuntimeException(STATUS_TCB_NOT_SUPPORTED); } @@ -123,28 +126,67 @@ Status checkTcbLevel(const dcap::parser::json::TcbInfo& tcbInfoJson, const dcap: if (tcbInfoJson.getVersion() >= 3 && tcbInfoJson.getId() == parser::json::TcbInfo::TDX_ID && tcbLevel.getTdxTcbComponent(1).getSvn() != quote.getTdReport().teeTcbSvn[1]) { + LOG_ERROR("SVNs at index 1 in TDX TCB Component SVNs ({}) and in TEE TCB SVNs array ({}) does not match", + bytesToHexString(std::vector{tcbLevel.getTdxTcbComponent(1).getSvn()}), + bytesToHexString(std::vector{quote.getTdReport().teeTcbSvn[1]})); return STATUS_TCB_INFO_MISMATCH; } const auto& tcbLevelStatus = tcbLevel.getStatus(); + const auto pckTcb = pckCert.getTcb(); + if(tcbInfoJson.getVersion() >= 3 && tcbInfoJson.getId() == parser::json::TcbInfo::TDX_ID) + { + const auto tdxComponents = tcbLevel.getTdxTcbComponents(); + std::vectortdxTcbComponentsSvnsVec; + for (auto component : tdxComponents) + { + tdxTcbComponentsSvnsVec.push_back(component.getSvn()); + } + + LOG_INFO("Selected TCB Level - sgx: {}, tdx: {}, pceSvn: {}, status: {},\n" + "PCK TCB - cpuSvn: {}, pceSvn: {}\n" + "TD Report - TdxSvn: {}", + bytesToHexString(tcbLevel.getCpuSvn()), + bytesToHexString(tdxTcbComponentsSvnsVec), + tcbLevel.getPceSvn(), + tcbLevelStatus, + bytesToHexString(pckTcb.getCpuSvn()), + pckTcb.getPceSvn(), + bytesToHexString(std::vector(begin(quote.getTdReport().teeTcbSvn), end(quote.getTdReport().teeTcbSvn)))); + } + else + { + LOG_INFO("Selected TCB Level - sgx: {}, pceSvn: {}, status: {},\n" + "PCK TCB - cpuSvn: {}, pceSvn: {}", + bytesToHexString(tcbLevel.getCpuSvn()), + tcbLevel.getPceSvn(), + tcbLevelStatus, + bytesToHexString(pckTcb.getCpuSvn()), + pckTcb.getPceSvn()); + } + if (tcbLevelStatus == "OutOfDate") { + LOG_INFO("TCB Level status is \"OutOfDate\""); return STATUS_TCB_OUT_OF_DATE; } if (tcbLevelStatus == "Revoked") { + LOG_INFO("TCB Level status is \"Revoked\""); return STATUS_TCB_REVOKED; } if (tcbLevelStatus == "ConfigurationNeeded") { + LOG_INFO("TCB Level status is \"ConfigurationNeeded\""); return STATUS_TCB_CONFIGURATION_NEEDED; } if (tcbLevelStatus == "ConfigurationAndSWHardeningNeeded") { + LOG_INFO("TCB Level status is \"ConfigurationAndSWHardeningNeeded\""); return STATUS_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED; } @@ -155,14 +197,17 @@ Status checkTcbLevel(const dcap::parser::json::TcbInfo& tcbInfoJson, const dcap: if (tcbLevelStatus == "SWHardeningNeeded") { + LOG_INFO("TCB Level status is \"SWHardeningNeeded\""); return STATUS_TCB_SW_HARDENING_NEEDED; } if(tcbInfoJson.getVersion() > 1 && tcbLevelStatus == "OutOfDateConfigurationNeeded") { + LOG_INFO("TCB Level status is \"OutOfDateConfigurationNeeded\""); return STATUS_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED; } + LOG_ERROR("TCB Level error status is unrecognized"); throw RuntimeException(STATUS_TCB_UNRECOGNIZED_STATUS); } @@ -170,6 +215,8 @@ Status convergeTcbStatus(Status tcbLevelStatus, Status qeTcbStatus) { if (qeTcbStatus == STATUS_SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE) { + LOG_INFO("QE TCB status is \"OutOfDate\" and TCB Level status is \"{}\"", + tcbLevelStatus); if (tcbLevelStatus == STATUS_OK || tcbLevelStatus == STATUS_TCB_SW_HARDENING_NEEDED) { @@ -183,6 +230,7 @@ Status convergeTcbStatus(Status tcbLevelStatus, Status qeTcbStatus) } if (qeTcbStatus == STATUS_SGX_ENCLAVE_REPORT_ISVSVN_REVOKED) { + LOG_INFO("QE TCB status is \"Revoked\""); return STATUS_TCB_REVOKED; } @@ -215,17 +263,30 @@ Status QuoteVerifier::verify(const Quote& quote, /// 4.1.2.4.4 if (!_baseVerififer.commonNameContains(pckCert.getSubject(), constants::SGX_PCK_CN_PHRASE)) { + LOG_ERROR("PCK Certificate. CN in Subject field does not contain \"SGX PCK Certificate\" phrase"); return STATUS_INVALID_PCK_CERT; } /// 4.1.2.4.6 - if (!PckCrlVerifier{}.checkIssuer(crl) || crl.getIssuer().raw != pckCert.getIssuer().getRaw()) { + if(!PckCrlVerifier{}.checkIssuer(crl)) + { + LOG_ERROR("PCK Revocation List. CN in Issuer field does not contain \"CA\" phrase"); + return STATUS_INVALID_PCK_CRL; + } + + const auto crlIssuerRaw = crl.getIssuer().raw; + const auto pckCertIssuerRaw = pckCert.getIssuer().getRaw(); + if(crlIssuerRaw != pckCertIssuerRaw) + { + LOG_ERROR("Issuers in PCK revocation List and PCK Certificate are not the same. RL: {}, Cert: {}", + crlIssuerRaw, pckCertIssuerRaw); return STATUS_INVALID_PCK_CRL; } /// 4.1.2.4.7 if(crl.isRevoked(pckCert)) { + LOG_ERROR("PCK Certificate is revoked by PCK Revocation List"); return STATUS_PCK_REVOKED; } @@ -234,10 +295,12 @@ Status QuoteVerifier::verify(const Quote& quote, { if(tcbInfoJson.getId() == parser::json::TcbInfo::TDX_ID && quote.getHeader().teeType != dcap::constants::TEE_TYPE_TDX) { + LOG_ERROR("TcbInfo is generated for TDX and does not match Quote's TEE"); return STATUS_TCB_INFO_MISMATCH; } if(tcbInfoJson.getId() == parser::json::TcbInfo::SGX_ID && quote.getHeader().teeType != dcap::constants::TEE_TYPE_SGX) { + LOG_ERROR("TcbInfo is generated for SGX and does not match Quote's TEE"); return STATUS_TCB_INFO_MISMATCH; } } @@ -245,6 +308,7 @@ Status QuoteVerifier::verify(const Quote& quote, { if(quote.getHeader().teeType == dcap::constants::TEE_TYPE_TDX) { + LOG_ERROR("TcbInfo version {} is invalid for TDX TEE", tcbInfoJson.getVersion()); return STATUS_TCB_INFO_MISMATCH; } } @@ -252,11 +316,15 @@ Status QuoteVerifier::verify(const Quote& quote, /// 4.1.2.4.10 if(pckCert.getFmspc() != tcbInfoJson.getFmspc()) { + LOG_ERROR("FMSPC value from TcbInfo ({}) and SGX Extension in PCK Cert ({}) do not match", + bytesToHexString(tcbInfoJson.getFmspc()), bytesToHexString(pckCert.getFmspc())); return STATUS_TCB_INFO_MISMATCH; } if(pckCert.getPceId() != tcbInfoJson.getPceId()) { + LOG_ERROR("PCEID value from TcbInfo ({}) and SGX Extension in PCK Cert ({}) do not match", + bytesToHexString(tcbInfoJson.getPceId()), bytesToHexString(pckCert.getPceId())); return STATUS_TCB_INFO_MISMATCH; } @@ -269,6 +337,7 @@ Status QuoteVerifier::verify(const Quote& quote, auto pubKey = crypto::rawToP256PubKey(pckCert.getPubKey()); if (pubKey == nullptr) { + LOG_ERROR("Public key parsing error. PCK Certificate is invalid"); return STATUS_INVALID_PCK_CERT; // if there were issues with parsing public key it means cert was invalid. // Probably it will never happen because parsing cert should fail earlier. } @@ -283,6 +352,8 @@ Status QuoteVerifier::verify(const Quote& quote, if (quoteMrSignerSeam.size() != tdxModuleMrSigner.size()) { + LOG_ERROR("MRSIGNERSEAM value size from TdReport in Quote ({}) and MRSIGNER value size from TcbInfo ({}) are not the same", + quoteMrSignerSeam.size(), tdxModuleMrSigner.size()); return STATUS_TDX_MODULE_MISMATCH; } @@ -290,6 +361,9 @@ Status QuoteVerifier::verify(const Quote& quote, { if (tdxModuleMrSigner[i] != quoteMrSignerSeam[i]) { + LOG_ERROR("MRSIGNERSEAM value from TdReport in Quote ({}) and MRSIGNER value from TcbInfo ({}) are not the same", + bytesToHexString(std::vector(begin(quoteMrSignerSeam), end(quoteMrSignerSeam))), + bytesToHexString(std::vector(begin(tdxModuleMrSigner), end(tdxModuleMrSigner)))); return STATUS_TDX_MODULE_MISMATCH; } } @@ -297,6 +371,9 @@ Status QuoteVerifier::verify(const Quote& quote, std::vector quoteSeamAttributes(quote.getTdReport().seamAttributes.begin(), quote.getTdReport().seamAttributes.end()); if (applyMask(quoteSeamAttributes, tdxModule.getAttributesMask()) != tdxModule.getAttributes()) { + LOG_ERROR("SEAMATTRIBUTES values from TdReport in Quote with attributesMask applied {} and from TcbInfo are not the same", + bytesToHexString(applyMask(quoteSeamAttributes, tdxModule.getAttributesMask())), + bytesToHexString(tdxModule.getAttributes())); return STATUS_TDX_MODULE_MISMATCH; } } @@ -304,6 +381,9 @@ Status QuoteVerifier::verify(const Quote& quote, /// 4.1.2.4.12 if (!crypto::verifySha256EcdsaSignature(quote.getQeReportSignature(), quote.getQeReport().rawBlob(), *pubKey)) { + LOG_ERROR("QE Report Signature extracted from quote ({}) cannot be verified with the Public Key extracted from PCK Certificate ({})", + bytesToHexString(std::vector(begin(quote.getQeReportSignature()), end(quote.getQeReportSignature()))), + bytesToHexString(pckCert.getPubKey())); return STATUS_INVALID_QE_REPORT_SIGNATURE; } @@ -322,6 +402,9 @@ Status QuoteVerifier::verify(const Quote& quote, hashedConcatOfAttestKeyAndQeReportData.end(), quote.getQeReport().reportData.begin())) { + LOG_ERROR("Report Data value extracted from QE Report in Quote ({}) and the value of SHA256 calculated over the concatenation of ECDSA Attestation Key and QE Authenticated Data extracted from Quote ({}) are not the same", + bytesToHexString(std::vector(begin(quote.getQeReport().reportData), end(quote.getQeReport().reportData))), + bytesToHexString(hashedConcatOfAttestKeyAndQeReportData)); return STATUS_INVALID_QE_REPORT_DATA; } @@ -332,12 +415,14 @@ Status QuoteVerifier::verify(const Quote& quote, { if(enclaveIdentity->getVersion() == 1) { + LOG_ERROR("Enclave Identity version 1 is invalid for TDX TEE"); return STATUS_QE_IDENTITY_MISMATCH; } else if(enclaveIdentity->getVersion() == 2) { if(enclaveIdentity->getID() != EnclaveID::TD_QE) { + LOG_ERROR("Enclave Identity is not generated for TDX and does not match Quote's TEE"); return STATUS_QE_IDENTITY_MISMATCH; } } @@ -346,11 +431,13 @@ Status QuoteVerifier::verify(const Quote& quote, { if(enclaveIdentity->getID() != EnclaveID::QE) { + LOG_ERROR("Enclave Identity is not generated for SGX and does not match Quote's TEE"); return STATUS_QE_IDENTITY_MISMATCH; } } else { + LOG_ERROR("Unknown Quote's TEE. Enclave Identity cannot be valid"); return STATUS_QE_IDENTITY_MISMATCH; } @@ -386,6 +473,9 @@ Status QuoteVerifier::verify(const Quote& quote, quote.getSignedData(), *attestKey)) { + LOG_ERROR("Quote Signature ({}) cannot be verified with ECDSA Attestation Key ({})", + bytesToHexString(std::vector(begin(quote.getQuoteSignature()), end(quote.getQuoteSignature()))), + bytesToHexString(std::vector(begin(quote.getAttestKeyData()), end(quote.getAttestKeyData())))); return STATUS_INVALID_QUOTE_SIGNATURE; } @@ -408,6 +498,7 @@ Status QuoteVerifier::verify(const Quote& quote, } catch (const RuntimeException &ex) { + LOG_ERROR("RuntimeException during quote verification has occurred: {}", ex.what()); return ex.getStatus(); } } @@ -416,6 +507,8 @@ Status QuoteVerifier::verifyCertificationData(const CertificationData& certifica { if (certificationData.parsedDataSize != certificationData.data.size()) { + LOG_ERROR("Unexpected parsed data size, expected: {}, actual: {}", + certificationData.data.size(), certificationData.parsedDataSize); return STATUS_UNSUPPORTED_QUOTE_FORMAT; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBInfoVerifier.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBInfoVerifier.cpp index 8c762188..4874a6f7 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBInfoVerifier.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBInfoVerifier.cpp @@ -34,6 +34,8 @@ #include #include +#include "Utils/Logger.h" + namespace intel { namespace sgx { namespace dcap { TCBInfoVerifier::TCBInfoVerifier() @@ -66,27 +68,32 @@ Status TCBInfoVerifier::verify( if(!_commonVerifier->checkSha256EcdsaSignature( tcbJson.getSignature(), tcbJson.getInfoBody(), tcbSigningCert->getPubKey())) { + LOG_ERROR("TCB Info signature is invalid"); return STATUS_TCB_INFO_INVALID_SIGNATURE; } const auto rootCa = chain.getRootCert(); if(expirationDate > rootCa->getValidity().getNotAfterTime()) { + LOG_ERROR("TCB Signing Chain Root CA is expired"); return STATUS_SGX_SIGNING_CERT_CHAIN_EXPIRED; } if (expirationDate > tcbSigningCert->getValidity().getNotAfterTime()) { + LOG_ERROR("TCB Signing Certificate is expired"); return STATUS_SGX_SIGNING_CERT_CHAIN_EXPIRED; } if (rootCaCrl.expired(expirationDate)) { + LOG_ERROR("ROOT CA CRL is expired"); return STATUS_SGX_CRL_EXPIRED; } if(expirationDate > tcbJson.getNextUpdate()) { + LOG_ERROR("TCB Info is expired"); return STATUS_SGX_TCB_INFO_EXPIRED; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBSigningChain.cpp b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBSigningChain.cpp index 3cfc066f..a5c3a240 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBSigningChain.cpp +++ b/QuoteVerification/QVL/Src/AttestationLibrary/src/Verifiers/TCBSigningChain.cpp @@ -30,6 +30,7 @@ */ #include "TCBSigningChain.h" +#include "Utils/Logger.h" #include #include @@ -54,47 +55,67 @@ Status TCBSigningChain::verify( const dcap::parser::x509::Certificate &trustedRoot) const { const auto rootCert = chain.getRootCert(); - if(!rootCert || !_baseVerifier.commonNameContains(rootCert->getSubject(), constants::SGX_ROOT_CA_CN_PHRASE)) + if(!rootCert) { + LOG_ERROR("ROOT CA is missing"); + return STATUS_SGX_ROOT_CA_MISSING; + } + if(!_baseVerifier.commonNameContains(rootCert->getSubject(), constants::SGX_ROOT_CA_CN_PHRASE)) + { + LOG_ERROR("ROOT CA has wrong subject. Expected: {}, actual: {}", constants::SGX_ROOT_CA_CN_PHRASE, + rootCert->getSubject().getCommonName()); return STATUS_SGX_ROOT_CA_MISSING; } const auto rootVerificationStatus = _commonVerifier->verifyRootCACert(*rootCert); if(rootVerificationStatus != STATUS_OK) { + LOG_ERROR("Root CA verification failed: {}", rootVerificationStatus); return rootVerificationStatus; } const auto tcbSigningCert = chain.getTopmostCert(); - if(!tcbSigningCert || !_baseVerifier.commonNameContains(tcbSigningCert->getSubject(), constants::SGX_TCB_SIGNING_CN_PHRASE)) + if(!tcbSigningCert) + { + LOG_ERROR("TCB Signing Cert is missing"); + return STATUS_SGX_TCB_SIGNING_CERT_MISSING; + } + if(!_baseVerifier.commonNameContains(tcbSigningCert->getSubject(), constants::SGX_TCB_SIGNING_CN_PHRASE)) { + LOG_ERROR("TCB Signing Cert has wrong common name. Expected: {}, actual: {}", constants::SGX_TCB_SIGNING_CN_PHRASE, + tcbSigningCert->getSubject().getCommonName()); return STATUS_SGX_TCB_SIGNING_CERT_MISSING; } const auto tcbCertVerificationStatus = verifyTCBCert(*tcbSigningCert, *rootCert); if(tcbCertVerificationStatus != STATUS_OK) { + LOG_ERROR("TCB Signing cert verification failed: {}", tcbCertVerificationStatus); return tcbCertVerificationStatus; } const auto crlVerificationStatus = _crlVerifier->verify(rootCaCrl, *rootCert); if(crlVerificationStatus != STATUS_OK) { + LOG_ERROR("TCB Signing Root CA CRL verification failed: {}", crlVerificationStatus); return crlVerificationStatus; } if(rootCaCrl.isRevoked(*tcbSigningCert)) { + LOG_ERROR("TCB Signing Cert is revoked by Root CA"); return STATUS_SGX_TCB_SIGNING_CERT_REVOKED; } if(trustedRoot.getSubject() != trustedRoot.getIssuer()) { + LOG_ERROR("TCB Signing Root CA is not self signed"); return STATUS_TRUSTED_ROOT_CA_INVALID; } if(rootCert->getSignature().getRawDer() != trustedRoot.getSignature().getRawDer()) { + LOG_ERROR("Signature of trusted root doesn't match signature of root cert from TCB Signing Chain. Chain is not trusted."); return STATUS_SGX_TCB_SIGNING_CERT_CHAIN_UNTRUSTED; } @@ -105,8 +126,16 @@ Status TCBSigningChain::verifyTCBCert(const dcap::parser::x509::Certificate &tcb const dcap::parser::x509::Certificate &rootCaCert) const { - if(tcbCert.getIssuer() != rootCaCert.getSubject() || !_commonVerifier->checkSignature(tcbCert, rootCaCert)) + if(tcbCert.getIssuer() != rootCaCert.getSubject()) + { + LOG_ERROR("TCB signing certificate has different issuer than provided Root CA. RootCA common name: {}, TCB Cert issuer common name: {}", + rootCaCert.getSubject().getCommonName(), tcbCert.getSubject().getCommonName()); + return STATUS_SGX_TCB_SIGNING_CERT_INVALID_ISSUER; + } + + if(!_commonVerifier->checkSignature(tcbCert, rootCaCert)) { + LOG_ERROR("TCB signing certificate signature verification failed"); return STATUS_SGX_TCB_SIGNING_CERT_INVALID_ISSUER; } diff --git a/QuoteVerification/QVL/Src/AttestationLibrary/test/IntegrationTests/CMakeLists.txt b/QuoteVerification/QVL/Src/AttestationLibrary/test/IntegrationTests/CMakeLists.txt index bf98cbee..34492f80 100644 --- a/QuoteVerification/QVL/Src/AttestationLibrary/test/IntegrationTests/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/AttestationLibrary/test/IntegrationTests/CMakeLists.txt @@ -63,5 +63,9 @@ target_link_libraries(${SUBPROJECT_NAME} GTest::gmock_main ) +if(MSVC) + target_compile_definitions(${SUBPROJECT_NAME} PUBLIC ATTESTATIONPARSERS_STATIC) +endif() + install(TARGETS ${SUBPROJECT_NAME} DESTINATION bin) add_test(NAME ${SUBPROJECT_NAME} COMMAND ) diff --git a/QuoteVerification/QVL/Src/AttestationParsers/CMakeLists.txt b/QuoteVerification/QVL/Src/AttestationParsers/CMakeLists.txt index 0a2379a4..1744af05 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/AttestationParsers/CMakeLists.txt @@ -52,6 +52,9 @@ endif() hunter_add_package(OpenSSL) find_package(OpenSSL 1.1.1 EXACT REQUIRED) +hunter_add_package(spdlog) +find_package(spdlog CONFIG REQUIRED) + file(GLOB SOURCE_FILES src/*.cpp src/Json/*.cpp src/X509/*.cpp @@ -76,6 +79,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC OpenSSL::SSL OpenSSL::Crypto rapidjson + spdlog::spdlog_header_only ) target_link_libraries(${PROJECT_NAME}Static PUBLIC @@ -83,6 +87,7 @@ target_link_libraries(${PROJECT_NAME}Static PUBLIC OpenSSL::SSL OpenSSL::Crypto rapidjson + spdlog::spdlog_header_only ) ######### QVL Enclave related settings ################################################################################# diff --git a/QuoteVerification/QVL/Src/AttestationParsers/include/SgxEcdsaAttestation/AttestationParsers.h b/QuoteVerification/QVL/Src/AttestationParsers/include/SgxEcdsaAttestation/AttestationParsers.h index 1b1143fd..e4ef0ee8 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/include/SgxEcdsaAttestation/AttestationParsers.h +++ b/QuoteVerification/QVL/Src/AttestationParsers/include/SgxEcdsaAttestation/AttestationParsers.h @@ -32,17 +32,44 @@ #ifndef SGX_DCAP_PARSERS_H_ #define SGX_DCAP_PARSERS_H_ +#ifndef _MSC_VER +#define ATTESTATION_PARSERS_API __attribute__ ((visibility("default"))) +#elif _ATTESTATIONPARSERS_EXPORTS +#define ATTESTATION_PARSERS_API __declspec(dllexport) +#elif ATTESTATIONPARSERS_STATIC + #define ATTESTATION_PARSERS_API +#else + #define ATTESTATION_PARSERS_API __declspec(dllimport) +#endif + #include #include #include #include #include -#include -#include + +// Forward declarations for rapidjson +namespace rapidjson { + template + class MemoryPoolAllocator; + class CrtAllocator; + template struct UTF8; + template + class GenericValue; + typedef GenericValue, MemoryPoolAllocator > Value; +} + +// Forward declarations for openssl +typedef struct X509_name_st X509_NAME; +typedef struct asn1_string_st ASN1_STRING; +typedef struct asn1_string_st ASN1_BIT_STRING; +typedef struct X509_extension_st X509_EXTENSION; +struct stack_st_ASN1_TYPE; +typedef struct asn1_type_st ASN1_TYPE; +typedef struct x509_st X509; namespace intel { namespace sgx { namespace dcap { namespace parser { - namespace json { class JsonParser; @@ -50,7 +77,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser class TcbInfo; class TcbLevel; - class TcbComponent + class ATTESTATION_PARSERS_API TcbComponent { public: uint8_t getSvn() const; @@ -73,7 +100,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class representing properties of Intelā€™s TDX SEAM module. */ - class TdxModule + class ATTESTATION_PARSERS_API TdxModule { public: TdxModule() = default; @@ -113,7 +140,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class representing a TCB information structure which holds information about TCB Levels for specific FMSPC */ - class TcbInfo + class ATTESTATION_PARSERS_API TcbInfo { public: /** @@ -249,7 +276,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class representing a single TCB Level */ - class TcbLevel + class ATTESTATION_PARSERS_API TcbLevel { public: /** @@ -429,7 +456,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents x509 distinguished name */ - class DistinguishedName + class ATTESTATION_PARSERS_API DistinguishedName { public: DistinguishedName() = default; @@ -500,7 +527,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents certificate's validity */ - class Validity + class ATTESTATION_PARSERS_API Validity { public: Validity() = default; @@ -538,13 +565,13 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents SGX specific X509 certificate extensions */ - class Extension + class ATTESTATION_PARSERS_API Extension { public: /** * Enum representing SGX extension type */ - enum class Type : int + enum class ATTESTATION_PARSERS_API Type : int { NONE = -1, PPID = 0, @@ -634,7 +661,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents ECDSA signature */ - class Signature + class ATTESTATION_PARSERS_API Signature { public: Signature(); @@ -688,7 +715,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents X509 Certificate */ - class Certificate + class ATTESTATION_PARSERS_API Certificate { public: Certificate(); @@ -767,6 +794,12 @@ namespace intel { namespace sgx { namespace dcap { namespace parser */ virtual const std::vector& getPubKey() const; + /** + * Get CRL distribution point + * @return string + */ + virtual const std::string& getCrlDistributionPoint() const; + /** * Parse PEM encoded X.509 certificate * @param pem PEM encoded X.509 certificate @@ -787,6 +820,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser std::vector _pubKey; std::vector _info; std::string _pem; + std::string _crlDistributionPoint; explicit Certificate(const std::string& pem); @@ -800,9 +834,10 @@ namespace intel { namespace sgx { namespace dcap { namespace parser void setExtensions(const X509* x509); void setSignature(const X509* x509); void setPublicKey(const X509* x509); + void setCrlDistributionPoint(const X509* x509); }; - enum SgxType + enum ATTESTATION_PARSERS_API SgxType { Standard, Scalable, @@ -814,7 +849,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents Intel SGX Trusted Computing Base information stored in Provisioning Certification Key Certificate */ - class Tcb + class ATTESTATION_PARSERS_API Tcb { public: Tcb() = default; @@ -876,7 +911,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser * Optional sequence of additional configuration settings. * This data is only relevant to PCK Certificates issued by PCK Platform CA */ - class Configuration { + class ATTESTATION_PARSERS_API Configuration { public: Configuration() = default; /** @@ -931,7 +966,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents Provisioning Certification Key Certificate */ - class PckCertificate : public Certificate + class ATTESTATION_PARSERS_API PckCertificate : public Certificate { public: PckCertificate() = default; @@ -1018,7 +1053,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents Processor Provisioning Certification Key Certificate */ - class ProcessorPckCertificate : public PckCertificate + class ATTESTATION_PARSERS_API ProcessorPckCertificate : public PckCertificate { using PckCertificate::PckCertificate; public: @@ -1047,7 +1082,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Class that represents Platform Certification Key Certificate */ - class PlatformPckCertificate : public PckCertificate { + class ATTESTATION_PARSERS_API PlatformPckCertificate : public PckCertificate { public: PlatformPckCertificate() = default; @@ -1106,7 +1141,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Exception when parsing fails due to incorrect format of the structure */ - class FormatException : public std::logic_error + class ATTESTATION_PARSERS_API FormatException : public std::logic_error { public: using std::logic_error::logic_error; @@ -1115,7 +1150,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser /** * Exception when invalid extension is found in certificate */ - class InvalidExtensionException : public std::logic_error + class ATTESTATION_PARSERS_API InvalidExtensionException : public std::logic_error { public: using std::logic_error::logic_error; diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbComponent.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbComponent.cpp index d5386dfe..e81b45d7 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbComponent.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbComponent.cpp @@ -32,6 +32,7 @@ #include #include "SgxEcdsaAttestation/AttestationParsers.h" #include "JsonParser.h" +#include "Utils/Logger.h" namespace intel { namespace sgx { namespace dcap { namespace parser { namespace json { uint8_t TcbComponent::getSvn() const { @@ -49,7 +50,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser { namespace TcbComponent::TcbComponent(const ::rapidjson::Value& tcbComponent) { if (!tcbComponent.IsObject()) { - throw FormatException("TCB Component should be an object"); + LOG_AND_THROW(FormatException, "TCB Component should be an object"); } _svn = 0; JsonParser jsonParser; @@ -60,7 +61,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser { namespace if (status != JsonParser::OK) { - throw FormatException("TCB Component JSON should has [svn] field and it should be unsigned integer"); + LOG_AND_THROW(FormatException, "TCB Component JSON should has [svn] field and it should be unsigned integer"); } _svn = static_cast(svnTemporary); @@ -68,13 +69,13 @@ namespace intel { namespace sgx { namespace dcap { namespace parser { namespace std::tie(_category, status) = jsonParser.getStringFieldOf(tcbComponent, "category"); if (status == JsonParser::Invalid) { - throw FormatException("TCB Component JSON's [category] field should be string"); + LOG_AND_THROW(FormatException, "TCB Component JSON's [category] field should be string"); } std::tie(_type, status) = jsonParser.getStringFieldOf(tcbComponent, "type"); if (status == JsonParser::Invalid) { - throw FormatException("TCB Component JSON's [type] field should be string"); + LOG_AND_THROW(FormatException, "TCB Component JSON's [type] field should be string"); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbInfo.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbInfo.cpp index ac9ad65c..d63d19ca 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbInfo.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbInfo.cpp @@ -34,6 +34,7 @@ #include "OpensslHelpers/Bytes.h" #include "X509Constants.h" #include "JsonParser.h" +#include "Utils/Logger.h" #include #include @@ -55,11 +56,12 @@ std::string TcbInfo::getId() const switch (_version) { case (Version::V2): - throw FormatException("TCB identifier is not a valid field in TCB Info V2 structure"); + LOG_AND_THROW(FormatException, "TCB identifier is not a valid field in TCB Info V2 structure"); + case (Version::V3): return _id; default: - throw FormatException("TCB identifier is not a valid field in TCB Info structure"); + LOG_AND_THROW(FormatException, "TCB identifier is not a valid field in TCB Info V2 structure"); } } @@ -117,12 +119,12 @@ const TdxModule& TcbInfo::getTdxModule() const { if (_version < Version::V3) { - throw FormatException("TdxModule is not a valid field in TCB Info V1 and V2 structure"); + LOG_AND_THROW(FormatException, "TdxModule is not a valid field in TCB Info V1 and V2 structure"); } if (_id != TDX_ID) { - throw FormatException("TdxModule is only valid for TDX TCB Info"); + LOG_AND_THROW(FormatException, "TdxModule is only valid for TDX TCB Info"); } return _tdxModule; @@ -134,24 +136,24 @@ TcbInfo::TcbInfo(const std::string& jsonString) JsonParser jsonParser; if(!jsonParser.parse(jsonString)) { - throw FormatException("Could not parse TCB info JSON"); + LOG_AND_THROW(FormatException, "Could not parse TCB info JSON"); } const auto* tcbInfo = jsonParser.getField("tcbInfo"); if(tcbInfo == nullptr) { - throw FormatException("Missing [tcbInfo] field of TCB info JSON"); + LOG_AND_THROW(FormatException, "Missing [tcbInfo] field of TCB info JSON"); } if(!tcbInfo->IsObject()) { - throw FormatException("[tcbInfo] field of TCB info JSON should be an object"); + LOG_AND_THROW(FormatException, "[tcbInfo] field of TCB info JSON should be an object"); } const auto* signatureField = jsonParser.getField("signature"); if(signatureField == nullptr) { - throw InvalidExtensionException("Missing [signature] field of TCB info JSON"); + LOG_AND_THROW(InvalidExtensionException, "Missing [signature] field of TCB info JSON"); } auto version = jsonParser.getUintFieldOf(*tcbInfo, "version"); @@ -159,9 +161,9 @@ TcbInfo::TcbInfo(const std::string& jsonString) switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [version] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [version] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [version] field of TCB info JSON to integer"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [version] field of TCB info JSON to integer"); case JsonParser::ParseStatus::OK: break; } @@ -174,7 +176,7 @@ TcbInfo::TcbInfo(const std::string& jsonString) + "] value for field of TCB info JSON. Supported versions are [" + std::to_string(static_cast(Version::V2)) + " | " + std::to_string(static_cast(Version::V3)) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } if (_version == Version::V3) @@ -183,20 +185,20 @@ TcbInfo::TcbInfo(const std::string& jsonString) switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [id] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [id] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } if (_id != SGX_ID && _id != TDX_ID) { std::string err = "Unsupported id[" + _id + "] value for field of TCB info JSON. Supported identifiers are [" + SGX_ID + " | " + TDX_ID + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } } else @@ -208,63 +210,63 @@ TcbInfo::TcbInfo(const std::string& jsonString) switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [issueDate] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [issueDate] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [issueDate] field of TCB info JSON to date. [issueDate] should be ISO formatted date"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [issueDate] field of TCB info JSON to date. [issueDate] should be ISO formatted date"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } std::tie(_nextUpdate, status) = jsonParser.getDateFieldOf(*tcbInfo, "nextUpdate"); switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [nextUpdate] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [nextUpdate] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [nextUpdate] field of TCB info JSON to date. [nextUpdate] should be ISO formatted date"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [nextUpdate] field of TCB info JSON to date. [nextUpdate] should be ISO formatted date"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } std::tie(_fmspc, status) = jsonParser.getBytesFieldOf(*tcbInfo, "fmspc", constants::FMSPC_BYTE_LEN * 2); switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [fmspc] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [fmspc] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [fmspc] field of TCB info JSON to bytes"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [fmspc] field of TCB info JSON to bytes"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } std::tie(_pceId, status) = jsonParser.getBytesFieldOf(*tcbInfo, "pceId", constants::PCEID_BYTE_LEN * 2); switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [pceId] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [pceId] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [pceId] field of TCB info JSON to bytes"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [pceId] field of TCB info JSON to bytes"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } if(!signatureField->IsString() || signatureField->GetStringLength() != constants::ECDSA_P256_SIGNATURE_BYTE_LEN * 2) { - throw InvalidExtensionException("Could not parse [signature] field of TCB info JSON to bytes"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [signature] field of TCB info JSON to bytes"); } _signature = hexStringToBytes(signatureField->GetString()); if(!tcbInfo->HasMember("tcbLevels")) { - throw InvalidExtensionException("Missing [tcbLevels] field of TCB info JSON"); + LOG_AND_THROW(InvalidExtensionException, "Missing [tcbLevels] field of TCB info JSON"); } if(_version >= Version::V2) @@ -280,7 +282,7 @@ TcbInfo::TcbInfo(const std::string& jsonString) const auto& tcbs = (*tcbInfo)["tcbLevels"]; if(!tcbs.IsArray()) { - throw InvalidExtensionException("[tcbLevels] field of TCB info JSON should be a nonempty array"); + LOG_AND_THROW(InvalidExtensionException, "[tcbLevels] field of TCB info JSON should be a nonempty array"); } for(uint32_t tcbLevelIndex = 0; tcbLevelIndex < tcbs.Size(); ++tcbLevelIndex) @@ -289,13 +291,14 @@ TcbInfo::TcbInfo(const std::string& jsonString) std::tie(std::ignore, inserted) = _tcbLevels.emplace(TcbLevel(tcbs[tcbLevelIndex], static_cast(_version), _id)); if (!inserted) { - throw InvalidExtensionException("Detected duplicated TCB levels"); + + LOG_AND_THROW(InvalidExtensionException, "Detected duplicated TCB levels"); } } if(_tcbLevels.empty()) { - throw InvalidExtensionException("Number of parsed [tcbLevels] should not be 0"); + LOG_AND_THROW(InvalidExtensionException, "Number of parsed [tcbLevels] should not be 0"); } rapidjson::StringBuffer buffer; @@ -315,26 +318,26 @@ void TcbInfo::parsePartV2(const ::rapidjson::Value &tcbInfo, JsonParser &jsonPar switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [tcbType] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [tcbType] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [tcbType] field of TCB Info JSON to number"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [tcbType] field of TCB Info JSON to number"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } std::tie(_tcbEvaluationDataNumber, status) = jsonParser.getUintFieldOf(tcbInfo, "tcbEvaluationDataNumber"); switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB Info JSON should has [tcbEvaluationDataNumber] field"); + LOG_AND_THROW(FormatException, "TCB Info JSON should has [tcbEvaluationDataNumber] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [tcbEvaluationDataNumber] field of TCB Info JSON to number"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [tcbEvaluationDataNumber] field of TCB Info JSON to number"); case JsonParser::ParseStatus::OK: break; default: - throw InvalidExtensionException("Could not parse [id] field of TCB info JSON to string"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [id] field of TCB info JSON to string"); } } @@ -344,18 +347,18 @@ void TcbInfo::parsePartV3(const ::rapidjson::Value &tcbInfo) if (_id == TcbInfo::SGX_ID && tdxModuleExists) { - throw InvalidExtensionException("TCB Info JSON for SGX should not have [tdxModule] field"); + LOG_AND_THROW(InvalidExtensionException, "TCB Info JSON for SGX should not have [tdxModule] field"); } else if (_id == TcbInfo::TDX_ID) { if (!tdxModuleExists) { - throw InvalidExtensionException("TCB Info JSON for TDX should have [tdxModule] field"); + LOG_AND_THROW(InvalidExtensionException, "TCB Info JSON for TDX should have [tdxModule] field"); } const auto tdxModuleJson = &tcbInfo["tdxModule"]; if (!tdxModuleJson->IsObject()) { - throw FormatException("[tdxModule] field should be an object"); + LOG_AND_THROW(FormatException, "[tdxModule] field should be an object"); } _tdxModule = TdxModule(*tdxModuleJson); } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbLevel.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbLevel.cpp index 285c0dfe..49b7e0fd 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbLevel.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TcbLevel.cpp @@ -37,6 +37,7 @@ #include #include #include +#include "Utils/Logger.h" namespace intel { namespace sgx { namespace dcap { namespace parser { namespace json { @@ -100,7 +101,7 @@ uint32_t TcbLevel::getSgxTcbComponentSvn(uint32_t componentNumber) const { std::string err = "Invalid component SVN number [" + std::to_string(componentNumber) + "]. Should be less than " + std::to_string(constants::CPUSVN_BYTE_LEN); - throw FormatException(err); + LOG_AND_THROW(FormatException, err); } return _cpuSvnComponents[componentNumber]; } @@ -111,7 +112,7 @@ const TcbComponent& TcbLevel::getSgxTcbComponent(uint32_t componentNumber) const { std::string err = "Invalid component SVN number [" + std::to_string(componentNumber) + "]. Should be less than " + std::to_string(constants::CPUSVN_BYTE_LEN); - throw FormatException(err); + LOG_AND_THROW(FormatException, err); } return _sgxTcbComponents[componentNumber]; } @@ -120,7 +121,7 @@ const std::vector& TcbLevel::getSgxTcbComponents() const { if (_version < TcbInfo::Version::V3) { - throw FormatException("SGX TCB Components is not a valid field in TCB Info V1 and V2 structure"); + LOG_AND_THROW(FormatException, "SGX TCB Components is not a valid field in TCB Info V1 and V2 structure"); } return _sgxTcbComponents; } @@ -131,15 +132,15 @@ const TcbComponent& TcbLevel::getTdxTcbComponent(uint32_t componentNumber) const { std::string err = "Invalid component SVN number [" + std::to_string(componentNumber) + "]. Should be less than " + std::to_string(constants::CPUSVN_BYTE_LEN); - throw FormatException(err); + LOG_AND_THROW(FormatException, err); } if (_version < TcbInfo::Version::V3) { - throw FormatException("TDX TCB Components is not a valid field in TCB Info V1 and V2 structure"); + LOG_AND_THROW(FormatException, "TDX TCB Components is not a valid field in TCB Info V1 and V2 structure"); } if (_id != TcbInfo::TDX_ID) { - throw FormatException("TDX TCB Components is not a valid field in SGX TCB Info structure"); + LOG_AND_THROW(FormatException, "TDX TCB Components is not a valid field in SGX TCB Info structure"); } return _tdxTcbComponents[componentNumber]; } @@ -148,11 +149,11 @@ const std::vector& TcbLevel::getTdxTcbComponents() const { if (_version < TcbInfo::Version::V3) { - throw FormatException("TDX TCB Components is not a valid field in TCB Info V1 and V2 structure"); + LOG_AND_THROW(FormatException, "TDX TCB Components is not a valid field in TCB Info V1 and V2 structure"); } if (_id != TcbInfo::TDX_ID) { - throw FormatException("TDX TCB Components is not a valid field in SGX TCB Info structure"); + LOG_AND_THROW(FormatException, "TDX TCB Components is not a valid field in SGX TCB Info structure"); } return _tdxTcbComponents; } @@ -198,7 +199,7 @@ TcbLevel::TcbLevel(const ::rapidjson::Value& tcbLevel, const uint32_t version, c parseTcbLevelV3(tcbLevel, jsonParser); break; default: - throw InvalidExtensionException("Unsupported version of tcbLevel"); + LOG_AND_THROW(InvalidExtensionException, "Unsupported version of tcbLevel"); } } @@ -209,18 +210,18 @@ void TcbLevel::parseStatus(const ::rapidjson::Value &tcbLevel, { if(!tcbLevel.HasMember(filedName.c_str())) { - throw FormatException("TCB level JSON should has [" + filedName + "] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should has [" + filedName + "] field"); } const ::rapidjson::Value& status_v = tcbLevel[filedName.c_str()]; if(!status_v.IsString()) { - throw FormatException("TCB level [" + filedName + "] JSON field should be a string"); + LOG_AND_THROW(FormatException, "TCB level [" + filedName + "] JSON field should be a string"); } _status = status_v.GetString(); if(std::find(validStatuses.cbegin(), validStatuses.cend(), _status) == validStatuses.cend()) { - throw InvalidExtensionException("TCB level [" + filedName + "] JSON field has invalid value [" + _status + "]"); + LOG_AND_THROW(InvalidExtensionException, "TCB level [" + filedName + "] JSON field has invalid value [" + _status + "]"); } } @@ -228,7 +229,7 @@ void TcbLevel::parseSvns(const ::rapidjson::Value &tcbLevel, JsonParser& jsonPar { if(!tcbLevel.HasMember("tcb")) { - throw FormatException("TCB level JSON should has [tcb] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should has [tcb] field"); } const ::rapidjson::Value& tcb = tcbLevel["tcb"]; @@ -239,7 +240,7 @@ void TcbLevel::parseSvns(const ::rapidjson::Value &tcbLevel, JsonParser& jsonPar std::tie(_pceSvn, pceSvnValid) = jsonParser.getUintFieldOf(tcb, "pcesvn"); if(pceSvnValid != JsonParser::OK) { - throw FormatException("Could not parse [pcesvn] field of TCB level JSON to unsigned integer"); + LOG_AND_THROW(FormatException, "Could not parse [pcesvn] field of TCB level JSON to unsigned integer"); } } @@ -247,7 +248,7 @@ void TcbLevel::parseTcbLevelCommon(const ::rapidjson::Value& tcbLevel, JsonParse { if(!tcbLevel.IsObject()) { - throw FormatException("TCB level should be a JSON object"); + LOG_AND_THROW(FormatException, "TCB level should be a JSON object"); } JsonParser::ParseStatus parsedStatus = JsonParser::Missing; @@ -255,9 +256,9 @@ void TcbLevel::parseTcbLevelCommon(const ::rapidjson::Value& tcbLevel, JsonParse switch (parsedStatus) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB level JSON should has [tcbDate] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should has [tcbDate] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [tcbDate] field of TCB info JSON to date. [tcbDate] should be ISO formatted date"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [tcbDate] field of TCB info JSON to date. [tcbDate] should be ISO formatted date"); case JsonParser::ParseStatus::OK: break; } @@ -267,7 +268,7 @@ void TcbLevel::parseTcbLevelCommon(const ::rapidjson::Value& tcbLevel, JsonParse switch (parsedStatus) { case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [advisoryIDs] field of TCB info JSON to an array."); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [advisoryIDs] field of TCB info JSON to an array."); case JsonParser::ParseStatus::Missing: // advisoryIDs field is optional case JsonParser::ParseStatus::OK: break; @@ -288,21 +289,21 @@ void TcbLevel::parseTcbLevelV3(const ::rapidjson::Value &tcbLevel, JsonParser& j parseTcbLevelCommon(tcbLevel, jsonParser); if(!tcbLevel.HasMember("tcb")) { - throw FormatException("TCB level JSON should has [tcb] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should has [tcb] field"); } const ::rapidjson::Value& tcb = tcbLevel["tcb"]; if(!tcb.IsObject()) { - throw FormatException("TCB level JSON [tcb] field should be an object"); + LOG_AND_THROW(FormatException, "TCB level JSON [tcb] field should be an object"); } JsonParser::ParseStatus pceSvnValid = JsonParser::Missing; std::tie(_pceSvn, pceSvnValid) = jsonParser.getUintFieldOf(tcb, "pcesvn"); if(pceSvnValid != JsonParser::OK) { - throw FormatException("Could not parse [pcesvn] field of TCB level JSON to unsigned integer"); + LOG_AND_THROW(FormatException, "Could not parse [pcesvn] field of TCB level JSON to unsigned integer"); } setTcbComponents(tcb); @@ -311,18 +312,18 @@ void TcbLevel::parseTcbLevelV3(const ::rapidjson::Value &tcbLevel, JsonParser& j void TcbLevel::setTcbComponents(const rapidjson::Value &tcb) { if(!tcb.HasMember("sgxtcbcomponents")) { - throw FormatException("TCB level JSON should have [sgxtcbcomponents] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should have [sgxtcbcomponents] field"); } const auto& sgxComponentsArray = tcb["sgxtcbcomponents"]; if(!sgxComponentsArray.IsArray()) { - throw FormatException("TCB level JSON's [sgxtcbcomponents] field should be an array"); + LOG_AND_THROW(FormatException, "TCB level JSON's [sgxtcbcomponents] field should be an array"); } if(sgxComponentsArray.Size() != SGX_TCB_SVN_COMP_COUNT) { - throw FormatException("TCB level [sgxtcbcomponents] array should have " + std::to_string(SGX_TCB_SVN_COMP_COUNT) + " entries"); + LOG_AND_THROW(FormatException, "TCB level [sgxtcbcomponents] array should have " + std::to_string(SGX_TCB_SVN_COMP_COUNT) + " entries"); } _sgxTcbComponents.reserve(SGX_TCB_SVN_COMP_COUNT); _cpuSvnComponents.reserve(SGX_TCB_SVN_COMP_COUNT); @@ -337,16 +338,16 @@ void TcbLevel::setTcbComponents(const rapidjson::Value &tcb) { { if(!tcb.HasMember("tdxtcbcomponents")) { - throw FormatException("TCB level JSON for TDX should have [tdxtcbcomponents] field"); + LOG_AND_THROW(FormatException, "TCB level JSON for TDX should have [tdxtcbcomponents] field"); } const auto& tdxComponentsArray = tcb["tdxtcbcomponents"]; if(!tdxComponentsArray.IsArray()) { - throw FormatException("TCB level JSON's [tdxtcbcomponents] field should be an array"); + LOG_AND_THROW(FormatException, "TCB level JSON's [tdxtcbcomponents] field should be an array"); } if(tdxComponentsArray.Size() != SGX_TCB_SVN_COMP_COUNT) { - throw FormatException("TCB level [tdxtcbcomponents] array should have " + std::to_string(SGX_TCB_SVN_COMP_COUNT) + " entries"); + LOG_AND_THROW(FormatException, "TCB level [tdxtcbcomponents] array should have " + std::to_string(SGX_TCB_SVN_COMP_COUNT) + " entries"); } _tdxTcbComponents.reserve(SGX_TCB_SVN_COMP_COUNT); @@ -381,7 +382,7 @@ void TcbLevel::setCpuSvn(const ::rapidjson::Value& tcb, JsonParser& jsonParser) if(!tcb.IsObject()) { - throw FormatException("[tcb] field of TCB level should be a JSON object"); + LOG_AND_THROW(FormatException, "[tcb] field of TCB level should be a JSON object"); } _cpuSvnComponents.reserve(SGX_TCB_SVN_COMP_COUNT); @@ -394,9 +395,9 @@ void TcbLevel::setCpuSvn(const ::rapidjson::Value& tcb, JsonParser& jsonParser) switch (status) { case JsonParser::ParseStatus::Missing: - throw FormatException("TCB level JSON should has [" + componentName + "] field"); + LOG_AND_THROW(FormatException, "TCB level JSON should has [" + componentName + "] field"); case JsonParser::ParseStatus::Invalid: - throw InvalidExtensionException("Could not parse [" + componentName + "] field of TCB level JSON to unsigned integer"); + LOG_AND_THROW(InvalidExtensionException, "Could not parse [" + componentName + "] field of TCB level JSON to unsigned integer"); case JsonParser::ParseStatus::OK: break; } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TdxModule.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TdxModule.cpp index 790290fa..9baccbfa 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TdxModule.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/Json/TdxModule.cpp @@ -32,6 +32,7 @@ #include "SgxEcdsaAttestation/AttestationParsers.h" #include "JsonParser.h" #include +#include "Utils/Logger.h" namespace intel { namespace sgx { namespace dcap { namespace parser { namespace json { const std::vector &TdxModule::getAttributes() const { @@ -49,7 +50,7 @@ namespace intel { namespace sgx { namespace dcap { namespace parser { namespace TdxModule::TdxModule(const ::rapidjson::Value& tdxModule) { if (!tdxModule.IsObject()) { - throw FormatException("TDX Module should be an object"); + LOG_AND_THROW(FormatException, "TDX Module should be an object"); } JsonParser jsonParser; @@ -58,21 +59,21 @@ namespace intel { namespace sgx { namespace dcap { namespace parser { namespace if (status != JsonParser::OK) { - throw FormatException("TDX Module JSON should have [mrsigner] field and it should be 48 bytes encoded as hexstring"); + LOG_AND_THROW(FormatException, "TDX Module JSON should have [mrsigner] field and it should be 48 bytes encoded as hexstring"); } std::tie(_attributes, status) = jsonParser.getBytesFieldOf(tdxModule, "attributes", 16); if (status != JsonParser::OK) { - throw FormatException("TDX Module JSON should have [attributes] field and it should be 8 bytes encoded as hexstring"); + LOG_AND_THROW(FormatException, "TDX Module JSON should have [attributes] field and it should be 8 bytes encoded as hexstring"); } std::tie(_attributesMask, status) = jsonParser.getBytesFieldOf(tdxModule, "attributesMask", 16); if (status != JsonParser::OK) { - throw FormatException("TDX Module JSON should have [attributesMask] field and it should be 8 bytes encoded as hexstring"); + LOG_AND_THROW(FormatException, "TDX Module JSON should have [attributesMask] field and it should be 8 bytes encoded as hexstring"); } } }}}}} diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/OpensslHelpers/OidUtils.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/OpensslHelpers/OidUtils.cpp index 1a1504cb..d339b7aa 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/OpensslHelpers/OidUtils.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/OpensslHelpers/OidUtils.cpp @@ -33,6 +33,7 @@ #include "SgxEcdsaAttestation/AttestationParsers.h" #include "ParserUtils.h" +#include "Utils/Logger.h" #include @@ -47,7 +48,7 @@ void validateOid(const std::string& oidName, const ASN1_TYPE *oidValue, int expe { std::string err = "OID [" + oidName + "] type expected [" + std::to_string(expectedType) + "] given [" + std::to_string(oidValue->type) + "]"; - throw parser::FormatException(err); + LOG_AND_THROW(parser::FormatException, err); } } @@ -73,14 +74,14 @@ void validateOid(const std::string& oidName, const ASN1_TYPE *oidValue, int expe default: { std::string err = "Unsupported OID [" + oidName + "] type [" + std::to_string(oidValue->type) + "]"; - throw parser::FormatException(err); + LOG_AND_THROW(parser::FormatException, err); } } if (oidValueLen != expectedLength) { std::string err = "OID [" + oidName + "] length expected [" + std::to_string(expectedLength) + "] given [" + std::to_string(oidValueLen) + "]"; - throw parser::FormatException(err); + LOG_AND_THROW(parser::FormatException, err); } } @@ -119,7 +120,8 @@ STACK_OF_ASN1TYPE_uptr oidToStack(const ASN1_TYPE *oidValue) if(!stack) { - throw parser::FormatException("d2i_ASN1_SEQUENCE_ANY failed " + parser::getLastError()); + auto err = "d2i_ASN1_SEQUENCE_ANY failed " + parser::getLastError(); + LOG_AND_THROW(parser::FormatException, err); } return stack; diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.cpp index f1021b42..64fea06b 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.cpp @@ -34,6 +34,7 @@ #include "ParserUtils.h" #include "OpensslHelpers/OpensslTypes.h" #include "Utils/TimeUtils.h" +#include "Utils/Logger.h" #include #include @@ -51,6 +52,7 @@ std::string obj2Str(const ASN1_OBJECT* obj) { if(!obj) { + LOG_WARN("Provided object is NULL"); return ""; } @@ -66,6 +68,7 @@ std::vector bn2Vec(const BIGNUM* bn) { if(!bn) { + LOG_WARN("Provided object is NULL"); return {}; } @@ -86,6 +89,7 @@ std::string x509NameToString(const X509_NAME* name) { if(!name) { + LOG_WARN("Provided object is NULL"); return ""; } @@ -115,6 +119,7 @@ std::string getNameEntry(X509_NAME* name, int nid) if(!name) { + LOG_WARN("Provided object is NULL"); return ""; } @@ -124,6 +129,7 @@ std::string getNameEntry(X509_NAME* name, int nid) if(X509_NAME_entry_count(name) <= 0) { + LOG_WARN("Can't get any entries"); return ""; } @@ -131,6 +137,7 @@ std::string getNameEntry(X509_NAME* name, int nid) if(position == -1) { + LOG_WARN("NID {} can't be found", nid); return ""; } @@ -138,6 +145,7 @@ std::string getNameEntry(X509_NAME* name, int nid) if(!entry) { + LOG_WARN("Entry at position {} can't be found"); return ""; } @@ -145,6 +153,7 @@ std::string getNameEntry(X509_NAME* name, int nid) const int asn1EstimatedStrLen = ASN1_STRING_length(asn1); if(asn1EstimatedStrLen <= 0) { + LOG_WARN("Can't get data for entry"); return ""; } @@ -154,6 +163,7 @@ std::string getNameEntry(X509_NAME* name, int nid) if(len < 0) { + LOG_WARN("Can't convert ASN1_STRING to UTF8"); return ""; } @@ -162,6 +172,7 @@ std::string getNameEntry(X509_NAME* name, int nid) // shouldn't happened, but who knows if(asn1EstimatedStrLen != len) { + LOG_WARN("Estimated string length is not equal to actual length {} != {}", asn1EstimatedStrLen, len); return ""; } @@ -189,7 +200,8 @@ std::time_t asn1TimeToTimet( { // We're here if the format is invalid, thus // validity settings in cert should be considered invalid - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_AND_THROW(FormatException, err); } return resultTime + pday * SECONDS_IN_A_DAY + psec; diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.h b/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.h index 289f0211..0245a828 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.h +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/ParserUtils.h @@ -37,7 +37,7 @@ class Type; #include "SgxEcdsaAttestation/AttestationParsers.h" #include - +#include #include #include #include diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Certificate.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Certificate.cpp index ab1a8450..ee3b4118 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Certificate.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Certificate.cpp @@ -33,6 +33,7 @@ #include "X509Constants.h" #include "ParserUtils.h" +#include "Utils/Logger.h" #include @@ -62,7 +63,8 @@ bool Certificate::operator==(const Certificate& other) const _signature == other._signature && _serialNumber == other._serialNumber && _pubKey == other._pubKey && - _info == other._info; + _info == other._info && + _crlDistributionPoint == other._crlDistributionPoint; } uint32_t Certificate::getVersion() const @@ -115,6 +117,10 @@ const std::string& Certificate::getPem() const return _pem; } +const std::string &Certificate::getCrlDistributionPoint() const { + return _crlDistributionPoint; +} + Certificate Certificate::parse(const std::string& pem) { return Certificate(pem); @@ -130,7 +136,9 @@ Certificate::Certificate(const std::string &pem) auto x509 = crypto::make_unique(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); if (!x509) { - throw FormatException("PEM_read_bio_X509 failed " + getLastError()); + auto err = getLastError(); + LOG_ERROR("Parsing certificate failed: {}, PEM: {}", err, pem); + throw FormatException("PEM_read_bio_X509 failed " + err); } setPublicKey(x509.get()); @@ -142,6 +150,7 @@ Certificate::Certificate(const std::string &pem) setIssuer(x509.get()); setValidity(x509.get()); setExtensions(x509.get()); + setCrlDistributionPoint(x509.get()); } // Private @@ -177,7 +186,9 @@ void Certificate::setSubject(const X509 *x509) if(!subject) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_ERROR("Retrieve SUBJECT from certificate failed: {}", err); + throw FormatException(err); } _subject = DistinguishedName(subject); @@ -190,7 +201,9 @@ void Certificate::setIssuer(const X509 *x509) if(!issuer) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_ERROR("Retrieve ISSUER from certificate failed: {}", err); + throw FormatException(err); } _issuer = DistinguishedName(issuer); @@ -204,13 +217,17 @@ void Certificate::setValidity(const X509 *x509) const ASN1_TIME *notBefore = X509_get0_notBefore(x509); if(!notBefore) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_ERROR("Retrieve NOT BEFORE from certificate failed: {}", err); + throw FormatException(err); } const ASN1_TIME *notAfter = X509_get0_notAfter(x509); if(!notAfter) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_ERROR("Retrieve NOT AFTER from certificate failed: {}", err); + throw FormatException(err); } auto period = asn1TimePeriodToCTime(notBefore, notAfter); @@ -224,7 +241,9 @@ void Certificate::setExtensions(const X509 *x509) if(extsCount < 0) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_ERROR("Error retrieving extensions from certificate {}", err); + throw FormatException(err); } std::vector extensions(static_cast(extsCount)); @@ -252,7 +271,7 @@ void Certificate::setExtensions(const X509 *x509) // Now add the last element with no delimiter err += oids::extension2Description(expectedExtensions.back()) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } _extensions = extensions; @@ -267,12 +286,13 @@ void Certificate::setSignature(const X509 *x509) if(!psig || !palg) { - throw FormatException(getLastError()); + auto err = getLastError(); + LOG_AND_THROW(FormatException, err); } if(psig->length == 0) { - throw FormatException("Signature should not be empty"); + LOG_AND_THROW(FormatException, "Signature should not be empty") } _signature = Signature(psig); @@ -284,7 +304,7 @@ void Certificate::setPublicKey(const X509 *x509) if(asn1PubKey == nullptr) { - throw FormatException("Certificate should not be NULL"); + LOG_AND_THROW(FormatException, "Certificate should not be NULL"); } size_t len = static_cast(asn1PubKey->length); @@ -292,5 +312,18 @@ void Certificate::setPublicKey(const X509 *x509) _pubKey = std::vector(asn1PubKey->data, asn1PubKey->data + len); } +void Certificate::setCrlDistributionPoint(const X509 *x509) +{ + auto idx = X509_get_ext_by_NID(x509, NID_crl_distribution_points, -1); + if (idx == -1) + { + LOG_AND_THROW(InvalidExtensionException, "CRL distribution point extension not found"); + } + + auto ext = Extension(X509_get_ext(x509, idx)); + auto end = std::find(ext.getValue().begin(), ext.getValue().end(), 0); + + _crlDistributionPoint = std::string(ext.getValue().begin(), end); +} }}}}} // namespace intel { namespace sgx { namespace dcap { namespace parser { namespace x509 { diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Configuration.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Configuration.cpp index cd42cce3..b99f3e2c 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Configuration.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Configuration.cpp @@ -35,6 +35,7 @@ #include "ParserUtils.h" #include "OpensslHelpers/OpensslTypes.h" #include "OpensslHelpers/OidUtils.h" +#include "Utils/Logger.h" #include @@ -97,7 +98,7 @@ Configuration::Configuration(const ASN1_TYPE *configurationSeq) { std::string err = "OID tuple [" + oids::CONFIGURATION + "] expected number of elements is [2] given [" + std::to_string(oidTupleEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } const auto oidName = sk_ASN1_TYPE_value(oidTuple.get(), 0); @@ -135,8 +136,7 @@ Configuration::Configuration(const ASN1_TYPE *configurationSeq) // Now add the last element with no delimiter err += oids::type2Description(expectedExtensions.back()) + "]"; - - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException,err); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PckCertificate.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PckCertificate.cpp index 6bdc3d1f..3005e552 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PckCertificate.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PckCertificate.cpp @@ -34,6 +34,7 @@ #include "ParserUtils.h" #include "X509Constants.h" #include "OpensslHelpers/OidUtils.h" +#include "Utils/Logger.h" #include // find_if @@ -103,7 +104,7 @@ stack_st_ASN1_TYPE* PckCertificate::getSgxExtensions() if(sgxExtension == _extensions.end()) { // Certificate has no SGX extensions, probably Root CA or Intermediate CA - throw InvalidExtensionException("Certificate is missing SGX Extensions OID[" + oids::SGX_EXTENSION + "]"); + LOG_AND_THROW(InvalidExtensionException, "Certificate is missing SGX Extensions OID[" + oids::SGX_EXTENSION + "]"); } // WARNING! Using this temporary pointer is mandatory! @@ -113,7 +114,8 @@ stack_st_ASN1_TYPE* PckCertificate::getSgxExtensions() if(!topSequence) { - throw InvalidExtensionException("d2i_ASN1_TYPE cannot parse data to correct type " + getLastError()); + auto err = "d2i_ASN1_TYPE cannot parse data to correct type " + getLastError(); + LOG_AND_THROW(InvalidExtensionException, err); } crypto::validateOid(oids::SGX_EXTENSION, topSequence.get(), V_ASN1_SEQUENCE); @@ -129,7 +131,7 @@ void PckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) std::string err = "OID [" + oids::SGX_EXTENSION + "] expected to contain [" + std::to_string(PROCESSOR_CA_EXTENSION_COUNT) + "] or [" + std::to_string(PLATFORM_CA_EXTENSION_COUNT) + "] elements when given [" + std::to_string(stackEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException,err); } std::vector expectedExtensions = constants::PCK_REQUIRED_SGX_EXTENSIONS; @@ -146,7 +148,7 @@ void PckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) { std::string err = "OID tuple [" + oids::SGX_EXTENSION + "] expected number of elements is [2] given [" + std::to_string(oidTupleEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } const auto oidName = sk_ASN1_TYPE_value(oidTuple.get(), 0); @@ -195,8 +197,7 @@ void PckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) // Now add the last element with no delimiter err += oids::type2Description(expectedExtensions.back()) + "]"; - - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PlatformPckCertificate.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PlatformPckCertificate.cpp index f490eaff..05a4dcf4 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PlatformPckCertificate.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/PlatformPckCertificate.cpp @@ -34,6 +34,7 @@ #include "OpensslHelpers/OidUtils.h" #include "X509Constants.h" #include "ParserUtils.h" +#include "Utils/Logger.h" #include // find_if @@ -85,7 +86,7 @@ void PlatformPckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) { std::string err = "OID [" + oids::SGX_EXTENSION + "] expected to contain [" + std::to_string(PLATFORM_CA_EXTENSION_COUNT) + "] elements when given [" + std::to_string(stackEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } std::vector expectedExtensions = constants::PLATFORM_PCK_REQUIRED_SGX_EXTENSIONS; @@ -100,7 +101,7 @@ void PlatformPckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) if (oidTupleEntries != 2) { std::string err = "OID tuple [" + oids::SGX_EXTENSION + "] expected number of elements is [2] given [" + std::to_string(oidTupleEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } const auto oidName = sk_ASN1_TYPE_value(oidTuple.get(), 0); @@ -133,7 +134,7 @@ void PlatformPckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) // Now add the last element with no delimiter err += oids::type2Description(expectedExtensions.back()) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/ProcessorPckCertificate.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/ProcessorPckCertificate.cpp index 116bc566..a09f4825 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/ProcessorPckCertificate.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/ProcessorPckCertificate.cpp @@ -33,6 +33,7 @@ #include "OpensslHelpers/OidUtils.h" #include "ParserUtils.h" +#include "Utils/Logger.h" namespace intel { namespace sgx { namespace dcap { namespace parser { namespace x509 { @@ -65,7 +66,7 @@ void ProcessorPckCertificate::setMembers(stack_st_ASN1_TYPE *sgxExtensions) { std::string err = "OID [" + oids::SGX_EXTENSION + "] expected to contain [" + std::to_string(PROCESSOR_CA_EXTENSION_COUNT) + "] elements when given [" + std::to_string(stackEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Signature.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Signature.cpp index 61b979b1..0df63a7c 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Signature.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Signature.cpp @@ -34,6 +34,7 @@ #include "OpensslHelpers/OpensslTypes.h" #include "ParserUtils.h" #include "Utils/SafeMemcpy.h" +#include "Utils/Logger.h" #include @@ -85,7 +86,7 @@ Signature::Signature(const ASN1_BIT_STRING* pSig) if(!sig) { - throw FormatException("Invalid certificate signature value: NULL"); + LOG_AND_THROW(FormatException, "Invalid certificate signature value: NULL"); } // internal pointers diff --git a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Tcb.cpp b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Tcb.cpp index bea7c4cb..b1f097eb 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Tcb.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/src/X509/Tcb.cpp @@ -35,6 +35,7 @@ #include "ParserUtils.h" #include "OpensslHelpers/OpensslTypes.h" #include "OpensslHelpers/OidUtils.h" +#include "Utils/Logger.h" #include @@ -91,7 +92,7 @@ Tcb::Tcb(const ASN1_TYPE *tcbSeq) { std::string err = "TCB length expected [" + std::to_string(constants::TCB_SEQUENCE_LEN) + "] given [" + std::to_string(stackEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } std::vector expectedExtensions = constants::TCB_REQUIRED_SGX_EXTENSIONS; @@ -109,7 +110,7 @@ Tcb::Tcb(const ASN1_TYPE *tcbSeq) { std::string err = "OID tuple [" + oids::TCB + "] expected number of elements is [2] given [" + std::to_string(oidTupleEntries) + "]"; - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } const auto oidName = sk_ASN1_TYPE_value(oidTuple.get(), 0); @@ -237,8 +238,7 @@ Tcb::Tcb(const ASN1_TYPE *tcbSeq) // Now add the last element with no delimiter err += oids::type2Description(expectedExtensions.back()) + "]"; - - throw InvalidExtensionException(err); + LOG_AND_THROW(InvalidExtensionException, err); } } diff --git a/QuoteVerification/QVL/Src/AttestationParsers/test/CommonTestUtils/ExtensionTestUtils.cpp b/QuoteVerification/QVL/Src/AttestationParsers/test/CommonTestUtils/ExtensionTestUtils.cpp index a34b84a4..9b8905b1 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/test/CommonTestUtils/ExtensionTestUtils.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/test/CommonTestUtils/ExtensionTestUtils.cpp @@ -30,6 +30,7 @@ */ #include "ExtensionTestUtils.h" +#include namespace intel { namespace sgx { namespace dcap { namespace parser { diff --git a/QuoteVerification/QVL/Src/AttestationParsers/test/UnitTests/CertificateUT.cpp b/QuoteVerification/QVL/Src/AttestationParsers/test/UnitTests/CertificateUT.cpp index 62f8e0b8..18b251ba 100644 --- a/QuoteVerification/QVL/Src/AttestationParsers/test/UnitTests/CertificateUT.cpp +++ b/QuoteVerification/QVL/Src/AttestationParsers/test/UnitTests/CertificateUT.cpp @@ -107,6 +107,7 @@ TEST_F(CertificateUT, certificateConstructors) ASSERT_EQ(copyCertificate.getPubKey(), certificate.getPubKey()); ASSERT_EQ(copyCertificate.getInfo(), certificate.getInfo()); ASSERT_EQ(copyCertificate.getPem(), certificate.getPem()); + ASSERT_EQ(copyCertificate.getCrlDistributionPoint(), certificate.getCrlDistributionPoint()); } TEST_F(CertificateUT, certificateGetters) @@ -132,6 +133,8 @@ TEST_F(CertificateUT, certificateGetters) const std::vector expectedExtensions = constants::REQUIRED_X509_EXTENSIONS; ASSERT_THAT(certificate.getExtensions().size(), expectedExtensions.size()); + ASSERT_EQ(certificate.getCrlDistributionPoint(), + "Full Name: URI:https://certificates.trustedservices.intel.com/IntelSGXRootCA.crl"); free(pubKey); } diff --git a/QuoteVerification/QVL/Src/CMakeLists.txt b/QuoteVerification/QVL/Src/CMakeLists.txt index 8656ee0b..abcf8ba5 100644 --- a/QuoteVerification/QVL/Src/CMakeLists.txt +++ b/QuoteVerification/QVL/Src/CMakeLists.txt @@ -31,7 +31,7 @@ option(BUILD_ATTESTATION_APP "Build AttestationApp" ON) option(BUILD_TESTS "Build tests for all components" ON) option(BUILD_DOCS "Build doxygen based documentation" OFF) option(BUILD_ENCLAVE "Build test sgx enclave and sample app that uses it" OFF) - +option(BUILD_LOGS "Build library with logging support" OFF) ######### QVL Enclave related settings ################################################################################# if(BUILD_ENCLAVE) @@ -147,19 +147,24 @@ set(CMAKE_TRY_COMPILE_TARGET_TYPE "STATIC_LIBRARY") set(HUNTER_STATUS_DEBUG OFF) #set to 'ON' to see debug hunter logs #set(HUNTER_ROOT ${CMAKE_SOURCE_DIR}/ThirdParty/hunter) # in case of Hunter build problems #set(HUNTER_BINARY_DIR ${CMAKE_SOURCE_DIR}/ThirdParty/hunter) # in case of Hunter build problems +#If windows build fails, consider changing HUNTER_ROOT dir to shorter one, by either modifying it's value or commenting out the line below: +set(HUNTER_ROOT "${CMAKE_SOURCE_DIR}/ThirdParty/hunter" CACHE STRING "hunter root" FORCE) option(HUNTER_NO_TOOLCHAIN_ID_RECALCULATION "No Toolchain-ID recalculation" ON) option(HUNTER_KEEP_PACKAGE_SOURCES "Keep third party sources" ON) include("${CMAKE_CURRENT_LIST_DIR}/ThirdParty/hunter/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.316.tar.gz" - SHA1 "167a33c4cc398504826a1a3014ef66f9ccc35db3" + URL "https://github.com/cpp-pm/hunter/archive/v0.24.8.tar.gz" + SHA1 "ca7838dded9a1811b04ffd56175f629e0af82d3d" FILEPATH "${CMAKE_SOURCE_DIR}/ThirdParty/hunter/config.cmake" ) project(SgxEcdsaAttestation) +message("HUNTER_GATE_ROOT: ${HUNTER_GATE_ROOT}") +file(COPY "${CMAKE_SOURCE_DIR}/ThirdParty/hunter/openssl/hunter.cmake" DESTINATION "${HUNTER_GATE_ROOT}/_Base/Download/Hunter/0.24.8/ca7838d/Unpacked/cmake/projects/OpenSSL/") + set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_THREAD_LIBS_INIT "-lpthread") # Could NOT find Threads (missing: Threads_FOUND) related workarounds set(CMAKE_HAVE_THREADS_LIBRARY 1) diff --git a/QuoteVerification/QVL/Src/ThirdParty/hunter/HunterGate.cmake b/QuoteVerification/QVL/Src/ThirdParty/hunter/HunterGate.cmake index d64ed8e2..f117ca09 100644 --- a/QuoteVerification/QVL/Src/ThirdParty/hunter/HunterGate.cmake +++ b/QuoteVerification/QVL/Src/ThirdParty/hunter/HunterGate.cmake @@ -73,9 +73,9 @@ option(HUNTER_ENABLED "Enable Hunter package manager support" ON) if(HUNTER_ENABLED) if(CMAKE_VERSION VERSION_LESS "3.2") message( - FATAL_ERROR - "At least CMake version 3.2 required for Hunter dependency management." - " Update CMake or set HUNTER_ENABLED to OFF." + FATAL_ERROR + "At least CMake version 3.2 required for Hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF." ) endif() endif() @@ -85,8 +85,9 @@ include(CMakeParseArguments) # cmake_parse_arguments option(HUNTER_STATUS_PRINT "Print working status" ON) option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) +set(HUNTER_ROOT "" CACHE FILEPATH "Override the HUNTER_ROOT.") -set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors") +set(HUNTER_ERROR_PAGE "https://hunter.readthedocs.io/en/latest/reference/errors") function(hunter_gate_status_print) if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) @@ -163,8 +164,8 @@ function(hunter_gate_self root version sha1 result) set(hunter_self "${root}") else() set( - hunter_self - "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" ) endif() @@ -174,24 +175,21 @@ endfunction() # Set HUNTER_GATE_ROOT cmake variable to suitable value. function(hunter_gate_detect_root) # Check CMake variable - string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) - if(not_empty) + if(HUNTER_ROOT) set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") return() endif() # Check environment variable - string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) - if(not_empty) + if(DEFINED ENV{HUNTER_ROOT}) set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") return() endif() # Check HOME environment variable - string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) - if(result) + if(DEFINED ENV{HOME}) set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") return() @@ -199,47 +197,45 @@ function(hunter_gate_detect_root) # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) if(WIN32) - string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) - if(result) + if(DEFINED ENV{SYSTEMDRIVE}) set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( - "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" ) return() endif() - string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) - if(result) + if(DEFINED ENV{USERPROFILE}) set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) hunter_gate_status_debug( - "HUNTER_ROOT set using USERPROFILE environment variable" + "HUNTER_ROOT set using USERPROFILE environment variable" ) return() endif() endif() hunter_gate_fatal_error( - "Can't detect HUNTER_ROOT" - ERROR_PAGE "error.detect.hunter.root" + "Can't detect HUNTER_ROOT" + ERROR_PAGE "error.detect.hunter.root" ) endfunction() function(hunter_gate_download dir) string( - COMPARE - NOTEQUAL - "$ENV{HUNTER_DISABLE_AUTOINSTALL}" - "" - disable_autoinstall + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall ) if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) hunter_gate_fatal_error( - "Hunter not found in '${dir}'" - "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" - "Settings:" - " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" - " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" - ERROR_PAGE "error.run.install" + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + ERROR_PAGE "error.run.install" ) endif() string(COMPARE EQUAL "${dir}" "" is_bad) @@ -281,30 +277,30 @@ function(hunter_gate_download dir) # Disabling languages speeds up a little bit, reduces noise in the output # and avoids path too long windows error file( - WRITE - "${cmakelists}" - "cmake_minimum_required(VERSION 3.2)\n" - "project(HunterDownload LANGUAGES NONE)\n" - "include(ExternalProject)\n" - "ExternalProject_Add(\n" - " Hunter\n" - " URL\n" - " \"${HUNTER_GATE_URL}\"\n" - " URL_HASH\n" - " SHA1=${HUNTER_GATE_SHA1}\n" - " DOWNLOAD_DIR\n" - " \"${dir}\"\n" - " TLS_VERIFY\n" - " ${HUNTER_TLS_VERIFY}\n" - " SOURCE_DIR\n" - " \"${dir}/Unpacked\"\n" - " CONFIGURE_COMMAND\n" - " \"\"\n" - " BUILD_COMMAND\n" - " \"\"\n" - " INSTALL_COMMAND\n" - " \"\"\n" - ")\n" + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.2)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ${HUNTER_TLS_VERIFY}\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" ) if(HUNTER_STATUS_DEBUG) @@ -335,36 +331,36 @@ function(hunter_gate_download dir) endif() execute_process( - COMMAND - "${CMAKE_COMMAND}" - "-H${dir}" - "-B${build_dir}" - "-G${CMAKE_GENERATOR}" - "${toolchain_arg}" - ${make_arg} - WORKING_DIRECTORY "${dir}" - RESULT_VARIABLE download_result - ${logging_params} + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} ) if(NOT download_result EQUAL 0) hunter_gate_internal_error( - "Configure project failed." - "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" - "In directory ${dir}" + "Configure project failed." + "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" + "In directory ${dir}" ) endif() hunter_gate_status_print( - "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" - " ${HUNTER_GATE_URL}" - " -> ${dir}" + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" ) execute_process( - COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" - WORKING_DIRECTORY "${dir}" - RESULT_VARIABLE download_result - ${logging_params} + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} ) if(NOT download_result EQUAL 0) @@ -399,22 +395,22 @@ macro(HunterGate) endfunction() set( - _hunter_gate_disabled_mode_dir - "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" ) if(EXISTS "${_hunter_gate_disabled_mode_dir}") hunter_gate_status_debug( - "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" ) list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") endif() elseif(_hunter_gate_done) hunter_gate_status_debug("Secondary HunterGate (use old settings)") hunter_gate_self( - "${HUNTER_CACHED_ROOT}" - "${HUNTER_VERSION}" - "${HUNTER_SHA1}" - _hunter_self + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self ) include("${_hunter_self}/cmake/Hunter") else() @@ -423,31 +419,31 @@ macro(HunterGate) string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) if(_have_project_name) hunter_gate_fatal_error( - "Please set HunterGate *before* 'project' command. " - "Detected project: ${PROJECT_NAME}" - ERROR_PAGE "error.huntergate.before.project" + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + ERROR_PAGE "error.huntergate.before.project" ) endif() cmake_parse_arguments( - HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} ) string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) string( - COMPARE - NOTEQUAL - "${HUNTER_GATE_UNPARSED_ARGUMENTS}" - "" - _have_unparsed + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed ) string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) if(_have_unparsed) hunter_gate_user_error( - "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" ) endif() if(_empty_sha1) @@ -485,27 +481,27 @@ macro(HunterGate) # Beautify path, fix probable problems with windows path slashes get_filename_component( - HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE ) hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") if(NOT HUNTER_ALLOW_SPACES_IN_PATH) string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) if(NOT _contain_spaces EQUAL -1) hunter_gate_fatal_error( - "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." - "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" - "(Use at your own risk!)" - ERROR_PAGE "error.spaces.in.hunter.root" + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + ERROR_PAGE "error.spaces.in.hunter.root" ) endif() endif() string( - REGEX - MATCH - "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" - HUNTER_GATE_VERSION - "${HUNTER_GATE_URL}" + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" ) string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) if(_is_empty) @@ -513,10 +509,10 @@ macro(HunterGate) endif() hunter_gate_self( - "${HUNTER_GATE_ROOT}" - "${HUNTER_GATE_VERSION}" - "${HUNTER_GATE_SHA1}" - _hunter_self + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self ) set(_master_location "${_hunter_self}/cmake/Hunter") @@ -543,19 +539,21 @@ macro(HunterGate) hunter_gate_internal_error("${_sha1_location} not found") endif() file(READ "${_sha1_location}" _sha1_value) - string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + string(TOLOWER "${_sha1_value}" _sha1_value_lower) + string(TOLOWER "${HUNTER_GATE_SHA1}" _HUNTER_GATE_SHA1_lower) + string(COMPARE EQUAL "${_sha1_value_lower}" "${_HUNTER_GATE_SHA1_lower}" _is_equal) if(NOT _is_equal) hunter_gate_internal_error( - "Short SHA1 collision:" - " ${_sha1_value} (from ${_sha1_location})" - " ${HUNTER_GATE_SHA1} (HunterGate)" + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" ) endif() if(NOT EXISTS "${_master_location}") hunter_gate_user_error( - "Master file not found:" - " ${_master_location}" - "try to update Hunter/HunterGate" + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" ) endif() endif() diff --git a/QuoteVerification/QVL/Src/ThirdParty/hunter/config.cmake b/QuoteVerification/QVL/Src/ThirdParty/hunter/config.cmake index 445027a6..33466539 100644 --- a/QuoteVerification/QVL/Src/ThirdParty/hunter/config.cmake +++ b/QuoteVerification/QVL/Src/ThirdParty/hunter/config.cmake @@ -24,7 +24,16 @@ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -hunter_config(OpenSSL VERSION "1.1.1j") +hunter_config(OpenSSL VERSION "1.1.1t") hunter_config(GTest VERSION "1.11.0" CMAKE_ARGS CMAKE_POSITION_INDEPENDENT_CODE=TRUE) + +hunter_config(spdlog + VERSION "1.9.2-p0" + CMAKE_ARGS CMAKE_POSITION_INDEPENDENT_CODE=TRUE) + +# This is dependency of spdlog +hunter_config(fmt + VERSION "8.1.1" + CMAKE_ARGS CMAKE_POSITION_INDEPENDENT_CODE=TRUE) diff --git a/QuoteVerification/QVL/ThirdPartyLicenses.txt b/QuoteVerification/QVL/ThirdPartyLicenses.txt index d358f0e5..4d037e3c 100644 --- a/QuoteVerification/QVL/ThirdPartyLicenses.txt +++ b/QuoteVerification/QVL/ThirdPartyLicenses.txt @@ -335,3 +335,31 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Spdlog license: +The MIT License (MIT) + +Copyright (c) 2016 Gabi Melman. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +-- NOTE: Third party dependency used by this software -- +This software depends on the fmt lib (MIT License), +and users must comply to its license: https://github.com/fmtlib/fmt/blob/master/LICENSE.rst + diff --git a/QuoteVerification/QuoteVerificationService/.dockerignore b/QuoteVerification/QuoteVerificationService/.dockerignore new file mode 100644 index 00000000..43313a4a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/.dockerignore @@ -0,0 +1,13 @@ +# 1. Ignore everything +** + +# 2. Add files and directories that should be included +!configuration-default/** +!native/QuoteVerificationLibraryWrapper.node +!native/lib/* +!src/** +!Dockerfile + +# 3. Exclude QVL Node plugin sources +src/qvl/* +!src/qvl/*.js \ No newline at end of file diff --git a/QuoteVerification/QuoteVerificationService/.gitignore b/QuoteVerification/QuoteVerificationService/.gitignore new file mode 100644 index 00000000..31343983 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/.gitignore @@ -0,0 +1,5 @@ +native +src/qvl/cmake-* +src/node_modules +test/node_modules +test/.nyc_output \ No newline at end of file diff --git a/QuoteVerification/QuoteVerificationService/Dockerfile b/QuoteVerification/QuoteVerificationService/Dockerfile new file mode 100644 index 00000000..54d64da1 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/Dockerfile @@ -0,0 +1,52 @@ +# +# Copyright (c) 2022, Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +FROM node:lts-slim + +LABEL description="Quote Verification Service" + +# Remove Node package managers and its dependencies and clear apt cache +RUN rm -rf /usr/local/lib/node_modules/ \ + && rm -rf /usr/local/bin/npm \ + && rm -rf /usr/local/bin/npx \ + && rm -rf /opt \ + && rm -rf /var/cache/apt/archives + +# Update the OS and install required dependencies +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get upgrade --assume-yes -o Dpkg::Options::="--force-confold" && \ + DEBIAN_FRONTEND=noninteractive apt-get install --assume-yes --no-install-recommends ca-certificates openssl && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Add QVS +ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/QVS/native/lib \ + NODE_ENV=production +COPY --chown=node:node . /QVS +USER node +ENTRYPOINT ["nodejs", "/QVS/src/bootstrap.js"] \ No newline at end of file diff --git a/QuoteVerification/QuoteVerificationService/README.md b/QuoteVerification/QuoteVerificationService/README.md new file mode 100644 index 00000000..040b94a0 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/README.md @@ -0,0 +1,170 @@ +# IntelĀ® Software Guard Extensions and IntelĀ® Trust Domain Extensions Data Center Attestation Primitives (IntelĀ® SGX and IntelĀ® TDX DCAP) Quote Verification Service + +## Introduction + +Quote Verification Service is a stateless server endpoint implementation that verifies attestation evidence (quote) of ISV (Independent Software Vendor) enclaves. +It can be used as a part of [SGX attestation](https://www.intel.com/content/www/us/en/developer/tools/software-guard-extensions/attestation-services.html) process. + +Service checks, that provided evidence comes from a genuine, non-revoked SGX/TDX capable platform on given TCB level and generates appriopriate JSON report. Based on that report and by verifying additional evidences (like: MRSIGNER, MRENCLAVE) client can decide whether to trust this enclave or not. + + +## Service architecture overview + +![QVS Diagram](diagram.png?raw=true "QVS Diagram") + +ISV Quote Verification Backend potential structure is presented on the diagram above. + +Quote Verification Service utilizes [QVL](../QVL) to perform it's business logic. It also communicates with Verification Collateral Distribution Services (PCS and CRL Distribution List) to obtain latest collaterals. When verification is done, report is generated and sent to a crypto provider - Verification Crypto Service (VCS) in order to be signed. + +VCS is a service capable of storing (Secure Key Storage) and using cryptographic keys in a secure manner (for example, by utilizing Hardware Secure Module). It has to be provided and protected by ISV, as a part of it's Quote Verification Backend. +Sample VCS implementation called [Simple Signing Service (SSS)](samples/simple-signing-service/README.md) has been created for demo purposes only - please do NOT use it in production environment. + +## System requirements +QVS is purely software component, and it doesn't require SGX capable platform to work. Although it can be additionally protected by running inside an enclave (for example by using [Gramine](https://gramineproject.io/)). +Following instructions were tested using Linux (Ubuntu) distribution. + +## Prerequisites for Linux + + - [Node.js](https://nodejs.org/en/) (tested with version 16.13.1) with `npm` and `cmake-js` addon + - [Docker](https://www.docker.com/) (tested with version 20.10.11) + - install prerequisites from [QVL](../QVL) + +#### Install software dependencies +For Ubuntu 18.04, the following command can be used to install all necessary software dependencies: +- Node.js + - ```$ wget https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-x64.tar.gz``` + - ```$ sudo tar -C /usr/local --strip-components 1 -xzf node-v16.13.1-linux-x64.tar.gz``` +- cmake-js + - ```$ sudo npm install -g cmake-js``` (assuming Node.js is already installed) +- Docker + - ```$ curl -fsSL https://get.docker.com -o get-docker.sh``` + - ```$ sudo sh ./get-docker.sh``` + +## Building on Linux +The build was tested on Ubuntu 18.04 and 20.04. +Here, we assume that the [build prerequisites](#prerequisites-for-linux) are fulfilled. + +Execute ```./build.sh```. This script will build QVL, QVS Service and finally will create Docker Image with service installed in it. + +Script will build: +- QVL +- download and install node modules +- copy native libs into service structure +- produce docker image ```qvs:latest``` + + +## Setting up local environment + +QVS service can be either [run locally](#running-locally) or [inside a docker container](#running-docker-image). +To set up local environment, a working instance of Verification Crypto Service (VCS) is required. + +For non-production/development purpose use mock VCS located in [samples/simple-signing-service](./samples/simple-signing-service/README.md). +It is recommended to write your own Verification Crypto Service that will be able to securely protect Signing Key for attestation evidence. + +By default QVS is using MTLS to communicate with VCS, it is recommended to keep [QVS_VCS_CLIENT_TLS_CLIENT_TYPE](#service-configuration) set to MTLS. + +In order to set up local environment one must generate required key pairs for MTLS. Commands in README files linked in next sections will show how to generate self-signed certificates for non-production environment. It is highly recommended not to use such self-signed certificates in production and replace them with certificates signed by widely trusted certification authority. + +### Set-up Simple-Signing-Service: +Go to [samples/simple-signing-service/README.md](samples/simple-signing-service/README.md). Simple-signing-service will provide output with correctly formatted certifcate (see mock-service log and search for: SIGNING_KEY_CERTIFCATE_URL_ENCODED) from Signing Key that needs to be included as env variable QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE to your Quote Verification Service. + +### Configure QVS (MTLS) +Go to [configuration-default/certificates/README.md](configuration-default/certificates/README.md) - (Some of the steps should be already done as a part of SSS instruction) +## Running locally + +```bash +QVS_VCS_CLIENT_HOST=localhost QVS_VCS_CLIENT_PORT=8797 QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE=SIGNING_KEY_CERTIFCATE_URL_ENCODED NODE_ENV=production node src/bootstrap.js +``` +## Running docker image + +```bash +docker run --network host --env QVS_VCS_CLIENT_HOST=localhost --env QVS_VCS_CLIENT_PORT=8797 --env QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE=SIGNING_KEY_CERTIFCATE_URL_ENCODED -it qvs:latest +``` + +## Healtcheck +```bash +curl --cacert ./configuration-default/certificates/qvs-cert.pem https://localhost:8799/health +``` + +## Service configuration + +### General Service Configuration +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_SERVICE_COMPONENT_NAME | QuoteVerificationService | | +| QVS_SERVICE_COMPONENT_SHORT | QVS | | +| QVS_SERVICE_PORT | 8799 | | +| QVS_SERVICE_CERT_FILE | | | +| QVS_SERVICE_KEY_FILE | | | +| QVS_SERVICE_TLS_SERVER_TYPE | TLS | None/TLS | +| QVS_CA_CERT_DIRECTORIES | 'certificates/internal_ca/' | Used in case QVS_SERVICE_TLS_SERVER_TYPE is MTLS. Should point to directories (seperated by `,` `;` or ` `) containing CA certificates (or subdirectories with certificates). Overrides the default trusted CA certificates. | +| QVS_SERVICE_BODY_SIZE_LIMITS | '{"json":"256kb"}' | | + +### Logger +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_LOGGER_CATEGORY | QuoteVerificationService | Name of the service that will be displayed in log before file name | +| QVS_LOGGER_LEVEL_FILE | off | options: off/on, configure also QVS_LOGGER_FILE_NAME | +| QVS_LOGGER_FILE_NAME | | value: specify filepath, then you have to mount a directory with r/w rights | +| QVS_LOGGER_LEVEL_CONSOLE | info | info/debug/trace | +| QVS_LOGGER_MULTILINE_ERROR_LOG | true | options: true/false | + +### Service healthcheck +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_HEALTH_CHECK_INTERVAL_MS | 60000 | Time period between checking health of component dependencies (positive status is cached for QVS_HEALTH_CHECK_FRESHNESS_MS) | +| QVS_HEALTH_CHECK_FRESHNESS_MS | 60000 | Health cache expiration time (in milliseconds) | + +### Verification Crypto Service Client +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_VCS_CLIENT_HOST | | VCS address | +| QVS_VCS_CLIENT_PORT | 0 | | +| QVS_VCS_CLIENT_RETRIES | | | +| QVS_VCS_CLIENT_INITIAL_INTERVAL | 100 | | +| QVS_VCS_CLIENT_INTERVAL_FACTOR | 1 | multiplier of wait period between retries | +| QVS_VCS_CLIENT_CERT_FILE | | Configure if QVS_VCS_CLIENT_TLS_CLIENT_TYPE=MTLS | +| QVS_VCS_CLIENT_KEY_FILE | | Configure if QVS_VCS_CLIENT_TLS_CLIENT_TYPE=MTLS | +| QVS_VCS_CLIENT_CA_CERT_DIRECTORIES | | Should point to directories (seperated by `,` `;` or ` `) containing CA certificates (or subdirectories with certificates). Overrides the default trusted CA certificates. | +| QVS_VCS_CLIENT_TLS_CLIENT_TYPE | MTLS | options: None/TLS/MTLS, recommendation is to use MTLS | +| QVS_VCS_CLIENT_PROXY | | Configure proxy for VCS client | +| QVS_VCS_CLIENT_SERVERNAME | | CN of the certificate expected in MTLS mode | + + +### IntelĀ® Provisioning Certification Service Client (PCS) +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_PCS_CLIENT_HOST | api.trustedservices.intel.com | | +| QVS_PCS_CLIENT_PORT | 443 | | +| QVS_PCS_CLIENT_RETRIES | 1 | | +| QVS_PCS_CLIENT_INITIAL_INTERVAL | 100 | | +| QVS_PCS_CLIENT_INTERVAL_FACTOR | 1 | | +| QVS_PCS_CLIENT_CA_CERT_DIRECTORIES | 'certificates/internal_ca/' | Should point to directories (seperated by `,` `;` or ` `) containing CA certificates (or subdirectories with certificates). Overrides the default trusted CA certificates. | +| QVS_PCS_CLIENT_TLS_CLIENT_TYPE | TLS | options: None/TLS/MTLS, notice: IntelĀ® Provisioning Certification Service will work only with TLS | +| QVS_PCS_CLIENT_PROXY | | Configure proxy for PCS client | +| QVS_PCS_CLIENT_SERVERNAME | | CN of the certificate expected in MTLS mode | + +### CRL Client +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_CRL_CLIENT_RETRIES | 2 | | +| QVS_CRL_CLIENT_INITIAL_INTERVAL | 100 | | +| QVS_CRL_CLIENT_INTERVAL_FACTOR | 3 | | +| QVS_CRL_CLIENT_PROXY | | Configure proxy for PCS client | + +### Cache for CRL and PCS clients +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_CACHE_CRL_TTL | 0 | 0 is unlimited / until next restart | +| QVS_CACHE_CRL_CHECK_PERIOD | 600 | | +| QVS_CACHE_CRL_MAX_KEYS | 100 | -1 is unlimited, 0 turns off caching | +| QVS_CACHE_PCS_TTL | 0 | 0 is unlimited / until next restart | +| QVS_CACHE_PCS_CHECK_PERIOD | 600 | | +| QVS_CACHE_PCS_MAX_KEYS | 100 | -1 is unlimited, 0 turns off caching | + +### Attestation Report and quote verifying +| ENV | Default Value | Additional Description | +| ------------ | ------------ | ------------ | +| QVS_ATTESTATION_REPORT_SIGNING_CA_CERTIFICATE | | URL encoded CA certificate (in PEM format) of QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE. As part of chain it will be returned in `X-IASReport-Signing-Certificate` header to enable verifying report. | +| QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE | | URL encoded certificate (in PEM format) that Verification Crypto Service uses to sign reports. Also returned as part of chain in `X-IASReport-Signing-Certificate` header. | +| QVS_TRUSTED_ROOT_PUBLIC_KEY | 3059301306072a8648ce3d020106082a8648ce3d030107034200040ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394 | Public key of CA certificate that is root for PCK certificate chain | diff --git a/QuoteVerification/QuoteVerificationService/build.sh b/QuoteVerification/QuoteVerificationService/build.sh new file mode 100755 index 00000000..0b10040f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/build.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# +# Copyright (c) 2022, Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +function fail { + printf '%s\n' "$1" >&2 ## Send message to stderr. + exit "${2-1}" ## Return a code specified by $2, or 1 by default. +} + +# Get absolute path to the script itself +SCRIPT_DIR="$(cd "$(dirname "$0")" || exit 1; pwd)" + +# Check if QVL path has been provided +if [ -z "$1" ] + then + QVL_PATH="$(cd "$(dirname "$SCRIPT_DIR"/../QVL/Src)" || exit 2; pwd)/Src" +else + QVL_PATH="$(cd "$(dirname "$1")" || exit 2; pwd)/$(basename "$1")" +fi + +echo "QVL_PATH=$QVL_PATH" +cd "$QVL_PATH" || fail "Failed to access QVL path" 2 + +# Check if QVL_PATH contains absolute path +case $QVL_PATH in + /*) ;; + *) fail "Absolute path to QVL sources should be provided" 3 ;; +esac + +# Build QVL +buildQvl() { + (cd "$QVL_PATH" && ./runUT) +} + +if ! buildQvl "$@"; then + fail "Error when building QVL" 4 +fi + +# Build QVS +buildQvs() { + (cd "$SCRIPT_DIR/src" && npm config set cmake_QVL_PATH="$QVL_PATH/Build/Release/dist" && npm install) +} + +if ! buildQvs "$@"; then + fail "Error when building QVS" 5 +fi + +# Copy built native libs +copyNativeLibs() { + mkdir -p "$SCRIPT_DIR"/native/lib && + cp "$QVL_PATH"/Build/Release/dist/lib/*.so "$SCRIPT_DIR"/native/lib/ && + cp "$SCRIPT_DIR"/src/qvl/cmake-build-release/Release/*.node "$SCRIPT_DIR"/native/ +} + +if ! copyNativeLibs "$@"; then + fail "Error when copying native files" 6 +fi + +# Build Docker Image +function buildDocker() { + docker build "$SCRIPT_DIR" -t qvs +} + +if ! buildDocker; then + fail "Error when building Docker image" 7 +fi + +echo "Build - Done" diff --git a/QuoteVerification/QuoteVerificationService/configuration-default/certificates/README.md b/QuoteVerification/QuoteVerificationService/configuration-default/certificates/README.md new file mode 100644 index 00000000..59a78db8 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/configuration-default/certificates/README.md @@ -0,0 +1,68 @@ +# Quote Verification Service certificates configuration +This guide describes how to create certificates required by the service. + +## Prerequisite + + - Install [OpenSSL](https://www.openssl.org/ "OpenSSL") (tested with version 1.1.0g) + - SSS MTLS client certificate has been [created](../../samples/simple-signing-service/README.md#configure-key-pairs). + +## Configure Key Pairs +Letā€™s create a key and self-signed cert for HTTPS enabling: + +``` +openssl genrsa -out qvs-key.pem +openssl req -new -key qvs-key.pem -out csr.pem +openssl x509 -req -days 9999 -in csr.pem -signkey qvs-key.pem -out qvs-cert.pem +rm csr.pem +``` + +Copy or mount created files to Docker container and provide paths in the following environment variables: + +* QVS_SERVICE_CERT_FILE +* QVS_SERVICE_KEY_FILE + +Then set QVS_SERVICE_TLS_SERVER_TYPE environment variable to TLS to enable HTTPS. + +### Configure MTLS with SSS + +Generate a second pair for MTLS connection between components: + +``` +openssl genrsa -out qvs-to-sss-client-key.pem +openssl req -new -key qvs-to-sss-client-key.pem -out csr.pem +openssl x509 -req -days 9999 -in csr.pem -signkey qvs-to-sss-client-key.pem -out qvs-to-sss-client-cert.pem +rm csr.pem +``` + + +Make sure SSS(VCS) certificate is trusted for QVS: +``` +cp ../../samples/simple-signing-service/cert.pem ./internal-ca/ +``` +Make sure QVS certificate is trusted for SSS: +``` +cp qvs-to-sss-client-cert.pem ../../samples/simple-signing-service/ +``` + +Make sure [../config.yml](../config.yml) contains certificate paths and client server name: +``` +certFile: '${QVS_SERVICE_CERT_FILE:certificates/qvs-cert.pem}' +keyFile: '${QVS_SERVICE_KEY_FILE:certificates/qvs-key.pem}' +tlsServerType: '${QVS_SERVICE_TLS_SERVER_TYPE:TLS}' +... +host: '${QVS_VCS_CLIENT_HOST:localhost}' +port: '${QVS_VCS_CLIENT_PORT:8797}' +certFile: '${QVS_VCS_CLIENT_CERT_FILE:certificates/qvs-to-sss-client-cert.pem}' +keyFile: '${QVS_VCS_CLIENT_KEY_FILE:certificates/qvs-to-sss-client-key.pem}' +servername: '${QVS_VCS_CLIENT_SERVERNAME:}' +``` + +Copy or mount created files to Docker container and provide paths in the following environment variables: + +* QVS_VCS_CLIENT_CERT_FILE +* QVS_VCS_CLIENT_KEY_FILE + +Provide host and port of VCS (e.g. simple-signing-service) in the following environment variables: + +* QVS_VCS_CLIENT_HOST +* QVS_VCS_CLIENT_PORT diff --git a/QuoteVerification/QuoteVerificationService/configuration-default/certificates/internal_ca/README.md b/QuoteVerification/QuoteVerificationService/configuration-default/certificates/internal_ca/README.md new file mode 100644 index 00000000..5fc6e8af --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/configuration-default/certificates/internal_ca/README.md @@ -0,0 +1 @@ +Your custom trusted CA certificates should be here \ No newline at end of file diff --git a/QuoteVerification/QuoteVerificationService/configuration-default/config.yml b/QuoteVerification/QuoteVerificationService/configuration-default/config.yml new file mode 100644 index 00000000..2c9a7618 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/configuration-default/config.yml @@ -0,0 +1,93 @@ +# +# Copyright (c) 2022, Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +service: + componentName: '${QVS_SERVICE_COMPONENT_NAME:QuoteVerificationService}' + componentShort: '${QVS_SERVICE_COMPONENT_SHORT:QVS}' + port: '${QVS_SERVICE_PORT:8799}' + certFile: '${QVS_SERVICE_CERT_FILE:}' + keyFile: '${QVS_SERVICE_KEY_FILE:}' + tlsServerType: '${QVS_SERVICE_TLS_SERVER_TYPE:TLS}' + caCertDirectories: '${QVS_CA_CERT_DIRECTORIES:certificates/internal_ca/}' + bodySizeLimits: '${QVS_SERVICE_BODY_SIZE_LIMITS:{"json":"256kb"}}' + restClientTimeout: '${QVS_SERVICE_REST_CLIENT_TIMEOUT:5000}' + +logger: + # Valid log levels: trace, debug, info, warn, error, fatal, off, UPPERCASE is not valid! + # global_log_level_js_file is used by the ansible to determine if any logs file will be created and in case of the "off" + # volumen /logs will not be mounted ... + category: '${QVS_LOGGER_CATEGORY:QuoteVerificationService}' + levelFile: '${QVS_LOGGER_LEVEL_FILE:off}' # You have to mount a directory with r/w rights + levelConsole: '${QVS_LOGGER_LEVEL_CONSOLE:info}' + fileName: '${QVS_LOGGER_FILE_NAME:}' # You have to mount a directory with r/w rights + multilineErrorLog: '${QVS_LOGGER_MULTILINE_ERROR_LOG:true}' + +healthCheck: + intervalMs: '${QVS_HEALTH_CHECK_INTERVAL_MS:60000}' + freshnessMs: '${QVS_HEALTH_CHECK_FRESHNESS_MS:60000}' + +vcsClient: + host: '${QVS_VCS_CLIENT_HOST:}' + port: '${QVS_VCS_CLIENT_PORT:0}' + retries: '${QVS_VCS_CLIENT_RETRIES:1}' + initialInterval: '${QVS_VCS_CLIENT_INITIAL_INTERVAL:100}' + factor: '${QVS_VCS_CLIENT_INTERVAL_FACTOR:1}' + certFile: '${QVS_VCS_CLIENT_CERT_FILE:}' + keyFile: '${QVS_VCS_CLIENT_KEY_FILE:}' + caCertDirectories: '${QVS_VCS_CLIENT_CA_CERT_DIRECTORIES:certificates/internal_ca/}' + tlsClientType: '${QVS_VCS_CLIENT_TLS_CLIENT_TYPE:MTLS}' + proxy: '${QVS_VCS_CLIENT_PROXY:}' + servername: '${QVS_VCS_CLIENT_SERVERNAME:}' + +pcsClient: + host: '${QVS_PCS_CLIENT_HOST:api.trustedservices.intel.com}' + port: '${QVS_PCS_CLIENT_PORT:443}' + retries: '${QVS_PCS_CLIENT_RETRIES:1}' + initialInterval: '${QVS_PCS_CLIENT_INITIAL_INTERVAL:100}' + factor: '${QVS_PCS_CLIENT_INTERVAL_FACTOR:1}' + caCertDirectories: '${QVS_PCS_CLIENT_CA_CERT_DIRECTORIES:certificates/internal_ca/}' + tlsClientType: '${QVS_PCS_CLIENT_TLS_CLIENT_TYPE:TLS}' + proxy: '${QVS_PCS_CLIENT_PROXY:}' + servername: '${QVS_PCS_CLIENT_SERVERNAME:}' + +crlClient: + retries: '${QVS_CRL_CLIENT_RETRIES:2}' + initialInterval: '${QVS_CRL_CLIENT_INITIAL_INTERVAL:100}' + factor: '${QVS_CRL_CLIENT_INTERVAL_FACTOR:3}' + proxy: '${QVS_CRL_CLIENT_PROXY:}' + +cache: + ttl: '${QVS_CACHE_TTL:900}' + checkPeriod: '${QVS_CACHE_CHECK_PERIOD:60}' + maxKeys: '${QVS_CACHE_MAX_KEYS:1000}' + +target: + attestationReportSigningCaCertificate: '${QVS_ATTESTATION_REPORT_SIGNING_CA_CERTIFICATE:}' + attestationReportSigningCertificate: '${QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE:}' + trustedRootPublicKey: '${QVS_TRUSTED_ROOT_PUBLIC_KEY:3059301306072a8648ce3d020106082a8648ce3d030107034200040ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394}' diff --git a/QuoteVerification/QuoteVerificationService/diagram.png b/QuoteVerification/QuoteVerificationService/diagram.png new file mode 100644 index 00000000..3d2b7954 Binary files /dev/null and b/QuoteVerification/QuoteVerificationService/diagram.png differ diff --git a/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/README.md b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/README.md new file mode 100644 index 00000000..302f2c2f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/README.md @@ -0,0 +1,75 @@ +# Simple Signing Service Example + +DO NOT USE THIS IN PRODUCTION. + +## Prerequisite + +Non-SGX platform is sufficient, tested with Ubuntu 18.04 and Ubuntu 20.04 + +Install: + - [Node.js](https://nodejs.org/en/) (tested with version 16.13.1) + - [OpenSSL](https://www.openssl.org/ "OpenSSL") (tested with version 1.1.0g) + +## Configure Key Pairs +In order to use the service, we need to generate 2 key pairs and corresponding self-signed certificates: one for HTTPS, second for signing Attestation Report. + +First, letā€™s create a key and self-signed cert for HTTPS enabling: + +``` +openssl genrsa -out key.pem +openssl req -new -key key.pem -out csr.pem +openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem +rm csr.pem +``` + +Finally, letā€™s create a key and self-signed cert for signing Attestation Report: + +``` +openssl genrsa -out sign-key.pem 3072 +openssl req -new -key sign-key.pem -out csr.pem +openssl x509 -req -days 9999 -in csr.pem -signkey sign-key.pem -out sign-cert.pem +rm csr.pem +``` + +## Communication with Quote Verification Service + +Sample simple-signing-service is configured by default to support MTLS. + +To allow requests from Quote Verification Service, QVS client certificate has to be added to SSS's trusted CA. It's configured by [QVS_VCS_CLIENT_CERT_FILE](../../README.md#service-configuration) configuration variable. + +To do so, please [create QVS Client certificate](../../configuration-default/certificates/README.md#configure-mtls-with-sss). Expected result is that qvs-to-sss-client-cert.pem copy is located in SSS's directory. + +## Building and Starting Service + +Build simple-signing-service: + +```npm install``` + +and start: + +```npm start``` or ```node simple-signing-service.js``` + +This service will run with two ports enabled: + +``` +Server Started: https://localhost:8797 +Server Started: http://localhost:8796 +``` +## Healtcheck +In order to use HTTPS (default port: 8797) please create qvs-to-sss-client key and cert first, +following: [../../configuration-default/certificates/README.md](../../configuration-default/certificates/README.md) + +``` +curl http://localhost:8796/health +curl --cacert ./cert.pem --key ../../configuration-default/certificates/qvs-to-sss-client-key.pem --cert ../../configuration-default/certificates/qvs-to-sss-client-cert.pem https://localhost:8797/health +``` + +## Configuration for Quote Verification Service + +Read the log and get information from the line below: + +``` +Signing Certificate in URL encoded: +``` + +That will be required to start Quote Verification Service. diff --git a/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/package.json b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/package.json new file mode 100644 index 00000000..a39428c6 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/package.json @@ -0,0 +1,17 @@ +{ + "name": "simple-signing-service", + "version": "0.0.1", + "description": "DO NOT USE IN PRODUCTION. This simple service demonstrates Verification Crypto Service concept.", + "main": "simple-signing-service.js", + "scripts": { + "start": "node simple-signing-service.js" + }, + "dependencies": { + "koa": "^2.13.4", + "koa-router": "^12.0.0", + "koa-body": "^5.0.0" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/simple-signing-service.js b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/simple-signing-service.js new file mode 100644 index 00000000..11a57e01 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/samples/simple-signing-service/simple-signing-service.js @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +'use strict'; + +// DO NOT USE IN PRODUCTION + +const https = require('https'); +const http = require('http'); +const fs = require('fs'); +const Koa = require('koa'); +const Router = require('koa-router'); +const koaBody = require('koa-body'); +const crypto = require('crypto'); + +const app = new Koa(); +const router = new Router(); +//Sample Configuration +//Load Signing key, to generate the signature over attestation report +const signingKey = fs.readFileSync('sign-key.pem', 'utf-8'); +const signingCert = fs.readFileSync('sign-cert.pem', 'utf-8'); +const algorithm = 'sha384WithRSAEncryption'; + +//Load HTTPS keys for this service +const httpsKey = fs.readFileSync('key.pem'); +const httpsCert = fs.readFileSync('cert.pem'); +const caCertPath = 'qvs-to-sss-client-cert.pem'; +const HTTPS_PORT = 8797; +const HTTP_PORT = 8796; +const isMtls = true; + +const options = { + key: httpsKey, + cert: httpsCert, + ca: isMtls ? [fs.readFileSync(caCertPath)] : undefined, + requestCert: isMtls, + rejectUnauthorized: isMtls, +}; + +router.get('/health', (ctx) => { + ctx.body = { status: 'OK', version: '0.0.1', lastChecked: new Date(Date.now()).toISOString() }; +}); + +router.post('/sign/attestation-verification-report', (ctx) => { + console.log('[Request]' + JSON.stringify(ctx.request.body)); + + const bodyJsonToString = JSON.stringify(ctx.request.body); + const signer = crypto.createSign(algorithm); + signer.update(bodyJsonToString); + signer.end(); + const signature = signer.sign(signingKey); + const buff = Buffer.from(signature); + //Adding extra check to verify if pair sign-key sign-cert is working well just for DEBUG purpose + crypto.verify(algorithm, Buffer.from(bodyJsonToString), signingCert, signature); + //returning signature + ctx.body = { signature: buff.toString('base64') }; + + console.log('[Response]' + JSON.stringify(ctx.body)); +}); + +app.use(koaBody()) + .use(router.routes()) + .use(router.allowedMethods()); + +https.createServer(options, app.callback()).listen(HTTPS_PORT); +http.createServer(app.callback()).listen(HTTP_PORT); + +console.log('Server Started: https://localhost:' + HTTPS_PORT); +console.log('Server Started: http://localhost:' + HTTP_PORT); +/** + * To run Quote Verification Service Docker image you need to override + * QVS_ATTESTATION_REPORT_SIGNING_CERTIFICATE + * with value from below log, as it consumes certificate in PEM, UrlEncoded + */ + +console.log('Signing Certificate in URL encoded:' + encodeURI(signingCert.toString())); diff --git a/QuoteVerification/QuoteVerificationService/src/bootstrap.js b/QuoteVerification/QuoteVerificationService/src/bootstrap.js new file mode 100644 index 00000000..91242e75 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/bootstrap.js @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const path = require('path'); +const process = require('process'); +const genericLogger = require('./common/logger').genericLogger; + +module.exports = (async function bootstrap() { + try { + await require('./configLoader').init(path.join(__dirname, '../configuration-default/')); + } + catch (e) { + genericLogger.error('During loading config and parsing templates occurred an', e); + /*eslint no-process-exit: 0 */ + return process.exit(1); + } + return require('./server'); // async function (use with await or return) +}()); diff --git a/QuoteVerification/QuoteVerificationService/src/clients/crlAccessLayer/CRLClient.js b/QuoteVerification/QuoteVerificationService/src/clients/crlAccessLayer/CRLClient.js new file mode 100644 index 00000000..3c5501f7 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/clients/crlAccessLayer/CRLClient.js @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const config = require('../../configLoader').getConfig(); +const RestClient = require('../../common/RestClient'); +const NodeCache = require('node-cache'); + +const cacheConfig = { + stdTTL: config.cache.ttl, // 0 is unlimited + checkperiod: config.cache.checkPeriod, + maxKeys: config.cache.maxKeys, // -1 is unlimited, 0 turns off caching +}; + +const clientsCacheConfig = { + stdTTL: 0, // 0 is unlimited + checkperiod: 0, // no periodic check + maxKeys: -1, // -1 is unlimited, 0 turns off caching +}; + +const cache = { + crls: new NodeCache(cacheConfig), + clients: new NodeCache(clientsCacheConfig) +}; + +/** +* @typedef {import('../../jsDoc/types').Logger} Logger +*/ + +/** + * Creates and caches RestClient which will communicate with specific host + * @param {string} host - url.host in format ':' + * @returns {RestClient} + */ +function getRestClientForHost(host, logger) { + const [hostname, port] = host.split(':'); + let client = cache.clients.get(host); + if (!client) { + logger.info(`Creating RestClient for host ${host}`); + client = new RestClient( + 'TLS', + hostname, + parseInt(port || 443), + config.crlClient.retries, + config.crlClient.initialInterval, + config.crlClient.factor, + undefined, + undefined, + undefined, + config.crlClient.proxy); + + try { + cache.clients.set(host, client); + logger.info('Created and indefinitely cached REST client ready to connect to host: ' + host); + } + catch (error) { + logger.warn('Problem with clients cache: ' + error); + // Not throwing error, if just saving to cache fails, it doesnt change the fact + // that client was created + } + } + return client; +} + +/** +* Retrieves SGX TcbInfo for provided fmspc from cache or directly from PCS +* @param {string} distributionPoint - address of file to download and cache +* @param {string} requestId +* @param {Logger} logger +* @returns {Promise<{ +* status: number, +* body: string, +* headers: Object. +* }|Error>} +*/ +async function getCrlFromDistributionPoint(distributionPoint, requestId, logger) { + const url = new URL(distributionPoint); + + let response = cache.crls.get(url.href); + if (!response) { + try { + const client = getRestClientForHost(url.host, logger); + + const path = url.pathname; + const searchParams = Object.fromEntries(url.searchParams); + const additionalHeaders = {}; + response = await client.getRequestWithBinaryResponsePromised(requestId, logger, null, path, additionalHeaders, searchParams); + if (config.cache.maxKeys !== 0) { + try { + cache.crls.set(url.href, response); + logger.info('Cached CRL downloaded from: ' + url.href); + } + catch (error) { + logger.warn('Problem with CRLs cache: ' + error); + // Not throwing error, if just saving to cache fails, it doesnt change the fact + // that response was positive and can be returned + } + } + } + catch (error) { + response = error; + } + } + else { + logger.info(`CRL taken from cache instead of ${distributionPoint}`); + } + + return response; +} + +module.exports = { + getCrlFromDistributionPoint +}; diff --git a/QuoteVerification/QuoteVerificationService/src/clients/pcsAccessLayer/PCSClient.js b/QuoteVerification/QuoteVerificationService/src/clients/pcsAccessLayer/PCSClient.js new file mode 100644 index 00000000..8732d3ea --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/clients/pcsAccessLayer/PCSClient.js @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const config = require('../../configLoader').getConfig(); +const RestClient = require('../../common/RestClient'); +const NodeCache = require('node-cache'); + +const client = new RestClient( + config.pcsClient.tlsClientType, + config.pcsClient.host, + config.pcsClient.port, + config.pcsClient.retries, + config.pcsClient.initialInterval, + config.pcsClient.factor, + config.pcsClient.certFile, + config.pcsClient.keyFile, + config.pcsClient.caCertDirectories, + config.pcsClient.proxy, + config.pcsClient.servername); + +const cacheConfig = { + stdTTL: config.cache.ttl, // 0 is unlimited + checkperiod: config.cache.checkPeriod, + maxKeys: config.cache.maxKeys, // -1 is unlimited, 0 turns off caching +}; + +const cache = { + sgxTcbInfo: new NodeCache(cacheConfig), + tdxTcbInfo: new NodeCache(cacheConfig), + sgxQeIdentity: new NodeCache(cacheConfig), + tdxQeIdentity: new NodeCache(cacheConfig) +}; + +/** + * @typedef {import('../../jsDoc/types').Logger} Logger + * @typedef {import('../../jsDoc/types').TcbInfo} TcbInfo + * @typedef {import('../../jsDoc/types').EnclaveIdentity} EnclaveIdentity + */ + +/** + * Makes first character of string upper + * @param {string} text + * @returns {string} with first character uppercase + */ +function capitalizeFirstLetter(text) { + return text.length > 0 ? text.charAt(0).toUpperCase() + text.slice(1) : ''; +} + +/** + * Saves response under specified key in specified cache + * @param {string} cacheName - key of global cache object + * @param {string} key + * @param {string} value + * @param {Logger} logger + */ +function saveInCache(cacheName, key, value, logger) { + if (config.cache.maxKeys !== 0) { + try { + cache[cacheName].set(key, value); + logger.info('Cached get' + capitalizeFirstLetter(cacheName) + ' response under key: ' + key); + } + catch (error) { + logger.warn('Problem with ' + cacheName + ' cache: ' + error); + } + } +} + +/** + * Retrieves SGX TcbInfo for provided fmspc from cache or directly from PCS + * @param {string} fmspc + * @param {string} requestId + * @param {Logger} logger + * @returns {Promise<{ + * status: number, + * body: {tcbInfo: TcbInfo, signature: string }, + * headers: Object. + * }|Error>} + */ +async function getSgxTcbInfo(fmspc, requestId, logger) { + const path = '/sgx/certification/v4/tcb'; + + let response = cache.sgxTcbInfo.get(fmspc); + if (!response) { + try { + response = await client.getRequestPromised(requestId, logger, null, path, {}, { + fmspc + }); + saveInCache('sgxTcbInfo', fmspc, response, logger); + } + catch (error) { + response = error; + } + } + else { + logger.info(`SGX TcbInfo for FMSPC=${fmspc} taken from cache`); + } + + return response; +} + +/** + * Retrieves TDX TcbInfo for provided fmspc from cache or directly from PCS + * @param {string} fmspc + * @param {string} requestId + * @param {Logger} logger + * @returns {Promise<{ + * status: number, + * body: {tcbInfo: TcbInfo, signature: string}, + * headers: Object. + * }|Error>} + */ +async function getTdxTcbInfo(fmspc, requestId, logger) { + const path = '/tdx/certification/v4/tcb'; + + let response = cache.tdxTcbInfo.get(fmspc); + if (!response) { + try { + response = await client.getRequestPromised(requestId, logger, null, path, {}, { + fmspc + }); + saveInCache('tdxTcbInfo', fmspc, response, logger); + } + catch (error) { + response = error; + } + } + else { + logger.info(`TDX TcbInfo for FMSPC=${fmspc} taken from cache`); + } + + return response; +} + +/** + * Retrieves SGX QE Identity from cache or directly from PCS + * @param {string} requestId + * @param {Logger} logger + * @returns {Promise<{ + * status: number, + * body: {enclaveIdentity: EnclaveIdentity, signature: string}, + * headers: Object. + * }|Error>} + */ +async function getSgxQeIdentity(requestId, logger) { + const path = '/sgx/certification/v4/qe/identity'; + const cacheKey = 'sgxQeIdentity'; + + let response = cache.sgxQeIdentity.get(cacheKey); + if (!response) { + try { + response = await client.getRequestPromised(requestId, logger, null, path); + saveInCache('sgxQeIdentity', cacheKey, response, logger); + } + catch (error) { + response = error; + } + } + else { + logger.info('SGX QeIdentity taken from cache'); + } + + return response; +} + +/** + * Retrieves TDX QE Identity from cache or directly from PCS + * @param {string} requestId + * @param {Logger} logger + * @returns {Promise<{ + * status: number, + * body: {enclaveIdentity: EnclaveIdentity, signature: string}, + * headers: Object. + * }|Error>} + */ +async function getTdxQeIdentity(requestId, logger) { + const path = '/tdx/certification/v4/qe/identity'; + const cacheKey = 'tdxQeIdentity'; + + let response = cache.tdxQeIdentity.get(cacheKey); + if (!response) { + try { + response = await client.getRequestPromised(requestId, logger, null, path); + saveInCache('tdxQeIdentity', cacheKey, response, logger); + } + catch (error) { + response = error; + } + } + else { + logger.info('TDX QeIdentity taken from cache'); + } + + return response; +} + +module.exports = { + getSgxTcbInfo, + getTdxTcbInfo, + getSgxQeIdentity, + getTdxQeIdentity +}; diff --git a/QuoteVerification/QuoteVerificationService/src/clients/vcsAccessLayer/VCSClient.js b/QuoteVerification/QuoteVerificationService/src/clients/vcsAccessLayer/VCSClient.js new file mode 100644 index 00000000..44463ad2 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/clients/vcsAccessLayer/VCSClient.js @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const config = require('../../configLoader').getConfig(); +const RestClient = require('../../common/RestClient'); + +const client = new RestClient( + config.vcsClient.tlsClientType, + config.vcsClient.host, + config.vcsClient.port, + config.vcsClient.retries, + config.vcsClient.initialInterval, + config.vcsClient.factor, + config.vcsClient.certFile, + config.vcsClient.keyFile, + config.vcsClient.caCertDirectories, + config.vcsClient.proxy, + config.vcsClient.servername); + +/** + * @typedef {import('../../jsDoc/types').Logger} Logger + */ + +/** + * Signs the attestation verification report + * @param {{}} body - attestation verification report to be signed + * @param {string} requestId + * @param {Logger} logger + * @returns {Promise<{ + * status: number, + * body: { + * signature: string + * }, + * headers: Object. + * }|Error>} + */ + async function signVerificationReport(body, requestId, logger) { + const path = '/sign/attestation-verification-report'; + + let response; + try { + response = await client.postRequestPromised(requestId, logger, body, path); + } + catch (error) { + response = error; + } + + return response; +} + +/** + * Retrieves health status of component and its dependencies + * @param {Logger} logger + * @param {Object.} headers + * @returns {status: number, body: JSON, headers: Object.} + */ +function getHealth(logger, headers) { + return client.health(logger, headers); +} + +module.exports = { + getHealth, + signVerificationReport +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/RestClient.js b/QuoteVerification/QuoteVerificationService/src/common/RestClient.js new file mode 100644 index 00000000..96a90e98 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/RestClient.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const http = require('http'); +const https = require('https'); + +const config = require('../configLoader').getConfig(); + +const HttpsProxyAgent = require('https-proxy-agent'); + +const RequestHandler = require('./requestHandler'); +const getCACertificatesSync = require('./getCACertificatesSync'); +const tlsType = require('./tlsType'); +const readFileSafely = require('../common/readFileSafely'); + +class RestClient { + + constructor(type, host, port, retryCount, retryDelay, factor, certFile, keyFile, caCertDirectories, proxy, servername) { + this.cfg = { + address: 'None', // Placeholder, will be populated with first request + protocol: (type === tlsType.None) ? 'http' : 'https', + host, + port, + retryCount, + retryDelay, + factor, + proxy, + servername + }; + this.prepareOptions(type, certFile, keyFile, caCertDirectories); + this.initializeRequestHandler(); + } + + prepareOptions(type, certFile, keyFile, caCertDirectories) { + const ca = getCACertificatesSync(caCertDirectories).map(file => readFileSafely(file, 'utf8')); + + this.agentOptions = { + keepAlive: true, + timeout: config.service.restClientTimeout, + scheduling: 'lifo', + }; + + const proxy = this.cfg.proxy; + let agent; + if (proxy) { + agent = new HttpsProxyAgent(proxy); + } + else { + agent = (type === tlsType.None) ? new http.Agent(this.agentOptions) : new https.Agent(this.agentOptions); + } + + const options = { + ca, + maxVersion: tlsType.MAX_SECURE_PROTOCOL, + minVersion: tlsType.MIN_SECURE_PROTOCOL, + ciphers: tlsType.CIPHERS, + requestCert: type === tlsType.MTLS, + rejectUnauthorized: type === tlsType.MTLS, + strictSSL: true, + agent + }; + + if (type === tlsType.MTLS) { + options.cert = readFileSafely(certFile, 'utf8'); + options.key = readFileSafely(keyFile, 'utf8'); + options.servername = this.cfg.servername; + } + + this.cfg.options = options; + } + + initializeRequestHandler() { + this.requestHandler = new RequestHandler(this.cfg); + } + + async requestPromised(logger, method, path, body, requestId, headers, queryParams, isResponseBinary = false) { + headers['Request-ID'] = requestId; + headers['Content-Type'] = 'application/json'; + + return this.requestHandler.sendRequestWithRetries(logger, method, path, body, headers, queryParams, isResponseBinary); + } + + async requestPromisedOctetStream(logger, method, path, body, requestId, headers, queryParams) { + headers['Request-ID'] = requestId; + headers['Content-Type'] = 'application/octet-stream'; + + return this.requestHandler.sendRequestWithRetries(logger, method, path, body, headers, queryParams); + } + + async health(logger, headers) { + return this.requestHandler.sendRequest(logger, 'GET', '/health', '', headers, undefined); + } + + async healthWithRetry(logger, headers) { + return this.requestHandler.sendRequestWithRetriesOnConnectionReset(logger, 'GET', '/health', '', headers, undefined); + } + + async getRequestPromised(requestId, logger, body, path, headers = {}, queryParams = {}) { + return this.requestPromised(logger, 'GET', path, body, requestId, headers, queryParams); + } + + async getRequestWithBinaryResponsePromised(requestId, logger, body, path, headers = {}, queryParams = {}) { + return this.requestPromised(logger, 'GET', path, body, requestId, headers, queryParams, true); + } + + async postOctetStreamRequestPromised(requestId, logger, body, path, headers = {}, queryParams = {}) { + return this.requestPromisedOctetStream(logger, 'POST', path, body, requestId, headers, queryParams); + } + + async postRequestPromised(requestId, logger, body, path, headers = {}, queryParams = {}, isResponseBinary = false) { + return this.requestPromised(logger, 'POST', path, body, requestId, headers, queryParams, isResponseBinary); + } + + async putRequestPromised(requestId, logger, body, path, headers = {}, queryParams = {}) { + return this.requestPromised(logger, 'PUT', path, body, requestId, headers, queryParams); + } +} + +module.exports = RestClient; diff --git a/QuoteVerification/QuoteVerificationService/src/common/config.js b/QuoteVerification/QuoteVerificationService/src/common/config.js new file mode 100644 index 00000000..b2b5f77c --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/config.js @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const Promise = require('bluebird'); +const _ = require('lodash'); +const Buffer = require('../util/buffer'); +const env = require('process').env; +const path = require('path'); +const jsYaml = require('js-yaml'); +const ZSchema = require('z-schema'); + +const logger = require('./logger').genericLogger; +const readFileSafely = require('./readFileSafely'); + +let configPath = '../configuration-default/'; + +const SPLIT_REGEXP = /[,; ]+/; + +const HIDDEN_CONFIG_FIELDS = ['PASSWORD', 'SECRET', 'VAULT', 'validationKey', 'restKey']; + +function readApplicationJsonFromEnv() { + if (env.APPLICATION_JSON) { + try { + return JSON.parse(env.APPLICATION_JSON); + } + catch (e) { + throw new Error('Error parsing APPLICATION_JSON env variable. Make sure it is in JSON format: ' + e); + } + } + else if (env.APPLICATION_JSON_BASE64) { + try { + return JSON.parse(Buffer.from(env.APPLICATION_JSON_BASE64, 'base64').toString('ascii')); + } + catch (e) { + throw new Error('Error parsing APPLICATION_JSON_BASE64 env variable. Make sure its content is proper base64 of a JSON: ' + e); + } + } + return {}; +} + +const applicationJson = readApplicationJsonFromEnv(); + +function appendPath(p) { + if (p.startsWith('/')) { + return p; + } + + return path.join(configPath || '.', p); +} + +function appendConfigPath(configObject, fieldList) { + for (const field of fieldList) { + if (_.isString(configObject[field])) { + configObject[field] = appendPath(configObject[field]); + } + else if (_.isArray(configObject[field])) { + configObject[field] = configObject[field].map(appendPath); + } + } +} + +function getSchemaErrors(name, errors) { + for (const error of errors) { + const schemaPath = error.path.replace(/^#\//, ''); // Replace '#/' from the beginning of the error.path + if (schemaPath) { // Extend error message with path info (if exists) + error.message += ` in ${schemaPath}`; + } + } + // Create and return single-line message with all errors (used ' :: ' as a separator) + return `Configuration parsing error(s) in "${configPath}" <${name}>: ${errors.map(e => e.message).join(' :: ')}`; +} + +class Base { + validate(config, schema) { + const validator = new ZSchema({ + breakOnFirstError: false + }); + const valid = validator.validate(config, Object.assign(schema, { type: 'object' })); + if (!valid) { + const msg = getSchemaErrors(this.constructor.name, validator.getLastErrors()); + logger.fatal(msg); + throw new Error(msg); + } + } +} + +class Logger extends Base { + constructor(config) { + super(); + this.validate(config, { + required: ['levelFile', 'levelConsole', 'fileName', 'category'], + properties: { + levelFile: { + 'type': 'string', 'enum': ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'off'] + }, + levelConsole: { + 'type': 'string', 'enum': ['trace', 'debug', 'info', 'warn', 'error', 'fatal', 'off'] + }, + fileName: { + type: 'string' + }, + fileCPP: { + type: 'string' + }, + category: { + type: 'string' + }, + maxDisplayingMessageLength: { + type: 'integer' + }, + multilineEnabled: { + type: 'boolean' + } + } + }); + this.levelFile = config.levelFile; + this.levelConsole = config.levelConsole; + this.fileName = config.fileName; + this.fileCPP = config.fileCPP; + this.multilineEnabled = config.multilineEnabled; + this.category = config.category; + this.maxDisplayingMessageLength = config.maxDisplayingMessageLength; + } +} + +class HealthCheck extends Base { + constructor(config) { + super(); + this.validate(config, { + required: ['intervalMs', 'freshnessMs'], + properties: { + intervalMs: { + type: 'number' + }, + freshnessMs: { + type: 'number' + } + } + }); + this.intervalMs = config.intervalMs; + this.freshnessMs = config.freshnessMs; + } +} + +class Service extends Base { + constructor(config) { + super(); + this.validate(config, { + required: (function checkMTLSType() { + switch (config.tlsServerType) { + case 'MTLS': + return ['componentName', 'componentShort', 'certFile', 'keyFile', 'tlsServerType', 'caCertDirectories']; + case 'TLS': + return ['componentName', 'componentShort', 'certFile', 'keyFile', 'tlsServerType']; + case 'None': + return ['componentName', 'componentShort', 'tlsServerType']; + default: + return ['componentName', 'componentShort', 'certFile', 'keyFile', 'tlsServerType']; + } + }()), + properties: { + componentName: { + type: 'string' + }, + componentShort: { + type: 'string' + }, + tlsServerType: { + 'type': 'string', 'enum': ['MTLS', 'TLS', 'None'] + }, + port: { + type: 'number' + }, + certFile: { + type: 'string' + }, + keyFile: { + type: 'string' + }, + caCertDirectories: { + type: 'string', + }, + bodySizeLimits: { + type: 'object', + properties: { + text: { + type: 'string' + }, + json: { + type: 'string' + }, + form: { + type: 'string' + }, + blob: { + type: 'string' + } + } + }, + restClientTimeout: { + type: 'number' + } + } + }); + + if (config.caCertDirectories) { + config.caCertDirectories = config.caCertDirectories.split(SPLIT_REGEXP); // since caCertDirectories are CSV we need to split them before prepending default config path + } + appendConfigPath(config, ['certFile', 'keyFile', 'caCertDirectories']); + + this.componentName = config.componentName; + this.componentShort = config.componentShort; + this.tlsServerType = config.tlsServerType; + this.port = config.port; + this.certFile = config.certFile; + this.keyFile = config.keyFile; + this.caCertDirectories = config.caCertDirectories; + this.bodySizeLimits = config.bodySizeLimits; + this.restClientTimeout = config.restClientTimeout; + } +} + +class Cache extends Base { + constructor(config) { + super(); + + this.validate(config, { + required: ['ttl', 'checkPeriod', 'maxKeys'], + properties: { + ttl: { + type: 'number' + }, + checkPeriod: { + type: 'number' + }, + maxKeys: { + type: 'number' + } + } + }); + + this.ttl = config.ttl; + this.checkPeriod = config.checkPeriod; + this.maxKeys = config.maxKeys; + } +} + +class RestClient extends Base { + constructor(config) { + super(); + this.validate(config, { + required: ['host', 'port', 'retries', 'initialInterval', 'factor', 'tlsClientType', 'caCertDirectories', 'servername'], + properties: { + tlsClientType: { + 'type': 'string', 'enum': ['MTLS', 'TLS', 'None'] + }, + host: { + type: 'string' + }, + port: { + type: 'number' + }, + retries: { + type: 'number' + }, + initialInterval: { + type: 'number' + }, + factor: { + type: 'number' + }, + certFile: { + type: 'string' + }, + keyFile: { + type: 'string' + }, + caCertDirectories: { + type: 'string', + }, + proxy: { + type: 'string', + }, + servername: { + type: 'string' + } + } + }); + + this.tlsClientType = config.tlsClientType; + this.host = config.host; + this.port = config.port; + this.retries = config.retries; + this.initialInterval = config.initialInterval; + this.factor = config.factor; + this.certFile = config.certFile; + this.keyFile = config.keyFile; + this.caCertDirectories = config.caCertDirectories.split(SPLIT_REGEXP); + this.proxy = config.proxy; + this.servername = config.servername; + + appendConfigPath(this, ['certFile', 'keyFile', 'caCertDirectories']); + } +} + +// This is skeleton for basic rest service with logger, healthcheck and service nodes possible. +// All nodes are optional. +class BaseRestService extends Base { + constructor(config) { + super(); + const svc = config.service; + if (svc) { + this.service = new Service(svc); + } + if (config.logger) { + this.logger = new Logger(config.logger); + } + if (config.healthCheck) { + this.healthCheck = new HealthCheck(config.healthCheck); + } + } +} + +async function parseJsonObject(value) { + let res = JSON.parse(value); + + res = _.mapValues(res, (item) => { + return item; + }); + res = await Promise.props(res); + + return res; +} + +async function parseJsonArray(value) { + let array = JSON.parse(value); + + const promisedArray = array.map((item) => { + return item; + }); + array = await Promise.all(promisedArray); + return array; +} + +async function parseTemplate(val) { + if (_.isBoolean(val) || _.isNumber(val)) { + return val; + } + + if (!_.isString(val)) { + throw new Error('Cannot parse template which is not string: ' + val); + } + + const trimmed = val.trim(); + // Recognize a template ${} + if (trimmed.startsWith('${') && trimmed.endsWith('}')) { + const inside = trimmed.substring(2, trimmed.length - 1); + const colonIndex = inside.indexOf(':'); + // Key should be at least 1 character long, value can be empty string, but colon is mandatory + if (colonIndex < 1) { + throw new Error('Non-empty key and default value in config are mandatory in template: ' + trimmed); + } + const envKey = inside.substring(0, colonIndex); + // Retrieve value from different sources + let value; + // APPLICATION_JSON or APPLICATION_JSON_BASE64 environment variable + if (!_.isUndefined(applicationJson[envKey])) { + value = applicationJson[envKey]; + } + // Specific env variable with prefix SGX_[DEVOPS_]_
_ + else if (!_.isUndefined(env[envKey])) { + value = env[envKey]; + } + // Default value from config.yml + else { + const defaultValue = inside.substring(colonIndex + 1); + value = defaultValue; + } + // Additional transformations or conversions + /* istanbul ignore else: value read from env or file is always string, check before trim left for safety reasons */ + if (_.isString(value)) { + value = value.trim(); + // Boolean + if (value === 'true') { + return true; + } + if (value === 'false') { + return false; + } + if (value === 'null') { + return null; + } + // String convertible to number + if (value.length > 0 && !isNaN(value)) { + return Number(value); + } + else if (value.length >= 2) { + // Remove optional '' around content + if (value[0] === '\'' && value[value.length - 1] === '\'') { + value = value.substring(1, value.length - 1); + } + // Remove optional "" around content + if (value[0] === '"' && value[value.length - 1] === '"') { + value = value.substring(1, value.length - 1); + } + + // JSON object + if (value[0] === '{' && value[value.length - 1] === '}') { + try { + const resJson = await parseJsonObject(value); + return resJson; + } + catch (e) { + throw new Error(value + ' cannot be parsed as valid JSON'); + } + } + // JSON array + if (value[0] === '[' && value[value.length - 1] === ']') { + try { + const resArray = await parseJsonArray(value); + return resArray; + } + catch (e) { + throw new Error(value + ' cannot be parsed as valid array'); + } + } + } + } + return value; + } + return trimmed; +} + +async function fillConfigFromEnv(obj) { + const mapper = (value) => { + if (_.isObject(value)) { // array is also an object + return fillConfigFromEnv(value); + } + else if (_.isString(value)) { + return parseTemplate(value); + } + return value; + }; + + if (_.isArray(obj)) { + obj = await Promise.map(obj, mapper); + } + /* istanbul ignore else */ + else if (_.isObject(obj)) { + const promisedObj = _.mapValues(obj, mapper); + obj = await Promise.props(promisedObj); + } + return obj; +} + +function removePasswords(obj) { + const configWithHiddenPasswords = JSON.parse(JSON.stringify(obj)); + /* istanbul ignore else */ + if (_.isObject(configWithHiddenPasswords)) { + for (const [key, value] of Object.entries(configWithHiddenPasswords)) { + if (_.isObject(value)) { + configWithHiddenPasswords[key] = removePasswords(value); + } + else if (_.isString(key) && HIDDEN_CONFIG_FIELDS.some(part => key.toUpperCase().includes(part.toUpperCase())) + ) { + configWithHiddenPasswords[key] = ''; + } + } + } + return configWithHiddenPasswords; +} + +/** + * Loads config from directory + * @param {string} configDir path to directory not file + * @returns {{config: Object, service: Object, target: Object, logger: Logger, healthcheck: Object}} jsYaml Object + */ +async function loadConfig(configDir) { + configPath = configDir; + const configFilePath = path.resolve(appendPath('config.yml')); + + try { + let config = jsYaml.load(readFileSafely(configFilePath, 'utf8'), { filename: configFilePath }); + config.configPath = configPath; + + config = await fillConfigFromEnv(config); + const configWithHiddenPasswords = removePasswords(config); + logger.info((config.service ? config.service.componentName : '') + ' CONFIG: ' + JSON.stringify(configWithHiddenPasswords)); + return config; + } + catch (err) { + let msg = err.message; + if (err instanceof jsYaml.YAMLException) { + // Replace multiple white characters (spaces, tabs, eol-s) with the single one; mainly for having single-line error message + // > without replacement: + // [...] end of the stream or a document separator is expected in "/GAS/configuration/config.yml" at line 25, column 16: + // ComponentName: 'GroupAssignmentStore' + // > with replacement: + // [...] Error: end of the stream or a document separator is expected in "/GAS/configuration/config.yml" at line 25, column 16: + // ComponentName: 'GroupAssignmentStore' + msg = `Error: ${msg.replace(/[ \t\r\n^]+/g, ' ')}`; + } + else { + msg = `Initialization failure: ${msg}`; + } + if (!msg.includes(configFilePath)) { + msg = `Error in ${configFilePath}: ${msg}`; + } + logger.fatal(msg); + throw new Error(msg); + } +} + +class ConfigLoader { + constructor(configClass) { + this.configClass = configClass; + this.instance = null; + } + + async init(path) { + const config = await loadConfig(path); + this.instance = new this.configClass(config); + } + + getConfig() { + if (!this.instance) { + throw Error('You need to call init(path) first and await initialization. Run server using `node ./bootstrap.js and set correct config profile`'); + } + return this.instance; + } +} + +module.exports = { + Service, + HealthCheck, + Logger, + RestClient, + Cache, + BaseRestService, + ConfigLoader, + Base, + appendConfigPath, + load: loadConfig +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/detailedErrorString.js b/QuoteVerification/QuoteVerificationService/src/common/detailedErrorString.js new file mode 100644 index 00000000..b7b5c460 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/detailedErrorString.js @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const { serializeError } = require('serialize-error'); +const onelinerFormat = require('../util/onelinerFormat'); + +function detailedErrorString(e) { + const errorJson = serializeError(e); + return onelinerFormat(JSON.stringify(errorJson)); +} + +module.exports = detailedErrorString; diff --git a/QuoteVerification/QuoteVerificationService/src/common/errorHandler.js b/QuoteVerification/QuoteVerificationService/src/common/errorHandler.js new file mode 100644 index 00000000..7e24c5b2 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/errorHandler.js @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * Returns error stack trace depending on if it's multiline + * + * @param {Error} error + * @param {boolean} isMultiline + * + * @returns {string} + */ +function errorStackTraceHandler(error, isMultiline) { + if (error) { + if (isMultiline) { + const errorStack = String(error.stack); + return errorStack; + } + else { + const errorStack = String(error.stack).replace(/\n/gi, ' | '); + return errorStack; + } + } + else { + return ''; + } +} + +module.exports = { errorStackTraceHandler }; diff --git a/QuoteVerification/QuoteVerificationService/src/common/getCACertificatesSync.js b/QuoteVerification/QuoteVerificationService/src/common/getCACertificatesSync.js new file mode 100644 index 00000000..a3d1ef0f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/getCACertificatesSync.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const logger = require('../common/logger').genericLogger; + +module.exports = (caCertDirectories) => { + let CACertsFiles = []; + + if (caCertDirectories) { + CACertsFiles = caCertDirectories + .map(dir => + fs.readdirSync(dir) + .map(caFile => path.join(dir, caFile)) + .filter(path => fs.statSync(path).isDirectory()) + .map(subdir => + fs.readdirSync(subdir) + .map(caFile => path.join(subdir, caFile)) + .filter(path => fs.statSync(path).isFile()) + ) + .reduce((all, inDir) => all.concat(inDir), []) + .concat(fs.readdirSync(dir) + .map(caFile => path.join(dir, caFile)) + .filter(path => fs.statSync(path).isFile())) + ) + .reduce((all, inDir) => all.concat(inDir), CACertsFiles); + } + logger.info('Read ' + CACertsFiles.length + ' certificates from ' + caCertDirectories); + return CACertsFiles; +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/httpStatusCodes.js b/QuoteVerification/QuoteVerificationService/src/common/httpStatusCodes.js new file mode 100644 index 00000000..e5dd7d5a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/httpStatusCodes.js @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const httpStatusCodes = { + // 2xx + SUCCESS: 200, + CREATED: 201, + + // 3xx + + // 4xx + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + NOT_FOUND: 404, + METHOD_NOT_ALLOWED: 405, + + // 5xx + INTERNAL_SERVER_ERROR: 500, + SERVICE_UNAVAILABLE: 503 +}; + +module.exports = httpStatusCodes; diff --git a/QuoteVerification/QuoteVerificationService/src/common/logger.js b/QuoteVerification/QuoteVerificationService/src/common/logger.js new file mode 100644 index 00000000..06658769 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/logger.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const log4js = require('log4js'); +const _ = require('lodash'); +const util = require('util'); +const LEVELS = log4js.levels; +const onelinerFormat = require('../util/onelinerFormat'); + +// General format of log output line: +// "[] [] [ :] " +// "[] [] [ :] [reqId=] " +const MESSAGE_TOKEN = 'messageBody'; +const LOG_PATTERN = `[%dZ] [%p] [%X{logCategory} %f{1}:%l] [hostname:%h pid:%z] %X{reqId} %x{${MESSAGE_TOKEN}}`; + +/** + * exact way as log4j handles %m (log data) + * https://github.com/log4js-node/log4js-node/blob/f8d46a939279c0ab4efc8bb5f0478c4b0949a4cf/lib/layouts.js#L165 + * @param {{data: string}} loggingEvent + * @returns {string} formatted message + */ +function formatMessage(loggingEvent) { + return util.format(...loggingEvent.data); +} + +/** + * Makes multiline log message a single line by replacing new lines with | + * @param {LoggingEvent} loggingEvent + * @returns {string} + */ +function oneliner(loggingEvent) { + return onelinerFormat(formatMessage(loggingEvent)); +} + +//increase stack size to catch the caller function in log4js pattern matching +Error.stackTraceLimit = 12; + +class Logger { + constructor(logCategory, logFile, logLevelFile, logLevelConsole, isMultilineLogEnabled) { + this.layout = { + type: 'pattern', + pattern: LOG_PATTERN, + tokens: { + [MESSAGE_TOKEN]: isMultilineLogEnabled ? formatMessage : oneliner + } + }; + + this.stdout = { + type: 'stdout', + layout: this.layout, + }; + this.file = { + type: 'file', + layout: this.layout, + filename: logFile, + }; + this.consoleLower = { + type: 'logLevelFilter', + layout: this.layout, + appender: 'file', + level: LEVELS.getLevel(logLevelFile) + }; + this.fileLower = { + type: 'logLevelFilter', + layout: this.layout, + appender: 'stdout', + level: LEVELS.getLevel(logLevelConsole) + }; + + this.appenders = { + stdout: this.stdout + }; + + this.categories = { + 'default': { appenders: ['stdout'], level: logLevelConsole, enableCallStack: true } + }; + + this.logFile = logFile; + this.logCategory = logCategory; + this.logLevelFile = LEVELS.getLevel(logLevelFile); + this.logLevelConsole = LEVELS.getLevel(logLevelConsole); + + if (logFile) { + this.appenders.file = this.file; + this.appenders.consoleLower = this.consoleLower; + this.appenders.fileLower = this.fileLower; + this.categories.consoleLower = { appenders: ['consoleLower', 'stdout'], level: logLevelConsole, enableCallStack: true }; + this.categories.fileLower = { appenders: ['fileLower', 'file'], level: logLevelFile, enableCallStack: true }; + this.categories.default = { appenders: ['stdout', 'file'], level: logLevelFile, enableCallStack: true }; + } + } + + getConfiguredLogger() { + if (this.logLevelFile.isEqualTo(this.logLevelConsole) || !this.logFile) { + return log4js.getLogger(); + } + else if (this.logLevelFile.isGreaterThanOrEqualTo(this.logLevelConsole)) { + return log4js.getLogger('consoleLower'); + } + else { + return log4js.getLogger('fileLower'); + } + } + + getLogger() { + log4js.configure({ + appenders: this.appenders, + categories: this.categories + }); + + const logger = this.getConfiguredLogger(); + logger.addContext('logCategory', this.logCategory); + logger.addContext('reqId', ''); + + logger.scoped = (reqId) => { + // This piece of code adds 'reqId' to log lines. logger.addContext('reqId', '') is NOT thread safe, + // so multiple requests in the system will mingle and override value of request-id in global logger. + // Deep cloning logger object so new instance is used with different request-id + const scopedLogger = _.cloneDeep(logger); + scopedLogger.addContext('reqId', `[reqId=${reqId}]`); + return scopedLogger; + }; + + return logger; + } +} + +class Singleton { + constructor() { + this.instance = null; + } + + createLogger(logCategory, logFile, logLevelFile, logLevelConsole, isMultilineLogEnabled) { + if (!this.instance) { + this.instance = new Logger(logCategory, logFile, logLevelFile, logLevelConsole, isMultilineLogEnabled).getLogger(); + } + + return this.instance; + } +} + +const logger = new Singleton(); +const genericLogger = new Logger('genericLogger', undefined, LEVELS.OFF, LEVELS.INFO, true).getLogger(); + +module.exports = { + genericLogger, + createLogger: (logCategory, logFile, logLevelFile, logLevelConsole, isMultilineLogEnabled = false) => logger.createLogger(logCategory, logFile, logLevelFile, logLevelConsole, isMultilineLogEnabled), +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/nodeRequestHandler.js b/QuoteVerification/QuoteVerificationService/src/common/nodeRequestHandler.js new file mode 100644 index 00000000..6dfa2c86 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/nodeRequestHandler.js @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const http = require('http'); +const https = require('https'); +const Buffer = require('safe-buffer').Buffer; + +const jsonUtils = require('../util/jsonUtils'); + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../jsDoc/types').KoaResponse} KoaResponse + * @typedef {import('../jsDoc/types').Logger} Logger + */ + +/** + * Check if content is JSON + * + * @param {KoaResponse} res + * + * @returns {boolean} + */ +function isContentTypeJson(res) { + if (res.headers && res.headers.hasOwnProperty('content-type')) { + return res.headers['content-type'].startsWith('application/json'); + } + return false; +} + +/** + * Create result + * + * @param {number} statusCode + * @param {Object} headers + * @param {Object} body + * + * @returns {{headers, body, statusCode}} + */ +function createResult(statusCode, headers, body) { + return { statusCode, headers, body }; +} + +/** + * @typedef {Object} NodeRequestOptions + * @property {string[]} ca + * @property {string} secureProtocol + * @property {string} ciphers + * @property {boolean} requestCert + * @property {boolean} rejectUnauthorized + * @property {boolean} strictSSL + * @property {Object} agent + * @property {string} protocol + * @property {string} method + * @property {string} host + * @property {number} port + * @property {string} path + * @property {Object} headers + */ + +const limit10MB = 1024 * 1024 * 10; // 10 MB + +/** + * Create node request + * @param {NodeRequestOptions} options + * @param {string} body + * @param {boolean=} isResponseBinary - default false + */ +async function nodeRequest(options, body, isResponseBinary = false) { + return new Promise((resolve, reject) => { + const requestFunctionForSelectedProtocol = options.protocol === 'http:' ? http.request : https.request; + const req = requestFunctionForSelectedProtocol(options, (res) => { + let remainingLimit = limit10MB; + if (res.headers && res.headers['content-length'] > remainingLimit) { + // Calling this will cause remaining data in the response to be dropped and the socket to be destroyed. + // Will emit error and close events. + req.destroy(new Error(`Expected response size is too big. Rejected to download ${res.headers['content-length']} bytes`)); + return; + } + if (isResponseBinary) { + let buffer = Buffer.from([]); + res.on('data', (b) => { + remainingLimit -= b.length; + if (remainingLimit < 0) { + // Calling this will cause remaining data in the response to be dropped and the socket to be destroyed. + // Will emit error and close events. + req.destroy(new Error('Response size limit exceeded')); + } + buffer = Buffer.concat([buffer, b], buffer.length + b.length); + }); + res.on('end', () => { + const result = createResult(res.statusCode, res.headers, buffer); + resolve(result); + }); + } + else { + let data = ''; + res.on('data', (d) => { + remainingLimit -= d.length; + if (remainingLimit < 0) { + // Calling this will cause remaining data in the response to be dropped and the socket to be destroyed. + // Will emit error and close events. + req.destroy(new Error('Response size limit exceeded')); + } + data += d; + }); + res.on('end', () => { + const result = isContentTypeJson(res) ? + createResult(res.statusCode, res.headers, jsonUtils.parse(data)) : + createResult(res.statusCode, res.headers, data); + resolve(result); + }); + } + }); + req.on('timeout', () => { + req.destroy(); + }); + req.on('error', (err) => { + reject(err); + }); + if (body) { + if (body instanceof Uint8Array) { + req.write(body); + } + else { + req.write(JSON.stringify(body)); + } + } + req.end(); + }); +} + +module.exports = nodeRequest; diff --git a/QuoteVerification/QuoteVerificationService/src/common/readFileSafely.js b/QuoteVerification/QuoteVerificationService/src/common/readFileSafely.js new file mode 100644 index 00000000..29e99c36 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/readFileSafely.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const Buffer = require('../util/buffer'); +const logger = require('./logger').genericLogger; + +function getRealPathFromFileDescriptor(fd) { + return fs.readlinkSync('/proc/self/fd/' + fd); +} + +/** + * Reads file using file descriptor, blocking changes in the meantime + * In case filePath is a symlink, it checks where it directs and if target path is in allowed location + * inside user home directory or allowed mount point + * + * @param {string} filePath - path to file to read + * @param {string} encoding - default is 'utf8' + * @returns {string} file content + */ +function readFileSafely(filePath, encoding = 'utf8') { + const absolutePath = path.resolve(filePath); + const allowedLocations = [ + os.homedir() + path.sep, // user home directory + ]; + const fd = fs.openSync(absolutePath); + let content = ''; + try { + const link = getRealPathFromFileDescriptor(fd); + logger.trace('Loading file from: ' + absolutePath); + if (absolutePath !== link) { + logger.debug('Loading file from symlink: ' + absolutePath + ' which directs to: ' + link); + const locationAllowed = allowedLocations.some((prefix) => link.startsWith(prefix)); + if (!locationAllowed) { + throw new Error('Loading link which directs outside of provided locations: ' + JSON.stringify(allowedLocations) + ' is forbidden!'); + } + } + + const stat = fs.fstatSync(fd); + if (stat.isDirectory()) { + throw new Error(`Expected path to a file, not a directory. Are you sure path "${filePath}" is correct?`); + } + const buff = Buffer.alloc(stat.size); + fs.readSync(fd, buff, 0, buff.length); + content = buff.toString(encoding); + } + catch (err) { + logger.error('Problem loading file: ' + err); + throw err; + } + finally { + try { + fs.closeSync(fd); + } + catch (err) { + logger.error('Problem closing file ' + fd + ': ' + err); + } + } + return content; +} + +module.exports = readFileSafely; diff --git a/QuoteVerification/QuoteVerificationService/src/common/requestHandler.js b/QuoteVerification/QuoteVerificationService/src/common/requestHandler.js new file mode 100644 index 00000000..c0b81377 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/requestHandler.js @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const _ = require('lodash'); +const retry = require('retry'); +const url = require('fast-url-parser'); + +const nodeRequest = require('./nodeRequestHandler'); +const responseUtils = require('../util/responseUtils'); +const httpStatusCodes = require('./httpStatusCodes'); +const detailedErrorString = require('./detailedErrorString'); +const { HttpError, HttpNoRetryError, RuntimeError } = require('./restClientErrors'); + +class RequestHandler { + + constructor(config) { + this.config = config; + } + + /* Request prepare */ + + removeHeaderContentLength(headers) { + const CONTENT_LENGTH_FIELD = 'Content-Length'; + const explicitHeaders = _.clone(headers || {}); + + for (const headerName in explicitHeaders) { + if (headerName.toLowerCase() === CONTENT_LENGTH_FIELD.toLowerCase()) { + // Json flag sets content length automatically + delete explicitHeaders[headerName]; + } + } + + return explicitHeaders; + } + + async prepareRequestOptions(method, requestUrl, headers) { + const options = _.clone(this.config.options); + options.protocol = requestUrl.protocol; + options.method = method; + options.host = this.config.host; + options.port = this.config.port; + options.path = requestUrl.path; + options.headers = this.removeHeaderContentLength(headers); + return options; + } + + /* Response status */ + + isSuccessStatusCode(statusCode) { + return statusCode === httpStatusCodes.SUCCESS || statusCode === httpStatusCodes.CREATED; + } + + isNotFatalError(error) { + const statusCodeError = error.status >= httpStatusCodes.INTERNAL_SERVER_ERROR; + const notHttpError = !(error instanceof HttpError); + const httpNoRetryError = error instanceof HttpNoRetryError; + return !httpNoRetryError && (statusCodeError || notHttpError); + } + + /* Request retry utils */ + + createRetryOperation() { + const maxTimeout = (this.config.retryDelay * (1 + this.config.factor + Math.pow(this.config.factor, 2))); + + return retry.operation({ + maxTimeout, + minTimeout: this.config.retryDelay, + retries: this.config.retryCount, // Previous version counter was lower + factor: this.config.factor + }); + } + + async sendRequest(logger, method, path, body, headers, queryParams, isResponseBinary = false) { + const requestUrl = url.parse(url.format({ + protocol: this.config.protocol, + hostname: this.config.host, + port: this.config.port, + pathname: path, + query: queryParams + })); + const self = this; + const options = await this.prepareRequestOptions(method, requestUrl, headers); + logger.info(`Sending request to: ${requestUrl.href}`); + + return nodeRequest(options, body, isResponseBinary) + .then(async res => { + + if (responseUtils.statusCodeNotExists(res)) { + throw new RuntimeError('Empty status code received'); + } + + if (!self.isSuccessStatusCode(res.statusCode)) { + throw new HttpError(`Response status unrecognized ${res.statusCode}`, res.statusCode, res.body, res.headers); + } + const result = { + status: responseUtils.getStatusCodeSafely(res), + body: res.body, + headers: res.headers + }; + + if (result.headers !== undefined && 'request-id' in result.headers && logger.context.reqId !== `[reqId=${result.headers['request-id']}]`) { + logger.info(`Related request ID: ${result.headers['request-id']}`); + } + /* istanbul ignore else */ + if (logger.isTraceEnabled()) { + logger.trace('Returned response:', result.status, JSON.stringify(result.body)); + } + + return result; + }) + .catch(async error => { + logger.error(`Error while trying to ${method} ${requestUrl.href}: ${detailedErrorString(error)}`); + if (error instanceof HttpError || error instanceof HttpNoRetryError) { + throw error; + } + + throw new RuntimeError(error.message, { code: error.code, stack: error.stack }); + }); + } + + async sendRequestWithRetries(logger, method, path, jsonBody, headers, queryParams, isResponseBinary = false) { + const retryConditionFn = this.isNotFatalError; + return this.sendRequestWithRetriesBase(logger, method, path, jsonBody, headers, queryParams, isResponseBinary, retryConditionFn); + } + + isECONNRESET(err) { + // return err.message === 'socket hang up'; + return err.details && err.details.code === 'ECONNRESET'; // more general than above + } + + async sendRequestWithRetriesOnConnectionReset(logger, method, path, jsonBody, headers, queryParams, isResponseBinary = false) { + const retryConditionFn = this.isECONNRESET; + return this.sendRequestWithRetriesBase(logger, method, path, jsonBody, headers, queryParams, isResponseBinary, retryConditionFn); + } + + async sendRequestWithRetriesBase(logger, method, path, jsonBody, headers, queryParams, isResponseBinary, retryConditionFn) { + const self = this; + const operation = self.createRetryOperation(this.config); + + let handlers = null; + const promise = new Promise((resolve, reject) => { + handlers = { + resolve, + reject + }; + }); + + operation.attempt(currentAttempt => { + const sendRequest = self.sendRequest(logger, method, path, jsonBody, headers, queryParams, isResponseBinary); + + sendRequest + .then(async result => { + return handlers.resolve(result); + }) + .catch(async error => { + if (operation.retry(retryConditionFn(error))) { + const retryInfo = `Retrying call host with address: ${self.config.host}:${self.config.port} ` + + `... Attempt number: ${currentAttempt} out of ${self.config.retryCount}`; + logger.warn(error, `Request ended with an error. ${retryInfo}`); + + return; + } + + if (error instanceof HttpError || error instanceof HttpNoRetryError || error instanceof RuntimeError) { + return handlers.reject(error); + } + + return handlers.reject(new RuntimeError(error.message)); + }); + }); + + return promise; + } +} + +module.exports = RequestHandler; diff --git a/QuoteVerification/QuoteVerificationService/src/common/requestLogFormatter.js b/QuoteVerification/QuoteVerificationService/src/common/requestLogFormatter.js new file mode 100644 index 00000000..5909d92d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/requestLogFormatter.js @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const util = require('util'); +const _ = require('lodash'); + +const LENGTH_LIMIT = 15000; + +/** + * Formats a key-value pair with an optional prefix + * + * @param {string} key + * @param {Object} value + * @param {string} prefix + * + * @returns {string} returns (prefix)key=value + */ +function printableValue(key, value, prefix = '') { + if (_.isNull(value) || _.isUndefined(value)) { + return ''; + } + else if (_.isObject(value) && !_.isEmpty(value)) { + return util.format(`${prefix}${key}=%j`, value); + } + else if (_.isString(value)) { + return `${prefix}${key}='${value}'`; + } + else { + return ''; + } +} + +/** Formats a string with request or response headers and/or body + * + * @param {Object} headers - headers as object + * @param {Object} body - body as object or string + */ +function addHeadersAndBody(headers, body) { + return util.format('[%s%s]', + printableValue('headers', headers), + printableValue('body', body, ' ') + ); +} + +/** Formats a string with request or response header + * + * @param {string} reqMethod + * @param {string} reqUrl + * @param {Object} reqHeaders + */ +function formatRequestMessageWithoutBody(reqMethod, reqUrl, reqHeaders) { + return util.format(' <-- request %s %s [%s]', + reqMethod, reqUrl, printableValue('headers', reqHeaders)); +} + +/** + * Formats a string with request body + * + * @param {*} reqBody + */ +function formatRequestBodyMessage(reqBody) { + const body = printableValue('body', reqBody); + return body ? util.format(' [%s]', body) : ''; +} + +/** Formats a string with request or response headers and/or body + * + * @param {string} reqMethod + * @param {string} reqUrl + * @param {number} resStatus + * @param {Object|string} resHeaders + * @param {Object|string} resBody + * @param {number} duration - duration in ms + */ +function formatResponseMessage(reqMethod, reqUrl, resStatus, resHeaders, resBody, duration) { + const headersAndBody = addHeadersAndBody(resHeaders, resBody); + + if (headersAndBody.length < LENGTH_LIMIT) { + return [util.format(' --> response %s %s %d took %sms %s', + reqMethod, reqUrl, resStatus, duration, headersAndBody)]; + } + + const body = util.format('%j', resBody); + const numChunks = Math.ceil(body.length / LENGTH_LIMIT) + 1; + const messages = new Array(numChunks); + messages[0] = util.format(' --> response %s %s %d took %sms [%s][find large body in next log]', + reqMethod, reqUrl, resStatus, duration, printableValue('headers', resHeaders)); + for (let i = 1, o = 0; i < numChunks; ++i, o += LENGTH_LIMIT) { + messages[i] = util.format(' --> response body part %s of %s [%s]', i, numChunks - 1, + printableValue('body', body.substr(o, LENGTH_LIMIT))); + } + return messages; +} + +module.exports = { + addHeadersAndBody, + formatRequestMessageWithoutBody, + formatRequestBodyMessage, + formatResponseMessage +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/restClientErrors.js b/QuoteVerification/QuoteVerificationService/src/common/restClientErrors.js new file mode 100644 index 00000000..49893aed --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/restClientErrors.js @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +class RuntimeError extends Error { + constructor(message, details = {}) { + super(message); + this.details = details; + } +} + +class HttpNoRetryError extends Error { + constructor(message, status, body, headers) { + super(message); + + this.body = {}; + this.status = status; + this.headers = {}; + + if (body) { + this.body = body; + } + + if (headers) { + this.headers = headers; + } + } +} + +class HttpError extends Error { + constructor(message, status, body, headers) { + super(message); + + this.body = {}; + this.status = status; + this.headers = {}; + + if (body) { + this.body = body; + } + + if (headers) { + this.headers = headers; + } + } +} + +module.exports = { + HttpError, + RuntimeError, + HttpNoRetryError +}; diff --git a/QuoteVerification/QuoteVerificationService/src/common/tlsType.js b/QuoteVerification/QuoteVerificationService/src/common/tlsType.js new file mode 100644 index 00000000..d6edb835 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/common/tlsType.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const MTLS = 'MTLS'; +const TLS = 'TLS'; +const None = 'None'; + +const CIPHERS = + 'TLS_AES_256_GCM_SHA384:' + + 'TLS_AES_128_GCM_SHA256:' + + 'TLS_AES_128_CCM_SHA256: ' + + 'DHE-PSK-AES256-GCM-SHA384:' + + 'ECDHE-ECDSA-CHACHA20-POLY1305:' + + 'ECDHE-ECDSA-AES256-GCM-SHA384:' + + 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:' + + 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:' + + 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:' + + 'ECDHE-PSK-CHACHA20-POLY1305:' + + 'DHE-PSK-CHACHA20-POLY1305:' + + 'DHE-DSS-AES256-GCM-SHA384:' + + 'DHE-DSS-AES128-GCM-SHA256:' + + 'DHE-PSK-AES128-GCM-SHA256:' + + 'ECDHE-ECDSA-AES128-GCM-SHA256'; +const MIN_SECURE_PROTOCOL = 'TLSv1.2'; +const MAX_SECURE_PROTOCOL = 'TLSv1.3'; + +module.exports = { + MTLS, + TLS, + None, + CIPHERS, + MIN_SECURE_PROTOCOL, + MAX_SECURE_PROTOCOL, +}; diff --git a/QuoteVerification/QuoteVerificationService/src/configLoader.js b/QuoteVerification/QuoteVerificationService/src/configLoader.js new file mode 100644 index 00000000..1f65588d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/configLoader.js @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const config = require('./common/config'); +const ConfigLoader = config.ConfigLoader; +const _ = require('lodash'); + +class QVSConfig extends config.BaseRestService { + + constructor(configJson) { + + super(configJson); + this.validate(configJson, { + required: ['service', 'pcsClient', 'crlClient', 'healthCheck', 'logger', 'cache', 'target'], + properties: { + crlClient: { + required: ['retries', 'initialInterval', 'factor'], + properties: { + retries: { + type: 'number' + }, + initialInterval: { + type: 'number' + }, + factor: { + type: 'number' + } + } + }, + target: { + required: ['attestationReportSigningCaCertificate', 'attestationReportSigningCertificate', 'trustedRootPublicKey'], + attestationReportSigningCaCertificate: { + type: 'string' + }, + attestationReportSigningCertificate: { + type: 'string' + }, + trustedRootPublicKey: { + type: 'string' + } + } + + } + }); + + this.pcsClient = new config.RestClient(configJson.pcsClient); + if (configJson.vcsClient) { + this.vcsClient = new config.RestClient(configJson.vcsClient); + } + this.crlClient = configJson.crlClient; + this.healthCheck = _.extend({}, configJson.healthCheck); + this.logger = _.extend({}, configJson.logger); + this.cache = new config.Cache(configJson.cache); + this.target = configJson.target; + } +} + +const loader = new ConfigLoader(QVSConfig); + +module.exports = loader; diff --git a/QuoteVerification/QuoteVerificationService/src/handlers/certificateChainParser.js b/QuoteVerification/QuoteVerificationService/src/handlers/certificateChainParser.js new file mode 100644 index 00000000..91210d0b --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/handlers/certificateChainParser.js @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + 'use strict'; + + const { X509Certificate } = require('crypto'); + +/** + * Splits chain of certificates in PEM format + * @param {string} certificateChain + * @returns {Array.} array of certificates in PEM format + */ + function splitCertificateChain(certificateChain) { + return certificateChain + .replaceAll('\n-----BEGIN CERTIFICATE-----', '-----BEGIN CERTIFICATE-----') + .split('-----END CERTIFICATE-----') + .slice(0, -1) + .map(i => i + '-----END CERTIFICATE-----'); +} + +/** + * + * @param {string[]} chain + * @returns {Promise[]>} + */ +async function parseChain(chain) { + return Promise.all(chain.map(async(pem) => new X509Certificate(pem))); +} + +/** + * + * @param {X509Certificate[]} parsedChain + * @param {string} rootPublicKey + * @returns {number} + */ +function findRoot(parsedChain, rootPublicKey) { + const rootCaIndex = parsedChain.findIndex(cert => { + const pubKey = cert.publicKey.export({ type: 'spki', format: 'der' }).toString('hex'); + return pubKey === rootPublicKey; + }); + if (rootCaIndex < 0) { + throw new Error(`No trusted root CA in provided chain. Expected public key: ${rootPublicKey}`); + } + + return rootCaIndex; +} + +/** + * @typedef {Object} TcbInfoSigningChainInformation + * + * @property {X509Certificate} rootCa + * @property {X509Certificate} tcbSigningCert + * + * @property {string} rootCaPem + * @property {string} tcbSigningCertPem + * + */ + +/** + * Parses TCB Signing chain + * @param {string} rootPublicKey + * @param {string} tcbInfoSigningChain + * @returns {TcbInfoSigningChainInformation} + * + */ +async function parseTcbInfoSigningChainWithSpecificRoot(rootPublicKey, tcbInfoSigningChain) { + const tcbChain = splitCertificateChain(tcbInfoSigningChain); + if (tcbChain.length !== 2) { + throw new Error('TCB Info Signing Chain is not a chain of 2 certificates in PEM format' + tcbInfoSigningChain); + } + + if (tcbChain.length !== [...new Set(tcbChain)].length) { + throw new Error('TCB Info Signing Chain contains duplicated certificates'); + } + const parsedChain = await parseChain(tcbChain); + const rootCaIndex = findRoot(parsedChain, rootPublicKey); + + const tcbInfoSigningCertIndex = parsedChain.findIndex((cert, index) => index !== rootCaIndex && + cert.issuer === parsedChain[rootCaIndex].subject); + + if (tcbInfoSigningCertIndex < 0) { + throw new Error('No TCB Info Signing Cert issued by trusted root CA found in provided chain.'); + } + return { + // X509 Certificates + rootCa: parsedChain[rootCaIndex], + tcbInfoSigningCert: parsedChain[tcbInfoSigningCertIndex], + // PEM Certificates + rootCaPem: tcbChain[rootCaIndex], + tcbInfoSigningCertPem: tcbChain[tcbInfoSigningCertIndex] + }; +} + +/** + * @typedef {Object} PckChainInformation + * + * @property {X509Certificate} rootCa + * @property {X509Certificate} intermediateCa + * @property {X509Certificate} pckCert + * + * @property {string} rootCaPem + * @property {string} intermediateCaPem + * @property {string} pckCertPem + * + */ + +/** + * Parses PCK certificate chain + * @param {string} rootPublicKey + * @param {string} certificationData + * @returns {PckChainInformation} + */ +async function parseCertificateChainWithSpecificRoot(rootPublicKey, certificationData) { + const pckChain = splitCertificateChain(certificationData); + + if (pckChain.length !== 3) { + throw new Error('Certification data is not a chain of 3 certificates in PEM format' + certificationData); + } + + if (pckChain.length !== [...new Set(pckChain)].length) { + throw new Error('Certification data contains duplicated certificates'); + } + + const parsedChain = await parseChain(pckChain); + const rootCaIndex = findRoot(parsedChain, rootPublicKey); + + const intermediateCaIndex = parsedChain.findIndex((cert, index) => index !== rootCaIndex && + cert.issuer === parsedChain[rootCaIndex].subject); + if (intermediateCaIndex < 0) { + throw new Error('No intermediate CA issued by trusted root CA found in provided chain.'); + } + + const pckCertIndex = parsedChain.findIndex((cert, index) => index !== rootCaIndex && + index !== intermediateCaIndex && + cert.issuer === parsedChain[intermediateCaIndex].subject); + if (pckCertIndex < 0) { + throw new Error('No PCK cert issued by intermediate CA found in provided chain.'); + } + + return { + // X509 Certificates + rootCa: parsedChain[rootCaIndex], + intermediateCa: parsedChain[intermediateCaIndex], + pckCert: parsedChain[pckCertIndex], + // PEM Certificates + rootCaPem: pckChain[rootCaIndex], + intermediateCaPem: pckChain[intermediateCaIndex], + pckCertPem: pckChain[pckCertIndex] + }; +} + +module.exports = { + parseCertificateChainWithSpecificRoot, + parseTcbInfoSigningChainWithSpecificRoot +}; diff --git a/QuoteVerification/QuoteVerificationService/src/handlers/health.js b/QuoteVerification/QuoteVerificationService/src/handlers/health.js new file mode 100644 index 00000000..cb48a530 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/handlers/health.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const healthChecker = require('../healthChecker'); + +module.exports = { + checkHealth: async(ctx) => { + await healthChecker.handleRequest(ctx); + } +}; diff --git a/QuoteVerification/QuoteVerificationService/src/handlers/verifyAttestationEvidence.js b/QuoteVerification/QuoteVerificationService/src/handlers/verifyAttestationEvidence.js new file mode 100644 index 00000000..039fe5ad --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/handlers/verifyAttestationEvidence.js @@ -0,0 +1,731 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const _ = require('lodash'); +const moment = require('moment'); + +const certificateChainParser = require('./certificateChainParser'); +const pcs = require('../clients/pcsAccessLayer/PCSClient'); +const crl = require('../clients/crlAccessLayer/CRLClient'); +const qvl = require('../qvl'); +const qvlStatus = require('../qvl/status'); +const errorSource = require('../qvl/verifyQuoteErrorSource'); +const vcs = require('../clients/vcsAccessLayer/VCSClient'); +const config = require('../configLoader').getConfig(); +const STATUSES = require('../koa/response').STATUSES; +const random = require('../util/random'); +const validator = require('validator'); + +const uriString = 'URI:'; +const tdxsvnOffsetInQuote = 48; // 48 is the size of header +const tdxsvnSize = 16; + +const attestationReportSigningChain = config.target.attestationReportSigningCertificate + config.target.attestationReportSigningCaCertificate; +const trustedRootPublicKey = decodeURIComponent(config.target.trustedRootPublicKey); + +/** + * @typedef {import('../jsDoc/types').Logger} Logger + * @typedef {import('../jsDoc/types').TcbInfo} TcbInfo + * @typedef {import('../jsDoc/types').EnclaveIdentity} EnclaveIdentity + * @typedef {import('../jsDoc/types').TcbLevel} TcbLevel + * @typedef {import('../jsDoc/types').EnclaveTcbLevel} EnclaveTcbLevel + */ + +/** + * Handler for verify attestation evidence endpoint + * @param {Object} ctx - koa context + * @returns + */ +async function verifyAttestationEvidence(ctx) { + const nonce = ctx.request.body.nonce; // optional + + if (_.isString(nonce) && nonce.length > 32) { + ctx.log.error('Provided nonce is longer than 32 characters: ', nonce); + ctx.status = 400; + return; + } + + const isvQuote = ctx.request.body.isvQuote; + if (!_.isString(isvQuote) || !validator.isBase64(isvQuote)) { + ctx.log.error('isvQuote is not provided or is not a base64 string: ', isvQuote); + ctx.status = 400; + return; + } + const quote = Buffer.from(isvQuote, 'base64'); + + let certificationData; + let pckCertData; + let rootCaPem; let intermediateCaPem; let pckCertPem; + let pckCertCrlDistributionPoint; let rootCaCrlDistributionPoint; + try { + certificationData = await getType5CertificationDataFromQuote(ctx.reqId, quote); + ({ rootCaPem, intermediateCaPem, pckCertPem } = await certificateChainParser.parseCertificateChainWithSpecificRoot(trustedRootPublicKey, certificationData)); + pckCertData = await getPckCertificateData(ctx.reqId, pckCertPem); + [pckCertCrlDistributionPoint, rootCaCrlDistributionPoint] = await Promise.all([pckCertPem, rootCaPem].map(cert => getCrlUrl(ctx.reqId, cert))); + } + catch (error) { + ctx.log.error(error); + ctx.status = 400; + return; + } + + try { + const { quoteType } = readQuoteVersion(quote); + const isvsvn = readIsvsvn(quote); + + const getTcbInfo = (quoteType === 'SGX') ? pcs.getSgxTcbInfo : pcs.getTdxTcbInfo; + const getQeIdentity = (quoteType === 'SGX') ? pcs.getSgxQeIdentity : pcs.getTdxQeIdentity; + + const requestPromises = { + tcbInfoData: getTcbInfo(pckCertData.fmspc, ctx.reqId, ctx.log), + qeIdentity: getQeIdentity(ctx.reqId, ctx.log), + pckCertCrl: crl.getCrlFromDistributionPoint(pckCertCrlDistributionPoint, ctx.reqId, ctx.log), + rootCrl: crl.getCrlFromDistributionPoint(rootCaCrlDistributionPoint, ctx.reqId, ctx.log) + }; + const requiredCollateral = _.zipObject(Object.keys(requestPromises), await Promise.all(Object.values(requestPromises))); + + const { tcbInfo, tcbInfoSigningChainData } = await readTcbInfoAndIssuerChainFromResponse(requiredCollateral.tcbInfoData); + const qeIdentity = readQeIdentityFromResponse(requiredCollateral.qeIdentity); + const pckCertCrl = parseCrlFromDistributionPoint(requiredCollateral.pckCertCrl); + const rootCrl = parseCrlFromDistributionPoint(requiredCollateral.rootCrl); + + const caChain = `${rootCaPem}\n${intermediateCaPem}`; + const tcbInfoSigningChain = `${tcbInfoSigningChainData.rootCaPem}\n${tcbInfoSigningChainData.tcbInfoSigningCertPem}`; + const tcbInfoString = JSON.stringify(tcbInfo); + const qeIdentityString = JSON.stringify(qeIdentity); + + const result = await qvl.verifyQuote(ctx.reqId, quote, pckCertPem, tcbInfoString, qeIdentityString, caChain, tcbInfoSigningChain, pckCertCrl, rootCrl, rootCaPem, tcbInfoSigningChainData.rootCaPem); + + const { status, isvQuoteStatus } = parseStatus(result.status, result.errorSource, ctx.log); + if (status !== 200) { + ctx.status = status; + return; + } + /* Mandatory report fields */ + const report = { + id: generateReportId(), + timestamp: prepareTimestampForReport(), + version: 5, + attestationType: 'ECDSA', + teeType: teeTypeForReport(quoteType, pckCertData.sgxType), + isvQuoteStatus, + isvQuoteBody: base64quoteHeaderAndBodyOnly(quote, quoteType), + tcbEvaluationDataNumber: qeIdentity.enclaveIdentity.tcbEvaluationDataNumber, + tcbDate: tcbInfo.tcbInfo.issueDate, + }; + + /* Optional report fields */ + if (nonce) { + report.nonce = nonce; + } + + const pcesvn = pckCertData.pcesvn; + const cpusvn = pckCertData.cpusvn; + const tdxsvn = (quoteType === 'TDX') ? quote.slice(tdxsvnOffsetInQuote, tdxsvnOffsetInQuote + tdxsvnSize) : undefined; + + const matchedTcbInfoTcbLevel = matchTcbInfoTcbLevel(tcbInfo, cpusvn, pcesvn, tdxsvn); + if (!matchedTcbInfoTcbLevel) { + ctx.log.error(`Could not match TCB level (${JSON.stringify(cpusvn)}|${pcesvn}|{${JSON.stringify(tdxsvn)}) from certificate/quote to any TCB level from TCB Info`); + ctx.status = 400; + return; + } + ctx.log.info(`Matched to ${matchedTcbInfoTcbLevel.tcbStatus}. Matched TCB level is ${JSON.stringify(matchedTcbInfoTcbLevel)}`); + + const matchedEnclaveTcbTcbLevel = matchEnclaveTcbTcbLevel(qeIdentity, isvsvn); + if (!matchedEnclaveTcbTcbLevel) { + ctx.log.error(`Could not match ISVSVN (${isvsvn}) from quote to any TCB level from Enclave Identity`); + ctx.status = 400; + return; + } + ctx.log.info(`Matched Enclave TCB Level ${JSON.stringify(matchedEnclaveTcbTcbLevel)}`); + + if (['OK', 'TCB_OUT_OF_DATE', 'TCB_OUT_OF_DATE_AND_CONFIGURATION_NEEDED', 'CONFIGURATION_NEEDED', 'SW_HARDENING_NEEDED', 'CONFIGURATION_AND_SW_HARDENING_NEEDED'].includes(isvQuoteStatus)) { + if (matchedTcbInfoTcbLevel.advisoryIDs || matchedEnclaveTcbTcbLevel.advisoryIDs) { + report.advisoryURL = 'https://security-center.intel.com'; + const advisoryIds = collectAdvisoryIds(matchedTcbInfoTcbLevel, matchedEnclaveTcbTcbLevel); + if (!_.isEmpty(advisoryIds)) { + report.advisoryIDs = _.sortedUniq(advisoryIds.sort()); + } + } + } + + if (['TCB_OUT_OF_DATE', 'TCB_OUT_OF_DATE_AND_CONFIGURATION_NEEDED'].includes(isvQuoteStatus)) { + const tcbComponentsOutOfDate = collectTcbComponentsOutOfDate(quoteType, tcbInfo, matchedTcbInfoTcbLevel); + if (!_.isEmpty(tcbComponentsOutOfDate)) { + report.tcbComponentsOutOfDate = tcbComponentsOutOfDate; + } + } + + const configuration = configurationForReport(pckCertData); + if (configuration.length > 0) { + report.configuration = configuration; + } + + await signReport(report, ctx); + + ctx.status = 200; + ctx.body = report; + } + catch (error) { + ctx.log.error(error); + ctx.status = error.status || 500; + } +} + +/** + * Concatenates and sorts advisoryIds from matched tcb levels + * @param {TcbLevel} matchedTcbInfoTcbLevel - Tcb level from TcbInfo + * @param {EnclaveTcbLevel} matchedEnclaveTcbTcbLevel - Tcb level from EnclaveTcb + * @return {Array.} + */ +function collectAdvisoryIds(matchedTcbInfoTcbLevel, matchedEnclaveTcbTcbLevel) { + const advisoryIds = []; + addAdvisoryIdsToArray(advisoryIds, matchedTcbInfoTcbLevel); + addAdvisoryIdsToArray(advisoryIds, matchedEnclaveTcbTcbLevel); + const sortedAdvisoryIds = _.sortedUniq(advisoryIds.sort()); + return sortedAdvisoryIds; +} + +/** + * Adds advisoryIds from tcbLevel to provided array + * @param {Array.} array + * @param {TcbLevel|EnclaveTcbLevel} tcbLevel + */ +function addAdvisoryIdsToArray(array, tcbLevel) { + const advisoryIDs = tcbLevel.advisoryIDs; + if (_.isArray(advisoryIDs) && !_.isEmpty(advisoryIDs)) { + array.push(...advisoryIDs); + } +} + +/** + * Selects TcbComponents which are out of date + * @param {string} quoteType + * @param {TcbInfo} tcbInfo + * @param {TcbLevel} matchedTcbInfoTcbLevel + * @return {Array.<{category: string, type: string}>} + */ +function collectTcbComponentsOutOfDate(quoteType, tcbInfo, matchedTcbInfoTcbLevel) { + const highestCpuSvn = getHighestCpuSvn(tcbInfo); + + let tcbComponentsOutOfDate = matchedTcbInfoTcbLevel.tcb.sgxtcbcomponents.reduce((filtered, svn, i) => { + if ((svn.category || svn.type) && highestCpuSvn[i].svn > svn.svn) { filtered.push(_.omit(svn, 'svn')); } // copy only 'category' and 'type' fields if present + return filtered; + }, []); + if (quoteType === 'TDX') { + const highestTdxSvn = getHighestTdxSvn(tcbInfo); + tcbComponentsOutOfDate = tcbComponentsOutOfDate.concat(matchedTcbInfoTcbLevel.tcb.tdxtcbcomponents.reduce((filtered, svn, i) => { + if ((svn.category || svn.type) && highestTdxSvn[i].svn > svn.svn) { filtered.push(_.omit(svn, 'svn')); } // copy only 'category' and 'type' fields if present + return filtered; + }, [])); + } + return tcbComponentsOutOfDate; +} + +/** + * Parse status from QVL to http status and isvQuoteStatus + * @param {Number} status + * @param {(Number|undefined)} source + * @param {Logger} log + * @returns {{status: Number, isvQuoteStatus: String?}} status + */ +function parseStatus(status, source, log) { + const isvQuoteStatus = getIsvQuoteStatus(status); + if (isvQuoteStatus !== null) { + return { status: 200, isvQuoteStatus }; + } + const msg = getErrorMessage(status, source); + if (msg !== null) { + log.error(msg); + return { status: 400 }; + } + + if (status === qvlStatus.STATUS_TRUSTED_ROOT_CA_INVALID || status === qvlStatus.STATUS_TRUSTED_ROOT_CA_UNSUPPORTED_FORMAT) { + log.error('Invalid trusted root CA cert. Check config.'); + } + else { + log.error('Unrecognized QVL status: ' + status); + } + + return { status: 500 }; +} + +/** + * Translates quote verification status (as defined in QuoteVerification.h in QVL) to isvQuoteStatus. + * @param {Number} status + * @returns {String?} isvQuoteStatus to corresponding qvlStatus or null if can't match qvlStatus + */ +function getIsvQuoteStatus(status) { + switch (status) { + case qvlStatus.STATUS_OK: + return 'OK'; + case qvlStatus.STATUS_INVALID_QUOTE_SIGNATURE: + return 'SIGNATURE_INVALID'; + case qvlStatus.STATUS_SGX_INTERMEDIATE_CA_REVOKED: + case qvlStatus.STATUS_SGX_PCK_REVOKED: + case qvlStatus.STATUS_PCK_REVOKED: + case qvlStatus.STATUS_TCB_REVOKED: + return 'REVOKED'; + case qvlStatus.STATUS_TCB_OUT_OF_DATE: + return 'TCB_OUT_OF_DATE'; + case qvlStatus.STATUS_TCB_CONFIGURATION_NEEDED: + return 'CONFIGURATION_NEEDED'; + case qvlStatus.STATUS_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED: + return 'TCB_OUT_OF_DATE_AND_CONFIGURATION_NEEDED'; + case qvlStatus.STATUS_TCB_SW_HARDENING_NEEDED: + return 'SW_HARDENING_NEEDED'; + case qvlStatus.STATUS_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED: + return 'CONFIGURATION_AND_SW_HARDENING_NEEDED'; + default: + return null; + } +} + +/** + * Returns error message for quote verification status (as defined in QuoteVerification.h in QVL) and error source (as defined in verifyQuoteErrorSource in QVL wrapper) + * @param {Number} status + * @param {Number} source + * @returns {String?} message or null if unrecognised status + */ +function getErrorMessage(status, source) { + if (!Number.isInteger(source)) { + return null; + } + const isStatusOneOf = (array, expectedErrorSource) => array.includes(status) && (expectedErrorSource === undefined || expectedErrorSource === source); + const statusMessage = ` (qvlStatus: ${status}, errorSource: ${source})`; + + if (status === qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT && source === errorSource.VERIFY_PCK_CERTIFICATE) { + return 'Some parameters may be null. ' + + 'Certs in PCK cert chain may not be properly encoded to PEM format (including trusted root). ' + + 'PCK cert chain may have wrong number of certs.'; + } + if (isStatusOneOf([ + qvlStatus.STATUS_SGX_ROOT_CA_MISSING, + qvlStatus.STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS, + qvlStatus.STATUS_SGX_ROOT_CA_INVALID_ISSUER, + ], errorSource.VERIFY_PCK_CERTIFICATE) || + status === qvlStatus.STATUS_SGX_PCK_CERT_CHAIN_UNTRUSTED) { + return 'Issue with parsing root CA cert' + statusMessage; + } + if (isStatusOneOf([ + qvlStatus.STATUS_SGX_INTERMEDIATE_CA_MISSING, + qvlStatus.STATUS_SGX_INTERMEDIATE_CA_INVALID_EXTENSIONS, + qvlStatus.STATUS_SGX_INTERMEDIATE_CA_INVALID_ISSUER + ])) { + return 'Issue with parsing intermediate CA cert' + statusMessage; + } + if (isStatusOneOf([ + qvlStatus.STATUS_SGX_PCK_MISSING, + qvlStatus.STATUS_SGX_PCK_INVALID_EXTENSIONS, + qvlStatus.STATUS_SGX_PCK_INVALID_ISSUER, + qvlStatus.STATUS_UNSUPPORTED_PCK_CERT_FORMAT, + qvlStatus.STATUS_INVALID_PCK_CERT + ])) { + return 'Issue with parsing PCK cert' + statusMessage; + } + if (isStatusOneOf([ + qvlStatus.STATUS_UNSUPPORTED_QUOTE_FORMAT, + qvlStatus.STATUS_INVALID_QE_REPORT_SIGNATURE, + qvlStatus.STATUS_INVALID_QE_REPORT_DATA, + qvlStatus.STATUS_QE_IDENTITY_MISMATCH, + qvlStatus.STATUS_TDX_MODULE_MISMATCH, + ])) { + return 'Issue with parsing quote' + statusMessage; + } + switch (status) { + case qvlStatus.STATUS_TCB_NOT_SUPPORTED: + return 'No matching TCB Level found'; + case qvlStatus.STATUS_SGX_PCK_CERT_CHAIN_EXPIRED: + return 'Either of certs in PCK cert chain has expired.'; + default: + return null; + } +} + +/** + * Reads certificate chain from quote + * @param {string} reqId - request id + * @param {Buffer} quote + * @throws {Error} if quote is invalid or has type different than 5 + * @returns {string} certification data + */ +async function getType5CertificationDataFromQuote(reqId, quote) { + let certificationData; + try { + certificationData = await qvl.getCertificationData(reqId, quote); + } + catch (e) { + throw new Error('Failed to retrieve certification data from quote', e); + } + + if (certificationData.type !== 5) { + throw new Error('Not supported certification data type: ' + certificationData.type); + } + return certificationData.data; +} + +/** + * Reads data from special extensions in PCK Certificate + * @param {string} reqId + * @param {string} pckCertPem + * @returns {Promise<{fmspc: string, sgxType: string, dynamicPlatform: boolean, cachedKeys: boolean, smtEnabled: boolean}>} + */ +async function getPckCertificateData(reqId, pckCertPem) { + try { + return await qvl.getPckCertificateData(reqId, pckCertPem); + } + catch (error) { + throw new Error('PCK Cert does not contain required extensions', error); + } +} + +/** + * Reads TcbInfo and TcbInfoIssuerChain from PCS response and parses TCB Info Signing Chain + * @param {{ status: number, body: {}}} response + * @throws {Error} if http status is different from 200 OK + * @returns {{tcbInfo: TcbInfo, tcbInfoIssuerChain: string}} response body + */ +async function readTcbInfoAndIssuerChainFromResponse(response) { + let err; + switch (response.status) { + case STATUSES.STATUS_OK.httpCode: + break; + case STATUSES.STATUS_TCB_NOT_FOUND.httpCode: + err = new Error('Failed to retrieve required TcbInfo. PCS returned status: ' + response.status); + err.status = 400; + throw err; + default: + err = new Error('Failed to retrieve required TcbInfo. PCS returned status: ' + response.status); + err.status = 500; + throw err; + } + const tcbInfo = response.body; + const tcbInfoIssuerChain = decodeURIComponent(response.headers['tcb-info-issuer-chain']); + const tcbInfoSigningChainData = await certificateChainParser.parseTcbInfoSigningChainWithSpecificRoot(trustedRootPublicKey, + tcbInfoIssuerChain); + return { + tcbInfo, + tcbInfoIssuerChain, + tcbInfoSigningChainData + }; +} + +/** + * Reads QeIdentity from PCS response + * @param {{ status: number, body: {}}} response + * @throws {Error} if http status is different from 200 OK + * @returns {{}} response body + */ +function readQeIdentityFromResponse(response) { + if (response.status !== STATUSES.STATUS_OK.httpCode) { + throw new Error('Failed to retrieve required QeIdentity. PCS returned status: ' + response.status); + } + return response.body; +} + +/** + * Requests VCS to sign provided report structure. + * Sets X-IASReport-Signing-Certificate and X-IASReport-Signature headers. + * @param {*} report to sign + * @param {*} ctx - koa context + */ +async function signReport(report, ctx) { + ctx.set('X-IASReport-Signing-Certificate', attestationReportSigningChain); + const signResponse = await vcs.signVerificationReport(report, ctx.reqId, ctx.log); + if (signResponse.status !== STATUSES.STATUS_OK.httpCode) { + throw new Error('Failed to sign the report. VCS returned status: ' + signResponse.status); + } + const signature = signResponse.body.signature; + ctx.set('X-IASReport-Signature', signature); +} + +/** + * @typedef {Object} QuoteVersion + * @property {number} quoteVersion - 3, 4 or 5 + * @property {string} quoteType - SGX or TDX + * @property {number} quoteBodyVersion - 1.0 or 1.5 + */ + +/** + * Determines quote version and type: SGX, TDX1.0, TDX1.5 + * @param {Buffer} quote + * @returns {QuoteVersion} - SGX or TDX + */ +function readQuoteVersion(quote) { + let quoteType = 'SGX'; + const quoteVersion = quote.readUInt16LE(); + if (quoteVersion >= 4) { + const teeType = quote.readUint32LE(4); + if (teeType === 0x00000081) { + quoteType = 'TDX'; + } + } + let quoteBodyVersion = 1.0; + if (quoteVersion === 5) { + const quoteBodyType = quote.readUInt16LE(48); + if (quoteBodyType === 3) { + quoteBodyVersion = 1.5; + } + } + return { + quoteVersion, + quoteType, + quoteBodyVersion + }; +} + +const V3_ISVSVN_QUOTE_OFFSET = 822; +const V4_SGX_ISVSVN_QUOTE_OFFSET = 828; +const V4_TDX10_ISVSVN_QUOTE_OFFSET = 1028; +const V5_SGX_ISVSVN_QUOTE_OFFSET = 834; +const V5_TDX10_ISVSVN_QUOTE_OFFSET = 1034; +const V5_TDX15_ISVSVN_QUOTE_OFFSET = 1098; + +/** + * Reads ISVSVN from different types of quote + * @param {Buffer} quote + * @return {number} ISVSVN + */ +function readIsvsvn(quote) { + const { quoteVersion, quoteType, quoteBodyVersion } = readQuoteVersion(quote); + + let isvsvnOffset = 0; + if (quoteVersion === 3) { + isvsvnOffset = V3_ISVSVN_QUOTE_OFFSET; + } + else if (quoteVersion === 4) { + isvsvnOffset = (quoteType === 'TDX') ? V4_TDX10_ISVSVN_QUOTE_OFFSET : V4_SGX_ISVSVN_QUOTE_OFFSET; + } + else if (quoteVersion === 5) { + if (quoteType === 'SGX') { + isvsvnOffset = V5_SGX_ISVSVN_QUOTE_OFFSET; + } + else if (quoteBodyVersion < 1.25) { // 1.0 + isvsvnOffset = V5_TDX10_ISVSVN_QUOTE_OFFSET; + } + else if (quoteBodyVersion < 1.75) { // 1.5 + isvsvnOffset = V5_TDX15_ISVSVN_QUOTE_OFFSET; + } + else { + throw new Error('Unsupported quote body version: ' + quoteBodyVersion); + } + } + else { + throw new Error('Unsupported quote version: ' + quoteType); + } + if (isvsvnOffset > quote.length - 2) { + throw new Error('Version detection issue. Quote is too short to read ISVSVN.'); + } + return quote.readUInt16LE(isvsvnOffset); +} + +/** + * Converts crl to string format expected by QVL + * @param {{status: number, body: Buffer}} crl - response from getCrlFromDistributionPoint + * @returns {string} utf8 string for PEM, hex string for DER + */ +function parseCrlFromDistributionPoint(crl) { + if (crl.status !== STATUSES.STATUS_OK.httpCode) { + throw new Error('Failed to retrieve one of CRLs. Distribution Point returned status: ' + crl.status); + } + // PEM format - QVL requires utf8 string + if (crl.body.toString().startsWith('-----BEGIN')) { + return crl.body.toString(); + } + // DER format - QVL requires hex string + else { + return crl.body.toString('hex'); + } +} + +/** + * Generates long numeric id as string + * @returns {string} + */ +function generateReportId() { + return String(BigInt('0x' + random.uuid())); +} + +/** + * Returns current date and time in UTC in ISO 8601 standard + * @returns {string} timestamp in ISO 8601 standard + */ +function prepareTimestampForReport() { + const format = 'YYYY-MM-DDTHH:mm:ss'; + const utcTimestamp = moment.utc(new Date()).format(format); + return utcTimestamp + 'Z'; +} + +/** + * Converts enum values returned from QVL to enum values to be returned by QVS + * @param {string} quoteType + * @param {string} sgxType + * @throws {Error} if sgxType has not accepted value + * @returns {string} string representing enum to be returned in report + */ +function teeTypeForReport(quoteType, sgxType) { + if (quoteType === 'TDX') { + return 'TDX'; + } + if (sgxType === 'Standard') { + return 'SGX_STANDARD'; + } + if (sgxType === 'Scalable') { + return 'SGX_SCALABLE'; + } + if (sgxType === 'ScalableWithIntegrity') { + return 'SGX_SCALABLE_WITH_INTEGRITY'; + } + throw new Error('Unsupported sgxType'); +} + +/** + * Prepares a configuration for report based on PCK certification data + * @param {*} pckCertData + * @returns {Array.} + */ +function configurationForReport(pckCertData) { + const configuration = []; + if (pckCertData.dynamicPlatform) { + configuration.push('DYNAMIC_PLATFORM'); + } + if (pckCertData.cachedKeys) { + configuration.push('CACHED_KEYS'); + } + if (pckCertData.smtEnabled) { + configuration.push('SMT_ENABLED'); + } + return configuration; +} + +/** + * Crops quote buffer to header and body fields and converts result to base64 string + * @param {Buffer} quote + * @param {string} quoteType - SGX or TDX + * @returns {string} base64 representation of quote header and body + */ +function base64quoteHeaderAndBodyOnly(quote, quoteType) { + const quoteHeaderSize = 48; + let quoteBodySize = 384; + if (quoteType === 'TDX') { + quoteBodySize = 584; + } + const croppedBuffer = quote.subarray(0, quoteHeaderSize + quoteBodySize); + return croppedBuffer.toString('base64'); +} + +/** + * Get URL from CRL distribution point from certificate + * @param {string} reqId + * @param {string} certificate - PEM format + * @returns {string} + */ +async function getCrlUrl(reqId, certificate) { + const crlDistributionPoint = await qvl.getCrlDistributionPoint(reqId, certificate); + const idx = crlDistributionPoint.indexOf(uriString); + if (idx === -1) { + throw Error('CRL Distribution point is in wrong format ' + crlDistributionPoint); + } + return crlDistributionPoint.substring(idx + uriString.length, crlDistributionPoint.length); +} + +/** + * Finds TcbLevel with provided cpusvn, pcesvn and optional tdxsvn in tcbInfo + * @param {TcbInfo} tcbInfo + * @param {string} cpusvn + * @param {string} pcesvn + * @param {string} [tdxsvn] + * @returns {TcbLevel} + */ +function matchTcbInfoTcbLevel(tcbInfo, cpusvn, pcesvn, tdxsvn) { + const matchedTcbLevel = tcbInfo.tcbInfo.tcbLevels.find(level => { + const cpuSvnEqualOrGreater = cpusvn.every((svn, i) => svn >= level.tcb.sgxtcbcomponents[i].svn); // Readability trade off for performance. Invert condition and use 'some' instead of 'every' to gain some performance + if (cpuSvnEqualOrGreater && pcesvn >= level.tcb.pcesvn) { + if (tdxsvn) { + const teeSvnEqualOrGreater = tdxsvn.every((svn, i) => svn >= level.tcb.tdxtcbcomponents[i].svn); // Readability trade off for performance. Invert condition and use 'some' instead of 'every' to gain some performance + return (teeSvnEqualOrGreater && tdxsvn[1] === level.tcb.tdxtcbcomponents[1].svn); + } + else { + return true; + } + } + return false; + }); + return matchedTcbLevel; +} + +/** + * + * @param {EnclaveIdentity} qeIdentity + * @param {string} isvsvn + * @returns {EnclaveTcbLevel} + */ +function matchEnclaveTcbTcbLevel(qeIdentity, isvsvn) { + const matchedTcbLevel = qeIdentity.enclaveIdentity.tcbLevels.find(level => level.tcb.isvsvn === isvsvn); + return matchedTcbLevel; +} + +/** + * + * @param {TcbInfo} tcbInfo + * @returns + */ +function getHighestCpuSvn(tcbInfo) { + return getHighestSvn(tcbInfo, x => x.sgxtcbcomponents); +} + +/** + * + * @param {TcbInfo} tcbInfo + * @returns + */ +function getHighestTdxSvn(tcbInfo) { + return getHighestSvn(tcbInfo, x => x.tdxtcbcomponents); +} + +/** + * + * @param {TcbInfo} tcbInfo + * @param {function} svnSelector + * @returns + */ +function getHighestSvn(tcbInfo, svnSelector) { + return tcbInfo.tcbInfo.tcbLevels + .map(x => svnSelector(x.tcb)) + .reduce((highest, curr) => (curr.every((svn, i) => svn.svn >= highest[i].svn) ? curr : highest)); + +} + +module.exports = { + verifyAttestationEvidence +}; diff --git a/QuoteVerification/QuoteVerificationService/src/healthChecker.js b/QuoteVerification/QuoteVerificationService/src/healthChecker.js new file mode 100644 index 00000000..2bc50a22 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/healthChecker.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const HealthCache = require('./koa/koaHealthCache'); +const detailedErrorString = require('./common/detailedErrorString'); +const config = require('./configLoader').getConfig(); +const logger = require('./logger')(config); +const { version } = require('./package.json'); +const vcsHealth = require('./clients/vcsAccessLayer/VCSClient').getHealth; +const errorHandler = require('./common/errorHandler'); +const qvl = require('./qvl'); + +// initialize from configuration +const koaHealthCache = new HealthCache({ + frequencyMS: config.healthCheck.intervalMs, + validityMS: config.healthCheck.freshnessMs, + version, + logger +}); + +async function handleHealthConditionCallback(svcClientHealth, logger, reqId, callback) { + try { // All exceptions have to be handled inside because function is executed without await in common code + const response = await svcClientHealth(logger, { 'Request-ID': reqId }); + return callback(null, response.body); + } + catch (err) { + logger.error(`Request health ended with an error: ${detailedErrorString(err)}`); + return callback(null, err.body); + } +} + +koaHealthCache.addComponentHealthCondition('VerificationCryptoService', async(logger, reqId, callback) => { + return handleHealthConditionCallback(vcsHealth, logger, reqId, callback); +}); + +koaHealthCache.addComponentHealthCondition('QuoteVerificationLibrary', async(logger, reqId, callback) => { + try { // All exceptions have to be handled inside because function is executed without await in common code + const response = await qvl.getVersion(reqId, logger); + return callback(null, response.body); + } + catch (err) { + logger.error(errorHandler.errorStackTraceHandler(err, logger.isMultiline), 'Request health ended with an error.'); + return callback(null, err.body); + } +}); + +koaHealthCache.addComponentHealthCondition('this', async(logger, reqId, callback) => { + callback(null, { + status: koaHealthCache.status.OK, + version + }); +}); + + +module.exports = koaHealthCache; diff --git a/QuoteVerification/QuoteVerificationService/src/jsDoc/types.js b/QuoteVerification/QuoteVerificationService/src/jsDoc/types.js new file mode 100644 index 00000000..f924005a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/jsDoc/types.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * @typedef {Object} Logger + * @property {function} fatal + * @property {function} error + * @property {function} warn + * @property {function} info + * @property {function} trace + */ + +/** + * @typedef {Object} TcbComponent + * @property {number} svn + * @property {string} [category] + * @property {string} [type] + */ + +/** + * @typedef {Object} TcbLevel + * + * @property {Object} tcb + * @property {Array.} tcb.sgxtcbcomponents - array of 16 SGX TCB Components (as in CPUSVN) + * @property {Array.} [tcb.tdxtcbcomponents] - array of 16 TDX TCB Components (as in TEE TCB SVN array in TD Report) + * @property {number} tcb.pcesvn - PCS SVN + * @property {Date} tcbDate - representation of date and time when the TCB level was certified not to be vulnerable + * to any issues described in SAs that were published on or prior to this date. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {string} tcbStatus - TCB level status + * @property {Array.} [advisoryIDs] - Advisory IDs describing vulnerabilities that this TCB level is vulnerable to + */ + +/** + * @typedef {Object} TdxModule + * @property {string} mrsigner - base 16 encoded string representation of the measurement of a TDX SEAM module's signer + * @property {string} attributes - 8 hex-encoded bytes representing attributes golden value + * @property {string} attributesMask - 8 hex-encoded bytes representing mask to be applied + * to attributes value retrieved from the platform + */ + +/** + * @typedef {Object} TcbInfo + * @property {string} id - identified of the TCB Info + * @property {number} version - version of the structure + * @property {string} issueDate - representation of date and time the TCB information was created. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {string} nextUpdate - representation of date and time by which next TCB information will be issued. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {string} fmspc - base 16-encoded string representation of FMSPC (Family-Model-Stepping-Platform-CustomSKU) + * @property {string} pceid - base 16-encoded string representation of PCE Identifier + * @property {number} tcbType - type of TCB level composition + * @property {number} tcbEvaluationDataNumber - a monotonically increasing sequence number + * changed when Intel updates the content of the TCB evaluation data set: + * TCB Info, QE Identity and QVE Identity + * @property {TdxModule} [tdxModule] - representation of the TDX SEAM module + * @property {Array.} tcbLevels + */ + +/** + * @typedef {Object} EnclaveTcbLevel + * @property {Object} tcb + * @property {number} tcb.isvsvn - enclave ISV SVN + * @property {Date} tcbDate - representation of date and time when the TCB level was certified not to be vulnerable + * to any issues described in SAs that were published on or prior to this date. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {string} tcbStatus - TCB level status + * @property {Array.} [advisoryIDs] - Advisory IDs describing vulnerabilities that this TCB level is vulnerable to + */ + +/** + * @typedef {Object} EnclaveIdentity + * @property {string} id - identifier of the enclave + * @property {number} version - version of the structure + * @property {string} issueDate - representation of date and time the TCB information was created. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {string} nextUpdate - representation of date and time by which next TCB information will be issued. + * The time shall be in UTC and the encoding shall be compliant to + * ISO 8601 standard (YYYY-MM-DDThh:mm:ssZ). + * @property {number} tcbEvaluationDataNumber - a monotonically increasing sequence number + * changed when Intel updates the content of the TCB evaluation data set: + * TCB Info, QE Identity and QVE Identity + * @property {string} miscselect - 4 hex-encoded bytes representing miscselect golden value + * @property {string} miscselectMask - 4 hex-encoded bytes representing mask to be applied + * to miscselect retrieved from the platform + * @property {string} attributes - 16 hex-encoded bytes representing attributes golden value + * @property {string} attributesMask - 16 hex-encoded bytes representing mask to be applied + * to attributes value retrieved from the platform + * @property {string} mrsigner - 16 hex-encoded bytes representing mrsigner hash + * @property {number} isvprodid - enclave product id + * @property {Array.} tcbLevels + */ + + module.exports = {}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/commonDataValidator.js b/QuoteVerification/QuoteVerificationService/src/koa/commonDataValidator.js new file mode 100644 index 00000000..dc6c6b8c --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/commonDataValidator.js @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const _ = require('lodash'); +const validator = require('validator'); + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../jsDoc/types').Logger} Logger + */ + +/** + * Validates hex string + * + * @param {string} name + * @param {string} hexstring + * @param {number} expectedLength + * @param {Logger} log logger instance + * + * @return {boolean} + */ +function validateHexstring(name, hexstring, expectedLength, log) { + if (!isHexString(name, hexstring, log)) { + return false; + } + if (hexstring.length !== expectedLength) { + log.error(`Parameter ${name}(${hexstring}) has invalid length: expected: ${expectedLength}, but found ${hexstring.length}.`); + return false; + } + return true; +} + +/** + * Checks if value is hexstring + * + * @param {string} name + * @param {string} hexstring + * @param {Logger} log + * + * @return {boolean} + */ +function isHexString(name, hexstring, log) { + if (!_.isString(hexstring)) { + log.error(`Parameter ${name}(${hexstring}) is not a string.`); + return false; + } + if (!validator.isHexadecimal(hexstring)) { + log.error(`Parameter ${name}(${hexstring}) is not a valid hexstring.`); + return false; + } + + return true; +} + +module.exports = { + validateHexstring, + isHexString, +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/errors.js b/QuoteVerification/QuoteVerificationService/src/koa/errors.js new file mode 100644 index 00000000..1a31384f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/errors.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +class SgxError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + } +} + +class TcbOutOfDate extends SgxError {} +class FmspNotFound extends SgxError {} +class DeviceKeyNotFound extends SgxError {} +class PpidNotFound extends SgxError {} +class EventDataNotFound extends SgxError {} +class EnclaveTcbNotFound extends SgxError {} +class EnclaveIdentityNotFound extends SgxError {} +class InvalidPlatformManifest extends SgxError {} +class IncompatiblePackage extends SgxError {} +class PackageNotFound extends SgxError {} +class InvalidOrRevokedPackage extends SgxError {} +class InvalidRegistrationServer extends SgxError {} +class InvalidRequestSyntax extends SgxError {} + +class InternalError extends SgxError { + constructor(message, cause) { + super(message); + this.cause = () => cause; + } +} + +class InternalErrorWithNoRetryStatus extends SgxError { + constructor(body) { + super(undefined); + this.body = body; + } +} + +module.exports = { + InternalError, + InternalErrorWithNoRetryStatus, + TcbOutOfDate, + PpidNotFound, + DeviceKeyNotFound, + FmspNotFound, + EnclaveTcbNotFound, + EnclaveIdentityNotFound, + EventDataNotFound, + InvalidPlatformManifest, + IncompatiblePackage, + PackageNotFound, + InvalidOrRevokedPackage, + InvalidRegistrationServer, + InvalidRequestSyntax +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/init/serverInit.js b/QuoteVerification/QuoteVerificationService/src/koa/init/serverInit.js new file mode 100644 index 00000000..2ce00a90 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/init/serverInit.js @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + + +const process = require('process'); +const Promise = require('bluebird'); +const Koa = require('koa'); +const https = require('https'); +const http = require('http'); +const tlsType = require('../../common/tlsType'); + +const getCACertificatesSync = require('../../common/getCACertificatesSync'); +const configureCtxLoggerAndLogResponse = require('../middleware/configureCtxLoggerAndLogResponse'); +const readFileSafely = require('../../common/readFileSafely'); + + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../../jsDoc/types').KoaApplication} KoaApplication + * @typedef {import('../../jsDoc/types').Logger} Logger + * @typedef {import('../../jsDoc/types').KoaRouter} KoaRouter + */ + +/** + * Initializes koa application middlewares used in each server + * @returns {Promise} - preconfigured koa application + */ +/* eslint-disable no-invalid-this */ +async function getPreconfiguredApp() { + const app = new Koa(); + app.use(configureCtxLoggerAndLogResponse(this.logger)); + + for (let i = 0; i < this.middlewares.length; i++) { + app.use(this.middlewares[i]); + } + return app; +} + +/** + * @typedef {Object} ServerInitOptions + * + * @property {string} serviceName - name of the service + * @property {Array.} middleware - a list of middlewares + * @property {Logger} logger - logger + * @property {bool} validatorApp - if true use validator + */ + +module.exports = class ServerInit { + /** + * @param {ServerInitOptions} options - json with parameters: + */ + constructor(options) { + this.serviceName = options.serviceName; + this.logger = options.logger; + this.middlewares = options.middlewares; + this.serviceConfig = options.serviceConfig; + this.__servers = []; + + this.__setupProcessEvents(); + } + + __setupProcessEvents() { + const closeGracefully = async(signal) => { + this.logger.warn(`Received signal to terminate: ${signal}`); + try { + await this.closeAllServers(); + this.logger.info('Servers closed properly'); + process.exit(0); + } + catch (err) { + this.logger.error('Error occurred while trying to close servers:', err); + process.exit(1); + } + }; + + process.on('SIGINT', closeGracefully); + process.on('SIGTERM', closeGracefully); + process.on('exit', (code) => { + const exitMsg = `Exit process with code: ${code}`; + (code === 0) ? this.logger.info(exitMsg) : this.logger.error(exitMsg); + }); + } + + async __createHttpServer(app) { + return http.createServer(app.callback()); + } + + async __createHttpsServer(app) { + const isMtls = this.serviceConfig.tlsServerType === 'MTLS'; + const sslOptions = { + key: readFileSafely(this.serviceConfig.keyFile, 'utf8'), + cert: readFileSafely(this.serviceConfig.certFile, 'utf8'), + ca: isMtls ? getCACertificatesSync(this.serviceConfig.caCertDirectories).map(file => readFileSafely(file, 'utf8')) : undefined, + requestCert: isMtls, + rejectUnauthorized: isMtls, + maxVersion: tlsType.MAX_SECURE_PROTOCOL, + minVersion: tlsType.MIN_SECURE_PROTOCOL, + ciphers: tlsType.CIPHERS, + }; + + return https.createServer(sslOptions, app.callback()); + } + + /** + * Starts HTTP/HTTPS server with service API + * + * @param {number} port - port to listen on + * @param {KoaRouter} router - koa-router instance + * @param {string} serverType - HTTP or HTTPS + * + */ + async __startServer(port, router, serverType) { + const app = await getPreconfiguredApp.bind(this)(); + app.use(router.routes()); + app.use(router.allowedMethods()); + + const createServerFn = async() => (serverType === 'HTTPS' ? this.__createHttpsServer(app) : this.__createHttpServer(app)); + const server = await createServerFn(); + + // ELB timeout is 60s. General rule from internets to avoid 502/504: ELB timeout < keepAliveTimeout < headersTimeout + server.keepAliveTimeout = 70000; + server.headersTimeout = 80000; + server.requestTimeout = 60000; + return new Promise((resolve) => { + server.on('listening', () => { + this.__servers.push(server); + this.logger.info(`${serverType} server started on port ${port}!`); + resolve(); + }); + server.listen(port); + }); + } + + /** + * Starts HTTPS server with service API + * + * @param {number} port - port to listen on + * @param {KoaRouter} router - koa-router instance + */ + async startHTTPSServer(port, router) { + return this.__startServer(port, router, 'HTTPS'); + } + + /** + * Starts plain HTTP server with only health endpoint available + * + * @param {number} port - port to listen on + * @param {KoaRouter} router - koa-router instance + */ + async startHTTPServer(port, router) { + return this.__startServer(port, router, 'HTTP'); + } + + async closeAllServers() { + const closeAllPromises = this.__servers.map(server => new Promise((resolve, reject) => { + server.close(err => { + if (err) { reject(err); } + resolve(); + }); + })); + return Promise.all(closeAllPromises); + } +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/koaHealthCache.js b/QuoteVerification/QuoteVerificationService/src/koa/koaHealthCache.js new file mode 100644 index 00000000..da53b898 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/koaHealthCache.js @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const _ = require('lodash'); + +const detailedErrorString = require('../common/detailedErrorString'); +const random = require('../util/random'); +const clone = require('../util/clone'); +const util = require('util'); +const { + STATUS_OK, + STATUS_NOT_FOUND, + STATUS_SERVICE_UNAVAILABLE +} = require('./response').STATUSES; + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../jsDoc/types').KoaContext} KoaContext + * @typedef {import('../jsDoc/types').Logger} Logger + */ + +/** + * @typedef {Object} KoaHealthCacheOptions + * @property {number} frequencyMS - how often to run health check methods [milliseconds], default : 5000 + * @property {number} validityMS - duration after which cached component health status is outdated [milliseconds], default : 20000 + * @property {string} version - version of the main component that uses health cache functionality + * @property {Logger} logger - OBLIGATORY - logger object so that you know what is happening around + * @property {boolean} details - OPTIONAL - allows adding additional fields to output (.details) + */ + +/** + * Creates health cache object that will contain all data about components and it's children health + * + # @constructor + * @param {KoaHealthCacheOptions} options + */ +function KoaHealthCache(options) { + + const status = { + OK: 'OK', // The component responded that it is healthy + FAILED: 'FAILED', // The component responded that it is not healthy at all + UNKNOWN: 'UNKNOWN', // There are still no cached reports for this component + OUTDATED: 'OUTDATED' // Component didn't respond for the very long time + }; + + /** + * HealthCache handler + * + * @param {KoaContext} ctx + */ + async function handleRequest(ctx) { + const logHeader = 'healthCache.handleRequest '; + ctx.log.trace(`${logHeader}started...`); + + // Resolve components statuses for current moment + const componentReports = clone(getComponentReports(ctx.log)); + + if (componentReports.AttestationService && componentReports.AttestationService['X-IASReport-Signing-Certificate']) { + ctx.set('X-IASReport-Signing-Certificate', componentReports.AttestationService['X-IASReport-Signing-Certificate']); + delete componentReports.AttestationService['X-IASReport-Signing-Certificate']; + } + + if (ctx.params && ctx.params.component) { + return handleComponentRequest(ctx.params.component, componentReports, options.version, ctx); + } + // Respond + const [httpStatus, response] = buildResponse(componentReports); + ctx.status = httpStatus; + ctx.body = response; + + if (response.status !== status.OK || ctx.log.isTraceEnabled()) { + const logMsg = `${logHeader}done. HTTP status : ${httpStatus}. response : ${JSON.stringify(response)}`; + const logLvl = response.status === status.OK ? 'trace' : 'warn'; + ctx.log[logLvl](logMsg); + } + } + + async function handleComponentRequest(component, componentReports, mainComponentVersion, ctx) { + ctx.assert(componentReports[component], STATUS_NOT_FOUND.httpCode); + const retJson = { + status: componentReports[component].status, + version: mainComponentVersion, + lastChecked: new Date() + }; + retJson[component] = componentReports[component]; + ctx.assert(componentReports[component].status === status.OK, STATUS_SERVICE_UNAVAILABLE.httpCode, retJson); + // Respond + ctx.status = STATUS_OK.httpCode; + ctx.body = retJson; + } + + function isPositiveNumber(value) { + return _.isNumber(value) && value >= 0; + } + + // Needed since _.extend does only shallow extend + function extendRHCResponse(target, source) { + let extendedComponentStatus = null; + + if (source && + target && + source.componentStatus && + target.componentStatus) { + extendedComponentStatus = _.extend(target.componentStatus, source.componentStatus); + } + + const extended = _.extend(target, source); + + if (extendedComponentStatus !== null) { + extended.componentStatus = extendedComponentStatus; + } + + return extended; + } + + // Conditions to check health for components + const conditions = []; + // Health results for each component + const reports = []; + + // Helper array to prevent multiple checks of the same component + const pending = []; + + // Params to say that health caching is stopped or not + let stopped = true; + + // Logger is obligatory + if (!options.logger) { + const errorMsg = 'healthCache initialization failed - options.logger is obligatory!'; + throw new Error(errorMsg); + } + + const log = options.logger; + + log.info('healthCache initialization started...'); + + // Validate input + options.frequencyMS = positiveNumberOrDefault(options.frequencyMS, 'options.frequencyMS', 5000); + + options.validityMS = positiveNumberOrDefault(options.validityMS, 'options.validityMS', 20000); + + if (!_.isString(options.version)) { + options.version = 'UNDEFINED'; + log.warn('options.componentVersion not defined so should be defined by this component health condition'); + } + + log.info('healthCache initialization done.'); + + const version = { + NA: 'NA' // The componentVersion is unknown + }; + + function positiveNumberOrDefault(field, fieldName, defaultValue) { + if (!isPositiveNumber(field)) { + log.warn(`${fieldName} not defined - will use default value :${defaultValue}`); + return defaultValue; + } + else { + log.debug(`${fieldName} = ${field}`); + return field; + } + } + + function stripResult(logger, result) { + const strippedResult = {}; + + if (!result) { + logger.warn('Healthcheck result is undefined. Setting status to UNKNOWN.'); + strippedResult.status = status.UNKNOWN; + } + else { + if (result.status) { + strippedResult.status = result.status; + } + else { + logger.warn(`Field 'status' is missing in healthcheck result. Setting it's status to UNKNOWN. Full object: ${JSON.stringify(result)}`); + strippedResult.status = status.UNKNOWN; + } + if (result.version) { + strippedResult.version = result.version; + } + else { + logger.warn(`Field 'version' is missing in healthcheck result. Setting it's version to NA. Full object: ${JSON.stringify(result)}`); + strippedResult.version = version.NA; + } + if (result.componentStatus) { + strippedResult.componentStatus = result.componentStatus; + } + if (options.details && result.details) { + strippedResult.details = result.details; + } + if (result['X-IASReport-Signing-Certificate']) { + strippedResult['X-IASReport-Signing-Certificate'] = result['X-IASReport-Signing-Certificate']; + } + if (result['X-IASReport-Signing-Certificate-Status']) { + strippedResult['X-IASReport-Signing-Certificate-Status'] = result['X-IASReport-Signing-Certificate-Status']; + } + } + return strippedResult; + } + + async function runComponentHealthCheck(logger, reqId, componentHealthCheck) { + const result = await componentHealthCheck(logger, reqId); + return stripResult(logger, result); + } + + function validateResult(logger, logHeader, result) { + let errorMsg = null; + if (!_.isString(result.status)) { + errorMsg = '- result.status [string] field is obligatory!'; + } + + if (!_.isString(result.version)) { + errorMsg = '- result.componentVersion [string] field is obligatory!'; + } + + if (errorMsg !== null) { + errorMsg = logHeader + errorMsg; + throw new Error(errorMsg); + } + } + + function createResultHandler(logger, logHeader, componentName, result) { + logger.trace(`${logHeader}- polling done for component ${componentName}`); + validateResult(logger, logHeader, result); + + // Remember when the check was made + result.lastChecked = new Date(); + + if (result.status !== status.OK || logger.isTraceEnabled()) { + const logMsg = `[${componentName}] result = ${JSON.stringify(result)}`; + const logLvl = result.status === status.OK ? 'trace' : 'warn'; + logger[logLvl](logMsg); + } + + reports[componentName] = result; + } + + function validateAddComponentHealthConditionParams(componentName, componentHealthCheck) { + const logHeader = 'healthCache.validateAddComponentHealthConditionParams '; + let errorMsg = null; + if (!_.isString(componentName)) { + errorMsg = '- componentName should be a string value'; + } + else if (componentName in conditions) { + errorMsg = '- componentName should be unique'; + } + else if (!_.isFunction(componentHealthCheck)) { + errorMsg = '- componentHealthCheck should be a function'; + } + + if (errorMsg !== null) { + errorMsg = logHeader + errorMsg; + throw new Error(errorMsg); + } + } + + function pollingAction(logHeader, componentName, componentHealthCheck, frequencyMS) { + // Ensure that only one check is made at a time for given component + if (!pending[componentName] && !stopped) { + const cachedRequestId = random.uuid(); + const logger = log.scoped(cachedRequestId); + logger.trace(`${logHeader}- component health check started for component ${componentName}`); + pending[componentName] = true; + runComponentHealthCheck(logger, cachedRequestId, componentHealthCheck) + .then(result => { + result.cachedRequestId = cachedRequestId; + createResultHandler(logger, logHeader, componentName, result); + }) + .catch(err => { + logger.error(`${logHeader}- error while polling component ${componentName}: ${err.errorMessage || err.message || detailedErrorString(err)}`); + }) + .then(() => { + pending[componentName] = false; + logger.trace(`${logHeader}- component health check done for component ${componentName}`); + setTimeout(() => { + pollingAction(logHeader, componentName, componentHealthCheck, frequencyMS); + }, frequencyMS); + }); + } + } + + function addComponentHealthCondition(componentName, componentHealthCheck) { + const logHeader = 'healthCache.addComponentHealthCondition '; + log.info(`${logHeader} for component ${componentName} started...`); + validateAddComponentHealthConditionParams(componentName, componentHealthCheck); + + // Initialize arrays for this component + reports[componentName] = null; + pending[componentName] = false; + + log.debug(`${logHeader}- creating polling object for component ${componentName} started...`); + conditions[componentName] = () => { pollingAction(logHeader, componentName, util.promisify(componentHealthCheck), options.frequencyMS); }; + log.debug(`${logHeader}- creating polling object for component ${componentName} done.`); + + log.info(`${logHeader} for component ${componentName} done`); + } + + function run() { + log.info('healthCache starting...'); + stopped = false; + for (const componentName in conditions) { + conditions[componentName](); + } + log.info('healthCache started.'); + } + + function stop() { + log.info('healthCache stopping...'); + stopped = true; + log.info('healthCache stopped.'); + } + + function getReportForComponent(logger, logHeader, componentName) { + logger.trace(`${logHeader}- checking component ${componentName}...`); + + // If there is no result reported yet + // Then report it also as an error + let result = {}; + let cachedRequestId = version.NA; + if (reports[componentName] === null) { + logger.info(`${logHeader}- there is no health report stored for component ${componentName} yet. Setting UNKNOWN status`); + result = { status: status.UNKNOWN, version: version.NA }; + } + else { + const componentResult = Object.assign({}, reports[componentName]); + cachedRequestId = componentResult.cachedRequestId === undefined ? cachedRequestId : componentResult.cachedRequestId; + delete componentResult.cachedRequestId; + + // Check if the report wasn't stored earlier than it is acceptable + if (new Date().getTime() - componentResult.lastChecked.getTime() > options.validityMS) { + logger.warn(`${logHeader}- health report for component ${componentName} is outdated (${options.validityMS}ms validity time exceeded)`); + + // Remember the original status + result.lastStatus = componentResult; + result.status = status.OUTDATED; + } + else { + result = componentResult; + } + } + + logger.trace(`${logHeader}- checking component ${componentName} done.`); + return { result, cachedRequestId }; + } + + function getComponentReports(logger) { + const logHeader = 'getComponentReports '; + logger.trace(`${logHeader}started...`); + const output = {}; + + // Gather all last reports from components health checks + for (const componentName in reports) { + const { result, cachedRequestId } = getReportForComponent(logger, logHeader, componentName); + output[componentName] = result; + + if (result.status !== status.OK || logger.isTraceEnabled()) { + const logMsg = `[${componentName}] result = ${JSON.stringify(output[componentName])}, cachedRequestId = ${cachedRequestId}`; + const logLvl = result.status === status.OK ? 'trace' : 'warn'; + logger[logLvl](logMsg); + } + } + + logger.trace(`${logHeader}done.`); + return output; + } + + function buildResponse(componentReports) { + let thisComponent = null; + let mainStatus = status.OK; + const mainVersion = options.version; + let httpStatus = STATUS_OK.httpCode; + + // If any of component healths is not OK + // Then the overall health is also not so good + for (const componentName in componentReports) { + if (componentReports[componentName].status !== status.OK) { + mainStatus = status.FAILED; + httpStatus = STATUS_SERVICE_UNAVAILABLE.httpCode; + break; + } + } + + // If there is a special this component then it will be formatted separately + if (_.has(componentReports, 'this')) { + thisComponent = _.clone(componentReports.this); + delete componentReports.this; + delete thisComponent.status; + } + + let response = { + status: mainStatus, + version: mainVersion, + lastChecked: new Date(), + }; + + if (!_.isEmpty(componentReports)) { + response.componentStatus = componentReports; + } + + if (thisComponent !== null) { + response = extendRHCResponse(response, thisComponent); + } + return [httpStatus, response]; + } + + return { + + /** + * Component statuses + */ + status, + + /** + * Version field constants + */ + version, + + /** + * Adds component's health check condition that will be checked with some defined (in constructor) frequency + * + * @param {string} componentName - unique component name. If componentName === 'this' then it will be treated as root component and it's health report will be glued with response JSON root + * @param {function} componentHealthCheck - callback that will check component's health and return a healthcheck result object (or error in case of any) + * Valid healthcheck result object SHALL contain following fields: + * {string} status - "OK" is success, any other status is fault, in particular you should use value defined by healthCache.status + * {string} componentVersion - componentVersion of given component + * Valid healthcheck result MAY contain following field: + * {object} componentStatus - map of children components where component name is key and value is healthcheck result object + * If this method throws exception it will be caught and handled. + * + */ + addComponentHealthCondition, + + /** + * Start gathering and caching component statuses + */ + run, + + /** + * Stop gathering and caching component statuses + */ + stop, + + /** + * REST request handler + */ + handleRequest + }; +} + +module.exports = KoaHealthCache; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/koaHealthRouter.js b/QuoteVerification/QuoteVerificationService/src/koa/koaHealthRouter.js new file mode 100644 index 00000000..995c088a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/koaHealthRouter.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const Router = require('koa-router'); + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../jsDoc/types').KoaRouter} KoaRouter + */ + +/** + * Creates new router as its instances are mutable and should be used only once + * + * @param {function} healthHandler - handler for health router + * @param {string} urlPrefix - optional url prefix, '/' as default + * + * @returns {KoaRouter} + */ +function createHealthRouter(healthHandler, urlPrefix = '/') { + const router = new Router(); + if (urlPrefix === '/') { + router.get('/', healthHandler); + router.get('/:component', healthHandler); + } + else { + router.get(urlPrefix, healthHandler); + router.get(`${urlPrefix}/:component`, healthHandler); + } + return router; +} + +module.exports = { + createHealthRouter +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/maxClientsHandler.js b/QuoteVerification/QuoteVerificationService/src/koa/maxClientsHandler.js new file mode 100644 index 00000000..f8844232 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/maxClientsHandler.js @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const random = require('./../util/random'); +const { + STATUS_SERVICE_UNAVAILABLE +} = require('./response').STATUSES; + +global.currentClients = 0; + +function decreaseCurrentClients(log, serviceName, taskType, taskID) { + global.currentClients--; + log.info('Decreasing number of clients. Current number: %s', global.currentClients); + log.trace('STOP task_type: %s.%s task_id: %s', serviceName, taskType, taskID); +} + +function handleServiceBusy(ctx) { + ctx.status = STATUS_SERVICE_UNAVAILABLE.httpCode; + ctx.body = { code: 'KO.', message: 'Server too busy.' }; +} + +module.exports.createRequestManager = (serviceName, maxClients) => { + return { + manageRequest(taskType, action) { + return async(ctx, next) => { + const taskID = random.uuid(); + if (global.currentClients + 1 <= maxClients) { + global.currentClients++; + ctx.log.info('Increasing number of clients. Current number: %s', global.currentClients); + ctx.log.trace('START task_type: %s.%s task_id: %s', serviceName, taskType, taskID); + + const log = ctx.log; //ctx is cleared after finish/close + ctx.res.on('finish', () => { + decreaseCurrentClients(log, serviceName, taskType, taskID); + }); + ctx.res.on('close', () => { + if (!ctx.res.writableFinished) { + log.error('res.end() was not called. Something bad has happened.'); + // ctx.res.finished = true; + decreaseCurrentClients(log, serviceName, taskType, taskID); + } + }); + return action(ctx, next); + } + else { + ctx.log.info('Too many requests'); + ctx.log.trace('Discarding task_type: %s task_id: %s', taskType, taskID); + handleServiceBusy(ctx); + } + }; + } + }; +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/bodyParser.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/bodyParser.js new file mode 100644 index 00000000..c75ceaa5 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/bodyParser.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const bodyParser = require('koa-bodyparser'); + +/** + * Configures and returns bodyParser + * + * @param {Object} config - service configuration + * + * @returns {function} bodyParser middleware + */ +function getConfiguredBodyParser(config) { + const bodyParserOpts = { + enableTypes: ['json', 'text', 'form'] + }; + if (config && config.service && config.service.bodySizeLimits) { + bodyParserOpts.jsonLimit = config.service.bodySizeLimits.json; // default is 1mb + bodyParserOpts.textLimit = config.service.bodySizeLimits.text; // default is 1mb + bodyParserOpts.formLimit = config.service.bodySizeLimits.form; // default is 56kb + } + return bodyParser(bodyParserOpts); +} + +module.exports = { + getConfiguredBodyParser +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/configureCtxLoggerAndLogResponse.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/configureCtxLoggerAndLogResponse.js new file mode 100644 index 00000000..30261e25 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/configureCtxLoggerAndLogResponse.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + + +const { ERROR, WARN, INFO } = require('log4js').levels; + +const { formatResponseMessage } = require('../../common/requestLogFormatter'); +const { STATUS_INTERNAL_ERROR, STATUS_BAD_REQUEST } = require('../response').STATUSES; + +function getLevel(status) { + if (status >= STATUS_INTERNAL_ERROR.httpCode) { + return ERROR.levelStr.toLowerCase(); + } + if (status >= STATUS_BAD_REQUEST.httpCode) { + return WARN.levelStr.toLowerCase(); + } + return INFO.levelStr.toLowerCase(); +} + +module.exports = (logger) => { + return async(ctx, next) => { + const t = Date.now(); + ctx.log = logger; + ctx.res.on('finish', () => { + const dt = Date.now() - t; + const messages = formatResponseMessage( + ctx.request.method, ctx.request.originalUrl, ctx.status, ctx.response.headers, ctx.response.body, dt + ); + messages.forEach(msg => ctx.log[getLevel(ctx.status)](msg)); + }); + + return next(); + }; +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/errorHandler.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/errorHandler.js new file mode 100644 index 00000000..9dc8161e --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/errorHandler.js @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * Handles errors thrown during request processing + * + * Removes charset from Content-Type response header + * Logs an error and can hide detailed error message from client + * + * @param sendBodyOnErrors if body should be send with error responses + */ + +function errorHandler(sendBodyOnErrors) { + return async(ctx, next) => { + try { + await next(); + removeCharsetFromContentTypeHeader(ctx); + if (((ctx.status === 400 || ctx.status === 403 || ctx.status === 404 || ctx.status === 500 || ctx.status === 503) && !ctx.body) || + (ctx.status > 400 && ctx.status !== 403 && ctx.status !== 404 && ctx.status !== 500 && ctx.status !== 503)) { + ctx.throw(ctx.status); + } + } + catch (err) { + // HTTP errors won't be additionally logged + const status = err.status || err.statusCode; + if (!status) { + ctx.log.error(err); + } + ctx.status = status || 500; + // Bad Request HTTP error is the only one which gives more information to the user + // Top level services should not return body on all errors + if (status !== 400 || !sendBodyOnErrors) { + ctx.body = ''; + } + else { + ctx.body = { status: err.message }; + removeCharsetFromContentTypeHeader(ctx); + } + } + removeContentTypeWhenEmptyBody(ctx); + }; +} + +function removeCharsetFromContentTypeHeader(ctx) { + let contentType = ctx.response.get('Content-Type'); + if (contentType) { + contentType = contentType.replace('; charset=utf-8', ''); + ctx.set('Content-Type', contentType); + } +} + +function removeContentTypeWhenEmptyBody(ctx) { + if (!ctx.body) { + ctx.type = undefined; + } +} + +module.exports = { + withBodyOnError: errorHandler(true), + withNoBodyOnError: errorHandler(false) +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/rawBodyParser.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/rawBodyParser.js new file mode 100644 index 00000000..fa57c3f1 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/rawBodyParser.js @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const getRawBody = require('raw-body'); +const PassThrough = require('stream').PassThrough; + +/** + * Configures and returns bodyParser + * @param {Object} config - service configuration + * @returns {function} rawBodyParser middleware + */ +function getConfiguredRawBodyParser(config) { + return async(ctx, next) => { + try { + /* + Observed behaviour: + 0. keep-alive must be enabled + 1. client sends too long request + 2. server's middleware pareses the stream and throws error + 3. koa catches the error, returns response + 4. koa kills socket w/o informing the client + 5. client sends an another request and receives 'socket hung' error + + Wrap the stream in another stream to prevent koa noticing an error. + If there is no error, connection remains alive. + */ + const wrappedStream = ctx.request.req.on('error', ctx.onerror).pipe(new PassThrough()); + ctx.request.body = await getRawBody(wrappedStream, { + length: ctx.request.headers['content-length'], + limit: config.service.bodySizeLimits.blob + }); + await next(); + } + catch (err) { + ctx.log.error(err); + ctx.throw(err.statusCode ? err.statusCode : 500); + } + }; +} + +module.exports = { + getConfiguredRawBodyParser +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestBodyLogger.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestBodyLogger.js new file mode 100644 index 00000000..c03b7328 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestBodyLogger.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const requestLogFormatter = require('../../common/requestLogFormatter'); + +module.exports = async(ctx, next) => { + // Body has to be logged after parsing and accepting + // It make sense to print request headers before, as they show the reason of return of 413 http code if it happens + const msg = requestLogFormatter.formatRequestBodyMessage(ctx.request.body); + if (msg) { + ctx.log.info(msg); + } + + return next(); +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestControl.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestControl.js new file mode 100644 index 00000000..2c8b2e13 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestControl.js @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * Request control + * @param {Object} opts + * @return {function} returns middleware + */ +function requestControl(opts) { + const options = opts || {}; + const allowedMethods = options.allowedMethods || ['PUT', 'GET', 'POST', 'DELETE']; // this is just to filter out HEAD, CONNECT, TRACE, etc. methods + const contentType = options.contentType || 'application/json'; // it should be configured per endpoint, I will leave this logic as it was until it bites us + + return async(ctx, next) => { + const path = ctx.path; + const method = ctx.req.method; + const router = ctx.app.middleware.find(x => x.name === 'dispatch').router; // pull out router from the request context + const matched = router.match(path, method); + + const allowed = {}; + for (let i = 0; i < matched.path.length; i++) { + const route = matched.path[i]; + for (let j = 0; j < route.methods.length; j++) { + const method = route.methods[j]; + if (allowedMethods.includes(method)) { + allowed[method] = method; + } + } + } + const allowedArr = Object.keys(allowed); // allowedArr is almost copy pasted from koa-router's allowedMethods method + + if (matched.path.length === 0 || allowedArr.length === 0) { + ctx.log.warn(`Path: ${path} is not allowed.`); + ctx.throw(404); + } + + if (!allowedArr.includes(method) || !matched.route) { + ctx.log.warn(`Method: ${method} is not allowed.`); + ctx.set('Allow', allowedArr.join(', ')); + ctx.throw(405); + } + + if (method === 'GET') { + // If there is no request body, null is returned. If there is no content type, + // or the match fails false is returned. Otherwise, it returns the matching content-type. + if (ctx.is('*/*') !== null) { + ctx.log.warn('Incoming request has not empty body for GET method. Return 415 Unsupported Media Type.'); + ctx.throw(415); + } + return next(); + } + + if (contentType !== 'None' && !ctx.is(contentType)) { + ctx.log.warn(`Only ${contentType} content type is supported. Return 415 Unsupported Media Type.`); + ctx.throw(415); + } + return next(); + }; +} + +module.exports = { + requestControl +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestId.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestId.js new file mode 100644 index 00000000..b22abd5a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/requestId.js @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const { WARN, INFO, DEBUG } = require('log4js').levels; +const uuidGen = require('uuid-random'); +const validators = require('../commonDataValidator'); +const requestLogFormatter = require('../../common/requestLogFormatter'); + +async function generateReqId() { + return uuidGen.bin().toString('hex'); +} + +module.exports = (serverLevel) => { + return async function requestId(ctx, next) { + let msg = ''; + let logLevel = INFO.levelStr.toLowerCase(); + const incomingReqIDHeader = ctx.req.headers['request-id']; + // Set correct reqId + if (incomingReqIDHeader) { + if (serverLevel.isTopLevel) { + ctx.reqId = ctx.request.reqId = await generateReqId(); + msg = `Received own Request-ID header: [${incomingReqIDHeader}]. New Request-ID is: [${ctx.reqId}]`; + } + else if (!validators.validateHexstring('Request-ID', incomingReqIDHeader, 32, ctx.log)) { + ctx.reqId = ctx.request.reqId = await generateReqId(); + msg = `Received own Request-ID header: [${incomingReqIDHeader}] has incorrect format (must be 32-sign hexadecimal). New Request-ID is: [${ctx.reqId}]`; + logLevel = WARN.levelStr.toLowerCase(); + } + else { + ctx.reqId = ctx.request.reqId = incomingReqIDHeader; + msg = `Using received Request-ID: [${ctx.reqId}]`; + logLevel = DEBUG.levelStr.toLowerCase(); + } + } + else { + ctx.reqId = ctx.request.reqId = await generateReqId(); + msg = `No Request-ID header. New Request-ID is: [${ctx.reqId}]`; + logLevel = DEBUG.levelStr.toLowerCase(); + } + + ctx.log = ctx.log.scoped(ctx.reqId); + ctx.log[logLevel](msg); + + ctx.log.info(requestLogFormatter.formatRequestMessageWithoutBody( + ctx.request.method, ctx.request.originalUrl, ctx.request.headers + )); + + // Add response header with Request-ID + ctx.set('Request-ID', ctx.reqId); + return next(); + }; +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/middleware/responseHandler.js b/QuoteVerification/QuoteVerificationService/src/koa/middleware/responseHandler.js new file mode 100644 index 00000000..6fdeba7e --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/middleware/responseHandler.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const { InternalErrorResponse, BadRequestResponse } = require('../response'); + +class BaseResponseHandler { + + constructor(ctx) { + this.ctx = ctx; + } + + isResponseValid(response) { + if (!response.httpCode || !response.jsonBody) { + this.ctx.log.error("Invalid response object set, can't send response"); + throw new InternalErrorResponse(this.ctx.log); + } + } + + setBody(response) { + this.ctx.status = response.httpCode; + this.ctx.body = response.jsonBody; + } + + setHeaders(response) { + if (!response.headers) { + return; + } + const headersNames = response.headers.keys(); + let headerName = headersNames.next().value; + while (headerName) { + this.ctx.set(headerName, response.headers.get(headerName)); + headerName = headersNames.next().value; + } + } + + setResponse(response) { + this.isResponseValid(response); + this.setBody(response); + this.setHeaders(response); + } +} + +class NoBodyOnErrorResponseHandler extends BaseResponseHandler { + isResponseValid(response) { + if (!response.httpCode || response.httpCode === 200 && !response.jsonBody) { + this.ctx.log.error("Invalid response object set, can't send response"); + throw new InternalErrorResponse(this.ctx.log); + } + } + + setBody(response) { + this.ctx.status = response.httpCode; + if (this.ctx.status !== 200) { + this.ctx.body = ''; + } + else { + this.ctx.body = response.jsonBody; + } + } +} + +function responseHandler(sendBodyOnErrors) { + return async(ctx, next) => { + const handler = sendBodyOnErrors ? new BaseResponseHandler(ctx) : new NoBodyOnErrorResponseHandler(ctx); + + ctx.setResponse = (response) => { + handler.setResponse(response); + }; + ctx.setBadRequest = () => { + const response = new BadRequestResponse(ctx.log); + handler.setResponse(response); + }; + ctx.setInternalError = () => { + const response = new InternalErrorResponse(ctx.log); + handler.setResponse(response); + }; + return next(); + }; +} + +module.exports = { + withBodyOnError: responseHandler(true), + withNoBodyOnError: responseHandler(false) +}; diff --git a/QuoteVerification/QuoteVerificationService/src/koa/response.js b/QuoteVerification/QuoteVerificationService/src/koa/response.js new file mode 100644 index 00000000..aa6785b2 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/koa/response.js @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const InternalError = require('./errors').InternalError; + +const STATUSES = { + STATUS_OK: { httpCode: 200, name: 'STATUS_OK' }, + STATUS_CREATED: { httpCode: 201, name: 'STATUS_CREATED' }, + STATUS_CERT_NOT_FOUND: { httpCode: 404, name: 'STATUS_CERTS_NOT_FOUND' }, + STATUS_PCK_NOT_FOUND: { httpCode: 404, name: 'STATUS_PCK_NOT_FOUND' }, + STATUS_PCK_REVOKED: { httpCode: 403, name: 'STATUS_PCK_REVOKED' }, + STATUS_TCB_OUT_OF_DATE: { httpCode: 400, name: 'STATUS_TCB_OUT_OF_DATE' }, + STATUS_TCB_NOT_FOUND: { httpCode: 404, name: 'STATUS_TCB_NOT_FOUND' }, + STATUS_TCB_NOT_SUPPORTED: { httpCode: 400, name: 'STATUS_TCB_NOT_SUPPORTED' }, + STATUS_DEVICE_KEY_NOT_FOUND: { httpCode: 404, name: 'STATUS_DEVICE_KEY_NOT_FOUND' }, + STATUS_INTERNAL_ERROR: { httpCode: 500, name: 'STATUS_INTERNAL_ERROR' }, + STATUS_GENERIC_ERROR: { httpCode: 500, name: 'STATUS_INTERNAL_ERROR' }, + STATUS_BAD_REQUEST: { httpCode: 400, name: 'STATUS_BAD_REQUEST' }, + STATUS_INVALID_CONTENT: { httpCode: 400, name: 'STATUS_INVALID_CONTENT' }, + STATUS_INVALID_PLATFORM_MANIFEST: { httpCode: 400, name: 'STATUS_INVALID_PLATFORM_MANIFEST' }, + STATUS_INVALID_REQUEST_SYNTAX: { httpCode: 400, name: 'STATUS_INVALID_REQUEST_SYNTAX' }, + STATUS_INCOMPATIBLE_PACKAGE: { httpCode: 400, name: 'STATUS_INCOMPATIBLE_PACKAGE' }, + STATUS_PACKAGE_NOT_FOUND: { httpCode: 400, name: 'STATUS_PACKAGE_NOT_FOUND' }, + STATUS_INVALID_OR_REVOKED_PACKAGE: { httpCode: 400, name: 'STATUS_INVALID_OR_REVOKED_PACKAGE' }, + STATUS_INVALID_REGISTRATION_SERVER: { httpCode: 400, name: 'STATUS_INVALID_REGISTRATION_SERVER' }, + STATUS_NOT_FOUND: { httpCode: 404, name: 'STATUS_NOT_FOUND' }, + STATUS_PLATFORM_NOT_FOUND: { httpCode: 404, name: 'STATUS_PLATFORM_NOT_FOUND' }, + STATUS_PLATFORM_KEYS_NOT_FOUND: { httpCode: 404, name: 'STATUS_PLATFORM_KEYS_NOT_FOUND' }, + STATUS_DECRYPTION_FAILED: { httpCode: 400, name: 'STATUS_DECRYPTION_FAILED' }, + STATUS_ENCLAVE_IDENTITY_NOT_FOUND: { httpCode: 404, name: 'STATUS_ENCLAVE_IDENTITY_NOT_FOUND' }, + STATUS_NO_GROUPS_REMAINING: { httpCode: 500, name: 'STATUS_NO_GROUPS_REMAINING' }, + STATUS_NO_ASSIGNMENT: { httpCode: 404, name: 'STATUS_NO_ASSIGNMENT' }, + STATUS_PAK_NOT_FOUND: { httpCode: 404, name: 'STATUS_PAK_NOT_FOUND' }, + STATUS_PAK_REVOKED: { httpCode: 403, name: 'STATUS_PAK_REVOKED' }, + STATUS_EPID_JOIN_DATA_ALREADY_EXISTS: { httpCode: 403, name: 'STATUS_EPID_JOIN_DATA_ALREADY_EXISTS' }, + STATUS_UNWRAPPING_FAILED: { httpCode: 400, name: 'STATUS_UNWRAPPING_FAILED' }, + STATUS_GONE: { httpCode: 410, name: 'STATUS_GONE' }, + STATUS_PAYLOAD_TOO_LARGE: { httpCode: 413, name: 'STATUS_PAYLOAD_TOO_LARGE' }, + STATUS_UNSUPPORTED_MEDIA_TYPE: { httpCode: 415, name: 'STATUS_UNSUPPORTED_MEDIA_TYPE' }, + STATUS_SERVICE_UNAVAILABLE: { httpCode: 503, name: 'STATUS_SERVICE_UNAVAILABLE' }, + STATUS_MAC_VERIFICATION_FAILED: { httpCode: 500, name: 'STATUS_MAC_VERIFICATION_FAILED' }, + STATUS_MIN_SUPPORTED_RL_VERSION: { httpCode: 500, name: 'STATUS_MIN_SUPPORTED_RL_VERSION' } +}; + +class GenericResponse { + constructor(status, log) { + if (!status.httpCode || !status.name) { + log.error(`Cannot construct response because 'status' object is invalid: ${JSON.stringify(status)}.`); + throw new InternalError(); + } + this.httpCode = status.httpCode; + this.jsonBody = { status: status.name }; + } +} + +class InternalErrorResponse extends GenericResponse { + constructor(log) { + super(STATUSES.STATUS_INTERNAL_ERROR, log); + } +} + +class BadRequestResponse extends GenericResponse { + constructor(log) { + super(STATUSES.STATUS_BAD_REQUEST, log); + } +} + +/** + * @typedef Response + * @type jsonBody + * @property {number} httpCode + */ + +/** + * Sets response body and status + * @param response + * @param ctx + */ +function setResponse(response, ctx) { + ctx.body = response.jsonBody; + ctx.status = response.httpCode; +} + +module.exports = { + STATUSES, + setResponse, + GenericResponse, + InternalErrorResponse, + BadRequestResponse +}; diff --git a/QuoteVerification/QuoteVerificationService/src/logger.js b/QuoteVerification/QuoteVerificationService/src/logger.js new file mode 100644 index 00000000..174e9d8c --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/logger.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const logger = require('./common/logger'); +const qvl = require('./qvl'); +const path = require('node:path'); + +module.exports = (config) => { + const loggerConfig = config.logger; + + const fileNameParsed = path.parse(loggerConfig.fileName); + const qvlLogFileName = path.format({ + dir: fileNameParsed.dir, + name: fileNameParsed.name + '-qvl', + ext: fileNameParsed.ext + }); + + qvl.loggerSetup(loggerConfig.category, loggerConfig.levelConsole.toUpperCase(), loggerConfig.levelFile.toUpperCase(), + qvlLogFileName, '[%Y-%m-%dT%H:%M:%S.%eZ] [%l] [%n %@] [pid:%P]%r %v'); + + return logger.createLogger(loggerConfig.category, loggerConfig.fileName, loggerConfig.levelFile, + loggerConfig.levelConsole, loggerConfig.isMultilineLogEnabled); +}; diff --git a/QuoteVerification/QuoteVerificationService/src/package.json b/QuoteVerification/QuoteVerificationService/src/package.json new file mode 100644 index 00000000..367ad4f3 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/package.json @@ -0,0 +1,33 @@ +{ + "name": "quoteverificationservice", + "scripts": { + "install": "cmake-js build -d qvl/ -O qvl/cmake-build-release" + }, + "version": "1.0.0", + "license": "SEE LICENSE IN RESPECTIVE FILES", + "main": "bootstrap.js", + "dependencies": { + "bluebird": "^3.7.2", + "fast-url-parser": "^1.1.3", + "https-proxy-agent": "^5.0.1", + "js-yaml": "^4.1.0", + "koa": "^2.13.4", + "koa-bodyparser": "^4.3.0", + "koa-router": "^12.0.0", + "lodash": "^4.17.21", + "log4js": "^6.7.0", + "moment": "^2.29.4", + "node-addon-api": "^5.0.0", + "node-cache": "^5.1.2", + "raw-body": "^2.5.1", + "retry": "^0.13.1", + "safe-buffer": "^5.2.1", + "serialize-error": "^8.1.0", + "uuid-random": "^1.3.2", + "validator": "^13.7.0", + "z-schema": "^5.0.4" + }, + "devDependencies": { + "cmake-js": "^7.0.0" + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.cpp new file mode 100644 index 00000000..e494ca56 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "BaseWorker.h" +#include + +namespace intel::sgx::dcap::qvlwrapper { + void BaseWorker::Execute() + { + sgxAttestationLoggerSetCustomField("reqId", requestId.c_str()); + Run(); + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.h new file mode 100644 index 00000000..4e7860dc --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/BaseWorker.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_BASEWORKER_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_BASEWORKER_H + +#include +#include // std::move + +namespace intel::sgx::dcap::qvlwrapper { + class BaseWorker : public Napi::AsyncWorker{ + public: + BaseWorker(Napi::Env &env, Napi::Promise::Deferred &promise, std::string requestId) + : Napi::AsyncWorker(env), promise(promise), requestId(std::move(requestId)) {} + + ~BaseWorker() override = default; + + void Execute() override; + + virtual void Run() = 0; + + protected: + std::string requestId; + Napi::Promise::Deferred promise; + }; +} + + +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_BASEWORKER_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/CMakeLists.txt b/QuoteVerification/QuoteVerificationService/src/qvl/CMakeLists.txt new file mode 100644 index 00000000..4351b1d2 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/CMakeLists.txt @@ -0,0 +1,84 @@ +# +# Copyright (c) 2022, Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +cmake_minimum_required(VERSION 3.18) +#Consider building from sources by including cmake from QVL +#set(QVL_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../../QVL/QVL/Src/CMakeLists.txt) +#include(${QVL_SRC}) + +project (QuoteVerificationLibraryWrapper) + +if(NOT DEFINED QVL_PATH) + message(FATAL_ERROR "You must provide QVL_PATH") +endif() +message(STATUS "QVL_PATH set to ${QVL_PATH}") + +set(CMAKE_DEBUG_POSTFIX d) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(QVL_LIB_NAME "QuoteVerificationStaticd") + set(ATTESTATIONPARSERS_LIB_NAME "AttestationParsersStaticd") + set(ATTESTATIONCOMMONS_LIB_NAME "AttestationCommonsStaticd") +else() + set(QVL_LIB_NAME "QuoteVerificationStatic") + set(ATTESTATIONPARSERS_LIB_NAME "AttestationParsersStatic") + set(ATTESTATIONCOMMONS_LIB_NAME "AttestationCommonsStatic") +endif() +message(STATUS "QVL_LIB_NAME set to ${QVL_LIB_NAME}") +message(STATUS "ATTESTATIONPARSERS_LIB_NAME set to ${ATTESTATIONPARSERS_LIB_NAME}") + + +find_library(QVL NAMES ${QVL_LIB_NAME} PATHS ${QVL_PATH}/lib) +find_library(AttestationParsers NAMES ${ATTESTATIONPARSERS_LIB_NAME} PATHS ${QVL_PATH}/lib) +find_library(AttestationCommons NAMES ${ATTESTATIONCOMMONS_LIB_NAME} PATHS ${QVL_PATH}/lib) + +file(GLOB SOURCE_FILES *.cpp *.h) +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") + +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) +string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +if(NOT DEFINED CMAKE_JS_INC) + message(FATAL_ERROR "You must provide CMAKE_JS_INC") +endif() +message(STATUS "CMAKE_JS_INC set to ${CMAKE_JS_INC}") + +target_include_directories(${PROJECT_NAME} + PRIVATE ${NODE_ADDON_API_DIR} + PRIVATE ${CMAKE_JS_INC} + PRIVATE ${QVL_PATH}/include) + +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} ${QVL} ${AttestationParsers} ${AttestationCommons}) diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.cpp new file mode 100644 index 00000000..f0f952e5 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "GetCertificationDataWorker.h" + + +namespace intel::sgx::dcap::qvlwrapper { + void GetCertificationDataWorker::Run() { + qvlStatus = sgxAttestationGetQECertificationDataSize(quote, quoteSize, &qeCertificationDataSize); + if (qvlStatus != STATUS_OK) { + SetError("sgxAttestationGetQECertificationDataSize failed"); + return; + } + + qeCertificationData = new uint8_t[qeCertificationDataSize]; + + qvlStatus = sgxAttestationGetQECertificationData(quote, quoteSize, qeCertificationDataSize, + qeCertificationData, + &qeCertificationDataType); + + if (qvlStatus != STATUS_OK) { + SetError("sgxAttestationGetQECertificationData failed"); + return; + } + } + + void GetCertificationDataWorker::OnOK() { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("type", qeCertificationDataType); + returnObj.Set("data",Napi::Buffer::Copy(Env(), qeCertificationData, qeCertificationDataSize)); + promise.Resolve(returnObj); + + delete qeCertificationData; + } + + void GetCertificationDataWorker::OnError(const Napi::Error &e) { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("status", (int) qvlStatus); + returnObj.Set("error", e.Message()); + promise.Reject(returnObj); + + delete qeCertificationData; + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.h new file mode 100644 index 00000000..12dfb2ad --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetCertificationDataWorker.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_GETCERTIFICATIONDATAWORKER_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_GETCERTIFICATIONDATAWORKER_H + +#include + +#include +#include "BaseWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + class GetCertificationDataWorker : public BaseWorker { + public: + GetCertificationDataWorker(Napi::Env &env, Napi::Promise::Deferred &promise, const std::string& requestId, + uint8_t *quote, uint32_t quoteSize) + : BaseWorker(env, promise, requestId), quote(quote), quoteSize(quoteSize) {} + + ~GetCertificationDataWorker() override = default; + + void Run() override; + + void OnOK() override; + + void OnError(const Napi::Error &e) override ; + + private: + uint8_t *quote = nullptr; + uint32_t quoteSize = 0; + uint16_t qeCertificationDataType{}; + uint8_t *qeCertificationData{}; + uint32_t qeCertificationDataSize{}; + Status qvlStatus = STATUS_OK; + }; +} +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_GETCERTIFICATIONDATAWORKER_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.cpp new file mode 100644 index 00000000..ae08ba00 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include "GetCrlDistributionPointWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + using namespace intel::sgx::dcap::parser; + + void GetCrlDistributionPointWorker::Run() { + try { + auto certificate = x509::Certificate::parse(pemCertificate); + crlDistributionPoint = certificate.getCrlDistributionPoint(); + } + catch (std::exception& e) { + std::string msg = "Error getting CRL distribution point: "; + msg.append(e.what()); + SetError(msg); + return; + } + } + + void GetCrlDistributionPointWorker::OnOK() { + auto napiValue = Napi::String::New(Env(), crlDistributionPoint); + promise.Resolve(napiValue); + } + + void GetCrlDistributionPointWorker::OnError(const Napi::Error &e) { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("error", e.Message()); + promise.Reject(returnObj); + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.h new file mode 100644 index 00000000..7b6c3518 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetCrlDistributionPointWorker.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_GETCRLDISTRIBUTIONPOINT_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_GETCRLDISTRIBUTIONPOINT_H + +#include + +#include // std::move +#include "BaseWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + class GetCrlDistributionPointWorker : public BaseWorker{ + public: + GetCrlDistributionPointWorker(Napi::Env &env, Napi::Promise::Deferred &promise, const std::string& requestId, + std::string pemCertificate) + : BaseWorker(env, promise, requestId), pemCertificate(std::move(pemCertificate)) {} + + ~GetCrlDistributionPointWorker() override = default; + + void Run() override; + + void OnOK() override; + + void OnError(const Napi::Error &e) override; + + private: + const std::string pemCertificate; + std::string crlDistributionPoint; + }; +} + +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_GETCRLDISTRIBUTIONPOINT_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.cpp new file mode 100644 index 00000000..c9340b31 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "GetPckCertificateDataWorker.h" +#include + +namespace intel::sgx::dcap::qvlwrapper { + using namespace intel::sgx::dcap::parser; + + void GetPckCertificateDataWorker::Run() { + try { + const auto certificate = x509::PckCertificate::parse(pemCertificate); + fmspc = certificate.getFmspc(); + pcesvn = certificate.getTcb().getPceSvn(); + cpusvn = certificate.getTcb().getCpuSvn(); + + sgxType = certificate.getSgxType(); + if (sgxType == parser::x509::Scalable || sgxType == parser::x509::ScalableWithIntegrity) { + const auto platformCertificate = x509::PlatformPckCertificate(certificate); + const auto &configuration = platformCertificate.getConfiguration(); + cachedKeys = configuration.isCachedKeys(); + dynamicPlatform = configuration.isDynamicPlatform(); + smtEnabled = configuration.isSmtEnabled(); + } + } + catch (std::exception& e) { + std::string msg = "Error getting data from PCK certificate: "; + msg.append(e.what()); + SetError(msg); + return; + } + } + + void GetPckCertificateDataWorker::OnOK() { + auto returnObj = Napi::Object::New(Env()); + auto buffer = Napi::Buffer::Copy(Env(), fmspc.data(), fmspc.size()); + returnObj.Set("fmspc", buffer); + buffer = Napi::Buffer::Copy(Env(), cpusvn.data(), cpusvn.size()); + returnObj.Set("cpusvn", buffer); + returnObj.Set("pcesvn", pcesvn); + switch (sgxType) { + case x509::Standard: + returnObj.Set("sgxType", "Standard"); + break; + case x509::Scalable: + returnObj.Set("sgxType", "Scalable"); + returnObj.Set("dynamicPlatform", dynamicPlatform); + returnObj.Set("cachedKeys", cachedKeys); + returnObj.Set("smtEnabled", smtEnabled); + break; + case x509::ScalableWithIntegrity: + returnObj.Set("sgxType", "ScalableWithIntegrity"); + returnObj.Set("dynamicPlatform", dynamicPlatform); + returnObj.Set("cachedKeys", cachedKeys); + returnObj.Set("smtEnabled", smtEnabled); + break; + } + promise.Resolve(returnObj); + } + + void GetPckCertificateDataWorker::OnError(const Napi::Error &e) { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("error", e.Message()); + promise.Reject(returnObj); + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.h new file mode 100644 index 00000000..aca614ab --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/GetPckCertificateDataWorker.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_GetPckCertificateDataWORKER_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_GetPckCertificateDataWORKER_H + +#include +#include "BaseWorker.h" +#include +#include // std::move + +namespace intel::sgx::dcap::qvlwrapper { + + class GetPckCertificateDataWorker : public BaseWorker { + public: + GetPckCertificateDataWorker(Napi::Env &env, Napi::Promise::Deferred &promise, const std::string& requestId, + std::string pemCertificate) + : BaseWorker(env, promise, requestId), pemCertificate(std::move(pemCertificate)) {} + + ~GetPckCertificateDataWorker() override = default; + + void Run() override; + + void OnOK() override; + + void OnError(const Napi::Error &e) override; + + private: + const std::string pemCertificate; // input + std::vector fmspc; // output + dcap::parser::x509::SgxType sgxType; // output + bool dynamicPlatform; // output + bool cachedKeys; // output + bool smtEnabled; // output + std::vector cpusvn; // output + std::uint32_t pcesvn; // output + }; + +} +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_GetPckCertificateDataWORKER_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteErrorSource.h b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteErrorSource.h new file mode 100644 index 00000000..9cd3007a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteErrorSource.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +namespace intel::sgx::dcap::qvlwrapper { + + enum VerifyQuoteErrorSource + { + VERIFY_PCK_CERTIFICATE, + VERIFY_TCB_INFO, + VERIFY_ENCLAVE_IDENTITY, + VERIFY_QUOTE + }; +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.cpp new file mode 100644 index 00000000..71e24f97 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "VerifyQuoteWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + void VerifyQuoteWorker::Run() { + auto pckCertChain = pckCertIssuerCertChain + pckCert; + const char *crls[] = {rootCaCrl.c_str(), pckCrl.c_str()}; + + qvlStatus = sgxAttestationVerifyPCKCertificate(pckCertChain.c_str(), crls, trustedRootCaPem.c_str(), nullptr); + if (qvlStatus != STATUS_OK) { + errorSource = VERIFY_PCK_CERTIFICATE; + SetError("PCK certificate verification failed"); + return; + } + + qvlStatus = sgxAttestationVerifyTCBInfo(tcbInfo.c_str(), tcbInfoIssuerCertChain.c_str(), rootCaCrl.c_str(), + tcbInfoSigningChainTrustedRoot.c_str(), nullptr); + if (qvlStatus != STATUS_OK) { + errorSource = VERIFY_TCB_INFO; + SetError("TCB info verification failed"); + return; + } + + qvlStatus = sgxAttestationVerifyEnclaveIdentity(qeIdentity.c_str(), tcbInfoIssuerCertChain.c_str(), + rootCaCrl.c_str(), tcbInfoSigningChainTrustedRoot.c_str(), nullptr); + if (qvlStatus != STATUS_OK) { + errorSource = VERIFY_ENCLAVE_IDENTITY; + SetError("Enclave identity verification failed"); + return; + } + + qvlStatus = sgxAttestationVerifyQuote(quote, quoteSize, pckCert.c_str(), pckCrl.c_str(), tcbInfo.c_str(), + qeIdentity.c_str()); + if (qvlStatus != STATUS_OK) { + errorSource = VERIFY_QUOTE; + SetError("Quote verification failed"); + return; + } + } + + void VerifyQuoteWorker::OnOK() { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("status", static_cast(qvlStatus)); + promise.Resolve(returnObj); + } + + void VerifyQuoteWorker::OnError(const Napi::Error &e) { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("status", static_cast(qvlStatus)); + returnObj.Set("errorSource", static_cast(errorSource)); + returnObj.Set("error", e.Message()); + promise.Resolve(returnObj); + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.h new file mode 100644 index 00000000..42735c7f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/VerifyQuoteWorker.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_VERIFYQUOTEQORKER_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_VERIFYQUOTEQORKER_H + +#include +#include +#include "BaseWorker.h" +#include "VerifyQuoteErrorSource.h" + +namespace intel::sgx::dcap::qvlwrapper { + + class VerifyQuoteWorker : public BaseWorker { + public: + VerifyQuoteWorker(Napi::Env &env, Napi::Promise::Deferred &promise, const std::string& requestId, + uint8_t *quote, + size_t quoteSize, + std::string pckCert, + std::string tcbInfo, + std::string qeIdentity, + std::string pckCertIssuerCertChain, + std::string tcbInfoIssuerCertChain, + std::string pckCrl, + std::string rootCaCrl, + std::string trustedRootCaPem, + std::string tcbInfoSigningChainTrustedRoot) + : BaseWorker(env, promise, requestId), + quote(quote), + quoteSize(quoteSize), + pckCert(std::move(pckCert)), + tcbInfo(std::move(tcbInfo)), + qeIdentity(std::move(qeIdentity)), + pckCertIssuerCertChain(std::move(pckCertIssuerCertChain)), + tcbInfoIssuerCertChain(std::move(tcbInfoIssuerCertChain)), + pckCrl(std::move(pckCrl)), + rootCaCrl(std::move(rootCaCrl)), + trustedRootCaPem(std::move(trustedRootCaPem)), + tcbInfoSigningChainTrustedRoot(std::move(tcbInfoSigningChainTrustedRoot)) {} + + ~VerifyQuoteWorker() override = default; + + void Run() override; + + void OnOK() override; + + void OnError(const Napi::Error &e) override ; + + private: + uint8_t *quote = nullptr; + size_t quoteSize = 0; + std::string pckCert; + std::string tcbInfo; + std::string qeIdentity; + std::string pckCertIssuerCertChain; + std::string tcbInfoIssuerCertChain; + std::string pckCrl; + std::string rootCaCrl; + std::string trustedRootCaPem; + std::string tcbInfoSigningChainTrustedRoot; + + Status qvlStatus = STATUS_OK; + VerifyQuoteErrorSource errorSource; + }; + +} +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_VERIFYQUOTEQORKER_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.cpp new file mode 100644 index 00000000..1824b4db --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "VersionWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + void VersionWorker::Run() { + size_t bufSize = 10; + auto version = std::make_unique(bufSize); + sgxEnclaveAttestationGetVersion(version.get(), bufSize); + result = std::string(version.get()); + } + + void VersionWorker::OnOK() { + auto returnObj = Napi::Object::New(Env()); + returnObj.Set("result", result); + promise.Resolve(returnObj); + } +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.h b/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.h new file mode 100644 index 00000000..d2a493ce --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/VersionWorker.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef QUOTEVERIFICATIONLIBRARYWRAPPER_VERSIONWORKER_H +#define QUOTEVERIFICATIONLIBRARYWRAPPER_VERSIONWORKER_H + +#include +#include +#include +#include "BaseWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + class VersionWorker : public BaseWorker { + public: + VersionWorker(Napi::Env &env, Napi::Promise::Deferred &promise, const std::string& requestId) + : BaseWorker(env, promise, requestId) {} + + ~VersionWorker() override = default; + + void Run() override; + void OnOK() override; + + private: + std::string result = "NA"; + }; + +} +#endif //QUOTEVERIFICATIONLIBRARYWRAPPER_VERSIONWORKER_H diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/index.js b/QuoteVerification/QuoteVerificationService/src/qvl/index.js new file mode 100644 index 00000000..eba87384 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/index.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/*eslint no-process-env: 0 */ +const env = process.env.NODE_ENV || 'development'; +const wrapper = (env === 'production') ? + require('../../native/QuoteVerificationLibraryWrapper.node') : // release binary + require('../../native/QuoteVerificationLibraryWrapperd.node'); // QVL with debug symbols + +//const wrapper = require('../qvl/cmake-build-debug/QuoteVerificationLibraryWrapperd'); + +async function getVersion(reqId, logger) { + try { + const result = await wrapper.version(reqId); + return { body: { status: 'OK', version: result.result } }; + } + catch (e) { + logger.error('Failed to retrieve version from QVL', e); + return { body: { status: 'FAILED', version: 'NA' } }; + } +} +/** + * Retrieves Certification Data from ECDSA quote using QVL + * @param {string} reqId + * @param {buffer} quote + * @returns {Promise<{type: number, data: string}>} + */ +async function getCertificationData(reqId, quote) { + const certificationData = await wrapper.getCertificationData(reqId, quote); + return { type: certificationData.type, data: certificationData.data.toString('ascii') }; +} + +/** + * Retrieves FMSPC, SGX Type, dynamicPlatform (optional), cachedKeys (optional), smtEnabled (optional) + * from PCK Certificate using Attestation Parsers + * @param {string} reqId + * @param {string} pemCertificate + * @returns {Promise<{fmspc: string, sgxType: string, dynamicPlatform: boolean, cachedKeys: boolean, smtEnabled: boolean}>} + */ +async function getPckCertificateData(reqId, pemCertificate) { + const certData = await wrapper.getPckCertificateData(reqId, pemCertificate); + certData.fmspc = certData.fmspc.toString('hex').toUpperCase(); + return certData; +} + +/** + * Retrieves CRL Distribution Point from X509 certificate using Attestation Parsers + * @param {string} reqId + * @param pemCertificate + * @returns {Promise} + */ +async function getCrlDistributionPoint(reqId, pemCertificate) { + return wrapper.getCrlDistributionPoint(reqId, pemCertificate); +} + +/** + * Verify quotes using QVL and all provided collaterals + * @param {string} reqId + * @param {Buffer} quote + * @param {string} pckCertPem - PEM format + * @param {string} tcbInfo - serialized JSON format + * @param {string} qeIdentity - serialized JSON format + * @param {string} pckCertIssuerCertChain - PEM format chain of 2 certificates + * @param {string} tcbInfoIssuerChain - PEM format chain of 2 certificates + * @param {string} pckCertCrl - hex encoded CRL + * @param {string} rootCrl - hex encoded CRL + * @param {string} trustedRootPem - PEM format + * @param {string} tcbSigningChainTrustedRoot - PEM format + * @returns {Promise<{status: number, error: (string|undefined), errorSource: (number|undefined)}>} + */ +async function verifyQuote(reqId, quote, pckCertPem, tcbInfo, qeIdentity, pckCertIssuerCertChain, tcbInfoIssuerChain, + pckCertCrl, rootCrl, trustedRootPem, tcbSigningChainTrustedRoot) { + return wrapper.verifyQuote(reqId, quote, pckCertPem, tcbInfo, qeIdentity, pckCertIssuerCertChain, + tcbInfoIssuerChain, pckCertCrl, rootCrl, trustedRootPem, tcbSigningChainTrustedRoot); +} + +/** + * Function setups logger in QVL + * @param {string} name - name for logger that will be appended to every log (in node.js world known as category) + * @param {string} consoleLogLevel - log level for console logger + * @param {string} fileLogLevel - log level for file logger + * @param {string} fileName - filename for file logger + * @param {string} pattern - pattern that will be used to generate log lines. + */ +function loggerSetup(name, consoleLogLevel, fileLogLevel, fileName, pattern) { + wrapper.loggerSetup(name, consoleLogLevel, fileLogLevel, fileName, pattern); +} +module.exports = { + getVersion, + getCertificationData, + getPckCertificateData, + getCrlDistributionPoint, + verifyQuote, + loggerSetup +}; diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/main.cpp b/QuoteVerification/QuoteVerificationService/src/qvl/main.cpp new file mode 100644 index 00000000..68e2333d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/main.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include "GetPckCertificateDataWorker.h" +#include "GetCertificationDataWorker.h" +#include "VersionWorker.h" +#include "GetCrlDistributionPointWorker.h" +#include "VerifyQuoteWorker.h" + +namespace intel::sgx::dcap::qvlwrapper { + +Napi::Value GetCertificationData(const Napi::CallbackInfo &info) { + + Napi::Env env = info.Env(); + auto deferred = Napi::Promise::Deferred::New(env); + + auto requestId = std::string(info[0].As()); + auto quote = info[1].As>(); + auto worker = new GetCertificationDataWorker(env, deferred, requestId, quote.Data(), quote.Length()); + worker->Queue(); + return deferred.Promise(); +} + +Napi::Value Version(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + auto deferred = Napi::Promise::Deferred::New(env); + + auto requestId = std::string(info[0].As()); + auto worker = new VersionWorker(env, deferred, requestId); + + worker->Queue(); + return deferred.Promise(); +} + +Napi::Value GetPckCertificateData(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + auto deferred = Napi::Promise::Deferred::New(env); + auto requestId = std::string(info[0].As()); + auto pemCertificate = std::string(info[1].As()); + auto worker = new GetPckCertificateDataWorker(env, deferred, requestId, pemCertificate); + worker->Queue(); + return deferred.Promise(); +} + +Napi::Value GetCrlDistributionPoint(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + auto deferred = Napi::Promise::Deferred::New(env); + auto requestId = std::string(info[0].As()); + auto pemCertificate = std::string(info[1].As()); + auto worker = new GetCrlDistributionPointWorker(env, deferred, requestId, pemCertificate); + worker->Queue(); + return deferred.Promise(); +} + +Napi::Value VerifyQuote(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + auto requestId = std::string(info[0].As()); + auto quote = info[1].As>(); + auto pckCert = std::string(info[2].As()); + auto tcbInfo = std::string(info[3].As()); + auto qeIdentity = std::string(info[4].As()); + + auto pckCertIssuerCertChain = std::string(info[5].As()); + auto tcbInfoIssuerCertChain = std::string(info[6].As()); + + auto pckCrl = std::string(info[7].As()); + auto rootCaCrl = std::string(info[8].As()); + auto trustedRootCaPem = std::string(info[9].As()); + auto tcbInfoSigningChainTrustedRoot = std::string(info[10].As()); + + auto deferred = Napi::Promise::Deferred::New(env); + + auto worker = new VerifyQuoteWorker(env, deferred, requestId, quote.Data(), quote.Length(), + pckCert, tcbInfo, qeIdentity, pckCertIssuerCertChain, + tcbInfoIssuerCertChain, pckCrl, rootCaCrl, trustedRootCaPem, + tcbInfoSigningChainTrustedRoot); + + worker->Queue(); + return deferred.Promise(); +} + +void LoggerSetup(const Napi::CallbackInfo &info) { + auto name = std::string(info[0].As()); + + auto consoleLogLevel = std::string(info[1].As()); + auto fileLogLevel = std::string(info[2].As()); + auto fileName = std::string(info[3].As()); + auto pattern = std::string(info[4].As()); + + sgxAttestationLoggerSetup(name.c_str(), consoleLogLevel.c_str(), fileLogLevel.c_str(), fileName.c_str(), + pattern.c_str()); +} + +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + + exports.Set(Napi::String::New(env, "getCertificationData"), + Napi::Function::New(env, GetCertificationData)); + + exports.Set(Napi::String::New(env, "version"), + Napi::Function::New(env, Version)); + + exports.Set(Napi::String::New(env, "getPckCertificateData"), + Napi::Function::New(env, GetPckCertificateData)); + + exports.Set(Napi::String::New(env, "getCrlDistributionPoint"), + Napi::Function::New(env, GetCrlDistributionPoint)); + + exports.Set(Napi::String::New(env, "verifyQuote"), + Napi::Function::New(env, VerifyQuote)); + + exports.Set(Napi::String::New(env, "loggerSetup"), + Napi::Function::New(env, LoggerSetup)); + + return exports; +} + +NODE_API_MODULE(qvl, InitAll) + +} diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/status.js b/QuoteVerification/QuoteVerificationService/src/qvl/status.js new file mode 100644 index 00000000..a9f0ac18 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/status.js @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * These statuses are taken from file QuoteVerification.h in QVL and should be kept in sync with it + * @readonly + * @enum {number} + */ +module.exports = { + STATUS_OK: 0, + STATUS_UNSUPPORTED_CERT_FORMAT: 1, + STATUS_SGX_ROOT_CA_MISSING: 2, + STATUS_SGX_ROOT_CA_INVALID: 3, + STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS: 4, + STATUS_SGX_ROOT_CA_INVALID_ISSUER: 5, + STATUS_SGX_ROOT_CA_UNTRUSTED: 6, + STATUS_SGX_INTERMEDIATE_CA_MISSING: 7, + STATUS_SGX_INTERMEDIATE_CA_INVALID: 8, + STATUS_SGX_INTERMEDIATE_CA_INVALID_EXTENSIONS: 9, + STATUS_SGX_INTERMEDIATE_CA_INVALID_ISSUER: 10, + STATUS_SGX_INTERMEDIATE_CA_REVOKED: 11, + STATUS_SGX_PCK_MISSING: 12, + STATUS_SGX_PCK_INVALID: 13, + STATUS_SGX_PCK_INVALID_EXTENSIONS: 14, + STATUS_SGX_PCK_INVALID_ISSUER: 15, + STATUS_SGX_PCK_REVOKED: 16, + STATUS_TRUSTED_ROOT_CA_INVALID: 17, + STATUS_SGX_PCK_CERT_CHAIN_UNTRUSTED: 18, + STATUS_SGX_TCB_INFO_UNSUPPORTED_FORMAT: 19, + STATUS_SGX_TCB_INFO_INVALID: 20, + STATUS_TCB_INFO_INVALID_SIGNATURE: 21, + STATUS_SGX_TCB_SIGNING_CERT_MISSING: 22, + STATUS_SGX_TCB_SIGNING_CERT_INVALID: 23, + STATUS_SGX_TCB_SIGNING_CERT_INVALID_EXTENSIONS: 24, + STATUS_SGX_TCB_SIGNING_CERT_INVALID_ISSUER: 25, + STATUS_SGX_TCB_SIGNING_CERT_CHAIN_UNTRUSTED: 26, + STATUS_SGX_TCB_SIGNING_CERT_REVOKED: 27, + STATUS_SGX_CRL_UNSUPPORTED_FORMAT: 28, + STATUS_SGX_CRL_UNKNOWN_ISSUER: 29, + STATUS_SGX_CRL_INVALID: 30, + STATUS_SGX_CRL_INVALID_EXTENSIONS: 31, + STATUS_SGX_CRL_INVALID_SIGNATURE: 32, + STATUS_SGX_CA_CERT_UNSUPPORTED_FORMAT: 33, + STATUS_SGX_CA_CERT_INVALID: 34, + STATUS_TRUSTED_ROOT_CA_UNSUPPORTED_FORMAT: 35, + STATUS_MISSING_PARAMETERS: 36, + STATUS_UNSUPPORTED_QUOTE_FORMAT: 37, + STATUS_UNSUPPORTED_PCK_CERT_FORMAT: 38, + STATUS_INVALID_PCK_CERT: 39, + STATUS_UNSUPPORTED_PCK_RL_FORMAT: 40, + STATUS_INVALID_PCK_CRL: 41, + STATUS_UNSUPPORTED_TCB_INFO_FORMAT: 42, + STATUS_PCK_REVOKED: 43, + STATUS_TCB_INFO_MISMATCH: 44, + STATUS_TCB_OUT_OF_DATE: 45, + STATUS_TCB_REVOKED: 46, + STATUS_TCB_CONFIGURATION_NEEDED: 47, + STATUS_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED: 48, + STATUS_TCB_NOT_SUPPORTED: 49, + STATUS_TCB_UNRECOGNIZED_STATUS: 50, + STATUS_UNSUPPORTED_QE_CERTIFICATION: 51, + STATUS_INVALID_QE_CERTIFICATION_DATA_SIZE: 52, + STATUS_UNSUPPORTED_QE_CERTIFICATION_DATA_TYPE: 53, + STATUS_PCK_CERT_MISMATCH: 54, + STATUS_INVALID_QE_REPORT_SIGNATURE: 55, + STATUS_INVALID_QE_REPORT_DATA: 56, + STATUS_INVALID_QUOTE_SIGNATURE: 57, + STATUS_SGX_QE_IDENTITY_UNSUPPORTED_FORMAT: 58, + STATUS_SGX_QE_IDENTITY_INVALID: 59, + STATUS_SGX_QE_IDENTITY_INVALID_SIGNATURE: 60, + STATUS_SGX_ENCLAVE_REPORT_UNSUPPORTED_FORMAT: 61, + STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_FORMAT: 62, + STATUS_SGX_ENCLAVE_IDENTITY_INVALID: 63, + STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_VERSION: 64, + STATUS_SGX_ENCLAVE_IDENTITY_OUT_OF_DATE: 65, + STATUS_SGX_ENCLAVE_REPORT_MISCSELECT_MISMATCH: 66, + STATUS_SGX_ENCLAVE_REPORT_ATTRIBUTES_MISMATCH: 67, + STATUS_SGX_ENCLAVE_REPORT_MRENCLAVE_MISMATCH: 68, + STATUS_SGX_ENCLAVE_REPORT_MRSIGNER_MISMATCH: 69, + STATUS_SGX_ENCLAVE_REPORT_ISVPRODID_MISMATCH: 70, + STATUS_SGX_ENCLAVE_REPORT_ISVSVN_OUT_OF_DATE: 71, + STATUS_UNSUPPORTED_QE_IDENTITY_FORMAT: 72, + STATUS_QE_IDENTITY_OUT_OF_DATE: 73, + STATUS_QE_IDENTITY_MISMATCH: 74, + STATUS_SGX_TCB_INFO_EXPIRED: 75, + STATUS_SGX_ENCLAVE_IDENTITY_INVALID_SIGNATURE: 76, + STATUS_INVALID_PARAMETER: 77, + STATUS_SGX_PCK_CERT_CHAIN_EXPIRED: 78, + STATUS_SGX_CRL_EXPIRED: 79, + STATUS_SGX_SIGNING_CERT_CHAIN_EXPIRED: 80, + STATUS_SGX_ENCLAVE_IDENTITY_EXPIRED: 81, + STATUS_TCB_SW_HARDENING_NEEDED: 82, + STATUS_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED: 83, + STATUS_SGX_ENCLAVE_REPORT_ISVSVN_REVOKED: 84, + STATUS_TDX_MODULE_MISMATCH: 85 +}; diff --git a/QuoteVerification/QuoteVerificationService/src/qvl/verifyQuoteErrorSource.js b/QuoteVerification/QuoteVerificationService/src/qvl/verifyQuoteErrorSource.js new file mode 100644 index 00000000..be392bc2 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/qvl/verifyQuoteErrorSource.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + + +/** + * These statuses are taken from file VerifyQuoteErrorSource.h in wrapper and should be kept in sync with it + * @readonly + * @enum {number} + */ +module.exports = { + VERIFY_PCK_CERTIFICATE: 0, + VERIFY_TCB_INFO: 1, + VERIFY_ENCLAVE_IDENTITY: 2, + VERIFY_QUOTE: 3 +}; diff --git a/QuoteVerification/QuoteVerificationService/src/routes/health.js b/QuoteVerification/QuoteVerificationService/src/routes/health.js new file mode 100644 index 00000000..18f80dec --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/routes/health.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const { createHealthRouter } = require('../koa/koaHealthRouter'); +const healthHandler = require('../handlers/health'); + +function createRouter(urlPrefix) { + return createHealthRouter(healthHandler.checkHealth, urlPrefix); +} + +module.exports = { + createRouter +}; diff --git a/QuoteVerification/QuoteVerificationService/src/routes/index.js b/QuoteVerification/QuoteVerificationService/src/routes/index.js new file mode 100644 index 00000000..7164fc74 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/routes/index.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const Router = require('koa-router'); + +/* Place to import all nested routers below */ +const healthRouter = require('./health').createRouter(); +const verifyAttestationEvidenceRouter = require('./v1/verifyAttestationEvidence').createRouter(); + +/* Place to compose service API below */ +const apiRouter = new Router(); +apiRouter.use('/health', healthRouter.routes(), healthRouter.allowedMethods()); + +apiRouter.use('/attestation/sgx/dcap/v1/report', verifyAttestationEvidenceRouter.routes(), verifyAttestationEvidenceRouter.allowedMethods()); + +module.exports = apiRouter; diff --git a/QuoteVerification/QuoteVerificationService/src/routes/v1/verifyAttestationEvidence.js b/QuoteVerification/QuoteVerificationService/src/routes/v1/verifyAttestationEvidence.js new file mode 100644 index 00000000..8bd02aa4 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/routes/v1/verifyAttestationEvidence.js @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const Router = require('koa-router'); + +const { verifyAttestationEvidence } = require('../../handlers/verifyAttestationEvidence'); + +/** + * @typedef Router + * @type {object} + * @property {function} routes + * @property {function} allowedMethods + */ + +/** + * Creates new router + * @returns {Router} + */ +function createRouter() { + const router = new Router(); + router.post('/', verifyAttestationEvidence); + return router; +} + +module.exports = { + createRouter +}; diff --git a/QuoteVerification/QuoteVerificationService/src/server.js b/QuoteVerification/QuoteVerificationService/src/server.js new file mode 100644 index 00000000..fd3581f0 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/server.js @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const process = require('process'); + +const config = require('./configLoader').getConfig(); +const logger = require('./logger')(config); + +const HealthCache = require('./healthChecker'); +const router = require('./routes'); + +const ServerInit = require('./koa/init/serverInit'); +const { getConfiguredBodyParser } = require('./koa/middleware/bodyParser'); +const requestId = require('./koa/middleware/requestId'); +const errorHandler = require('./koa/middleware/errorHandler').withNoBodyOnError; +const { requestControl } = require('./koa/middleware/requestControl'); + +const options = { + serverName: 'QVS', + logger, + middlewares: [ + errorHandler, + requestId({ isTopLevel: true }), + requestControl(), + getConfiguredBodyParser(config) + ], + serviceConfig: config.service +}; + +const init = new ServerInit(options); + +process.on('unhandledRejection', (result, error) => { + logger.error('Unhandled Rejection occured:', result, 'Error:', error); +}); + +module.exports = (async function initialize() { + try { + if (config.service.tlsServerType === 'None') { + await init.startHTTPServer(config.service.port, router); + } + else { + await init.startHTTPSServer(config.service.port, router); + } + HealthCache.run(); + } + catch (e) { + logger.error('Error occurred during server initialization:', e); + /*eslint no-process-exit: 0 */ + process.exit(1); + } +}()); diff --git a/QuoteVerification/QuoteVerificationService/src/util/buffer.js b/QuoteVerification/QuoteVerificationService/src/util/buffer.js new file mode 100644 index 00000000..e1cc89d9 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/buffer.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const Buffer = require('safe-buffer').Buffer; + +Buffer.prototype.toJSON = function toJSON() { return { type: 'Buffer', data: this.toString('hex').toUpperCase() }; }; + +module.exports = Buffer; diff --git a/QuoteVerification/QuoteVerificationService/src/util/clone.js b/QuoteVerification/QuoteVerificationService/src/util/clone.js new file mode 100644 index 00000000..b3ded1fc --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/clone.js @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * Returns a clone of an object + * + * @param {Object} input + * + * @return {Object} + */ +function clone(input) { + return JSON.parse(JSON.stringify(input)); +} + +module.exports = clone; diff --git a/QuoteVerification/QuoteVerificationService/src/util/jsonUtils.js b/QuoteVerification/QuoteVerificationService/src/util/jsonUtils.js new file mode 100644 index 00000000..de29a89a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/jsonUtils.js @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const _ = require('lodash'); + +module.exports.parse = (data) => { + if (_.isEmpty(data)) { + return undefined; + } + else if (_.isObject(data)) { + return data; + } + try { + return JSON.parse(data); + } + catch (e) { + throw new Error('Input data must be in JSON string format', e); + } +}; diff --git a/QuoteVerification/QuoteVerificationService/src/util/onelinerFormat.js b/QuoteVerification/QuoteVerificationService/src/util/onelinerFormat.js new file mode 100644 index 00000000..cd0aebb0 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/onelinerFormat.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const LINE_SEPARATOR = ' | '; + +function onelinerFormat(message) { + return message.replace(/[\r\n]+/gm, LINE_SEPARATOR); +} + +module.exports = onelinerFormat; diff --git a/QuoteVerification/QuoteVerificationService/src/util/random.js b/QuoteVerification/QuoteVerificationService/src/util/random.js new file mode 100644 index 00000000..606769cf --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/random.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const uuidGen = require('uuid-random'); + +module.exports.uuid = () => { + return uuidGen.bin().toString('hex'); +}; diff --git a/QuoteVerification/QuoteVerificationService/src/util/responseUtils.js b/QuoteVerification/QuoteVerificationService/src/util/responseUtils.js new file mode 100644 index 00000000..82a7568e --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/src/util/responseUtils.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/** + * IntelliJ IDEA has long lasting bugs: + * https://youtrack.jetbrains.com/issue/WEB-31971 + * https://youtrack.jetbrains.com/issue/WEB-52385 + * JSDoc import works for example in Visual Studio Code. + * + * @typedef {import('../jsDoc/types').KoaResponse} KoaResponse + */ + +class ResponseUtils { + + /** + * Get status code safely + * @param {KoaResponse} response + * @returns {number} + */ + getStatusCodeSafely(response) { + if (response && response.statusCode) { + return response.statusCode; + } + } + + /** + * check if status code does not exist + * @param {KoaResponse} response + * @returns {boolean} + */ + statusCodeNotExists(response) { + return !this.getStatusCodeSafely(response); + } +} + +module.exports = new ResponseUtils(); diff --git a/QuoteVerification/QuoteVerificationService/test/bootstrapTest.js b/QuoteVerification/QuoteVerificationService/test/bootstrapTest.js new file mode 100644 index 00000000..c1d96ad0 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/bootstrapTest.js @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const helpers = require('./mocks/helpers'); + +class TestContext { + constructor() { + this.process = { + exit: sinon.stub() + }; + this.path = { + join: sinon.stub().returns('/path') + }; + this.commonLogger = { + genericLogger: { + error: sinon.stub() + } + }; + this.server = 'server'; + } + + async getTarget(initStub) { + return proxyquire('../src/bootstrap', { + './common/logger': this.commonLogger, + 'path': this.path, + 'process': this.process, + './configLoader': { + init: initStub + }, + './server': this.server, + }); + } +} + +describe('bootstrapTest', () => { + + describe('positive', () => { + it('config processed, server run', async() => { + // GIVEN + const c = new TestContext(); + const init = sinon.stub().resolves(); + // WHEN + const run = await c.getTarget(init); + // THEN + assert(init.calledOnce); + assert.strictEqual(run, c.server); + }); + + + }); + + describe('negative', () => { + it('error processing config', async() => { + // GIVEN + const c = new TestContext(); + const init = sinon.stub().rejects(new Error('Error processing config')); + // WHEN + const run = await c.getTarget(init); + // THEN + assert(init.calledOnce); + assert(c.commonLogger.genericLogger.error.calledOnce); + assert.strictEqual(c.commonLogger.genericLogger.error.args[0][0], + 'During loading config and parsing templates occurred an'); + helpers.assertMockFirstCalledWithArgs(c.process.exit, 1); + assert.strictEqual(run, undefined); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/clients/CRLClientTest.js b/QuoteVerification/QuoteVerificationService/test/clients/CRLClientTest.js new file mode 100644 index 00000000..cf43e566 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/clients/CRLClientTest.js @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + 'use strict'; + + const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); + const sinon = require('sinon'); + const assert = require('assert'); + const helpers = require('../mocks/helpers'); + + class TestContext { + constructor() { + this.config = { + cache: { + ttl: 900, + checkPeriod: 60, + maxKeys: -1 + }, + crlClient: { + retries: 3, + initialInterval: 100, + factor: 2 + } + }; + this.configLoader = { + getConfig: () => this.config + }; + const restClient = { + getRequestWithBinaryResponsePromised: sinon.stub() + }; + this.RestClientMock = function construct(type, host, port, retryCount, retryDelay, factor, certFile, keyFile, caCertDirectories) { + this.type = type; + this.host = host; + this.port = port; + this.retryCount = retryCount; + this.retryDelay = retryDelay; + this.factor = factor; + this.certFile = certFile; + this.keyFile = keyFile; + this.caCertDirectories = caCertDirectories; + + this.getRequestWithBinaryResponsePromised = restClient.getRequestWithBinaryResponsePromised; + }; + this.restClient = restClient; + this.logger = { + info: sinon.stub(), + warn: sinon.stub() + }; + const nodeCache = { + crls: { + getStub: sinon.stub(), + setStub: sinon.stub() + }, + clients: { + getStub: sinon.stub(), + setStub: sinon.stub() + } + }; + this.NodeCacheMock = function construct(config) { + assert.equal(typeof config.stdTTL, 'number'); + assert.equal(typeof config.checkperiod, 'number'); + assert.equal(typeof config.maxKeys, 'number'); + + if (config.stdTTL === 0) { // Any way to differentiate + this.get = nodeCache.clients.getStub; + this.set = nodeCache.clients.setStub; + } + else { + this.get = nodeCache.crls.getStub; + this.set = nodeCache.crls.setStub; + } + + }; + this.nodeCache = nodeCache; + } + + async getTarget() { + return proxyquire('../../src/clients/crlAccessLayer/CRLClient', { + '../../configLoader': this.configLoader, + '../../common/RestClient': this.RestClientMock, + 'node-cache': this.NodeCacheMock + }); + } +} + +describe('PCSClient tests', () => { + + describe('getCrlFromDistributionPoint call', () => { + + it('response already cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.nodeCache.crls.getStub.returns(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.strictEqual(testCtx.nodeCache.crls.getStub.args[0][0], distributionPoint); + assert.strictEqual(testCtx.logger.info.callCount, 1); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.info, 'CRL taken from cache instead of ' + distributionPoint); + }); + + it('response saved to cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.crls.setStub, distributionPoint, mockedResponse); + assert.strictEqual(testCtx.logger.info.callCount, 3); + helpers.assertMockCalledWithArgs(2, testCtx.logger.info, 'Cached CRL downloaded from: ' + distributionPoint); + }); + + it('error thrown', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const error = new Error('Error'); + testCtx.restClient.getRequestWithBinaryResponsePromised.rejects(error); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, error); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + assert.strictEqual(testCtx.nodeCache.crls.setStub.callCount, 0); + assert.strictEqual(testCtx.logger.warn.callCount, 0); + assert.strictEqual(testCtx.logger.info.callCount, 2); + }); + + it('client already cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + testCtx.nodeCache.clients.getStub.returns(testCtx.restClient); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.strictEqual(testCtx.nodeCache.crls.getStub.args[0][0], distributionPoint); + assert.strictEqual(testCtx.nodeCache.clients.setStub.callCount, 0); + assert.strictEqual(testCtx.logger.info.args[0][0].startsWith('Creating RestClient for host'), false); + }); + + it('no port provided - 443 by default', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.crls.setStub, distributionPoint, mockedResponse); + assert.strictEqual(testCtx.logger.info.callCount, 3); + helpers.assertMockCalledWithArgs(2, testCtx.logger.info, 'Cached CRL downloaded from: ' + distributionPoint); + assert.strictEqual(testCtx.nodeCache.clients.setStub.args[0][1].port, 443); + }); + + it('CRL cache turned off', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + testCtx.config.cache.maxKeys = 0; + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + assert.strictEqual(testCtx.nodeCache.crls.setStub.callCount, 0); + assert.strictEqual(testCtx.logger.info.callCount, 2); + }); + + it('saving rest client in cache fails - just a warning', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + const error = new Error('Problem with cache'); + testCtx.nodeCache.clients.setStub.throws(error); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + assert.strictEqual(testCtx.logger.warn.callCount, 1); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.warn, 'Problem with clients cache: Error: Problem with cache'); + }); + + it('saving CRL in cache fails - just a warning', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const distributionPoint = 'https://localhost:1234/CRL.crl?encoding=pem'; + const mockedResponse = { status: 200, body: Buffer.from('CRL'), headers: ['header'] }; + testCtx.restClient.getRequestWithBinaryResponsePromised.resolves(mockedResponse); + const error = new Error('Problem with cache'); + testCtx.nodeCache.crls.setStub.throws(error); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getCrlFromDistributionPoint(distributionPoint, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestWithBinaryResponsePromised, + reqId, testCtx.logger, null, '/CRL.crl', {}, { encoding: 'pem' }); + assert.strictEqual(testCtx.logger.warn.callCount, 1); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.warn, 'Problem with CRLs cache: Error: Problem with cache'); + }); + + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/clients/PCSClientTest.js b/QuoteVerification/QuoteVerificationService/test/clients/PCSClientTest.js new file mode 100644 index 00000000..b6fd9877 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/clients/PCSClientTest.js @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const helpers = require('../mocks/helpers'); + +class TestContext { + constructor() { + this.config = { + pcsClient: {}, + cache: { + ttl: 900, + checkPeriod: 60, + maxKeys: 1 + } + }; + this.configLoader = { + getConfig: () => this.config + }; + const restClient = { + getRequestPromisedStub: sinon.stub(), + healthStub: sinon.stub() + }; + this.RestClientMock = function construct() { + this.getRequestPromised = restClient.getRequestPromisedStub; + this.health = restClient.healthStub; + }; + this.restClient = restClient; + this.logger = { + info: sinon.stub() + }; + const nodeCache = { + getStub: sinon.stub(), + setStub: sinon.stub() + }; + this.NodeCacheMock = function construct(config) { + assert.equal(typeof config.stdTTL, 'number'); + assert.equal(typeof config.checkperiod, 'number'); + assert.equal(typeof config.maxKeys, 'number'); + + this.get = nodeCache.getStub; + this.set = nodeCache.setStub; + }; + this.nodeCache = nodeCache; + } + + async getTarget() { + return proxyquire('../../src/clients/pcsAccessLayer/PCSClient', { + '../../configLoader': this.configLoader, + '../../common/RestClient': this.RestClientMock, + 'node-cache': this.NodeCacheMock + }); + } +} + +describe('PCSClient tests', () => { + + describe('getSgxTcbInfo call', () => { + it('response saved to cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const fmspc = 'fmspc'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/sgx/certification/v4/tcb', {}, { fmspc }); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.setStub, fmspc, mockedResponse); + assert.equal(testCtx.logger.info.callCount, 1); + }); + + it('response read from cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const fmspc = 'fmspc'; + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.nodeCache.getStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.equal(testCtx.restClient.getRequestPromisedStub.callCount, 0); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.info, + `SGX TcbInfo for FMSPC=${fmspc} taken from cache`); + }); + + it('error not cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const fmspc = 'fmspc'; + const reqId = 'requestId'; + const mockedResponse = { status: 500, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.rejects(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/sgx/certification/v4/tcb', {}, { fmspc }); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + assert.equal(testCtx.logger.info.callCount, 0); + }); + }); + + describe('getTdxTcbInfo call', () => { + it('response saved to cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const fmspc = 'fmspc'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/tdx/certification/v4/tcb', {}, { fmspc }); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.setStub, fmspc, mockedResponse); + assert.equal(testCtx.logger.info.callCount, 1); + }); + + it('response read from cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const fmspc = 'fmspc'; + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.nodeCache.getStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.equal(testCtx.restClient.getRequestPromisedStub.callCount, 0); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.info, + `TDX TcbInfo for FMSPC=${fmspc} taken from cache`); + }); + + it('error not cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const fmspc = 'fmspc'; + const reqId = 'requestId'; + const mockedResponse = { status: 500, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.rejects(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxTcbInfo(fmspc, reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/tdx/certification/v4/tcb', {}, { fmspc }); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + assert.equal(testCtx.logger.info.callCount, 0); + }); + }); + + describe('getSgxQeIdentity call', () => { + it('response saved to cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/sgx/certification/v4/qe/identity'); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.setStub, 'sgxQeIdentity', mockedResponse); + assert.equal(testCtx.logger.info.callCount, 1); + }); + + it('response read from cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.nodeCache.getStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.equal(testCtx.restClient.getRequestPromisedStub.callCount, 0); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.info, 'SGX QeIdentity taken from cache'); + }); + + it('error not cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 500, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.rejects(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getSgxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/sgx/certification/v4/qe/identity'); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + assert.equal(testCtx.logger.info.callCount, 0); + }); + }); + + describe('getTdxQeIdentity call', () => { + it('response saved to cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/tdx/certification/v4/qe/identity'); + helpers.assertMockFirstCalledWithArgs(testCtx.nodeCache.setStub, 'tdxQeIdentity', mockedResponse); + assert.equal(testCtx.logger.info.callCount, 1); + }); + + it('response read from cache', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 200, body: 'body', headers: ['header'] }; + testCtx.nodeCache.getStub.resolves(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + assert.equal(testCtx.restClient.getRequestPromisedStub.callCount, 0); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + helpers.assertMockFirstCalledWithArgs(testCtx.logger.info, 'TDX QeIdentity taken from cache'); + }); + + it('error not cached', async() => { + // GIVEN + const testCtx = new TestContext(); + const reqId = 'requestId'; + const mockedResponse = { status: 500, body: 'body', headers: ['header'] }; + testCtx.restClient.getRequestPromisedStub.rejects(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.getTdxQeIdentity(reqId, testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + helpers.assertMockFirstCalledWithArgs(testCtx.restClient.getRequestPromisedStub, + reqId, testCtx.logger, null, '/tdx/certification/v4/qe/identity'); + assert.equal(testCtx.nodeCache.setStub.callCount, 0); + assert.equal(testCtx.logger.info.callCount, 0); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/clients/VCSClientTest.js b/QuoteVerification/QuoteVerificationService/test/clients/VCSClientTest.js new file mode 100644 index 00000000..0a3844cd --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/clients/VCSClientTest.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +class TestContext { + constructor() { + this.config = { + vcsClient: {}, + }; + this.configLoader = { + getConfig: () => this.config + }; + const restClient = { + getRequestPromisedStub: sinon.stub(), + postRequestPromisedStub: sinon.stub(), + healthStub: sinon.stub() + }; + this.RestClientMock = function construct() { + this.getRequestPromised = restClient.getRequestPromisedStub; + this.postRequestPromised = restClient.postRequestPromisedStub; + this.health = restClient.healthStub; + }; + this.restClient = restClient; + this.logger = { + info: sinon.stub() + }; + const nodeCache = { + getStub: sinon.stub(), + setStub: sinon.stub() + }; + this.NodeCacheMock = function construct(config) { + assert.equal(typeof config.stdTTL, 'number'); + assert.equal(typeof config.checkperiod, 'number'); + assert.equal(typeof config.maxKeys, 'number'); + + this.get = nodeCache.getStub; + this.set = nodeCache.setStub; + }; + this.nodeCache = nodeCache; + } + + async getTarget() { + return proxyquire('../../src/clients/vcsAccessLayer/VCSClient', { + '../../configLoader': this.configLoader, + '../../common/RestClient': this.RestClientMock + }); + } +} + +describe('VCSClient tests', () => { + + describe('signVerificationReport', () => { + + it('positive response', async() => { + // GIVEN + const testCtx = new TestContext(); + const mockedResponse = { status: 200 }; + testCtx.restClient.postRequestPromisedStub.returns(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = await target.signVerificationReport({}, 'reqId', testCtx.logger); + // THEN + assert.deepStrictEqual(response, mockedResponse); + }); + + it('error response', async() => { + // GIVEN + const testCtx = new TestContext(); + const err = new Error('Signing failed'); + testCtx.restClient.postRequestPromisedStub.rejects(err); + const target = await testCtx.getTarget(); + const response = await target.signVerificationReport({}, 'reqId', testCtx.logger); + assert.deepStrictEqual(response, err); + }); + }); + + describe('getHealth call', () => { + + it('response with body.status from checked service', async() => { + // GIVEN + const testCtx = new TestContext(); + const mockedResponse = { status: 200, body: { status: 'OK' } }; + testCtx.restClient.healthStub.returns(mockedResponse); + const target = await testCtx.getTarget(); + // WHEN + const response = target.getHealth(null, {}); + + // THEN + assert.deepStrictEqual(response, mockedResponse); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/RestClientTest.js b/QuoteVerification/QuoteVerificationService/test/common/RestClientTest.js new file mode 100644 index 00000000..21d9f643 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/RestClientTest.js @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const logger = require('../../src/common/logger').genericLogger; +const retry = require('retry'); + +const assert = require('assert'); +const sinon = require('sinon'); + + +class TestContext { + + constructor(mockResponse) { + + this.mockResponse = mockResponse; + this.mockCertificates = sinon.fake(x => x); + this.mockReadfile = sinon.fake(() => []); + + this.RestClient = proxyquire('../../src/common/RestClient', { + './getCACertificatesSync': this.mockCertificates, + '../common/readFileSafely': this.mockReadfile, + '../configLoader': { + getConfig: () => ({ + service: { + restClientTimeout: 5000 + } + }) + }, + './requestHandler': proxyquire('../../src/common/requestHandler', { + './nodeRequestHandler': async(options, body) => { + return mockResponse(options, body); + }, + 'createRetryOperation': () => retry.operation({ + maxTimeout: 100, + minTimeout: 100, + retries: 1, + factor: 2 + }) + }), + }); + } + + static okJson200(body) { + return { statusCode: 200, body, headers: { 'content-type': 'application/json' } }; + } +} + + +describe('RestClientTests', () => { + + describe('validate', () => { + it('execution flow during requesting health method', async() => { + // GIVEN + const defaultBody = { testField: 'testFieldValue' }; + const responseFake = sinon.fake.resolves(TestContext.okJson200(defaultBody)); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + // WHEN + const result = await target.health(logger, {}); + + // THEN + assert.deepEqual(result.body, defaultBody); + assert.equal(context.mockCertificates.callCount, 1); + assert.equal(context.mockReadfile.callCount, 3); + assert.equal(responseFake.callCount, 1); + }); + + it('error during requesting health method - internal error', async() => { + // GIVEN + const responseFake = sinon.fake.rejects(new Error('request rejected')); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + // WHEN, THEN + try { + await target.health(logger, {}); + + } + catch (err) { + assert.equal(err.message, 'request rejected'); + assert.equal(responseFake.callCount, 1); + assert.equal(context.mockCertificates.callCount, 1); + assert.equal(context.mockReadfile.callCount, 3); + } + sinon.restore(); + }); + }); + + describe('getRequestPromised', () => { + it('should resolve when receives success response with body', async() => { + // GIVEN + const body = { testField: 'testFieldValue' }; + const responseFake = sinon.fake.resolves(TestContext.okJson200(body)); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + + // WHEN + const result = await target.getRequestPromised(1, logger, { body }, 'path', {}, {}); + + + // THEN + assert.deepEqual(result.body, body); + assert.deepEqual(result.status, 200); + assert(responseFake.calledOnce); + }); + + it('should resolve when receives success response without body', async() => { + // GIVEN + const responseFake = sinon.fake.resolves(TestContext.okJson200(null)); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + // WHEN + const result = await target.getRequestPromised(1, logger, { body: null }, 'path', {}, {}); + + // THEN + assert.deepEqual(result.body, null); + assert.deepEqual(result.status, 200); + assert(responseFake.calledOnce); + }); + + + it('should resolve when receives success response with serialized body', async() => { + // GIVEN + const body = '{"serialized": "true"}'; + const responseFake = sinon.fake.resolves(TestContext.okJson200(body)); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + + // WHEN + const result = await target.getRequestPromised(1, logger, { body }, 'path', {}, {}); + + // THEN + assert.deepEqual(result.body, body); + assert.deepEqual(result.status, 200); + assert(responseFake.calledOnce); + }); + + it('should reject when receives response with wrong body format', async() => { + // GIVEN + const body = '{ unexpectedField }'; + const responseFake = sinon.fake.resolves(TestContext.okJson200(body)); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + + // WHEN + try { + await target.getRequestPromised(1, logger, { body }, 'path', {}, {}); + assert.fail('Should throw error'); + } + catch (err) { + assert(err instanceof Error); + } + }); + + it('should reject with error when error occurred', async() => { + // GIVEN + const responseFake = sinon.fake.rejects(new Error('error example')); + const context = new TestContext(responseFake); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + + // WHEN + try { + await target.getRequestPromised(1, logger, { body: 'request' }, 'path', {}, {}); + assert.fail('Should throw error'); + } + catch (err) { + assert(err instanceof Error); + } + }); + + + it('should call internal method with expected arguments (GET)', async() => { + // GIVEN + const body = 'request'; + const context = new TestContext(sinon.fake()); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + const stub = sinon.stub(target, 'requestPromised').returns(); + + // WHEN + await target.getRequestPromised(1, logger, { body }, 'path'); + + // THEN + assert(stub.calledWithExactly(logger, 'GET', 'path', { body }, 1, {}, {})); + }); + + it('should call internal method with expected arguments (POST)', async() => { + // GIVEN + const body = 'request'; + const context = new TestContext(sinon.fake()); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + const stub = sinon.stub(target, 'requestPromised').returns(); + + // WHEN + await target.postRequestPromised(1, logger, { body }, 'path'); + + // THEN + assert(stub.calledWithExactly(logger, 'POST', 'path', { body }, 1, {}, {}, false)); + }); + + it('should call internal method with expected arguments (PUT)', async() => { + // GIVEN + const body = 'request'; + const context = new TestContext(sinon.fake()); + const target = new context.RestClient('MTLS', '127.0.0.1', 'aPort', 2, 0, 3, 'cert.crt', 'cert.key', ['CA/ca.crt']); + const stub = sinon.stub(target, 'requestPromised').returns(); + + // WHEN + await target.putRequestPromised(1, logger, { body }, 'path'); + + // THEN + assert(stub.calledWithExactly(logger, 'PUT', 'path', { body }, 1, {}, {})); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/configTest.js b/QuoteVerification/QuoteVerificationService/test/common/configTest.js new file mode 100644 index 00000000..3d0d558d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/configTest.js @@ -0,0 +1,1628 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/* eslint no-new: off */ + +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); +const sinon = require('sinon'); +const config = require('../../src/common/config'); + + +// valid yaml: service config as an example +const validYaml = ` +service: + componentName: 'Service' + componentShort: 'SRV' + port: 8443 + certFile: 'certificates/internal/srv.crt' + keyFile: 'certificates/internal/srv.key' + caCertDirectories: '../../Tools/AnsibleScripts/group_files/ALL/internal_use_ca_certificates/, ../../Tools/AnsibleScripts/files/testPurposesCA/' + bodySizeLimits: + text: '16mb' + json: '16mb' +`; + +// valid yaml with templates +const validYamlWithTemplates = ` +service: + componentName: \${QVS_SERVICE_COMPONENT_NAME:RegistrationDataService} + componentShort: \${QVS_SERVICE_COMPONENT_SHORT:RDS} + port: \${QVS_SERVICE_PORT:9998} + certFile: \${QVS_SERVICE_CERT_FILE:certificates/internal/rds.crt} + keyFile: \${QVS_SERVICE_KEY_FILE:certificates/internal/rds.key} + tlsServerType: \${QVS_SERVICE_TLS_SERVER_TYPE:TLS} + caCertDirectories: \${QVS_CA_CERT_DIRECTORIES:certificates/internal_ca/internal_use_ca_certificates/} + bodySizeLimits: \${QVS_SERVICE_BODY_SIZE_LIMITS:{"json":"1mb"}} + +cassandra: + contactPoints : \${QVS_CASSANDRA_CONTACT_POINTS:"127.0.0.1"} + healthCheckTable: \${QVS_CASSANDRA_HEALTH_CHECK_TABLE:"processorregistrationkey"} + keyspace : \${QVS_CASSANDRA_KEYSPACE:"registration_db"} + maxRetries : \${QVS_CASSANDRA_MAX_RETRIES:3} + password : \${QVS_CASSANDRA_PASSWORD:registration_US} + timeoutMs : \${QVS_CASSANDRA_TIMEOUT_MS:100} + user : \${QVS_CASSANDRA_USER:registration_US} + datacenter : \${QVS_CASSANDRA_DATACENTER:DC1} +`; + +// invalid yaml: lack of ':' in first line +const invalidYaml = ` +service + componentName: 'Service' + componentShort: 'SRV' + port: 8443 + certFile: 'certificates/internal/srv.crt' + keyFile: 'certificates/internal/srv.key' + caCertDirectories: '../../Tools/AnsibleScripts/group_files/ALL/internal_use_ca_certificates/,../../Tools/AnsibleScripts/files/testPurposesCA/' + bodySizeLimits: + text: '16mb' + json: '16mb' +`; + +function readFile(returnedContent, throws = false) { + return function readFileSafely() { + if (throws) { + throw new Error('ENOENT: no such file or directory'); + } + return returnedContent; + }; +} + +describe('configTest', () => { + + const assertErrorReport = { + missingProperty(error, property) { + const regex = new RegExp(`Missing required property: ${property}`); + assert(regex.test(error), `Missing of ${property} not reported`); + } + }; + + describe('config_load', () => { + + it('positive', async() => { + //GIVEN + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(validYaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject( + //WHEN + localConfig.load + ); + }); + + describe('templates support', () => { + + it('positive with templates', async() => { + //GIVEN + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(validYamlWithTemplates), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject( + //WHEN + localConfig.load + ); + }); + + it('optional \'\'', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:\'default_value\'}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'default_value'); + }); + }); + + it('optional ""', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:"default_value"}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'default_value'); + }); + }); + + it('boolean true', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:true}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, true); + }); + }); + + it('boolean false', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:false}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, false); + }); + }); + + it('number', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:7}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 7); + }); + }); + + it('null', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:null}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, null); + }); + }); + + it('null as string', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:"null"}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'null'); + }); + }); + + it('number as string', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:"7"}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, '7'); + }); + }); + + it('array', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:[7, 9, 12]}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.deepEqual(config.key, [7, 9, 12]); + }); + }); + + it('invalid array', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:[1,3]]}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + //WHEN + localConfig.load, + /Initialization failure: \[1,3]] cannot be parsed as valid array/ + ); + }); + + it('object', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:{"a":"b"}}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.deepEqual(config.key, { a: 'b' }); + }); + }); + + it('object without "" on key', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:{a:5}}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + localConfig.load, + /Initialization failure: {a:5} cannot be parsed as valid JSON/ + ); + }); + + it('object without "" on string value', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:{"a":a}}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + //WHEN + localConfig.load, + /Initialization failure: {"a":a} cannot be parsed as valid JSON/ + ); + }); + + it('too short string to have "", no need to check first and last char', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:a}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'a'); + }); + }); + + it('complex structure - test recurrency', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:{"a":{"b":{"c":{"d":[1]}}}}}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.deepEqual(config.key.a.b.c.d[0], 1); + }); + }); + + it('mandatory default', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + //WHEN + localConfig.load, + /Non-empty key and default value in config are mandatory in template/ + ); + }); + + it('empty default', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, ''); + }); + }); + + it('default value with a colon', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:host:port}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'host:port'); + }); + }); + + it('from env', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'env_value'); + }); + }); + + it('APPLICATION_JSON overrides', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value', + APPLICATION_JSON: '{"KEY_ENV":"value_from_APPLICATION_JSON"}' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'value_from_APPLICATION_JSON'); + }); + }); + + it('both APPLICATION_JSON and APPLICATION_JSON_BASE64 defined, use APPLICATION_JSON', async() => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value', + APPLICATION_JSON: '{"KEY_ENV":"value_from_APPLICATION_JSON"}', + // base64('{"KEY_ENV":"value_from_APPLICATION_JSON_BASE64"}') + APPLICATION_JSON_BASE64: 'eyJLRVlfRU5WIjoidmFsdWVfZnJvbV9BUFBMSUNBVElPTl9KU09OX0JBU0U2NCJ9Cg==' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject(async() => { + //WHEN + const config = await localConfig.load(); + assert.strictEqual(config.key, 'value_from_APPLICATION_JSON'); + }); + }); + + it('APPLICATION_JSON not as JSON', () => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + //THEN + assert.throws( + () => { + //WHEN + proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value', + APPLICATION_JSON: 'notJSON' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + }, + /Error parsing APPLICATION_JSON env variable. Make sure it is in JSON format/ + ); + }); + + it('APPLICATION_JSON_BASE64 encoded string is not as JSON', () => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + //THEN + assert.throws( + () => { + //WHEN + proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value', + APPLICATION_JSON_BASE64: 'bm90SlNPTgo=' // base64(notJSON) + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + }, + /Error parsing APPLICATION_JSON_BASE64 env variable. Make sure its content is proper base64 of a JSON/ + ); + }); + + it('APPLICATION_JSON_BASE64 not base64', () => { + //GIVEN + const yaml = 'key: ${KEY_ENV:default}'; + //THEN + assert.throws( + () => { + //WHEN + proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(yaml), + 'process': { + env: { + KEY_ENV: 'env_value', + APPLICATION_JSON_BASE64: 'ZZZ' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + }, + /Error parsing APPLICATION_JSON_BASE64 env variable. Make sure its content is proper base64 of a JSON/ + ); + }); + + }); + + it('no_such_file', async() => { + //GIVEN + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile('', true), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + //WHEN + localConfig.load, + /no such file/ + ); + }); + + it('invalid_yaml', async() => { + //GIVEN + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(invalidYaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.rejects( + //WHEN + localConfig.load, + /end of the stream/ + ); + }); + + describe('local', () => { + + it('positive', async() => { + //GIVEN + const localConfig = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(validYamlWithTemplates), + 'process': { + env: { + QVS_CONFIG_SOURCE: 'local' + } + }, + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + //THEN + await assert.doesNotReject( + //WHEN + localConfig.load + ); + }); + + }); + + }); + + describe('base_validate', () => { + it('success', () => { + //GIVEN + const base = new config.Base(); + //WHEN / THEN + base.validate( + { + test: 'test' + }, + { + type: 'object', + properties: { + test: { + type: 'string' + } + } + } + ); + }); + + it('fail', () => { + //GIVEN + const base = new config.Base(); + //WHEN / THEN + assert.throws(() => { + base.validate( + { + test2: 'test' + }, + { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string' + } + } + }); + }, /Missing required property: test/); + }); + }); + + describe('logger_constructor', () => { + it('success', () => { + //GIVEN / WHEN + const logger = new config.Logger({ + levelFile: 'trace', + levelConsole: 'off', + fileName: 'fileName', + category: 'category' + }); + //THEN + assert.equal('trace', logger.levelFile); + assert.equal('off', logger.levelConsole); + assert.equal('fileName', logger.fileName); + assert.equal('category', logger.category); + }); + + it('missing_file_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Logger({ + levelFile: 'off', + levelConsole: 'trace', + category: 'category' + }); + }, /Missing required property: fileName/); + }); + + it('missing_levelFile_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Logger({ + levelConsole: 'off', + fileName: 'fileName', + category: 'category' + }); + }, /Missing required property: levelFile/); + }); + + it('missing_levelConsole_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Logger({ + levelFile: 'off', + fileName: 'fileName', + category: 'category' + }); + }, /Missing required property: levelConsole/); + }); + + it('missing_category_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Logger({ + levelFile: 'off', + levelConsole: 'off', + fileName: 'fileName' + }); + }, /Missing required property: category/); + }); + }); + + describe('healthCheck_constructor', () => { + it('success', () => { + //GIVEN / WHEN + const healthCheck = new config.HealthCheck({ + intervalMs: 1000, + freshnessMs: 5000 + }); + //THEN + assert.equal(1000, healthCheck.intervalMs); + assert.equal(5000, healthCheck.freshnessMs); + }); + + it('no_interavalMs_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.HealthCheck({ + freshnessMs: 1000 + }); + }, /Missing required property: intervalMs/); + }); + + it('no_freshnessMs_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.HealthCheck({ + intervalMs: 1000 + }); + }, /Missing required property: freshnessMs/); + }); + + it('no_freshnessMs_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.HealthCheck({ + intervalMs: 1000, + freshnessMs: 'not a number' + }); + }, /Expected type number but found type string in freshnessMs/); + }); + + it('no_intervalMs_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.HealthCheck({ + freshnessMs: 1000, + intervalMs: 'not a number' + }); + }, /Expected type number but found type string in intervalMs/); + }); + }); + + describe('service_constructor', () => { + it('success', () => { + //GIVEN / WHEN + const service = new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: '/../configuration-default/certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + //THEN + assert.equal(80, service.port); + assert.equal('/../configuration-default/certFile', service.certFile); + assert.equal('../configuration-default/keyFile', service.keyFile); + assert.equal(1, service.caCertDirectories.length); + assert.equal('../configuration-default/certDirectory', service.caCertDirectories[0]); + }); + + it('multiple_caCertDirectories_success', () => { + //GIVEN / WHEN + const service = new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory,certDirectory2,certDirectory3', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + //THEN + assert.equal(80, service.port); + assert.equal('../configuration-default/certFile', service.certFile); + assert.equal('../configuration-default/keyFile', service.keyFile); + assert.equal(3, service.caCertDirectories.length); + assert.equal('../configuration-default/certDirectory', service.caCertDirectories[0]); + assert.equal('../configuration-default/certDirectory2', service.caCertDirectories[1]); + assert.equal('../configuration-default/certDirectory3', service.caCertDirectories[2]); + }); + + it('required_success', () => { + //GIVEN / WHEN + const service = new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory,certDirectory2,certDirectory3', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + //THEN + assert.equal(undefined, service.port); + assert.equal('../configuration-default/certFile', service.certFile); + assert.equal('../configuration-default/keyFile', service.keyFile); + assert.equal(3, service.caCertDirectories.length); + assert.equal('../configuration-default/certDirectory', service.caCertDirectories[0]); + assert.equal('../configuration-default/certDirectory2', service.caCertDirectories[1]); + assert.equal('../configuration-default/certDirectory3', service.caCertDirectories[2]); + }); + + it('tls_no_certFile_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'TLS', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + }, /Missing required property: certFile/); + }); + + it('tls_no_keyFile_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'TLS', + certFile: 'test', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + }, /Missing required property: keyFile/); + }); + + it('no_tls_cert_and_key_not_required', () => { + //GIVEN / WHEN + const service = new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'None', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }); + //THEN + assert.equal(undefined, service.port); + }); + + it('no_caCertDirectories_for_MTLS_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + certFile: 'certFile', + keyFile: 'keyFile' + }); + }, /Missing required property: caCertDirectories/); + }); + + it('no_caCertDirectories_is_not_string_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Service({ + componentName: 'ServiceName', + componentShort: 'SRV', + certFile: 'test', + keyFile: 'keyFile', + caCertDirectories: ['not a string'] + }); + }, /Expected type string but found type array in caCertDirectories/); + }); + + it('multiple_fails', () => { + /* Full error info: + Error: Configuration parsing error(s) in "/configuration/config.yml" : + Missing required property: keyFile :: + Missing required property: certFile :: + Expected type string but found type integer in caCertDirectories :: + Expected type string but found type undefined in keyFile :: + Expected type number but found type string in port :: + Expected type string but found type array in componentName + */ + function checkException(e) { + // all following cases should be true for proper exception validation + return ( + e instanceof Error && + (/Missing required property: keyFile/).test(e) && + (/Missing required property: certFile/).test(e) && + (/Expected type string but found type integer in caCertDirectories/).test(e) && + (/Expected type string but found type undefined in keyFile/).test(e) && + (/Expected type number but found type string in port/).test(e) && + (/Expected type string but found type array in componentName/).test(e) + ); + } + + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.Service({ + componentName: ['ServiceName'], // should be string, not an array + componentShort: 'SRV', + port: 'not-a-number', // should be number, not a string + //certFile: 'certFile', // should be present + keyFile: undefined, // should have a string value (also interpreted as non-existent value) + caCertDirectories: 1234 // should be a string, not a number + }); + }, checkException); + }); + }); + + describe('restclient_constructor', () => { + it('success', () => { + //GIVEN / WHEN + const restclient = new config.RestClient({ + tlsClientType: 'MTLS', + host: 'host', + port: 80, + retries: 3, + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certificates/internal_ca/internal_use_ca_certificates/', + servername: '' + }); + //THEN + assert.equal('host', restclient.host); + assert.equal(80, restclient.port); + assert.equal(3, restclient.retries); + assert.equal(100, restclient.initialInterval); + assert.equal(3, restclient.factor); + assert.equal('../configuration-default/certFile', restclient.certFile); + assert.equal('../configuration-default/keyFile', restclient.keyFile); + assert.deepEqual(['../configuration-default/certificates/internal_ca/internal_use_ca_certificates/'], restclient.caCertDirectories); + }); + + it('no_host_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + port: 80, + retries: 3, + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile' + }); + }, /Missing required property: host/); + }); + + it('no_port_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + retries: 3, + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Missing required property: port/); + }); + + it('no_retries_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Missing required property: retries/); + }); + + it('no_initialInterval_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + retries: 3, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Missing required property: initialInterval/); + }); + + it('no_factor_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + retries: 3, + initialInterval: 100, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Missing required property: factor/); + }); + + it('port_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 'not a number', + retries: 3, + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Expected type number but found type string in port/); + }); + + it('retries_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + retries: 'not a number', + initialInterval: 100, + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Expected type number but found type string in retries/); + }); + + it('initialInterval_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + retries: 3, + initialInterval: 'not a number', + factor: 3, + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Expected type number but found type string in initialInterval/); + }); + + it('factor_not_a_number_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.RestClient({ + host: 'host', + port: 80, + retries: 3, + initialInterval: 100, + factor: 'not a number', + certFile: 'certFile', + keyFile: 'keyFile', + servername: '' + }); + }, /Expected type number but found type string in factor/); + }); + }); + + describe('cache constructor', () => { + it('should set properties as expected', () => { + //GIVEN + const configuration = { + ttl: 900, + checkPeriod: 60, + maxKeys: -1 + }; + //WHEN + const cache = new config.Cache(configuration); + //THEN + assert.equal(configuration.ttl, cache.ttl); + assert.equal(configuration.checkPeriod, cache.checkPeriod); + assert.equal(configuration.maxKeys, cache.maxKeys); + }); + + it('should report missing required fields as expected', async() => { + //GIVEN + const configuration = {}; + try { + //WHEN + await new config.Cache(configuration); + assert.fail('Should throw error'); + } + catch (error) { + //THEN + assertErrorReport.missingProperty(error, 'ttl'); + assertErrorReport.missingProperty(error, 'checkPeriod'); + assertErrorReport.missingProperty(error, 'maxKeys'); + } + }); + }); + + describe('baseRestService_constructor', () => { + it('all_success', () => { + //GIVEN / WHEN + const baseRestService = new config.BaseRestService({ + logger: { + levelFile: 'debug', + levelConsole: 'debug', + fileName: 'fileName', + category: 'category' + }, + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + }); + + //THEN + assert.notEqual(undefined, baseRestService.logger); + assert.notEqual(undefined, baseRestService.service); + assert.notEqual(undefined, baseRestService.healthCheck); + }); + + it('no_logger_success', () => { + //GIVEN / WHEN + const baseRestService = new config.BaseRestService({ + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + }); + + //THEN + assert.equal(undefined, baseRestService.logger); + assert.notEqual(undefined, baseRestService.service); + assert.notEqual(undefined, baseRestService.healthCheck); + }); + + it('no_service_success', () => { + //GIVEN / WHEN + const baseRestService = new config.BaseRestService({ + logger: { + levelFile: 'trace', + levelConsole: 'off', + fileName: 'fileName', + category: 'category' + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + }); + + //THEN + assert.notEqual(undefined, baseRestService.logger); + assert.equal(undefined, baseRestService.service); + assert.notEqual(undefined, baseRestService.healthCheck); + }); + + it('maxClient_success', () => { + //GIVEN / WHEN + const baseRestService = new config.BaseRestService({ + logger: { + levelFile: 'trace', + levelConsole: 'off', + fileName: 'fileName', + category: 'category' + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + service: { + componentName: 'componentName', + componentShort: 'componentShort', + tlsServerType: 'None', + maxClients: 4, + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + } + }); + + //THEN + assert.notStrictEqual(undefined, baseRestService.logger); + assert.notStrictEqual(undefined, baseRestService.service); + assert.notStrictEqual(undefined, baseRestService.healthCheck); + }); + + it('no_healthCheck_success', () => { + //GIVEN / WHEN + const baseRestService = new config.BaseRestService({ + logger: { + levelFile: 'trace', + levelConsole: 'trace', + fileName: 'fileName', + category: 'category' + }, + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + }); + + //THEN + assert.notEqual(undefined, baseRestService.logger); + assert.notEqual(undefined, baseRestService.service); + assert.equal(undefined, baseRestService.healthCheck); + }); + + it('broken_logger_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.BaseRestService({ + logger: { + levelFile: 'error', + levelConsole: 'error', + category: 'category' + }, + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + }); + }, / Missing required property: fileName/); + }); + + it('broken_service_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.BaseRestService({ + logger: { + levelFile: 'error', + levelConsole: 'fatal', + fileName: 'fileName', + category: 'category' + }, + service: { + componentName: 'ServiceName', + tlsServerType: 'MTLS', + keyFile: 'keyFile', + certFile: 'certFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + healthCheck: { + intervalMs: 1000, + freshnessMs: 1000, + extraHttpPort: 80, + tables: { testTable: 'test' } + }, + }); + }, /Missing required property: componentShort/); + }); + + it('broken_healthCheck_fail', () => { + //THEN + assert.throws(() => { + //GIVEN / WHEN + new config.BaseRestService({ + logger: { + levelFile: 'off', + levelConsole: 'debug', + fileName: 'fileName', + category: 'category' + }, + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + healthCheck: { + intervalMs: 1000, + extraHttpPort: 80, + table: 'test' + }, + }); + }, /Missing required property: freshnessMs/); + }); + + }); + + describe('testService_constructor', () => { + it('lack_of_whole_section_fail', () => { + //GIVEN + // all real configs (per service) are created that way: + class SvcConfig extends config.BaseRestService { + constructor(configJson) { + super(configJson); + this.validate(configJson, { + //this list is a list of sections that are required for our test service configuration + required: ['service', 'healthCheck', 'logger'] + }); + } + } + + //THEN + assert.throws(() => { + //WHEN + new SvcConfig({ + service: { + componentName: 'ServiceName', + componentShort: 'SRV', + tlsServerType: 'MTLS', + port: 80, + certFile: 'certFile', + keyFile: 'keyFile', + caCertDirectories: 'certDirectory', + bodySizeLimits: { + text: '2mb', + json: '2mb', + form: '32kb' + } + }, + //healthCheck: { // this is our missing section + // intervalMs : 1000, + // freshnessMs: 1000, + // port : 80, + // table : 'test' + //}, + logger: { + levelFile: 'fatal', + levelConsole: 'fatal', + fileName: 'fileName', + category: 'category' + } + }); + }, /Missing required property: healthCheck/); + }); + }); + + describe('appendConfigPath', () => { + it('should not modify field when field format is number', () => { + //GIVEN + const object = { + numberField: 1 + }; + + //WHEN + config.appendConfigPath(object, ['numberField']); + + //THEN + assert.equal(1, object.numberField); + }); + + it('should modify only selected fields', () => { + //GIVEN + const object = { + modifiedField: 'modifiedField', + otherField: 'otherField' + }; + + //WHEN + config.appendConfigPath(object, ['modifiedField']); + + //THEN + assert.equal('../configuration-default/modifiedField', object.modifiedField); + assert.equal('otherField', object.otherField); + }); + + it('should modify as expected when field is a string', () => { + //GIVEN + const object = { + stringField: 'value' + }; + + //WHEN + config.appendConfigPath(object, ['stringField']); + + //THEN + assert.equal('../configuration-default/value', object.stringField); + }); + + it('should modify as expected when field is an array', () => { + //GIVEN + const object = { + arrayField: ['value'] + }; + + //WHEN + config.appendConfigPath(object, ['arrayField']); + + //THEN + assert.equal(object.arrayField, '../configuration-default/value'); + }); + }); + + describe('configLoaderTest', () => { + it('positive', async() => { + //GIVEN + class configClass {} + const config = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(validYaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + + const configLoader = new config.ConfigLoader(configClass); + await configLoader.init(''); + + //WHEN + const resultFirstCall = configLoader.getConfig(); + const resultSecondCall = configLoader.getConfig(); + + //THEN + assert.ok(resultFirstCall instanceof configClass); + assert.ok(resultSecondCall instanceof configClass); + assert.strictEqual(resultFirstCall, resultSecondCall); + }); + + it('uninitialized', async() => { + //GIVEN + class configClass {} + const config = proxyquire('../../src/common/config', { + '../common/readFileSafely': readFile(validYaml), + 'os': { + homedir: sinon.stub().returns('/home/user') + } + }); + + const configLoader = new config.ConfigLoader(configClass); + + //THEN + assert.throws(() => { + //WHEN + configLoader.getConfig(); + }, + /You need to call init\(path\) first and await initialization. Run server using `node .\/bootstrap.js and set correct config profile/ + ); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/getCACertificatesSyncTest.js b/QuoteVerification/QuoteVerificationService/test/common/getCACertificatesSyncTest.js new file mode 100644 index 00000000..53632b1e --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/getCACertificatesSyncTest.js @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +function getTarget(isFile, isDirectory) { + return proxyquire('../../src/common/getCACertificatesSync', { + fs: { + readdirSync: sinon.stub().returns(['file', 'additionalFile']), + statSync: () => { + return { + isFile, + isDirectory + }; + } + } + }); +} + + +describe('getCACertificatesSyncTests', () => { + + it('should return expected path combination for given directories and files', () => { + // GIVEN / WHEN + const isFileStub = sinon.stub().returns(true); + const isDirStub = sinon.stub().returns(false); + const result = getTarget(isFileStub, isDirStub)(['directory', 'additionalDirectory']); + // THEN + assert.equal(result.length, 4); + assert.equal(result[0], 'directory/file'); + assert.equal(result[1], 'directory/additionalFile'); + assert.equal(result[2], 'additionalDirectory/file'); + assert.equal(result[3], 'additionalDirectory/additionalFile'); + }); + + it('should check also subdirs', () => { + // GIVEN / WHEN + const isFileStub = sinon.stub().returns(true); + const isDirStub = sinon.stub(); + isDirStub.onCall(3).returns(true); + isDirStub.returns(false); + const result = getTarget(isFileStub, isDirStub)(['directory', 'additionalDirectory']); + // THEN + assert.equal(result.length, 6); + assert.equal(result[0], 'directory/file'); + assert.equal(result[1], 'directory/additionalFile'); + assert.equal(result[2], 'additionalDirectory/additionalFile/file'); + assert.equal(result[3], 'additionalDirectory/additionalFile/additionalFile'); + assert.equal(result[4], 'additionalDirectory/file'); + assert.equal(result[5], 'additionalDirectory/additionalFile'); + }); + + it('should return empty array when empty array given', () => { + // GIVEN / WHEN + const isFileStub = sinon.stub().returns(true); + const isDirStub = sinon.stub().returns(false); + const result = getTarget(isFileStub, isDirStub)([]); + // THEN + assert.equal(result.length, 0); + }); + + it('should return empty array when no array given', () => { + // WHEN + const isFileStub = sinon.stub().returns(true); + const isDirStub = sinon.stub().returns(false); + const result = getTarget(isFileStub, isDirStub)(); + // THEN + assert.equal(result.length, 0); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/nodeRequestHandlerTest.js b/QuoteVerification/QuoteVerificationService/test/common/nodeRequestHandlerTest.js new file mode 100644 index 00000000..9dd14702 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/nodeRequestHandlerTest.js @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const assert = require('assert'); +const sinon = require('sinon'); +const PassThrough = require('stream').PassThrough; +const https = require('https'); +const Buffer = require('safe-buffer').Buffer; + +const httpsHandler = require('../../src/common/nodeRequestHandler'); + +let request; + +describe('nodeRequestHandler unit tests', () => { + beforeEach(() => { + request = sinon.stub(https, 'request'); + }); + + afterEach(() => { + https.request.restore(); + }); + + it('should convert json result to object', async() => { + const expected = { hello: 'world' }; + const response = new PassThrough(); + response.write(JSON.stringify(expected)); + response.statusCode = 200; + response.headers = { 'content-type': 'application/json' }; + response.end(); + + const req = new PassThrough(); + + request.callsArgWith(1, response) + .returns(req); + + const res = await httpsHandler({}, ''); + assert.deepEqual(res.body, { hello: 'world' }); + assert.deepEqual(res.statusCode, 200); + assert.deepEqual(res.headers, { 'content-type': 'application/json' }); + }); + + it('should return string object', async() => { + const expected = 'expected response'; + const response = new PassThrough(); + response.write(JSON.stringify(expected)); + response.statusCode = 200; + response.headers = { 'content-type': 'text/plain' }; + response.end(); + + const req = new PassThrough(); + + request.callsArgWith(1, response) + .returns(req); + + const res = await httpsHandler({}, ''); + assert.deepEqual(res.body, '"expected response"'); + assert.deepEqual(res.statusCode, 200); + assert.deepEqual(res.headers, { 'content-type': 'text/plain' }); + }); + + it('should return byte Buffer, when isResponseBinary = true', async() => { + const isResponseBinary = true; + + const expectedResponseBody = Buffer.from([1, 2, 3]); + const expectedResponseHeaders = { 'content-type': 'application/pkix-crl' }; + + const response = new PassThrough(); + response.write(expectedResponseBody); + response.statusCode = 200; + response.headers = expectedResponseHeaders; + response.end(); + + const req = new PassThrough(); + + request.callsArgWith(1, response) + .returns(req); + + const res = await httpsHandler({}, '', isResponseBinary); + assert.deepEqual(res.body, expectedResponseBody); + assert.deepEqual(res.statusCode, 200); + assert.deepEqual(res.headers, expectedResponseHeaders); + }); + + it('should return string object when no content type given', async() => { + const expected = 'expected response'; + const response = new PassThrough(); + response.write(JSON.stringify(expected)); + response.statusCode = 200; + response.end(); + + const req = new PassThrough(); + + request.callsArgWith(1, response) + .returns(req); + + const res = await httpsHandler({}, ''); + assert.deepEqual(res.body, '"expected response"'); + assert.deepEqual(res.statusCode, 200); + assert.deepEqual(res.headers, undefined); + }); + + it('should call write when json', () => { + const body = { somekey: 'somevalue' }; + const expected = JSON.stringify(body); + + const req = new PassThrough(); + const write = sinon.spy(req, 'write'); + + request.returns(req); + + httpsHandler({}, body); + assert(write.withArgs(expected).calledOnce); + }); + + it('should call write when Uint8Array', () => { + const body = new Uint8Array([21, 31]); + + const req = new PassThrough(); + const write = sinon.spy(req, 'write'); + + request.returns(req); + + httpsHandler({}, body); + assert(write.withArgs(new Uint8Array([21, 31])).calledOnce); + }); + + it('should reject request error', async() => { + const expectedError = new Error('expected error'); + const req = new PassThrough(); + const response = new PassThrough(); + response.on('end', () => { + req.emit('error', expectedError); + }); + response.end(); + + request.callsArgWith(1, response) + .returns(req); + + try { + await httpsHandler({}, ''); + assert.fail('Expected test to throw'); + } + catch (err) { + assert.deepEqual(err, expectedError); + } + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/readFileSafelyTest.js b/QuoteVerification/QuoteVerificationService/test/common/readFileSafelyTest.js new file mode 100644 index 00000000..8bd3d407 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/readFileSafelyTest.js @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); + +const { assertMockFirstCalledWithArgs, assertMockCalledWithArgs } = require('../mocks/helpers'); + +class TestContext { + + constructor() { + this.fd = 21; // file descriptor is a number + this.targetFile = '/home/user/targetFile'; + this.targetFileContent = 'Content'; + + this.fs = { + openSync: sinon.stub().returns(this.fd), + readlinkSync: sinon.stub().withArgs('/proc/self/fd/' + this.fd).returns(this.targetFile), + fstatSync: sinon.stub().withArgs(this.fd).returns({ + size: this.targetFileContent.length, + isDirectory: sinon.stub().returns(false) + }), + readSync: (d, buffer/*, offset, length, position*/) => { buffer.write(this.targetFileContent); }, + closeSync: sinon.stub() + }; + this.os = { + homedir: sinon.stub().returns('/home/user') + }; + this.logger = { + trace: sinon.stub(), + debug: sinon.stub(), + info: sinon.stub(), + error: sinon.stub() + }; + } + + getTarget() { + return proxyquire('../../src/common/readFileSafely', { + 'fs': this.fs, + 'os': this.os, + './logger': { + genericLogger: this.logger + } + }); + } +} + +describe('readFileSafelyTests', () => { + + it('normal file', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + + // WHEN + const response = target(c.targetFile); + + //THEN + assert.strictEqual(response, c.targetFileContent); + assert(c.fs.closeSync.calledOnce); + }); + + it('symlink to mount point', async() => { + // GIVEN + const c = new TestContext(); + c.fs.readlinkSync = sinon.stub().returns('/home/user/targetFile2'); + const target = c.getTarget(); + + // WHEN + const response = target(c.targetFile); + + //THEN + assertMockFirstCalledWithArgs(c.logger.debug, 'Loading file from symlink: /home/user/targetFile which directs to: /home/user/targetFile2'); + assert.strictEqual(response, c.targetFileContent); + assert(c.fs.closeSync.calledOnce); + }); + + it('symlink outside approved location', async() => { + // GIVEN + const c = new TestContext(); + c.fs.readlinkSync = sinon.stub().returns('/tmp/otherFile'); + const target = c.getTarget(); + + try { + // WHEN + target(c.targetFile); + } + catch (err) { + //THEN + assertMockFirstCalledWithArgs(c.logger.debug, 'Loading file from symlink: /home/user/targetFile which directs to: /tmp/otherFile'); + assertMockFirstCalledWithArgs(c.logger.error, 'Problem loading file: Error: Loading link which directs outside of provided locations: ["/home/user/"] is forbidden!'); + assert(c.fs.closeSync.calledOnce); + return; + } + assert.fail('Should throw error'); + + }); + + it('not existing file', async() => { + // GIVEN + const c = new TestContext(); + c.fs.readlinkSync = sinon.stub().throws(new Error({ code: 'ENOENT' })); + c.fs.closeSync = sinon.stub().throws(new Error('File does not exist')); + const target = c.getTarget(); + try { + // WHEN + target(c.targetFile); + } + catch (err) { + //THEN + assert(c.fs.closeSync.calledOnce); + assertMockCalledWithArgs(1, c.logger.error, 'Problem closing file 21: Error: File does not exist'); + return; + } + assert.fail('Should throw error'); + }); + + it('unexpected directory', async() => { + // GIVEN + const c = new TestContext(); + c.fs.fstatSync = sinon.stub().returns({ + size: c.targetFileContent.length, + isDirectory: sinon.stub().returns(true) + }); + const target = c.getTarget(); + try { + // WHEN + target(c.targetFile); + } + catch (err) { + //THEN + assert(c.fs.closeSync.calledOnce); + assertMockFirstCalledWithArgs(c.logger.error, 'Problem loading file: Error: Expected path to a file, not a directory. Are you sure path "/home/user/targetFile" is correct?'); + return; + } + assert.fail('Should throw error'); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/requestHandlerTest.js b/QuoteVerification/QuoteVerificationService/test/common/requestHandlerTest.js new file mode 100644 index 00000000..934ce97a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/requestHandlerTest.js @@ -0,0 +1,726 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire'); +const assert = require('assert'); +const sinon = require('sinon'); +const _ = require('lodash'); + +const clientErrors = require('./../../src/common/restClientErrors'); + +const logger = { + error() {}, + info() {}, + trace() {}, + warn() {}, + isTraceEnabled: sinon.stub().returns(true) +}; + +class TestCaseSetup { + constructor() { + this.resetDefaults(); + } + + resetDefaults() { + + this.default = { + protocol: 'https', + method: 'GET', + path: '/path', + bodySent: { test: 'field' }, + headersSent: { 'content-length': 'testLength', 'testField': 'testLValue' }, + queryParamsSent: { q: 'value' }, + destroyedJson: '{wrongJson', + bodyReceived: { testField: 'exampleValue' }, + headersSentWithContentType: { 'content-type': 'application/x-pem-file', 'content-length': 'testLength', 'testField': 'testLValue' }, + headersSentJson: { 'content-type': 'application/json', 'content-length': 'testLength', 'testField': 'testLValue' }, + xPemFile: 'some_pck_crl' + }; + + const options = { + ca: [], + secureProtocol: 'TLSv1_2_method', + ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256', + requestCert: true, + rejectUnauthorized: true, + agent: false, + strictSSL: false, + checkForDnsChanges: () => {} + }; + + this.config = { + protocol: 'https', + host: 'localhost', + port: 8081, + retryCount: 0, + retryDelay: 0, + retryMaxDelay: 0, + options + }; + } + + setupTestDoubles(err, statusCode, body, headers) { + const response = { + statusCode, + body, + headers + }; + + const requestStub = sinon.stub(); + const handlerStubs = { + './nodeRequestHandler': (options) => { + + requestStub(options); + if (err) { + return new Promise((resolve, reject) => reject(err)); + } + else { + return new Promise((resolve) => resolve(response)); + } + } + }; + + const RequestHandler = proxyquire('./../../src/common/requestHandler', handlerStubs); + this.requestHandler = new RequestHandler(_.clone(this.config)); + this.requestSpy = requestStub; + } + + get retryCount() { + return this.requestHandler.config.retryCount; + } + + set retryCount(value) { + this.requestHandler.config.retryCount = value; + } + + get expectedOptions() { + const options = _.clone(this.config.options); + options.protocol = this.default.protocol + ':'; + options.method = this.default.method; + options.headers = this.default.headersSent; + delete options.headers['content-length']; + options.host = this.config.host; + options.port = this.config.port; + options.path = this.default.path + '?q=value'; + return options; + } +} + +class PerformRequestScenarios { + + static callPerformRequest(currentSetup) { + + return currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent, + currentSetup.default.queryParamsSent + ); + } +} + +describe('requestHandlerTests', () => { + let currentSetup = null; + + beforeEach(() => { + currentSetup = new TestCaseSetup(); + }); + + describe('removeHeaderContentLength', () => { + + it('should change null header to empty object', (done) => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived); + // WHEN / THEN + assert.deepEqual(currentSetup.requestHandler.removeHeaderContentLength(null), done()); + }); + }); + + describe('sendRequest', () => { + + it('should call then callback with expected parameters', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived, currentSetup.default.headersSentJson); + // WHEN + const result = await currentSetup.requestHandler.sendRequest( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + //THEN + assert.deepEqual(result.body, currentSetup.default.bodyReceived); + assert.deepEqual(result, { + body: currentSetup.default.bodyReceived, + status: 200, + headers: currentSetup.default.headersSentJson + }); + }); + + it('should not retry if error or error status detected', async() => { + // GIVEN + const errMsg = 'errorMessage'; + currentSetup.setupTestDoubles(new Error(errMsg), 'statusExample', currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequest( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.strictEqual(err.message, errMsg); + } + }); + + it('should call request module with expected parameters', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + // WHEN + await currentSetup.requestHandler + .sendRequest( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent, + currentSetup.default.queryParamsSent + ); + //THEN + assert(currentSetup.requestSpy.calledWithExactly(currentSetup.expectedOptions)); + }); + + it('should throw HttpError when status received is not successful', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 220, currentSetup.default.bodyReceived); + try { + //WHEN + await currentSetup.requestHandler.sendRequest( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(err instanceof clientErrors.HttpError, true); + assert.deepEqual(err.status, 220); + assert.deepEqual(err.body, currentSetup.default.bodyReceived); + } + }); + + }); + + describe('performRequest', () => { + it('should resolve with expected status and body when status success', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived); + // WHEN /THEN + const result = await PerformRequestScenarios.callPerformRequest(currentSetup); + assert(currentSetup.requestSpy.calledOnce); + assert(currentSetup.requestSpy.calledWithExactly(currentSetup.expectedOptions)); + assert.equal(result.status, 200); + assert.deepEqual(result.body, currentSetup.default.bodyReceived); + }); + + it('should resolve with expected status and body when status created', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 201, currentSetup.default.bodyReceived); + // WHEN / THEN + const result = await PerformRequestScenarios.callPerformRequest(currentSetup); + assert(currentSetup.requestSpy.calledOnce); + assert(currentSetup.requestSpy.calledWithExactly(currentSetup.expectedOptions)); + assert.equal(result.status, 201); + assert.deepEqual(result.body, currentSetup.default.bodyReceived); + }); + + it('should log warning on retry', async() => { + // GIVEN + const loggerStub = { + error() {}, + info() {}, + trace() {}, + warn: sinon.stub() + }; + loggerStub.warn.callsFake(() => {}); + + const errMsg = 'request rejected'; + currentSetup.setupTestDoubles(new Error(errMsg)); + currentSetup.retryCount = 1; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + loggerStub, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + sinon.assert.calledOnce(loggerStub.warn); + assert.strictEqual(err.message, errMsg); + } + }); + + it('should resolve with expected status and body when content-type is x-pem-file', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.xPemFile, currentSetup.default.headersSentWithContentType); + // WHEN /THEN + + const result = await + currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.headersSentWithContentType + ); + assert(currentSetup.requestSpy.calledOnce); + assert.equal(result.status, 200); + assert.deepEqual(result.body, currentSetup.default.xPemFile); + }); + + it('should reject with expected error report when empty status returned', async() => { + //GIVEN + currentSetup.setupTestDoubles(null, null, currentSetup.default.bodyReceived); + try { + //WHEN + await PerformRequestScenarios.callPerformRequest(currentSetup); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.deepEqual(err.body, undefined); + assert(err instanceof clientErrors.RuntimeError); + } + }); + + /*** RETRY SCENARIOS ***/ + + it('should retry expected times when unknown error occurrs', async() => { + // GIVEN + const error = new Error('unknown error'); + currentSetup.setupTestDoubles(error); + currentSetup.retryCount = 2; + try { + //WHEN + await PerformRequestScenarios.callPerformRequest(currentSetup); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 3); + assert(err instanceof clientErrors.RuntimeError); + } + }); + }); + + describe('sendRequestWithRetries', () => { + it('should call callback with expected parameters', async() => { + // GIVEN + const errMsg = 'errorMessage'; + currentSetup.setupTestDoubles(new Error(errMsg), 509, currentSetup.default.bodyReceived); + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.strictEqual(err.message, errMsg); + } + }); + + it('should not retry when success status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived, currentSetup.default.headersSentJson); + currentSetup.retryCount = 2; + // WHEN / THEN + const result = await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.deepEqual(result.body, currentSetup.default.bodyReceived); + assert.deepEqual(result, { + body: currentSetup.default.bodyReceived, + status: 200, + headers: currentSetup.default.headersSentJson + }); + }); + + it('should retry when error occurred', async() => { + // GIVEN + const errMsg = 'errMsg'; + currentSetup.setupTestDoubles(new Error(errMsg), 200, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 3); + assert.strictEqual(err.message, errMsg); + } + }); + + it('should retry when internal error status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(new Error('request rejected')); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 3); + } + }); + + it('should retry when no status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, null, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 3); + } + }); + + it('should not retry when no success status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 220, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(err instanceof clientErrors.HttpError, true); + assert.deepEqual(err.status, 220); + assert.deepEqual(err.body, currentSetup.default.bodyReceived); + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should not retry when no success status received with no body', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 220, null); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(err instanceof clientErrors.HttpError, true); + assert.deepEqual(err.status, 220); + assert.deepEqual(err.body, {}); + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should call request module with expected parameters', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + // WHEN / THEN + await currentSetup.requestHandler + .sendRequestWithRetries( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent, + currentSetup.default.queryParamsSent + ); + assert(currentSetup.requestSpy.calledWithExactly(currentSetup.expectedOptions)); + }); + }); + + describe('sendRequestWithRetriesOnConnectionReset', () => { + function getSocketHangUpError() { + const err = new Error('socket hang up'); + err.code = 'ECONNRESET'; + return err; + } + + it('should call callback with expected parameters', async() => { + // GIVEN + const errMsg = 'errorMessage'; + currentSetup.setupTestDoubles(new Error(errMsg), 509, currentSetup.default.bodyReceived); + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.strictEqual(err.message, errMsg); + } + }); + + it('should not retry when success status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived, currentSetup.default.headersSentJson); + currentSetup.retryCount = 2; + // WHEN / THEN + const result = await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.deepEqual(result.body, currentSetup.default.bodyReceived); + assert.deepEqual(result, { + body: currentSetup.default.bodyReceived, + status: 200, + headers: currentSetup.default.headersSentJson + }); + }); + + it('should retry when ECONNRESET error occurred', async() => { + // GIVEN + const errThrown = getSocketHangUpError(); + currentSetup.setupTestDoubles(errThrown, undefined, undefined); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 3); + assert.strictEqual(err.message, errThrown.message); + assert.deepStrictEqual(err.details.code, errThrown.code); + } + }); + + it('should NOT retry when non-ECONNRESET error occurred', async() => { + // GIVEN + const errThrown = new Error('errMsg'); + errThrown.code = 'ECONNREFUSED'; + currentSetup.setupTestDoubles(errThrown, 200, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + assert.strictEqual(err.message, errThrown.message); + assert.deepStrictEqual(err.details.code, errThrown.code); + } + }); + + it('should NOT retry when internal error status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(new Error('request rejected')); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should NOT retry when no status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, null, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should not retry when no success status received', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 220, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(err instanceof clientErrors.HttpError, true); + assert.deepEqual(err.status, 220); + assert.deepEqual(err.body, currentSetup.default.bodyReceived); + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should not retry when no success status received with no body', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 220, null); + currentSetup.retryCount = 2; + try { + //WHEN + await currentSetup.requestHandler.sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent); + assert.fail('Should throw error'); + } + catch (err) { + //THEN + assert.equal(err instanceof clientErrors.HttpError, true); + assert.deepEqual(err.status, 220); + assert.deepEqual(err.body, {}); + assert.equal(currentSetup.requestSpy.callCount, 1); + } + }); + + it('should call request module with expected parameters', async() => { + // GIVEN + currentSetup.setupTestDoubles(null, 200, currentSetup.default.bodyReceived); + currentSetup.retryCount = 2; + // WHEN / THEN + await currentSetup.requestHandler + .sendRequestWithRetriesOnConnectionReset( + logger, + currentSetup.default.method, + currentSetup.default.path, + currentSetup.default.bodySent, + currentSetup.default.headersSent, + currentSetup.default.queryParamsSent + ); + assert(currentSetup.requestSpy.calledWithExactly(currentSetup.expectedOptions)); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/requestLogFormatterTest.js b/QuoteVerification/QuoteVerificationService/test/common/requestLogFormatterTest.js new file mode 100644 index 00000000..11456253 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/requestLogFormatterTest.js @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const assert = require('assert'); +const requestLogFormatter = require('../../src/common/requestLogFormatter'); + +describe('requestLogFormatterTests', () => { + describe('formatRequestMessage', () => { + function makeOpts() { + return { + reqMethod: 'GET', + reqUrl: '/health', + reqHeaders: { connection: 'keep-alive' } + }; + } + + const testRequestFormatter = (opts) => { + return requestLogFormatter.formatRequestMessageWithoutBody(opts.reqMethod, opts.reqUrl, opts.reqHeaders); + }; + + it('positive', () => { + //GIVEN + const opts = makeOpts(); + opts.reqMethod = 'POST'; + //WHEN / THEN + assert.equal(testRequestFormatter(opts), + ' <-- request POST /health [headers={"connection":"keep-alive"}]'); + }); + + it('negative - all undefined values', () => { + //GIVEN / WHEN / THEN + assert.equal(testRequestFormatter({}), ' <-- request undefined undefined []'); + }); + }); + + describe('formatRequestBodyMessage', () => { + + const testRequestBodyFormatter = (reqBody) => { + return requestLogFormatter.formatRequestBodyMessage(reqBody); + }; + + it('positive', () => { + //GIVEN / WHEN / THEN + assert.equal(testRequestBodyFormatter({ key: 'value' }), + ' [body={"key":"value"}]'); + }); + + it('negative - empty string', () => { + //WHEN / THEN + assert.equal(testRequestBodyFormatter(), ''); + }); + }); + + describe('formatResponseMessage', () => { + function makeOpts() { + return { + reqMethod: 'GET', + reqUrl: '/health', + + resStatus: 200, + resHeaders: { 'Request-ID': '38a9794bfa1841e397aeefe1975d54f4' }, + resBody: undefined, + + duration: 213 + }; + } + + const testResponseFormatter = (opts) => { + return requestLogFormatter.formatResponseMessage(opts.reqMethod, opts.reqUrl, opts.resStatus, opts.resHeaders, opts.resBody, opts.duration); + }; + + it('positive - json body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = { status: 'OK' }; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"} body={"status":"OK"}]'); + }); + + it('positive - no body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = undefined; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"}]'); + }); + + it('positive - not string, not json body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = 9; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"}]'); + }); + + it('positive - null body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = null; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"}]'); + }); + + it('positive - empty json body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = {}; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"}]'); + }); + + it('positive - empty body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = ''; + //WHEN / THEN + assert.equal(testResponseFormatter(opts), + " --> response GET /health 200 took 213ms [headers={\"Request-ID\":\"38a9794bfa1841e397aeefe1975d54f4\"} body='']"); + }); + + it('negative - all undefined values', () => { + //GIVEN / WHEN / THEN + assert.equal(testResponseFormatter({}), ' --> response undefined undefined NaN took undefinedms []'); + }); + + it('positive - long body', () => { + //GIVEN + const opts = makeOpts(); + opts.resBody = { arr: ['x'.repeat(15 * 1000 + 1) + 'y'.repeat(15 * 1000) + 'z'.repeat(15 * 1000)] }; + //WHEN / THEN + const expected = [ + ' --> response GET /health 200 took 213ms [headers={"Request-ID":"38a9794bfa1841e397aeefe1975d54f4"}][find large body in next log]', + " --> response body part 1 of 4 [body='{\"arr\":[\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']", + " --> response body part 2 of 4 [body='xxxxxxxxxxyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy']", + " --> response body part 3 of 4 [body='yyyyyyyyyyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz']", + " --> response body part 4 of 4 [body='zzzzzzzzzz\"]}']" + ]; + + assert.deepEqual(testResponseFormatter(opts), expected); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/common/restClientErrorsTest.js b/QuoteVerification/QuoteVerificationService/test/common/restClientErrorsTest.js new file mode 100644 index 00000000..2e51b421 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/common/restClientErrorsTest.js @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + + +const proxyquire = require('proxyquire').noCallThru(); +const assert = require('assert'); + +class TestContext { + getTarget() { + return proxyquire('../../src/common/restClientErrors', {}); + } +} + +describe('restClientErrorsTests', () => { + + describe('HttpNoRetryError', () => { + it('passing body', async() => { + const c = new TestContext(); + const target = c.getTarget(); + + const error = new target.HttpNoRetryError('message', 400, { key: 'value' }, { 'Content-Type': 'application/json' }); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body.key, 'value'); + assert.deepStrictEqual(error.headers['Content-Type'], 'application/json'); + }); + + it('no body passed', async() => { + const c = new TestContext(); + const target = c.getTarget(); + + const error = new target.HttpNoRetryError('message', 400, undefined, { 'Content-Type': 'application/json' }); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body, {}); + assert.deepStrictEqual(error.headers['Content-Type'], 'application/json'); + }); + + it('no body nor headers passed', async() => { + const c = new TestContext(); + + const target = c.getTarget(); + + const error = new target.HttpNoRetryError('message', 400); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body, {}); + assert.deepStrictEqual(error.headers, {}); + }); + }); + + describe('HttpError', () => { + it('passing body', async() => { + const c = new TestContext(); + const target = c.getTarget(); + + const error = new target.HttpError('message', 400, { key: 'value' }, { 'Content-Type': 'application/json' }); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body.key, 'value'); + assert.deepStrictEqual(error.headers['Content-Type'], 'application/json'); + }); + + it('no body passed', async() => { + const c = new TestContext(); + const target = c.getTarget(); + + const error = new target.HttpError('message', 400, undefined, { 'Content-Type': 'application/json' }); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body, {}); + assert.deepStrictEqual(error.headers['Content-Type'], 'application/json'); + }); + + it('no body nor headers passed', async() => { + const c = new TestContext(); + + const target = c.getTarget(); + + const error = new target.HttpError('message', 400); + + assert.deepStrictEqual(error.status, 400); + assert.deepStrictEqual(error.body, {}); + assert.deepStrictEqual(error.headers, {}); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/configLoaderTest.js b/QuoteVerification/QuoteVerificationService/test/configLoaderTest.js new file mode 100644 index 00000000..b857b573 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/configLoaderTest.js @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +/* eslint max-len: off */ + +const assert = require('chai').assert; +const sinon = require('sinon'); +const proxyquire = require('proxyquire').noCallThru(); + +const assertMockCalledOnceWithArgs = require('./mocks/helpers').assertMockCalledOnceWithArgs; + +class ConfigLoaderMock { + constructor(configClass) { + this.configClass = configClass; + } + + async init(json) { + this.instance = new this.configClass(json); + } + + getConfig() { + return this.instance; + } +} + +class TestContext { + constructor() { + const self = this; + + this.baseValidator = sinon.spy(); + this.baseRestServiceValidator = sinon.spy(); + this.cacheValidator = sinon.spy(); + + this.config = { + logger: { + category: 'category', + fileName: 'logfile.log', + levelFile: 'trace', + levelConsole: 'trace' + }, + Base: class BaseMock { + constructor() { + this.validate = self.baseValidator; + } + }, + BaseRestService: class BaseRestServiceMock { + constructor() { + this.validate = self.baseRestServiceValidator; + } + }, + Cache: class CacheMock { + constructor() { + this.validate = self.cacheValidator; + } + }, + ConfigLoader: ConfigLoaderMock, + HealthCheck: sinon.stub(), + RestClient: sinon.stub(), + appendConfigPath: sinon.spy(), + load: sinon.stub(), + }; + + this.commonLogger = { + genericLogger: { + fatal: sinon.stub() + } + }; + } + + getTarget() { + return proxyquire('../src/configLoader', { + './common/config': this.config + }); + } +} + +const getCorrectConfig = () => { + return { + service: { + caCertDirectories: 'someElement' + }, + pcsClient: { + retries: 3, + initialInterval: 100, + factor: 2 + } + }; +}; + +const getCorrectConfigWithVCS = () => { + return { + service: { + caCertDirectories: 'someElement' + }, + pcsClient: { + retries: 3, + initialInterval: 100, + factor: 2 + }, + vcsClient: { + retries: 3, + initialInterval: 100, + factor: 2 + } + }; +}; + +describe('ConfigTest', () => { + + const configuration = { + // this list is a list of sections that are required in valid RB configuration + required: ['service', 'pcsClient', 'crlClient', 'healthCheck', 'logger', 'cache', 'target'], + properties: { + crlClient: { + required: ['retries', 'initialInterval', 'factor'], + properties: { + retries: { + type: 'number' + }, + initialInterval: { + type: 'number' + }, + factor: { + type: 'number' + } + } + }, + target: { + required: ['attestationReportSigningCaCertificate', 'attestationReportSigningCertificate', 'trustedRootPublicKey'], + attestationReportSigningCaCertificate: { + type: 'string' + }, + attestationReportSigningCertificate: { + type: 'string' + }, + trustedRootPublicKey: { + type: 'string' + } + } + } + }; + + it('Config_constructor_success', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + const jsonConfig = getCorrectConfig(); + // WHEN + await target.init(jsonConfig); + target.getConfig(); + // THEN + assertMockCalledOnceWithArgs(c.baseRestServiceValidator, jsonConfig, configuration); + assert.strictEqual(c.config.RestClient.callCount, 1); + }); + + it('Config_constructor_with_vcs_success', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + const jsonConfig = getCorrectConfigWithVCS(); + // WHEN + await target.init(jsonConfig); + target.getConfig(); + // THEN + assertMockCalledOnceWithArgs(c.baseRestServiceValidator, jsonConfig, configuration); + assert.strictEqual(c.config.RestClient.callCount, 2); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/handlers/certificateChainParserTest.js b/QuoteVerification/QuoteVerificationService/test/handlers/certificateChainParserTest.js new file mode 100644 index 00000000..e8055d0a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/handlers/certificateChainParserTest.js @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + 'use strict'; + + const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); + const assert = require('assert'); + const { X509Certificate } = require('crypto'); + + class TestContext { + constructor() { + this.rootCaPublicKey = '3059301306072a8648ce3d020106082a8648ce3d030107034200040ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394'; + this.rootCaPem = `-----BEGIN CERTIFICATE----- +MIICjjCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDExMVoXDTMzMDUyMTEwNDExMFowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSAAwRQIgQQs/08rycdPauCFk8UPQXCMAlsloBe7NwaQGTcdpa0EC +IQCUt8SGvxKmjpcM/z0WP9Dvo8h2k5du1iWDdBkAn+0iiA== +-----END CERTIFICATE-----`; + this.intermediateCaPem = `-----BEGIN CERTIFICATE----- +MIIClzCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC +MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD +b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw +CQYDVQQGEwJVUzAeFw0xODA1MjExMDQ1MDhaFw0zMzA1MjExMDQ1MDhaMHExIzAh +BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl +bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB +MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg +tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i +HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww +UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl +cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5jcmwwHQYDVR0OBBYEFNDo +qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG +AQH/AgEAMAoGCCqGSM49BAMCA0cAMEQCIC/9j+84T+HztVO/sOQBWJbSd+/2uexK +4+aA0jcFBLcpAiA3dhMrF5cD52t6FqMvAIpj8XdGmy2beeljLJK+pzpcRA== +-----END CERTIFICATE-----`; + this.pckPem = `-----BEGIN CERTIFICATE----- +MIIEgDCCBCagAwIBAgIUVvUthlAB2c3lo6k5OS9bYNRQvhIwCgYIKoZIzj0EAwIwcTEjMCEGA1UE +AwwaSW50ZWwgU0dYIFBDSyBQcm9jZXNzb3IgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9u +MRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMB4XDTE5MTAx +NTA3MjA1NFoXDTI2MTAxNTA3MjA1NFowcDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBDZXJ0aWZp +Y2F0ZTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQsw +CQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQEzIscIEkt +CSl6gLNCvkxZIXMblNqOJY88pWiPCL+/UfEbRKiguBQT61M1VP2Hh2ii6zpkPHkOsSIm6gnI218S +o4ICmzCCApcwHwYDVR0jBBgwFoAU0Oiq2nXX+S5JF5g8exRl0NXyWU0wXwYDVR0fBFgwVjBUoFKg +UIZOaHR0cHM6Ly9hcGkudHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9zZ3gvY2VydGlmaWNhdGlv +bi92Mi9wY2tjcmw/Y2E9cHJvY2Vzc29yMB0GA1UdDgQWBBR20paRDIQsuFRq4kk1jfIEeWIqdjAO +BgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAdQGCSqGSIb4TQENAQSCAcUwggHBMB4GCiqG +SIb4TQENAQEEEIa7nbGV6riZJHz6CJQl+I4wggFkBgoqhkiG+E0BDQECMIIBVDAQBgsqhkiG+E0B +DQECAQIBDTAQBgsqhkiG+E0BDQECAgIBDTAQBgsqhkiG+E0BDQECAwIBAjAQBgsqhkiG+E0BDQEC +BAIBBDAQBgsqhkiG+E0BDQECBQIBATARBgsqhkiG+E0BDQECBgICAIAwEAYLKoZIhvhNAQ0BAgcC +AQMwEAYLKoZIhvhNAQ0BAggCAQAwEAYLKoZIhvhNAQ0BAgkCAQAwEAYLKoZIhvhNAQ0BAgoCAQAw +EAYLKoZIhvhNAQ0BAgsCAQAwEAYLKoZIhvhNAQ0BAgwCAQAwEAYLKoZIhvhNAQ0BAg0CAQAwEAYL +KoZIhvhNAQ0BAg4CAQAwEAYLKoZIhvhNAQ0BAg8CAQAwEAYLKoZIhvhNAQ0BAhACAQAwEAYLKoZI +hvhNAQ0BAhECAQkwHwYLKoZIhvhNAQ0BAhIEEA0NAgQBgAMAAAAAAAAAAAAwEAYKKoZIhvhNAQ0B +AwQCAAAwFAYKKoZIhvhNAQ0BBAQGAJBuoQAAMA8GCiqGSIb4TQENAQUKAQAwCgYIKoZIzj0EAwID +SAAwRQIgf8Bbj/9xVbejhqnthr1cHcX5fF5MStS5tCgZmwCL98ICIQCTX+Li5drO74gsdXbjrawh +uZ0WsNREI7WIEPEd9fp/Kg== +-----END CERTIFICATE-----`; + this.anotherCert = `-----BEGIN CERTIFICATE----- +MIIDnDCCAoSgAwIBAgIJAMmlcQsaw/9fMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExFDASBgNV +BAoMC1NHWCBOb25wcm9kMRswGQYDVQQDDBJzZ3gtbm9ucHJvZC1zZXJ2ZXIwHhcN +MjAwNDAzMTEzMDU3WhcNMzAwNDAxMTEzMDU3WjBjMQswCQYDVQQGEwJVUzELMAkG +A1UECAwCQ0ExFDASBgNVBAcMC1NhbnRhIENsYXJhMRQwEgYDVQQKDAtTR1ggTm9u +cHJvZDEbMBkGA1UEAwwSc2d4LW5vbnByb2Qtc2VydmVyMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAs6CFe+MyEcy9ALgVvyACCWXs3xyKSlC7qzIjtFJF +eXuWogI2cpqmv+E6Yq9cyzV3wWyZqyfAsWNrs4z1SRlPpNEC65P+LqofIYFQVFV9 +/b4ok/uBCxL5fN7iYSZzTTyJ5d6NDdKYKtcu6ZtTID9lgXapXu9wNi2lgWqci2Sb +EvYGpc225TKA64DYZNjoPKEyyJz21Re2ZZqYUcPTGB/oDGjw+J3GRXhtcptSrLmw +RFjwFflAq7q00fR+DOKYzmwQtQUeIuEWaaOjrwAzvW8rKdvJqwb44Tc41IcJGSlu +HrKsB/39/xGdNa7rda54YAQ97cYm6s8TfW2jgL39/9rbIwIDAQABo1MwUTAdBgNV +HQ4EFgQUCK7s7Jis2JlAuOmOKMZOEV1PKNMwHwYDVR0jBBgwFoAUCK7s7Jis2JlA +uOmOKMZOEV1PKNMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA +UhzsZ65wmPO3ChQURU8lSFxRH+dte7ILWLrnsbiKXhctLa51K7mJW3alEFj9exvh +/vRuXelp9beNkTPUuXUuVu8rMEchaVLy/fI3vGzn8gf4r6yKYbZ0vET91jwwR1Di +WuF2JBhi2dE/QLZDAguzbWdx+Y4cDUbmjsn/asf6cYauI6wPgAHwthTjuR+pr8qV +jPMIv0C05DmEhlc1RMDan8DdhdxO9LipZwdKEkM8PhBKy0iHcLhq6NSKWZLcLDQX +JyDpy2i+mxBomCMr4Q1gk6VWBSAz0stYwwg/g06jPiahFXYr2hkHerlNudrk8Wo+ +gVhdEh0IOF2UJJVNYn9+2A== +-----END CERTIFICATE-----`; + this.badEndLinePem = `-----BEGIN CERTIFICATE----- +MIICjjCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDExMVoXDTMzMDUyMTEwNDExMFowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSAAwRQIgQQs/08rycdPauCFk8UPQXCMAlsloBe7NwaQGTcdpa0EC +IQCUt8SGvxKmjpcM/z0WP9Dvo8h2k5du1iWDdBkAn+0iiA== + -----END CERTIFICATE-----`; + this.tcbInfoSigningCertPem = `-----BEGIN CERTIFICATE----- +MIICiDCCAi6gAwIBAgIUOGmGYZPE2lFghsw8+siUsai+2yEwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMCAXDTIyMDUxOTA4NDkxNVoYDzIwNTIwNTE5MDg0OTE1WjBsMR4w +HAYDVQQDDBVJbnRlbCBTR1ggVENCIFNpZ25pbmcxGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXYVFKEWN6t8kISYy +Sy0MnWA5uhMmc2/DkPhDjrcXOJBL7J41hU44IccEtMDeLtC6tZS4a5fsf1BsVBnP +lcD0LKOBrzCBrDAfBgNVHSMEGDAWgBQxQ5A8cqzvyMiAJ1b7mU3kCIi1GjBMBgNV +HR8ERTBDMEGgP6A9hjtodHRwOi8vbm9uLWV4aXN0aW5nLWRlYnVnLW9ubHkuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUOGmGYZPE2lFghsw8 ++siUsai+2yEwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0E +AwIDSAAwRQIhANeHlhzC5Lp4EnRSQUQfS2hFbG5P6OM0IsVjRvIIWs78AiA7hXqH +qwb1ASfXtioQB5XXC2O46KRaGiwpvz/oAOD/rg== +-----END CERTIFICATE-----`; + this.pck = new X509Certificate(this.pckPem); + this.intermediateCa = new X509Certificate(this.intermediateCaPem); + this.rootCa = new X509Certificate(this.rootCaPem); + this.tcbInfoSigningCert = new X509Certificate(this.tcbInfoSigningCertPem); + + } + + async getTarget() { + return proxyquire('../../src/handlers/certificateChainParser', {}); + } +} + +describe('certificate chain parser tests', () => { + + it('less than 3 certs provided', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.intermediateCaPem); + // THEN + await assert.rejects(resultPromise, /Certification data is not a chain of 3 certificates in PEM format/); + }); + + it('more than 3 certs provided', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.intermediateCaPem + c.pckPem + c.pckPem); + // THEN + await assert.rejects(resultPromise, /Certification data is not a chain of 3 certificates in PEM format/); + }); + + it('no provided root ca in chain', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.pckPem + c.intermediateCaPem + c.anotherCert); + // THEN + await assert.rejects(resultPromise, /No trusted root CA in provided chain/); + }); + + it('no cert signed by root ca in chain', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.pckPem + c.anotherCert + c.rootCaPem); + // THEN + await assert.rejects(resultPromise, /No intermediate CA issued by trusted root CA found in provided chain/); + }); + + it('no cert signed by intermediate ca in chain', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.anotherCert + c.intermediateCaPem + c.rootCaPem); + // THEN + await assert.rejects(resultPromise, /No PCK cert issued by intermediate CA found in provided chain/); + }); + + it('2 self signed', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.intermediateCaPem + c.rootCaPem); + // THEN + await assert.rejects(resultPromise, /Certification data contains duplicated certificates/); + }); + + it('parse certificates - order from top to bottom', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + const { + // X509 Certificates + rootCa, + intermediateCa, + pckCert, + // PEM Certificates + rootCaPem, + intermediateCaPem, + pckCertPem + } = await target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.pckPem + c.intermediateCaPem + c.rootCaPem); + // THEN + assert.strictEqual(rootCaPem, c.rootCaPem); + assert.strictEqual(intermediateCaPem, c.intermediateCaPem); + assert.strictEqual(pckCertPem, c.pckPem); + assert.deepEqual(rootCa, c.rootCa); + assert.deepEqual(intermediateCa, c.intermediateCa); + assert.deepEqual(pckCert, c.pck); + }); + + it('parse certificates - order from bottom to top', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + const { + // X509 Certificates + rootCa, + intermediateCa, + pckCert, + // PEM Certificates + rootCaPem, + intermediateCaPem, + pckCertPem + } = await target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.intermediateCaPem + c.pckPem); + // THEN + assert.strictEqual(rootCaPem, c.rootCaPem); + assert.strictEqual(intermediateCaPem, c.intermediateCaPem); + assert.strictEqual(pckCertPem, c.pckPem); + assert.deepEqual(rootCa, c.rootCa); + assert.deepEqual(intermediateCa, c.intermediateCa); + assert.deepEqual(pckCert, c.pck); + }); + + it('parse certificates - support illogical but possibly used order', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + const { + // X509 Certificates + rootCa, + intermediateCa, + pckCert, + // PEM Certificates + rootCaPem, + intermediateCaPem, + pckCertPem + } = await target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.pckPem + c.rootCaPem + c.intermediateCaPem); + // THEN + assert.strictEqual(rootCaPem, c.rootCaPem); + assert.strictEqual(intermediateCaPem, c.intermediateCaPem); + assert.strictEqual(pckCertPem, c.pckPem); + assert.deepEqual(rootCa, c.rootCa); + assert.deepEqual(intermediateCa, c.intermediateCa); + assert.deepEqual(pckCert, c.pck); + }); + + it('cannot parse certificates - bad end line', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + // THEN + const resultPromise = target.parseCertificateChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.badEndLinePem + c.intermediateCaPem); + // THEN + await assert.rejects(resultPromise, /bad end line/); + }); + + it('root with public key not found', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseCertificateChainWithSpecificRoot('0a0a0a0a', c.rootCaPem + c.intermediateCaPem + c.pckPem); + // THEN + await assert.rejects(resultPromise, /No trusted root CA in provided chain. Expected public key: 0a0a0a0a/); + }); + +}); + +describe('TCB Info Signing chain parser tests', () => { + + it('success', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const { + // X509 Certificates + rootCa, + tcbInfoSigningCert, + rootCaPem, + tcbInfoSigningCertPem, + } = await target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.tcbInfoSigningCertPem); + // THEN + assert.strictEqual(rootCaPem, c.rootCaPem); + assert.strictEqual(tcbInfoSigningCertPem, c.tcbInfoSigningCertPem); + assert.deepEqual(rootCa, c.rootCa); + assert.deepEqual(tcbInfoSigningCert, c.tcbInfoSigningCert); + }); + + it('success with different order', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const { + // X509 Certificates + rootCa, + tcbInfoSigningCert, + rootCaPem, + tcbInfoSigningCertPem, + } = await target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.tcbInfoSigningCertPem + c.rootCaPem); + // THEN + assert.strictEqual(rootCaPem, c.rootCaPem); + assert.strictEqual(tcbInfoSigningCertPem, c.tcbInfoSigningCertPem); + assert.deepEqual(rootCa, c.rootCa); + assert.deepEqual(tcbInfoSigningCert, c.tcbInfoSigningCert); + }); + + it('root with public key not found', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseTcbInfoSigningChainWithSpecificRoot('0a0a0a0a', c.rootCaPem + c.tcbInfoSigningCertPem); + // THEN + await assert.rejects(resultPromise, /No trusted root CA in provided chain. Expected public key: 0a0a0a0a/); + }); + + it('less than 2 certs provided', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem); + // THEN + await assert.rejects(resultPromise, /TCB Info Signing Chain is not a chain of 2 certificates in PEM format/); + }); + + it('duplicates provided', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.rootCaPem); + // THEN + await assert.rejects(resultPromise, /TCB Info Signing Chain contains duplicated certificates/); + }); + + it('bad cert parsing error', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.badEndLinePem); + // THEN + await assert.rejects(resultPromise, /bad end line/); + }); + + it('tcb info signing cert is not found', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + const resultPromise = target.parseTcbInfoSigningChainWithSpecificRoot(c.rootCaPublicKey, c.rootCaPem + c.anotherCert); + // THEN + await assert.rejects(resultPromise, /No TCB Info Signing Cert issued by trusted root CA found in provided chain./); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/handlers/healthTest.js b/QuoteVerification/QuoteVerificationService/test/handlers/healthTest.js new file mode 100644 index 00000000..37324278 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/handlers/healthTest.js @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +class TestContext { + constructor() { + this.healthCache = { + handleRequest: sinon.stub() + }; + } + + async getTarget() { + return proxyquire('../../src/handlers/health', { + '../healthChecker': this.healthCache + }); + } +} + +describe('health handler tests', () => { + it('check health', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + await target.checkHealth({}); + // THEN + assert(c.healthCache.handleRequest.calledOnce); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/handlers/verifyAttestationEvidenceTest.js b/QuoteVerification/QuoteVerificationService/test/handlers/verifyAttestationEvidenceTest.js new file mode 100644 index 00000000..68c61d9b --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/handlers/verifyAttestationEvidenceTest.js @@ -0,0 +1,1897 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const qvlStatus = require('../../src/qvl/status'); +const errorSource = require('../../src/qvl/verifyQuoteErrorSource'); +const assertMockCalledOnceWithArgs = require('../mocks/helpers').assertMockCalledOnceWithArgs; + +const iso8601Regex = (/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/i); + +class TestContext { + constructor() { + this.certificateChainParser = { + parseCertificateChainWithSpecificRoot: sinon.stub(), + parseTcbInfoSigningChainWithSpecificRoot: sinon.stub() + }; + this.pcsClient = { + getSgxTcbInfo: sinon.stub(), + getTdxTcbInfo: sinon.stub(), + getSgxQeIdentity: sinon.stub(), + getTdxQeIdentity: sinon.stub() + }; + this.crlClient = { + getCrlFromDistributionPoint: sinon.stub() + }; + this.logger = { + info: sinon.stub(), + error: sinon.stub() + }; + this.reqId = 'requestId'; + this.nonce = 'abcdefabcdef01234567890123456789'; + this.qvl = { + getCertificationData: sinon.stub(), + getPckCertificateData: sinon.stub(), + getCrlDistributionPoint: sinon.stub(), + verifyQuote: sinon.stub() + }; + this.vcsClient = { + signVerificationReport: sinon.stub(), + }; + this.configLoader = { + getConfig: () => ({ + target: { + attestationReportSigningCaCertificate: sinon.stub(), + attestationReportSigningCertificate: sinon.stub() + } + }) + }; + + this.sgxQuote = 'AwACAAAAAAAEAAkAk5pyM/ecTKmUCg2zlX8GB3cEhXlSheEulmeokact7P4AAAAADQ0DBf+AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAA/NbIKlebNUxhlIX/ysdhdjxtT2O+JTpqaPcLeJTp9SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyEBhE5TtWVUKTgl2yS/DCkZ8aq0eu1aesDBuOxNUj9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALhAAAExARVhwI2Qn6dtUK6O0qXpk97fZreM6ZtKxZVT5qFn/ZAqhUiOaiGMMIpu9SxOZfKnaZ3zTwkCVf2sApkYogL94Ijy59eb/nU0Lj+srbesE7PmrZ5M54oX1drz7Xxc74J+kA8tTxBaAZLNR6Z4BC1nUDtqqhaglnM8WwRXwXvH9DQ0DBf+AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAAAAHAAAAAAAAAAoqYO/EltvIi0w5bLQ4hA9bfdxLGAaAdJiKy10HVPF7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMT1d115ZQPpYTf3fGioKaAFasje1wFAsIGwlEkMV7/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKe7gLvTbu8WKg4PBEDSsru/MqCZWIrP03ttBj2bRkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeEbJ4qdQbfFHhAGIjijcPWHDmKmD4EMd3ONCtaLkQNHXBB9+H84y46sChtz9w4aYlBGpPOFQYFPwFxdBZEFhBiAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8FAMYNAAAtLS0tLUJFR0lOIENFUlRJRklDQVRFLS0tLS0KTUlJRWdEQ0NCQ2FnQXdJQkFnSVVWdlV0aGxBQjJjM2xvNms1T1M5YllOUlF2aEl3Q2dZSUtvWkl6ajBFQXdJd2NURWpNQ0VHQTFVRQpBd3dhU1c1MFpXd2dVMGRZSUZCRFN5QlFjbTlqWlhOemIzSWdRMEV4R2pBWUJnTlZCQW9NRVVsdWRHVnNJRU52Y25CdmNtRjBhVzl1Ck1SUXdFZ1lEVlFRSERBdFRZVzUwWVNCRGJHRnlZVEVMTUFrR0ExVUVDQXdDUTBFeEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRFNU1UQXgKTlRBM01qQTFORm9YRFRJMk1UQXhOVEEzTWpBMU5Gb3djREVpTUNBR0ExVUVBd3daU1c1MFpXd2dVMGRZSUZCRFN5QkRaWEowYVdacApZMkYwWlRFYU1CZ0dBMVVFQ2d3UlNXNTBaV3dnUTI5eWNHOXlZWFJwYjI0eEZEQVNCZ05WQkFjTUMxTmhiblJoSUVOc1lYSmhNUXN3CkNRWURWUVFJREFKRFFURUxNQWtHQTFVRUJoTUNWVk13V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFFeklzY0lFa3QKQ1NsNmdMTkN2a3haSVhNYmxOcU9KWTg4cFdpUENMKy9VZkViUktpZ3VCUVQ2MU0xVlAySGgyaWk2enBrUEhrT3NTSW02Z25JMjE4UwpvNElDbXpDQ0FwY3dId1lEVlIwakJCZ3dGb0FVME9pcTJuWFgrUzVKRjVnOGV4UmwwTlh5V1Uwd1h3WURWUjBmQkZnd1ZqQlVvRktnClVJWk9hSFIwY0hNNkx5OWhjR2t1ZEhKMWMzUmxaSE5sY25acFkyVnpMbWx1ZEdWc0xtTnZiUzl6WjNndlkyVnlkR2xtYVdOaGRHbHYKYmk5Mk1pOXdZMnRqY213L1kyRTljSEp2WTJWemMyOXlNQjBHQTFVZERnUVdCQlIyMHBhUkRJUXN1RlJxNGtrMWpmSUVlV0lxZGpBTwpCZ05WSFE4QkFmOEVCQU1DQnNBd0RBWURWUjBUQVFIL0JBSXdBRENDQWRRR0NTcUdTSWI0VFFFTkFRU0NBY1V3Z2dIQk1CNEdDaXFHClNJYjRUUUVOQVFFRUVJYTduYkdWNnJpWkpIejZDSlFsK0k0d2dnRmtCZ29xaGtpRytFMEJEUUVDTUlJQlZEQVFCZ3NxaGtpRytFMEIKRFFFQ0FRSUJEVEFRQmdzcWhraUcrRTBCRFFFQ0FnSUJEVEFRQmdzcWhraUcrRTBCRFFFQ0F3SUJBakFRQmdzcWhraUcrRTBCRFFFQwpCQUlCQkRBUUJnc3Foa2lHK0UwQkRRRUNCUUlCQVRBUkJnc3Foa2lHK0UwQkRRRUNCZ0lDQUlBd0VBWUxLb1pJaHZoTkFRMEJBZ2NDCkFRTXdFQVlMS29aSWh2aE5BUTBCQWdnQ0FRQXdFQVlMS29aSWh2aE5BUTBCQWdrQ0FRQXdFQVlMS29aSWh2aE5BUTBCQWdvQ0FRQXcKRUFZTEtvWklodmhOQVEwQkFnc0NBUUF3RUFZTEtvWklodmhOQVEwQkFnd0NBUUF3RUFZTEtvWklodmhOQVEwQkFnMENBUUF3RUFZTApLb1pJaHZoTkFRMEJBZzRDQVFBd0VBWUxLb1pJaHZoTkFRMEJBZzhDQVFBd0VBWUxLb1pJaHZoTkFRMEJBaEFDQVFBd0VBWUxLb1pJCmh2aE5BUTBCQWhFQ0FRa3dId1lMS29aSWh2aE5BUTBCQWhJRUVBME5BZ1FCZ0FNQUFBQUFBQUFBQUFBd0VBWUtLb1pJaHZoTkFRMEIKQXdRQ0FBQXdGQVlLS29aSWh2aE5BUTBCQkFRR0FKQnVvUUFBTUE4R0NpcUdTSWI0VFFFTkFRVUtBUUF3Q2dZSUtvWkl6ajBFQXdJRApTQUF3UlFJZ2Y4QmJqLzl4VmJlamhxbnRocjFjSGNYNWZGNU1TdFM1dENnWm13Q0w5OElDSVFDVFgrTGk1ZHJPNzRnc2RYYmpyYXdoCnVaMFdzTlJFSTdXSUVQRWQ5ZnAvS2c9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNsekNDQWo2Z0F3SUJBZ0lWQU5Eb3F0cDExL2t1U1JlWVBIc1VaZERWOGxsTk1Bb0dDQ3FHU000OUJBTUMKTUdneEdqQVlCZ05WQkFNTUVVbHVkR1ZzSUZOSFdDQlNiMjkwSUVOQk1Sb3dHQVlEVlFRS0RCRkpiblJsYkNCRApiM0p3YjNKaGRHbHZiakVVTUJJR0ExVUVCd3dMVTJGdWRHRWdRMnhoY21FeEN6QUpCZ05WQkFnTUFrTkJNUXN3CkNRWURWUVFHRXdKVlV6QWVGdzB4T0RBMU1qRXhNRFExTURoYUZ3MHpNekExTWpFeE1EUTFNRGhhTUhFeEl6QWgKQmdOVkJBTU1Ha2x1ZEdWc0lGTkhXQ0JRUTBzZ1VISnZZMlZ6YzI5eUlFTkJNUm93R0FZRFZRUUtEQkZKYm5SbApiQ0JEYjNKd2IzSmhkR2x2YmpFVU1CSUdBMVVFQnd3TFUyRnVkR0VnUTJ4aGNtRXhDekFKQmdOVkJBZ01Ba05CCk1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCTDlxK05NcDJJT2cKdGRsMWJrL3VXWjUrVEdRbThhQ2k4ejc4ZnMrZktDUTNkK3VEelhuVlRBVDJaaERDaWZ5SXVKd3ZOM3dOQnA5aQpIQlNTTUpNSnJCT2pnYnN3Z2Jnd0h3WURWUjBqQkJnd0ZvQVVJbVVNMWxxZE5JbnpnN1NWVXI5UUd6a25CcXd3ClVnWURWUjBmQkVzd1NUQkhvRVdnUTRaQmFIUjBjSE02THk5alpYSjBhV1pwWTJGMFpYTXVkSEoxYzNSbFpITmwKY25acFkyVnpMbWx1ZEdWc0xtTnZiUzlKYm5SbGJGTkhXRkp2YjNSRFFTNWpjbXd3SFFZRFZSME9CQllFRk5EbwpxdHAxMS9rdVNSZVlQSHNVWmREVjhsbE5NQTRHQTFVZER3RUIvd1FFQXdJQkJqQVNCZ05WSFJNQkFmOEVDREFHCkFRSC9BZ0VBTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUMvOWorODRUK0h6dFZPL3NPUUJXSmJTZCsvMnVleEsKNCthQTBqY0ZCTGNwQWlBM2RoTXJGNWNENTJ0NkZxTXZBSXBqOFhkR215MmJlZWxqTEpLK3B6cGNSQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNqakNDQWpTZ0F3SUJBZ0lVSW1VTTFscWROSW56ZzdTVlVyOVFHemtuQnF3d0NnWUlLb1pJemowRUF3SXcKYURFYU1CZ0dBMVVFQXd3UlNXNTBaV3dnVTBkWUlGSnZiM1FnUTBFeEdqQVlCZ05WQkFvTUVVbHVkR1ZzSUVOdgpjbkJ2Y21GMGFXOXVNUlF3RWdZRFZRUUhEQXRUWVc1MFlTQkRiR0Z5WVRFTE1Ba0dBMVVFQ0F3Q1EwRXhDekFKCkJnTlZCQVlUQWxWVE1CNFhEVEU0TURVeU1URXdOREV4TVZvWERUTXpNRFV5TVRFd05ERXhNRm93YURFYU1CZ0cKQTFVRUF3d1JTVzUwWld3Z1UwZFlJRkp2YjNRZ1EwRXhHakFZQmdOVkJBb01FVWx1ZEdWc0lFTnZjbkJ2Y21GMAphVzl1TVJRd0VnWURWUVFIREF0VFlXNTBZU0JEYkdGeVlURUxNQWtHQTFVRUNBd0NRMEV4Q3pBSkJnTlZCQVlUCkFsVlRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVDNm5Fd01ESVlaT2ovaVBXc0N6YUVLaTcKMU9pT1NMUkZoV0dqYm5CVkpmVm5rWTR1M0lqa0RZWUwwTXhPNG1xc3lZamxCYWxUVll4RlAyc0pCSzV6bEtPQgp1ekNCdURBZkJnTlZIU01FR0RBV2dCUWlaUXpXV3AwMGlmT0R0SlZTdjFBYk9TY0dyREJTQmdOVkhSOEVTekJKCk1FZWdSYUJEaGtGb2RIUndjem92TDJObGNuUnBabWxqWVhSbGN5NTBjblZ6ZEdWa2MyVnlkbWxqWlhNdWFXNTAKWld3dVkyOXRMMGx1ZEdWc1UwZFlVbTl2ZEVOQkxtTnliREFkQmdOVkhRNEVGZ1FVSW1VTTFscWROSW56ZzdTVgpVcjlRR3prbkJxd3dEZ1lEVlIwUEFRSC9CQVFEQWdFR01CSUdBMVVkRXdFQi93UUlNQVlCQWY4Q0FRRXdDZ1lJCktvWkl6ajBFQXdJRFNBQXdSUUlnUVFzLzA4cnljZFBhdUNGazhVUFFYQ01BbHNsb0JlN053YVFHVGNkcGEwRUMKSVFDVXQ4U0d2eEttanBjTS96MFdQOUR2bzhoMms1ZHUxaVdEZEJrQW4rMGlpQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K'; + this.tdxQuote = 'BAACAIEAAAAAAAAAk5pyM/ecTKmUCg2zlX8GB0PP8xCV14+q/cEZXptW+nYebWwaUsGjjPft8wpSS067BJ9ZxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0CwAAas1IsBDD6CJFI9LdTmpACMLG+u61DNypI+9oSQMCzCzG7MzABtMPfCMvUdMj0FBt1ippWnZHOXmA8zJ2FjvziuWdEVcA/VNDOLdIqW8fnJgheH5UQsKhurONMHJqEVpJpBYRRF+0HyMEmsp471chPQ3QCTdhmwcQ7hPxcMVpIt8GAG4LAABSwaOM9+3zClJLTrsEn1nHEzc7CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMzvqWzh7Q8b/BG79Zm8C+/SsuWqu2X+m+16UTMVdrEdJaXaUnIAjVRoMcnU3gn4EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOS9n8ISmDW6Hcyhk51X0I6IVhs0dA1ZQFly1Lolp+X3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnnSFMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIi/WH/f39Yr85pZCUemaY+X2+1xwW5fKRBXdzEMgVjCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA59GvyBZ+i96pHQUyD20r9vkJfw4SqmodocYCnFBixYFd9RfihdcI61FxiXJ1+eZ8nhCkshyXOD8naUvLoVXK9AAAFAKYJAAAwggKIMIICLqADAgECAhQaWGUBnRiwTgRa+C0GwIVQ4g60djAKBggqhkjOPQQDAjBoMRowGAYDVQQDDBFJbnRlbCBTR1ggUm9vdCBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwHhcNMjEwODA2MTM1NTE0WhcNNDkxMjMxMjM1OTU5WjBoMRowGAYDVQQDDBFJbnRlbCBTR1ggUm9vdCBDQTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARSjvv2l0U07815+ZWBUwjjZNIDF3H+8n+OAuuhTV4LVdocoaPf4Fptau7f6Q+GZzqPWnRBcIGin+ELWaAe6n5Oo4G1MIGyMB8GA1UdIwQYMBaAFBpYZQGdGLBOBFr4LQbAhVDiDrR2MEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9ub24tZXhpc3RpbmctZGVidWctb25seS5pbnRlbC5jb20vSW50ZWxTR1hSb290Q0EuY3JsMB0GA1UdDgQWBBQaWGUBnRiwTgRa+C0GwIVQ4g60djAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAKBggqhkjOPQQDAgNIADBFAiAed9utJ03/LsIYoVOIXJo5CVqH8Ne1trdFj6fx9emV6QIhAJKyfPwV/0VNN9Q43/qovzsBDpf8Wk71DkDwUAtxC5S/MIICkDCCAjegAwIBAgIUA1YhILpbhc/U23ql5HG4efi1mLEwCgYIKoZIzj0EAwIwaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMB4XDTIxMDgwNjEzNTUxNFoXDTM2MDgwNjEzNTUxNFowcTEjMCEGA1UEAwwaSW50ZWwgU0dYIFBDSyBQcm9jZXNzb3IgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEl4YCKIpLYXTWOPx5KSbISZnOPYKI2nyDRUjCB2tnK6B5GLjAtJt0DJl4VIcebgw5PdQj/1+sWX5DVlHXvnRea6OBtTCBsjAfBgNVHSMEGDAWgBQaWGUBnRiwTgRa+C0GwIVQ4g60djBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vbm9uLWV4aXN0aW5nLWRlYnVnLW9ubHkuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUA1YhILpbhc/U23ql5HG4efi1mLEwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgRlzlKPNi7J+9oOQsXKc5YNov3Hbi2peVq726UksTzUcCIDSJb+kQNQGgIlwGzl7JiQmN44tCjLG0s15LLOtoAGerMIIEgjCCBCmgAwIBAgIVAPj86af3pXdXSaait3YKJklN2QV2MAoGCCqGSM49BAMCMHExIzAhBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYDVQQGEwJVUzAeFw0yMTA4MDYxMzU1MTRaFw0yODA4MDYxMzU1MTRaMHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgQ2VydGlmaWNhdGUxGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIcT6WLztCuV6iT8zziAYQb/k2fBUVL2rYYL9ifodAbswe1E2vHfIl3nX5TKmXsPp1PQ64JP8Wa5UK5TiCxdmC6OCAp0wggKZMB8GA1UdIwQYMBaAFANWISC6W4XP1Nt6peRxuHn4tZixMFgGA1UdHwRRME8wTaBLoEmGR2h0dHBzOi8vY2VydGlmaWNhdGVzLnRydXN0ZWRzZXJ2aWNlcy5pbnRlbC5jb20vSW50ZWxTR1hQQ0tQcm9jZXNzb3IuY3JsMB0GA1UdDgQWBBSUMBN/O1dgNPo1uGvZXakTI9+FcTAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAd0GCSqGSIb4TQENAQSCAc4wggHKMB4GCiqGSIb4TQENAQEEECEddULzbDUR7X+23WNJs+IwggFtBgoqhkiG+E0BDQECMIIBXTAQBgsqhkiG+E0BDQECAQIBUjARBgsqhkiG+E0BDQECAgICAMEwEQYLKoZIhvhNAQ0BAgMCAgCjMBEGCyqGSIb4TQENAQIEAgIAjDARBgsqhkiG+E0BDQECBQICAPcwEQYLKoZIhvhNAQ0BAgYCAgDtMBEGCyqGSIb4TQENAQIHAgIA8zAQBgsqhkiG+E0BDQECCAIBCjAQBgsqhkiG+E0BDQECCQIBUjAQBgsqhkiG+E0BDQECCgIBSzAQBgsqhkiG+E0BDQECCwIBTjARBgsqhkiG+E0BDQECDAICALswEAYLKoZIhvhNAQ0BAg0CAQQwEQYLKoZIhvhNAQ0BAg4CAgCfMBAGCyqGSIb4TQENAQIPAgFZMBEGCyqGSIb4TQENAQIQAgIAxzARBgsqhkiG+E0BDQECEQICKWEwHwYLKoZIhvhNAQ0BAhIEEFLBo4z37fMKUktOuwSfWccwEAYKKoZIhvhNAQ0BAwQCimcwFAYKKoZIhvhNAQ0BBAQG7XQq+K31MA8GCiqGSIb4TQENAQUKAQAwCgYIKoZIzj0EAwIDRwAwRAIgX3COA7iS3GwLO1v4Ft2fL1WUlShk19OJb1W5GcZSrPMCIEwEmDStayUNO/c02Vas+Oc9rGkC6VVagXmxjE1xxVlK'; + this.certificationData = `-----BEGIN CERTIFICATE----- +MIIEgDCCBCagAwIBAgIUVvUthlAB2c3lo6k5OS9bYNRQvhIwCgYIKoZIzj0EAwIwcTEjMCEGA1UE +AwwaSW50ZWwgU0dYIFBDSyBQcm9jZXNzb3IgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9u +MRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYTAlVTMB4XDTE5MTAx +NTA3MjA1NFoXDTI2MTAxNTA3MjA1NFowcDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBDZXJ0aWZp +Y2F0ZTEaMBgGA1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQsw +CQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQEzIscIEkt +CSl6gLNCvkxZIXMblNqOJY88pWiPCL+/UfEbRKiguBQT61M1VP2Hh2ii6zpkPHkOsSIm6gnI218S +o4ICmzCCApcwHwYDVR0jBBgwFoAU0Oiq2nXX+S5JF5g8exRl0NXyWU0wXwYDVR0fBFgwVjBUoFKg +UIZOaHR0cHM6Ly9hcGkudHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9zZ3gvY2VydGlmaWNhdGlv +bi92Mi9wY2tjcmw/Y2E9cHJvY2Vzc29yMB0GA1UdDgQWBBR20paRDIQsuFRq4kk1jfIEeWIqdjAO +BgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAdQGCSqGSIb4TQENAQSCAcUwggHBMB4GCiqG +SIb4TQENAQEEEIa7nbGV6riZJHz6CJQl+I4wggFkBgoqhkiG+E0BDQECMIIBVDAQBgsqhkiG+E0B +DQECAQIBDTAQBgsqhkiG+E0BDQECAgIBDTAQBgsqhkiG+E0BDQECAwIBAjAQBgsqhkiG+E0BDQEC +BAIBBDAQBgsqhkiG+E0BDQECBQIBATARBgsqhkiG+E0BDQECBgICAIAwEAYLKoZIhvhNAQ0BAgcC +AQMwEAYLKoZIhvhNAQ0BAggCAQAwEAYLKoZIhvhNAQ0BAgkCAQAwEAYLKoZIhvhNAQ0BAgoCAQAw +EAYLKoZIhvhNAQ0BAgsCAQAwEAYLKoZIhvhNAQ0BAgwCAQAwEAYLKoZIhvhNAQ0BAg0CAQAwEAYL +KoZIhvhNAQ0BAg4CAQAwEAYLKoZIhvhNAQ0BAg8CAQAwEAYLKoZIhvhNAQ0BAhACAQAwEAYLKoZI +hvhNAQ0BAhECAQkwHwYLKoZIhvhNAQ0BAhIEEA0NAgQBgAMAAAAAAAAAAAAwEAYKKoZIhvhNAQ0B +AwQCAAAwFAYKKoZIhvhNAQ0BBAQGAJBuoQAAMA8GCiqGSIb4TQENAQUKAQAwCgYIKoZIzj0EAwID +SAAwRQIgf8Bbj/9xVbejhqnthr1cHcX5fF5MStS5tCgZmwCL98ICIQCTX+Li5drO74gsdXbjrawh +uZ0WsNREI7WIEPEd9fp/Kg== +-----END CERTIFICATE----------BEGIN CERTIFICATE----- +MIIClzCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC +MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD +b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw +CQYDVQQGEwJVUzAeFw0xODA1MjExMDQ1MDhaFw0zMzA1MjExMDQ1MDhaMHExIzAh +BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl +bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB +MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg +tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i +HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww +UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl +cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5jcmwwHQYDVR0OBBYEFNDo +qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG +AQH/AgEAMAoGCCqGSM49BAMCA0cAMEQCIC/9j+84T+HztVO/sOQBWJbSd+/2uexK +4+aA0jcFBLcpAiA3dhMrF5cD52t6FqMvAIpj8XdGmy2beeljLJK+pzpcRA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICjjCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDExMVoXDTMzMDUyMTEwNDExMFowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSAAwRQIgQQs/08rycdPauCFk8UPQXCMAlsloBe7NwaQGTcdpa0EC +IQCUt8SGvxKmjpcM/z0WP9Dvo8h2k5du1iWDdBkAn+0iiA== +-----END CERTIFICATE-----`; + } + + async getCtx() { + return { + log: this.logger, + reqId: this.reqId, + request: { + body: { + nonce: this.nonce + } + }, + set: sinon.stub() + }; + } + + async getTarget() { + return proxyquire('../../src/handlers/verifyAttestationEvidence', { + './certificateChainParser': this.certificateChainParser, + '../clients/pcsAccessLayer/PCSClient': this.pcsClient, + '../clients/crlAccessLayer/CRLClient': this.crlClient, + '../qvl': this.qvl, + '../clients/vcsAccessLayer/VCSClient': this.vcsClient, + '../configLoader': this.configLoader + }); + } + + setupCertificationData() { + this.qvl.getCertificationData.resolves({ + type: 5, + data: this.certificationData + }); + return this; + } + + setupPckCertificateData(override = {}) { + this.qvl.getPckCertificateData.resolves({ + fmspc: override.fmspc || '00906EA10000', + sgxType: override.sgxType || 'Standard', + cpusvn: override.cpusvn || Buffer.from([3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + pcesvn: override.pcesvn || 3, + dynamicPlatform: Boolean(override.dynamicPlatform), + cachedKeys: Boolean(override.cachedKeys), + smtEnabled: Boolean(override.smtEnabled), + }); + return this; + } + + setupCrlDistributionPoint() { + this.qvl.getCrlDistributionPoint.resolves('Full Name: URI:https://certificates.trustedservices.intel.com/IntelSGXRootCA.crl'); + this.crlClient.getCrlFromDistributionPoint.resolves({ status: 200, body: '-----BEGIN CERTIFICATE-----' }); + return this; + } + + setupTcbInfo(tcbInfo) { + tcbInfo = tcbInfo ? + tcbInfo : + { + tcbInfo: { + issueDate: '2021-08-06T13:55:15Z', + tcbLevels: [ + { + tcb: { + sgxtcbcomponents: [ + { + svn: 3 + }, + { + svn: 2 + }, + { + svn: 1 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + } + ], + tdxtcbcomponents: [ + { + svn: 82, + category: 'KRYYIXKC8U', + type: '@TU9B14XQO' + }, + { + svn: 193, + category: '504X036OI0', + type: 'EQJ363GEJV' + }, + { + svn: 163, + category: 'JZT5BUBBET', + type: '@6ZH630STD' + }, + { + svn: 140, + category: 'XMJJ814468', + type: '1G0N1J G9D' + }, + { + svn: 247, + category: 'HGVSWL@TCR', + type: '9WZUC8QV@G' + }, + { + svn: 237 + }, + { + svn: 243 + }, + { + svn: 10 + }, + { + svn: 82 + }, + { + svn: 75, + category: 'GDXEGMOEMR', + type: 'TC145 MZV0' + }, + { + svn: 78, + category: 'L9GDQAPJEN', + type: 'I YVRGWSOR' + }, + { + svn: 187, + category: 'AW 0H0XEFY', + type: 'PNGZ1XU075' + }, + { + svn: 4 + }, + { + svn: 159 + }, + { + svn: 89, + category: 'JLH22L7UTB', + type: 'GX3A1IZC82' + }, + { + svn: 199 + }, + ], + pcesvn: 3, + }, + tcbDate: '2019-09-01T00:00:00Z', + tcbStatus: 'UpToDate' + }, + { + tcb: { + sgxtcbcomponents: [ + { + svn: 3 + }, + { + svn: 2 + }, + { + svn: 0, + category: 'cat1', + type: 'type1' + }, + { + svn: 0, + category: 'cat1', + }, + { + svn: 0, + type: 'type1' + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + } + ], + tdxtcbcomponents: [ + { + svn: 81, + category: 'KRYYIXKC8U', + type: '@TU9B14XQO' + }, + { + svn: 193, + category: '504X036OI0', + type: 'EQJ363GEJV' + }, + { + svn: 163, + type: '@6ZH630STD' + }, + { + svn: 140, + category: 'XMJJ814468', + }, + { + svn: 247, + category: 'HGVSWL@TCR', + type: '9WZUC8QV@G' + }, + { + svn: 237 + }, + { + svn: 243 + }, + { + svn: 10 + }, + { + svn: 82 + }, + { + svn: 75, + category: 'GDXEGMOEMR', + type: 'TC145 MZV0' + }, + { + svn: 78, + category: 'L9GDQAPJEN', + type: 'I YVRGWSOR' + }, + { + svn: 187, + category: 'AW 0H0XEFY', + type: 'PNGZ1XU075' + }, + { + svn: 4 + }, + { + svn: 159 + }, + { + svn: 89, + category: 'JLH22L7UTB', + type: 'GX3A1IZC82' + }, + { + svn: 199 + }, + ], + pcesvn: 3, + }, + tcbDate: '2019-09-01T00:00:00Z', + tcbStatus: 'ConfigurationNeeded', + advisoryIDs: ['INTEL-SA-38861', 'INTEL-SA-68515'] + } + ] + } + }; + this.pcsClient.getSgxTcbInfo.resolves({ status: 200, body: tcbInfo, headers: { 'tcb-info-issuer-chain': '' } }); + this.pcsClient.getTdxTcbInfo.resolves({ status: 200, body: tcbInfo, headers: { 'tcb-info-issuer-chain': '' } }); + return this; + } + + setupQeIdentity(advisoryIDs) { + const qeIdentity = { + enclaveIdentity: { + tcbEvaluationDataNumber: 0, + tcbLevels: [ + { + tcb: { + isvsvn: 4 + }, + advisoryIDs + }, + { + tcb: { + isvsvn: 12677 + }, + advisoryIDs + } + ] + } + }; + this.pcsClient.getSgxQeIdentity.resolves({ status: 200, body: qeIdentity }); + this.pcsClient.getTdxQeIdentity.resolves({ status: 200, body: qeIdentity }); + return this; + } + + setupVerifyQuote(status = 0, errorSource = undefined) { + this.qvl.verifyQuote.resolves({ status, errorSource }); + return this; + } + + setupCertificateChain() { + this.certificateChainParser.parseCertificateChainWithSpecificRoot.resolves({ rootCaPem: '', intermediateCaPem: '', pckCertPem: '' }); + this.certificateChainParser.parseTcbInfoSigningChainWithSpecificRoot.resolves({ rootCaPem: '', tcbSigningCertChain: '' }); + return this; + } + + setupSignature() { + this.vcsClient.signVerificationReport.resolves({ status: 200, body: { signature: '' } }); + return this; + } + + defaultSetup() { + return this.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + } +} + +describe('verify attestation evidence handler tests', () => { + it('execute - SGX - UpToDate', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(Object.keys(ctx.body).length, 9, 'Unexpected number of fields in response'); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'SGX_STANDARD'); + assert.equal(ctx.body.isvQuoteStatus, 'OK'); + assert.equal(ctx.body.isvQuoteBody, 'AwACAAAAAAAEAAkAk5pyM/ecTKmUCg2zlX8GB3cEhXlSheEulmeokact7P4AAAAADQ0DBf+AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAA/NbIKlebNUxhlIX/ysdhdjxtT2O+JTpqaPcLeJTp9SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyEBhE5TtWVUKTgl2yS/DCkZ8aq0eu1aesDBuOxNUj9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + assert.equal(iso8601Regex.test(ctx.body.tcbDate), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.advisoryIDs, undefined); + }); + + it('execute - SGX - ConfigurationNeeded', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }) + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity(['INTEL-SA-54321']) + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(Object.keys(ctx.body).length, 11, 'Unexpected number of fields in response'); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'SGX_STANDARD'); + assert.equal(ctx.body.isvQuoteStatus, 'OK'); + assert.equal(ctx.body.isvQuoteBody, 'AwACAAAAAAAEAAkAk5pyM/ecTKmUCg2zlX8GB3cEhXlSheEulmeokact7P4AAAAADQ0DBf+AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAA/NbIKlebNUxhlIX/ysdhdjxtT2O+JTpqaPcLeJTp9SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyEBhE5TtWVUKTgl2yS/DCkZ8aq0eu1aesDBuOxNUj9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + assert.equal(ctx.body.advisoryURL, 'https://security-center.intel.com'); + assert.equal(JSON.stringify(ctx.body.advisoryIDs), JSON.stringify(['INTEL-SA-38861', 'INTEL-SA-54321', 'INTEL-SA-68515'])); + }); + + it('execute - SGX - ConfigurationNeeded/TCB_OUT_OF_DATE', async() => { + // GIVEN + const nonce = '12345678901234567890123456789012'; + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + dynamicPlatform: true, + cachedKeys: true, + smtEnabled: true, + }) + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote(qvlStatus.STATUS_TCB_OUT_OF_DATE) + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'SGX_STANDARD'); + assert.equal(ctx.body.isvQuoteStatus, 'TCB_OUT_OF_DATE'); + assert.equal(ctx.body.isvQuoteBody, 'AwACAAAAAAAEAAkAk5pyM/ecTKmUCg2zlX8GB3cEhXlSheEulmeokact7P4AAAAADQ0DBf+AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAA/NbIKlebNUxhlIX/ysdhdjxtT2O+JTpqaPcLeJTp9SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyEBhE5TtWVUKTgl2yS/DCkZ8aq0eu1aesDBuOxNUj9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + assert.equal(ctx.body.advisoryURL, 'https://security-center.intel.com'); + assert.equal(JSON.stringify(ctx.body.advisoryIDs), JSON.stringify(['INTEL-SA-38861', 'INTEL-SA-68515'])); + assert.equal(JSON.stringify(ctx.body.tcbComponentsOutOfDate), JSON.stringify([{ category: 'cat1', type: 'type1' }])); + assert.equal(ctx.body.nonce, nonce); + assert.equal(JSON.stringify(ctx.body.configuration), JSON.stringify(['DYNAMIC_PLATFORM', 'CACHED_KEYS', 'SMT_ENABLED'])); + assert.equal(Object.keys(ctx.body).length, 14, 'Unexpected number of fields in response'); + }); + + it('execute - TDX - UpToDate', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(Object.keys(ctx.body).length, 9, 'Unexpected number of fields in response'); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'TDX'); + assert.equal(ctx.body.isvQuoteStatus, 'OK'); + assert.equal(ctx.body.isvQuoteBody, 'BAACAIEAAAAAAAAAk5pyM/ecTKmUCg2zlX8GB0PP8xCV14+q/cEZXptW+nYebWwaUsGjjPft8wpSS067BJ9ZxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + }); + + it('execute - TDX - ConfigurationNeeded', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }) + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(Object.keys(ctx.body).length, 11, 'Unexpected number of fields in response'); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'TDX'); + assert.equal(ctx.body.isvQuoteStatus, 'OK'); + assert.equal(ctx.body.isvQuoteBody, 'BAACAIEAAAAAAAAAk5pyM/ecTKmUCg2zlX8GB0PP8xCV14+q/cEZXptW+nYebWwaUsGjjPft8wpSS067BJ9ZxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + assert.equal(ctx.body.advisoryURL, 'https://security-center.intel.com'); + assert.equal(JSON.stringify(ctx.body.advisoryIDs), JSON.stringify(['INTEL-SA-38861', 'INTEL-SA-68515'])); + }); + + it('execute - TDX - ConfigurationNeeded/TCB_OUT_OF_DATE', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + const nonce = '12345678901234567890123456789012'; + ctx.request.body = { + isvQuote: c.tdxQuote, + nonce + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + dynamicPlatform: true, + cachedKeys: true, + smtEnabled: true, + }) + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote(qvlStatus.STATUS_TCB_OUT_OF_DATE) + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal((/^\d+$/).test(ctx.body.id), true, 'ID field should be a 128 bit number'); + assert.equal(iso8601Regex.test(ctx.body.timestamp), true, 'Date format should be in ISO 8601'); + assert.equal(ctx.body.version, 5); + assert.equal(ctx.body.attestationType, 'ECDSA'); + assert.equal(ctx.body.teeType, 'TDX'); + assert.equal(ctx.body.isvQuoteStatus, 'TCB_OUT_OF_DATE'); + assert.equal(ctx.body.isvQuoteBody, 'BAACAIEAAAAAAAAAk5pyM/ecTKmUCg2zlX8GB0PP8xCV14+q/cEZXptW+nYebWwaUsGjjPft8wpSS067BJ9ZxwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='); + assert.equal(ctx.body.tcbEvaluationDataNumber, 0); + assert.equal(ctx.body.advisoryURL, 'https://security-center.intel.com'); + assert.equal(JSON.stringify(ctx.body.advisoryIDs), JSON.stringify(['INTEL-SA-38861', 'INTEL-SA-68515'])); + assert.equal(JSON.stringify(ctx.body.tcbComponentsOutOfDate), JSON.stringify([{ category: 'cat1', type: 'type1' }, { category: 'KRYYIXKC8U', type: '@TU9B14XQO' }])); + assert.equal(JSON.stringify(ctx.body.configuration), JSON.stringify(['DYNAMIC_PLATFORM', 'CACHED_KEYS', 'SMT_ENABLED'])); + assert.equal(ctx.body.nonce, nonce); + assert.equal(Object.keys(ctx.body).length, 14, 'Unexpected number of fields in response'); + }); + + it('execute - SGX - TCB level mismatch', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }) + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(ctx.body, undefined, 'Unexpected body response'); + }); + + it('execute - SGX - Enclave TCB level mismatch', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + const qeIdentity = { + enclaveIdentity: { + tcbEvaluationDataNumber: 0, + tcbLevels: [ + { + tcb: { + isvsvn: 555 + } + } + ] + } + }; + c.pcsClient.getSgxQeIdentity.resolves({ status: 200, body: qeIdentity }); + c.pcsClient.getTdxQeIdentity.resolves({ status: 200, body: qeIdentity }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(ctx.body, undefined, 'Unexpected body response'); + }); + + it('execute - TDX - TCB level mismatch', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo({ + tcbInfo: { + issueDate: '2021-08-06T13:55:15Z', + tcbLevels: [ + { + tcb: { + sgxtcbcomponents: [ + { + svn: 3 + }, + { + svn: 2 + }, + { + svn: 1 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + } + ], + tdxtcbcomponents: [ + { + svn: 83, + category: 'KRYYIXKC8U', + type: '@TU9B14XQO' + }, + { + svn: 193, + category: '504X036OI0', + type: 'EQJ363GEJV' + }, + { + svn: 163, + category: 'JZT5BUBBET', + type: '@6ZH630STD' + }, + { + svn: 140, + category: 'XMJJ814468', + type: '1G0N1J G9D' + }, + { + svn: 247, + category: 'HGVSWL@TCR', + type: '9WZUC8QV@G' + }, + { + svn: 237 + }, + { + svn: 243 + }, + { + svn: 10 + }, + { + svn: 82 + }, + { + svn: 75, + category: 'GDXEGMOEMR', + type: 'TC145 MZV0' + }, + { + svn: 78, + category: 'L9GDQAPJEN', + type: 'I YVRGWSOR' + }, + { + svn: 187, + category: 'AW 0H0XEFY', + type: 'PNGZ1XU075' + }, + { + svn: 4 + }, + { + svn: 159 + }, + { + svn: 89, + category: 'JLH22L7UTB', + type: 'GX3A1IZC82' + }, + { + svn: 199 + }, + ], + pcesvn: 3, + }, + tcbDate: '2019-09-01T00:00:00Z', + tcbStatus: 'UpToDate' + } + ] + } + }) + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 1); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 2); + assert.equal(ctx.body, undefined, 'Unexpected body response'); + }); + + it('execute - TDX - unexpected empty advisoryIDs', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData({ + cpusvn: Buffer.from([3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) + }) + .setupCrlDistributionPoint() + .setupTcbInfo({ + tcbInfo: { + issueDate: '2021-08-06T13:55:15Z', + tcbLevels: + [ + { + tcb: { + sgxtcbcomponents: [ + { + svn: 3 + }, + { + svn: 2 + }, + { + svn: 1 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + } + ], + tdxtcbcomponents: [ + { + svn: 82 + }, + { + svn: 193 + }, + { + svn: 163 + }, + { + svn: 140 + }, + { + svn: 247 + }, + { + svn: 237 + }, + { + svn: 243 + }, + { + svn: 10 + }, + { + svn: 82 + }, + { + svn: 75 + }, + { + svn: 78 + }, + { + svn: 187 + }, + { + svn: 4 + }, + { + svn: 159 + }, + { + svn: 89 + }, + { + svn: 199 + }, + ], + pcesvn: 3, + }, + tcbDate: '2019-09-01T00:00:00Z', + tcbStatus: 'UpToDate' + }, + { + tcb: { + sgxtcbcomponents: [ + { + svn: 3 + }, + { + svn: 2 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + }, + { + svn: 0 + } + ], + tdxtcbcomponents: [ + { + svn: 82 + }, + { + svn: 193 + }, + { + svn: 163 + }, + { + svn: 140, + }, + { + svn: 247 + }, + { + svn: 237 + }, + { + svn: 243 + }, + { + svn: 10 + }, + { + svn: 82 + }, + { + svn: 75 + }, + { + svn: 78 + }, + { + svn: 187 + }, + { + svn: 4 + }, + { + svn: 159 + }, + { + svn: 89 + }, + { + svn: 199 + }, + ], + pcesvn: 3, + }, + tcbDate: '2019-09-01T00:00:00Z', + tcbStatus: 'ConfigurationNeeded', + advisoryIDs: [] // should be filled in real scenario + } + ] + } + }) + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 200); + assert.equal(ctx.body.advisoryIDs, undefined); + }); + + it('execute - Invalid nonce', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote, + nonce: '123456789012345678901234567890123' // too long nonce + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 0); + assert.equal(c.qvl.getPckCertificateData.callCount, 0); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 0); + assert.equal(ctx.body, undefined, 'Unexpected body response'); + }); + + it('execute - quote parsing failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote() + .setupCertificateChain() + .setupSignature(); + // WHEN + c.qvl.getCertificationData.throws('Problem'); + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 1); + assert.equal(c.qvl.getPckCertificateData.callCount, 0); + assert.equal(c.qvl.getCrlDistributionPoint.callCount, 0); + assert.equal(ctx.body, undefined, 'Unexpected body response'); + }); + + const anyErrorSource = -1; + const allErrorSources = [...Array(Object.keys(errorSource).length).keys()]; + const qvlPositiveStatuses = [ + { qvlStatus: qvlStatus.STATUS_OK, isvQuoteStatus: 'OK' }, + { qvlStatus: qvlStatus.STATUS_INVALID_QUOTE_SIGNATURE, isvQuoteStatus: 'SIGNATURE_INVALID' }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_REVOKED, isvQuoteStatus: 'REVOKED' }, + { qvlStatus: qvlStatus.STATUS_PCK_REVOKED, isvQuoteStatus: 'REVOKED' }, + { qvlStatus: qvlStatus.STATUS_TCB_REVOKED, isvQuoteStatus: 'REVOKED' }, + { qvlStatus: qvlStatus.STATUS_SGX_INTERMEDIATE_CA_REVOKED, isvQuoteStatus: 'REVOKED' }, + { qvlStatus: qvlStatus.STATUS_TCB_OUT_OF_DATE, isvQuoteStatus: 'TCB_OUT_OF_DATE' }, + { qvlStatus: qvlStatus.STATUS_TCB_CONFIGURATION_NEEDED, isvQuoteStatus: 'CONFIGURATION_NEEDED' }, + { qvlStatus: qvlStatus.STATUS_TCB_OUT_OF_DATE_CONFIGURATION_NEEDED, isvQuoteStatus: 'TCB_OUT_OF_DATE_AND_CONFIGURATION_NEEDED' }, + { qvlStatus: qvlStatus.STATUS_TCB_SW_HARDENING_NEEDED, isvQuoteStatus: 'SW_HARDENING_NEEDED' }, + { qvlStatus: qvlStatus.STATUS_TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED, isvQuoteStatus: 'CONFIGURATION_AND_SW_HARDENING_NEEDED' } + ]; + const qvlExpectedNegativeStatuses = [ + { qvlStatus: qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT, errorSource: errorSource.VERIFY_PCK_CERTIFICATE }, + { qvlStatus: qvlStatus.STATUS_SGX_ROOT_CA_MISSING, errorSource: errorSource.VERIFY_PCK_CERTIFICATE }, + { qvlStatus: qvlStatus.STATUS_SGX_ROOT_CA_INVALID_EXTENSIONS, errorSource: errorSource.VERIFY_PCK_CERTIFICATE }, + { qvlStatus: qvlStatus.STATUS_SGX_ROOT_CA_INVALID_ISSUER, errorSource: errorSource.VERIFY_PCK_CERTIFICATE }, + { qvlStatus: qvlStatus.STATUS_SGX_INTERMEDIATE_CA_MISSING, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_INTERMEDIATE_CA_INVALID_EXTENSIONS, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_INTERMEDIATE_CA_INVALID_ISSUER, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_MISSING, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_INVALID_EXTENSIONS, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_INVALID_ISSUER, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_CERT_CHAIN_UNTRUSTED, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_UNSUPPORTED_QUOTE_FORMAT, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_UNSUPPORTED_PCK_CERT_FORMAT, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_INVALID_PCK_CERT, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_TCB_NOT_SUPPORTED, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_INVALID_QE_REPORT_SIGNATURE, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_INVALID_QE_REPORT_DATA, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_QE_IDENTITY_MISMATCH, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_SGX_PCK_CERT_CHAIN_EXPIRED, errorSource: anyErrorSource }, + { qvlStatus: qvlStatus.STATUS_TDX_MODULE_MISMATCH, errorSource: anyErrorSource } + ]; + const qvlUnexpectedStatuses = [...Array(Object.keys(qvlStatus).length).keys()] + .filter(i => !qvlPositiveStatuses.map(s => s.qvlStatus).includes(i)) + .concat([-1, Object.keys(qvlStatus).length]) // values outside of range + .map(i => { + const obj = qvlExpectedNegativeStatuses.find(x => x.qvlStatus === i); + if (obj?.errorSource === anyErrorSource) { + return null; + } + return { qvlStatus: i, errorSources: allErrorSources.filter(x => x !== obj?.errorSource) }; + }) + .filter(x => x !== null); + + async function testQvlStatus(qvlStatus, errorSource, expectedHttpStatus, errors, isvQuoteStatus) { + + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.setupCertificationData() + .setupPckCertificateData() + .setupCrlDistributionPoint() + .setupTcbInfo() + .setupQeIdentity() + .setupVerifyQuote(qvlStatus, errorSource) + .setupCertificateChain() + .setupSignature(); + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, expectedHttpStatus, `Http statuses don't match for QVL status ${qvlStatus}`); + assert.equal(ctx.body?.isvQuoteStatus, isvQuoteStatus, `isvQuoteStatus don't match for QVL status ${qvlStatus}`); + assert.equal(c.logger.error.callCount, errors, `Unexpected number of errors for QVL status ${qvlStatus}`); + } + /* eslint-disable no-await-in-loop */ + async function testUnexpectedQvlStatuses(source) { + for (const { qvlStatus } of qvlUnexpectedStatuses.filter(x => x.errorSources.includes(source))) { + await testQvlStatus(qvlStatus, source, 500, 1); + } + } + + it('execute - qvl returns positive status results in status 200', async() => { + for (const { qvlStatus, isvQuoteStatus } of qvlPositiveStatuses) { + await testQvlStatus(qvlStatus, undefined, 200, 0, isvQuoteStatus); + } + }); + + it('execute - qvl returns expected negative status results in status 400', async() => { + for (const qvlExpectedNegativeStatus of qvlExpectedNegativeStatuses) { + await testQvlStatus(qvlExpectedNegativeStatus.qvlStatus, qvlExpectedNegativeStatus.errorSource, 400, 1); + } + }); + /* eslint-enable no-await-in-loop */ + + it('execute - qvl returns unexpected status during pck cert verification results in status 500', async() => { + await testUnexpectedQvlStatuses(errorSource.VERIFY_PCK_CERTIFICATE); + }); + it('execute - qvl returns unexpected status during tcb info verification results in status 500', async() => { + await testUnexpectedQvlStatuses(errorSource.VERIFY_TCB_INFO); + }); + it('execute - qvl returns unexpected status during QE identity verification results in status 500', async() => { + await testUnexpectedQvlStatuses(errorSource.VERIFY_ENCLAVE_IDENTITY); + }); + it('execute - qvl returns unexpected status during quote verification results in status 500', async() => { + await testUnexpectedQvlStatuses(errorSource.VERIFY_QUOTE); + }); + + it('execute - qvl returns no errorSource despite throwing error', async() => { + const expectedStatusForAnySource = qvlExpectedNegativeStatuses.find(x => x.errorSource === anyErrorSource).qvlStatus; + await testQvlStatus(expectedStatusForAnySource, undefined, 500, 1); + }); + + describe('nonce', () => { + it('too long', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: '123456789012345678901234567890123' // 33 characters + }; + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.hasOwnProperty('body'), false); + assertMockCalledOnceWithArgs(ctx.log.error, 'Provided nonce is longer than 32 characters: ', ctx.request.body.nonce); + }); + + it('not provided', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.strictEqual(ctx.body.hasOwnProperty('nonce'), false); + assert.strictEqual(ctx.log.error.callCount, 0); + }); + }); + + describe('configuration', () => { + + it('no flags - no configuration field', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.strictEqual(ctx.body.hasOwnProperty('configuration'), false); + }); + + it('dynamic platform', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.setupPckCertificateData({ dynamicPlatform: true }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepEqual(ctx.body.configuration, ['DYNAMIC_PLATFORM']); + }); + + it('cached keys', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.setupPckCertificateData({ cachedKeys: true }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepEqual(ctx.body.configuration, ['CACHED_KEYS']); + }); + + it('SMT enabled', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.setupPckCertificateData({ smtEnabled: true }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepEqual(ctx.body.configuration, ['SMT_ENABLED']); + }); + + it('multiple flags', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.setupPckCertificateData({ + dynamicPlatform: true, + cachedKeys: true, + smtEnabled: true + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepEqual(ctx.body.configuration, ['DYNAMIC_PLATFORM', 'CACHED_KEYS', 'SMT_ENABLED']); + }); + + }); + + describe('wrong certification data', () => { + + it('error retrieving data from quote', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.qvl.getCertificationData.rejects(new Error()); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve certification data from quote'); + }); + + it('not supported certification data type', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.qvl.getCertificationData.resolves({ + type: 1, + data: c.certificationData + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(String(ctx.log.error.args[0][0]), 'Error: Not supported certification data type: 1'); + }); + + }); + + it('PCK certificate without required extensions', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.qvl.getPckCertificateData.rejects(new Error()); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.log.error.args[0][0].message, 'PCK Cert does not contain required extensions'); + }); + + it('Sgx TcbInfo retrieval failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.pcsClient.getSgxTcbInfo.resolves({ status: 404 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve required TcbInfo. PCS returned status: 404'); + }); + + it('TcbInfo retrieval failure, FMSP/TCB not found', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.pcsClient.getSgxTcbInfo.resolves({ status: 400 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve required TcbInfo. PCS returned status: 400'); + }); + + it('Tdx TcbInfo retrieval failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.pcsClient.getTdxTcbInfo.resolves({ status: 404 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve required TcbInfo. PCS returned status: 404'); + }); + + it('Sgx QeIdentity retrieval failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.pcsClient.getSgxQeIdentity.resolves({ status: 404 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve required QeIdentity. PCS returned status: 404'); + }); + + it('Tdx QeIdentity retrieval failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.pcsClient.getTdxQeIdentity.resolves({ status: 404 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve required QeIdentity. PCS returned status: 404'); + }); + + it('execute - get certification data fails', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.qvl.getCertificationData.rejects({ + error: 'sgxAttestationGetQECertificationDataSize failed', + status: 37 + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.equal(ctx.status, 400); + assert.equal(c.qvl.getCertificationData.callCount, 1); + }); + + it('execute - CRL distribution point is in wrong format', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.qvl.getCrlDistributionPoint.resolves('https://certificates.trustedservices.intel.com/IntelSGXRootCA.crl'); + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(c.qvl.getCertificationData.callCount, 1); + assert.strictEqual(c.qvl.getPckCertificateData.callCount, 1); + assert.strictEqual(c.qvl.getCrlDistributionPoint.callCount, 2); + }); + + it('execute - get CRL distribution point fails', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.qvl.getCrlDistributionPoint.rejects({ + error: 'Error getting CRL distribution point' + }); + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(c.qvl.getCertificationData.callCount, 1); + assert.strictEqual(c.qvl.getPckCertificateData.callCount, 1); + assert.strictEqual(c.qvl.getCrlDistributionPoint.callCount, 2); + }); + + it('execute - get CRL from distribution point fails', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.crlClient.getCrlFromDistributionPoint.resolves({ status: 404 }); + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to retrieve one of CRLs. Distribution Point returned status: 404'); + }); + + it('execute - get CRL in DER format from distribution point', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + const hexString = 'AABBCCDDEEFF0123456789'; + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.crlClient.getCrlFromDistributionPoint.resolves({ status: 200, body: Buffer.from(hexString, 'hex') }); + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.strictEqual(c.qvl.verifyQuote.callCount, 1); + assert.strictEqual(c.qvl.verifyQuote.args[0][7], hexString.toLowerCase()); + assert.strictEqual(c.qvl.verifyQuote.args[0][8], hexString.toLowerCase()); + }); + + it('execute - tcb levels out of order', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup() + .setupVerifyQuote(qvlStatus.STATUS_TCB_OUT_OF_DATE); + + const tcbInfo = (await c.pcsClient.getSgxTcbInfo()).body.tcbInfo; + tcbInfo.tcbLevels = tcbInfo.tcbLevels.reverse(); + c.setupTcbInfo({ tcbInfo }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + }); + + it('scalable sgx type', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.setupPckCertificateData({ + sgxType: 'Scalable' + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.strictEqual(ctx.body.teeType, 'SGX_SCALABLE'); + }); + + it('scalable with integrity sgx type', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.setupPckCertificateData({ + sgxType: 'ScalableWithIntegrity' + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 200); + assert.strictEqual(ctx.body.teeType, 'SGX_SCALABLE_WITH_INTEGRITY'); + }); + + it('not supported sgx type', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.sgxQuote + }; + c.defaultSetup(); + c.setupPckCertificateData({ + sgxType: 'NotSupported' + }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Unsupported sgxType'); + }); + + it('sign verification report failure', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: c.tdxQuote, + nonce: c.nonce + }; + c.defaultSetup(); + c.vcsClient.signVerificationReport.resolves({ status: 404 }); + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.log.error.args[0][0].message, 'Failed to sign the report. VCS returned status: 404'); + }); + + it('no isvQuote', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = {}; + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + }); + + it('quote not base64', async() => { + // GIVEN + const c = new TestContext().defaultSetup(); + const target = await c.getTarget(); + const ctx = await c.getCtx(); + ctx.request.body = { + isvQuote: '!@#$%^&*()' //special characters that aren't base64 + }; + + // WHEN + await target.verifyAttestationEvidence(ctx); + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.log.error.callCount, 1); + assert.strictEqual(c.qvl.getCertificationData.callCount, 0); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/healthCheckerTest.js b/QuoteVerification/QuoteVerificationService/test/healthCheckerTest.js new file mode 100644 index 00000000..e4e91ae9 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/healthCheckerTest.js @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const helpers = require('./mocks/helpers'); +const _ = require('lodash'); +const Promise = require('bluebird'); + +class TestContext { + constructor() { + this.addComponentHealthCondition = sinon.stub(); + this.HealthCache = sinon.stub().returns({ + addComponentHealthCondition: this.addComponentHealthCondition, + status: { OK: 'OK' } + }); + this.logger = { + trace: () => {}, + debug: () => {}, + info: () => {}, + error: () => {}, + }; + this.version = { + version: '123' + }; + this.config = { + healthCheck: { + intervalMs: 1500, + freshnessMs: 1500 + } + }; + this.configLoader = { + getConfig: () => this.config + }; + this.client = { getHealth: sinon.stub() }; + this.callback = sinon.stub(); + this.response = { + body: { + status: 'OK' + } + }; + } + + getTarget() { + return proxyquire('../src/healthChecker', { + './koa/koaHealthCache': this.HealthCache, + './configLoader': this.configLoader, + './logger': () => this.logger, + './package.json': this.version, + './clients/pcsAccessLayer/PCSClient': this.client, + './clients/vcsAccessLayer/VCSClient': this.client, + './qvl': { getVersion: this.client.getHealth } + }); + } +} + +describe('healthChecker', () => { + + it('configuration passed to HealthCache', async() => { + // GIVEN + const testCtx = new TestContext(); + const expectedConfiguration = { + frequencyMS: testCtx.config.healthCheck.intervalMs, + validityMS: testCtx.config.healthCheck.freshnessMs, + version: testCtx.version.version, + logger: testCtx.logger + }; + + // WHEN + await testCtx.getTarget(); + + // THEN + helpers.assertMockFirstCalledWithArgs(testCtx.HealthCache, expectedConfiguration); + }); + + async function awaitHealthConditions(testCtx, howMany) { + assert.strictEqual(testCtx.addComponentHealthCondition.callCount, howMany); + const conditions = _.range(howMany) + .map(index => testCtx.addComponentHealthCondition.args[index][1]) + .map(componentHealth => componentHealth(testCtx.logger, 'reqId', testCtx.callback)); + await Promise.all(conditions); + } + + const healthConditionsCount = 3; + + it('Positive', async() => { + // GIVEN + const testCtx = new TestContext(); + + testCtx.client.getHealth = sinon.stub().resolves(testCtx.response); + + // WHEN + await testCtx.getTarget(); + await awaitHealthConditions(testCtx, healthConditionsCount); + + // THEN + const hcStatuses = testCtx.callback.args.map(args => args[1].status); + assert.strictEqual(hcStatuses.length, healthConditionsCount); + assert.ok((hcStatuses).every(status => status === 'OK'), 'Each status should be OK'); + }); + + it('Negative', async() => { + // GIVEN + const testCtx = new TestContext(); + testCtx.client.getHealth = sinon.stub().rejects({ + body: { + status: 'FAILED' + } + }); + + // WHEN + await testCtx.getTarget(); + await awaitHealthConditions(testCtx, healthConditionsCount); + + // THEN + const hcStatuses = testCtx.callback.args.map(args => args[1].status); + assert.strictEqual(hcStatuses.length, healthConditionsCount); + assert.ok((hcStatuses).some(status => status === 'FAILED'), 'Some HC should fail'); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/jsDoc/typesTest.js b/QuoteVerification/QuoteVerificationService/test/jsDoc/typesTest.js new file mode 100644 index 00000000..e4d06fa8 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/jsDoc/typesTest.js @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + + 'use strict'; + + const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); + const assert = require('assert'); + + class TestContext { + + async getTarget() { + return proxyquire('../../src/jsDoc/types', {}); + } + +} + +describe('include JSDoc - no other functionality exported', () => { + it('types', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const target = await c.getTarget(); + + // THEN + assert.deepStrictEqual(target, {}); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/errorTest.js b/QuoteVerification/QuoteVerificationService/test/koa/errorTest.js new file mode 100644 index 00000000..6a08e55d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/errorTest.js @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const errors = require('../../src/koa/errors'); +const assert = require('assert'); + +describe('Error test', () => { + it('Should call TcbOutOfDate error with expected message', () => { + // GIVEN + const msg = 'TcbOutOfDate error'; + // WHEN + const error = new errors.TcbOutOfDate(msg); + + // THEN + assert.strictEqual(error.name, 'TcbOutOfDate'); + assert.strictEqual(error.message, msg); + }); + + it('Should call PpidNotFound error with expected message', () => { + // GIVEN + const msg = 'PpidNotFound error'; + // WHEN + const error = new errors.PpidNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'PpidNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call InternalError error with expected message', () => { + // GIVEN + const msg = 'InternalError error'; + // WHEN + const error = new errors.InternalError(msg); + + // THEN + assert.strictEqual(error.name, 'InternalError'); + assert.strictEqual(error.message, msg); + assert.strictEqual(typeof error.cause(), 'undefined'); + }); + + it('Should call FmspNotFound error with expected message', () => { + // GIVEN + const msg = 'FmspNotFound error'; + // WHEN + const error = new errors.FmspNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'FmspNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call DeviceKeyNotFound error with expected message', () => { + // GIVEN + const msg = 'DeviceKeyNotFound error'; + // WHEN + const error = new errors.DeviceKeyNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'DeviceKeyNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call EventDataNotFound error with expected message', () => { + // GIVEN + const msg = 'EventDataNotFound error'; + // WHEN + const error = new errors.EventDataNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'EventDataNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call EnclaveTcbNotFound error with expected message', () => { + // GIVEN + const msg = 'EnclaveTcbNotFound error'; + // WHEN + const error = new errors.EnclaveTcbNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'EnclaveTcbNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call EnclaveIdentityNotFound error with expected message', () => { + // GIVEN + const msg = 'EnclaveIdentityNotFound error'; + // WHEN + const error = new errors.EnclaveIdentityNotFound(msg); + + // THEN + assert.strictEqual(error.name, 'EnclaveIdentityNotFound'); + assert.strictEqual(error.message, msg); + }); + + it('Should call InternalErrorWithNoRetryStatus error with expected message', () => { + // GIVEN + const msg = 'InternalErrorWithNoRetryStatus error'; + // WHEN + const error = new errors.InternalErrorWithNoRetryStatus(msg); + + // THEN + assert.strictEqual(error.name, 'InternalErrorWithNoRetryStatus'); + assert.strictEqual(error.body, msg); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/init/serverInitTest.js b/QuoteVerification/QuoteVerificationService/test/koa/init/serverInitTest.js new file mode 100644 index 00000000..5e1eb653 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/init/serverInitTest.js @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +const assertMockFirstCalledWithArgs = require('../../mocks/helpers').assertMockFirstCalledWithArgs; + +class KoaMock { + constructor() { + this.context = {}; + } + + use() {} + + callback() {} +} + +class TestContext { + constructor() { + this.logger = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + }; + this.httpsServer = { + listen: sinon.stub(), + on: (event, callback) => callback(), + }; + this.https = { + createServer: sinon.stub().returns(this.httpsServer), + }; + this.httpServer = { + listen: sinon.stub(), + on: (event, callback) => callback(), + }; + this.router = { + routes: sinon.stub(), + allowedMethods: sinon.stub() + }; + this.http = { + createServer: sinon.stub().returns(this.httpServer) + }; + this.session = sinon.stub(); + this.serviceName = 'serviceName'; + this.port = 999; + this.readFileSafely = sinon.stub(); + + this.getCACertificatesSyncMock = sinon.stub().returns(['file1, file2']); + } + + getOptions(options) { + return { + serviceName: this.serviceName, + logger: this.logger, + middlewares: [sinon.stub(), sinon.stub()], + serviceConfig: { + tlsServerType: options.serverType, + keyFile: 'file.key', + certFile: 'file.crt', + caCertDirectories: [], + } + }; + } + + async getTarget() { + return proxyquire('../../../src/koa/init/serverInit', { + 'https': this.https, + 'http': this.http, + 'koa': KoaMock, + '../../utilities/getCACertificatesSync': this.getCACertificatesSyncMock, + '../../common/readFileSafely': this.readFileSafely + }); + } +} + +describe('serverTest', () => { + describe('https server - positive', () => { + it('started with MTLS', async() => { + // GIVEN + const c = new TestContext(); + const expLogInfo = `HTTPS server started on port ${c.port}!`; + const initServer = await c.getTarget(); + + // WHEN + const ctx = new initServer(c.getOptions({ serverType: 'MTLS' })); + await ctx.startHTTPSServer(c.port, c.router); + + // THEN + assert(c.https.createServer.calledOnce); + assert(c.logger.info.calledOnce); + assertMockFirstCalledWithArgs(c.logger.info, expLogInfo); + }); + + it('started with TLS', async() => { + // GIVEN + const c = new TestContext(); + const expLogInfo = `HTTPS server started on port ${c.port}!`; + const initServer = await c.getTarget(); + + // WHEN + const ctx = new initServer(c.getOptions({ serverType: 'TLS' })); + await ctx.startHTTPSServer(c.port, c.router); + + // THEN + assert(c.https.createServer.calledOnce); + assert(c.logger.info.calledOnce); + assertMockFirstCalledWithArgs(c.logger.info, expLogInfo); + }); + + it('started', async() => { + // GIVEN + const c = new TestContext(); + const expLogInfo = `HTTP server started on port ${c.port}!`; + const initServer = await c.getTarget(); + + // WHEN + const ctx = new initServer(c.getOptions({})); + await ctx.startHTTPServer(c.port, c.router); + + // THEN + assert(c.http.createServer.calledOnce); + assert(c.logger.info.calledOnce); + assertMockFirstCalledWithArgs(c.logger.info, expLogInfo); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/init/serverTerminationTest.js b/QuoteVerification/QuoteVerificationService/test/koa/init/serverTerminationTest.js new file mode 100644 index 00000000..c8761db1 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/init/serverTerminationTest.js @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const http = require('http'); +const https = require('https'); + +const assertMockCalledWithArgs = require('../../mocks/helpers').assertMockCalledWithArgs; + +const EventEmitter = require('events'); + +class ProcessMock extends EventEmitter { + exit(code) { + this.emit('exit', code); + } +} + +class KoaMock { + constructor() { + this.context = {}; + } + + use() {} + + callback() {} +} + +class HttpServerStub { + constructor() { + // HTTP server + this._server = undefined; + this.listenStub = undefined; + this.closeStub = undefined; + } + + createServer() { + this._server = http.createServer((req, res) => { + res.writeHead(200); + res.end('Server mock'); + }); + this.listenStub = sinon.stub(this._server, 'listen').callsFake(() => this._server.emit('listening')); + this.closeStub = sinon.stub(this._server, 'close').callsFake(closeCallback => closeCallback(undefined)); // undefined = no error + return this._server; + } +} + +class HttpsServerStub { + constructor() { + // HTTPS server + this._server = undefined; + this.listenStub = undefined; + this.closeStub = undefined; + } + + createServer() { + this._server = https.createServer((req, res) => { + res.writeHead(200); + res.end('Server mock'); + }); + this.listenStub = sinon.stub(this._server, 'listen').callsFake(() => this._server.emit('listening')); + this.closeStub = sinon.stub(this._server, 'close').callsFake(closeCallback => closeCallback(undefined)); // undefined = no error + return this._server; + } +} + +class TestContext { + constructor() { + this.httpServerStub = new HttpServerStub(); + this.httpsServerStub = new HttpsServerStub(); + + this.processMock = new ProcessMock(); + this.processExitSpy = sinon.spy(this.processMock, 'exit'); + this.processOnSpy = sinon.spy(this.processMock, 'on'); + + this.logger = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + }; + + this._router = { + routes: sinon.stub(), + allowedMethods: sinon.stub() + }; + + this._httpPort = '9999'; + this._httpsPort = '9998'; + } + + async _getTarget() { + return proxyquire('../../../src/koa/init/serverInit', { + 'http': { + createServer: () => this.httpServerStub.createServer() + }, + 'https': { + createServer: () => this.httpsServerStub.createServer() + }, + 'process': this.processMock, + 'koa': KoaMock, + '../../utilities/getCACertificatesSync': sinon.stub().returns(['file1, file2']), + '../../common/readFileSafely': sinon.stub() + }); + } + + _getOptions(isHttps) { + return { + serviceName: 'serviceName', + logger: this.logger, + middlewares: [sinon.stub(), sinon.stub()], + serviceConfig: { + tlsServerType: isHttps ? 'TLS' : undefined, + keyFile: 'file.key', + certFile: 'file.crt', + caCertDirectories: [], + } + }; + } + + async startServerHTTP() { + const isHttps = false; + const ServerInit = await this._getTarget(); + const ctx = new ServerInit(this._getOptions(isHttps)); + await ctx.startHTTPServer(this._httpPort, this._router); + sinon.assert.calledOnce(this.httpServerStub.listenStub); + } + + async startServerHTTPS() { + const isHttps = true; + const ServerInit = await this._getTarget(); + const ctx = new ServerInit(this._getOptions(isHttps)); + await ctx.startHTTPSServer(this._httpsPort, this._router); + sinon.assert.calledOnce(this.httpsServerStub.listenStub); + } +} + +describe('serverTerminationTest', () => { + + let testContext; + const itSleep = 10; // ms + const signalsToTest = ['SIGINT', 'SIGTERM']; + + describe('http', () => { + beforeEach(async() => { + testContext = new TestContext(); + await testContext.startServerHTTP(); + }); + + signalsToTest.forEach(signal => { + it(signal, (done) => { + // THEN + testContext.processMock.once(signal, () => { + setTimeout(() => { + sinon.assert.calledOnce(testContext.httpServerStub.closeStub); + sinon.assert.calledOnce(testContext.processExitSpy); + sinon.assert.calledWithExactly(testContext.processExitSpy, 0); + done(); + }, itSleep); + }); + + // WHEN + testContext.processMock.emit(signal); + }); + }); + }); + + describe('https', () => { + beforeEach(async() => { + testContext = new TestContext(); + await testContext.startServerHTTPS(); + }); + + signalsToTest.forEach(signal => { + it(signal, (done) => { + // THEN + testContext.processMock.once(signal, () => { + setTimeout(() => { + sinon.assert.calledOnce(testContext.httpsServerStub.closeStub); + sinon.assert.calledOnce(testContext.processExitSpy); + sinon.assert.calledWithExactly(testContext.processExitSpy, 0); + done(); + }, itSleep); + }); + + // WHEN + testContext.processMock.emit(signal); + }); + }); + }); + + describe('both = http + https', () => { + beforeEach(async() => { + testContext = new TestContext(); + await testContext.startServerHTTP(); + await testContext.startServerHTTPS(); + }); + + signalsToTest.forEach(signal => { + it(signal, (done) => { + // THEN + testContext.processMock.once(signal, () => { + setTimeout(() => { + sinon.assert.calledOnce(testContext.httpServerStub.closeStub); + sinon.assert.calledOnce(testContext.httpsServerStub.closeStub); + sinon.assert.calledTwice(testContext.processExitSpy); // FIXME: no idea why it is not called once + const expectedExitCode = 0; + assertMockCalledWithArgs(0, testContext.processExitSpy, expectedExitCode); + assertMockCalledWithArgs(1, testContext.processExitSpy, expectedExitCode); + done(); + }, itSleep); + }); + + // WHEN + testContext.processMock.emit(signal); + }); + }); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/koaHealthCacheTest.js b/QuoteVerification/QuoteVerificationService/test/koa/koaHealthCacheTest.js new file mode 100644 index 00000000..daab664a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/koaHealthCacheTest.js @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const HealthCache = require('../../src/koa/koaHealthCache'); +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const _ = require('lodash'); +const assert = require('assert'); +const sinon = require('sinon'); +const { assertMockCalledWithArgs, assertMockCalledOnceWithArgs } = require('../mocks/helpers'); + + +const { + STATUS_OK, + STATUS_NOT_FOUND, + STATUS_SERVICE_UNAVAILABLE +} = require('../../src/koa/response').STATUSES; + +class TestContext { + constructor(input) { + + if (!input) { + input = { + stripResult: {} + }; + } + + this.logger = { + trace: sinon.stub(), + debug: sinon.stub(), + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub(), + scoped: null + }; + this.logger.scoped = sinon.stub().returns(this.logger); + this.defaultOptions = { + frequencyMS: 100, + validityMS: 5000, + version: '999.9', + logger: this.logger + }; + this.healthCondition = (status) => { + return async(logger, reqId, callback) => { + callback(null, { status }); + }; + }; + + this.utilMock = { + promisify: sinon.stub().returns(sinon.stub().resolves(input.stripResult)) + }; + + this.statusFailed = 'FAILED'; + this.statusUnknown = 'UNKNOWN'; + this.NA = 'NA'; + } + + getCtx() { + return { + log: this.logger, + status: STATUS_NOT_FOUND, + body: {}, + set: sinon.stub(), + assert: () => {} + }; + } + + getTarget(options) { + const modifiedDefaultOptions = _.extend(this.defaultOptions, options); + const HealthCache = proxyquire('../../src/koa/koaHealthCache', { + util: this.utilMock, + }); + + return new HealthCache(modifiedDefaultOptions); + } +} + +describe('healthCacheTests', () => { + describe('healthcheck all components', () => { + it('should throw error when component with given name already exists', () => { + // GIVEN + const c = new TestContext(); + const healthCache = c.getTarget(); + healthCache.addComponentHealthCondition('component', c.healthCondition('OK')); + + const addComponentStep = () => { + // WHEN + healthCache.addComponentHealthCondition('component', c.healthCondition('OK')); + }; + + const errorContainsHealthCheckFormatError = (error) => { + // THEN + const expectedMessage = 'componentName should be unique'; + return error.message.indexOf(expectedMessage) >= 0; + }; + + assert.throws(addComponentStep, errorContainsHealthCheckFormatError); + }); + }); + + it('logger is mandatory', () => { + // GIVEN + const c = new TestContext(); + + // WHEN + const action = () => { c.getTarget({ logger: undefined }); }; + + // THEN + assert.throws(action); + }); + + it('constructor should throw error when no parameters passed', () => { + const action = () => { + //WHEN + return new HealthCache(); + }; + + //THEN + assert.throws(action, Error); + }); + + it('addComponentHealthCondition should throw error when wrong component format given', () => { + //GIVEN + const c = new TestContext(); + const healthCache = c.getTarget(); + + const addComponentStep = () => { + //WHEN + healthCache.addComponentHealthCondition(); + }; + + const errorContainsComponentFormatError = (error) => { + //THEN + const expectedMessage = 'componentName should be a string'; + return error.message.indexOf(expectedMessage) >= 0; + }; + + assert.throws(addComponentStep, errorContainsComponentFormatError); + }); + + it('addComponentHealthCondition should throw error when wrong component health check format given', () => { + //GIVEN + const c = new TestContext(); + const healthCache = c.getTarget(); + + const addComponentStep = () => { + //WHEN + healthCache.addComponentHealthCondition('componentName', 'notAFunction'); + }; + + const errorContainsHealthCheckFormatError = (error) => { + // THEN + const expectedMessage = 'componentHealthCheck should be a function'; + return error.message.indexOf(expectedMessage) >= 0; + }; + + assert.throws(addComponentStep, errorContainsHealthCheckFormatError); + }); + + it('handleRequest, should return STATUS_OK when params is present', (done) => { + // GIVEN + const stripResult = { + status: STATUS_OK.name, + version: '1', + componentStatus: STATUS_OK.httpCode, + details: 'details' + }; + const c = new TestContext({ stripResult }); + const ctx = c.getCtx(); + ctx.params = { + component: 'this' + }; + const healthCache = c.getTarget({ details: 'details' }); + + // WHEN + healthCache.addComponentHealthCondition('this', () => {}); + healthCache.run(); + + + setTimeout(() => { + healthCache.handleRequest(ctx); + healthCache.stop(); + + // THEN + assert.deepStrictEqual(ctx.status, STATUS_OK.httpCode); + assert.deepStrictEqual(ctx.body.status, STATUS_OK.name); + assert.deepStrictEqual(ctx.body.version, c.defaultOptions.version); + assert.deepStrictEqual(ctx.body.this.status, STATUS_OK.name); + assert.deepStrictEqual(ctx.body.this.version, stripResult.version); + done(); + }, 1); + + }); + + it('handleRequest, should return OUTDATED when params is present AS', (done) => { + // GIVEN + const stripResult = { + 'status': STATUS_OK.name, + 'version': '1', + 'componentStatus': STATUS_OK.httpCode, + 'details': 'details', + 'X-IASReport-Signing-Certificate': 'X-IASReport-Signing-Certificate', + 'X-IASReport-Signing-Certificate-Status': 'X-IASReport-Signing-Certificate-Status' + }; + const c = new TestContext({ stripResult }); + const ctx = c.getCtx(); + ctx.params = { + component: 'AttestationService' + }; + const healthCache = c.getTarget({ details: 'details', validityMS: 1 }); + + // WHEN + healthCache.addComponentHealthCondition('this', () => {}); + healthCache.addComponentHealthCondition('AttestationService', () => {}); + healthCache.run(); + + + setTimeout(() => { + healthCache.handleRequest(ctx); + healthCache.stop(); + + // THEN + assert.deepStrictEqual(ctx.status, STATUS_OK.httpCode); + assert.deepStrictEqual(ctx.body.AttestationService.status, 'OUTDATED'); + assert.deepStrictEqual(ctx.body.AttestationService['X-IASReport-Signing-Certificate-Status'], undefined); + assert.deepStrictEqual(ctx.body.AttestationService['X-IASReport-Signing-Certificate'], undefined); + assert.deepStrictEqual(ctx.set.called, false); + done(); + }, 100); + + }); + + it('handleRequest, should return STATUS_OK when params is present AS', (done) => { + // GIVEN + const stripResult = { + 'status': STATUS_OK.name, + 'version': '1', + 'componentStatus': STATUS_OK.httpCode, + 'details': 'details', + 'X-IASReport-Signing-Certificate': 'Certificate', + 'X-IASReport-Signing-Certificate-Status': 'cert_status' + }; + const c = new TestContext({ stripResult }); + const ctx = c.getCtx(); + ctx.params = { + component: 'AttestationService' + }; + const healthCache = c.getTarget({ details: 'details' }); + + // WHEN + healthCache.addComponentHealthCondition('this', () => {}); + healthCache.addComponentHealthCondition('AttestationService', () => {}); + healthCache.run(); + + + setTimeout(() => { + healthCache.handleRequest(ctx); + healthCache.stop(); + + // THEN + assert.deepStrictEqual(ctx.status, STATUS_OK.httpCode); + assert.deepStrictEqual(ctx.body.AttestationService.status, STATUS_OK.name); + assert.deepStrictEqual(ctx.body.AttestationService['X-IASReport-Signing-Certificate-Status'], 'cert_status'); + assert.deepStrictEqual(ctx.body.AttestationService['X-IASReport-Signing-Certificate'], undefined); + assertMockCalledOnceWithArgs(ctx.set, 'X-IASReport-Signing-Certificate', 'Certificate'); + done(); + }, 1); + + }); + + + it('handleRequest, should return STATUS_SERVICE_UNAVAILABLE when params is not present', (done) => { + // GIVEN + const c = new TestContext(); + const ctx = c.getCtx(); + + const healthCache = c.getTarget({ details: 'details' }); + + // WHEN + healthCache.addComponentHealthCondition('this', () => {}); + healthCache.run(); + + setTimeout(() => { + healthCache.handleRequest(ctx); + healthCache.stop(); + + // THEN + assert.deepStrictEqual(ctx.status, STATUS_SERVICE_UNAVAILABLE.httpCode); + assert.deepStrictEqual(ctx.body.status, c.statusFailed); + assert.deepStrictEqual(ctx.body.version, c.NA); + assert.deepStrictEqual(ctx.body.this, undefined); + done(); + }, 1); + }); + + it('handleRequest, should return STATUS_SERVICE_UNAVAILABLE when stripResult is undefined', (done) => { + // GIVEN + const c = new TestContext({ stripResult: undefined }); + const ctx = c.getCtx(); + const healthCache = c.getTarget(); + + // WHEN + healthCache.addComponentHealthCondition('component', () => {}); + healthCache.run(); + setTimeout(() => { + healthCache.handleRequest(ctx); + healthCache.stop(); + + // THEN + assert.deepStrictEqual(ctx.status, STATUS_SERVICE_UNAVAILABLE.httpCode); + assert.deepStrictEqual(ctx.body.status, c.statusFailed); + assert.deepStrictEqual(ctx.body.version, c.defaultOptions.version); + assert.deepStrictEqual(ctx.body.componentStatus.component.status, c.statusUnknown); + assert.deepStrictEqual(ctx.body.componentStatus.component.version, c.NA); + done(); + }, 1); + }); + + it('run, positive', () => { + // GIVEN + const c = new TestContext(); + const expInfoMsg = ['healthCache starting...', 'healthCache started.']; + const healthCache = c.getTarget(); + + // WHEN + healthCache.addComponentHealthCondition('component', () => {}); + healthCache.run(); + healthCache.stop(); + + + // THEN + assertMockCalledWithArgs(4, c.logger.info, expInfoMsg[0]); + assertMockCalledWithArgs(5, c.logger.info, expInfoMsg[1]); + }); + + it('stop, positive', () => { + // GIVEN + const c = new TestContext(); + const expInfoMsg = ['healthCache stopping...', 'healthCache stopped.']; + const healthCache = c.getTarget(); + healthCache.addComponentHealthCondition('component', () => {}); + + // WHEN + healthCache.stop(); + + // THEN + assertMockCalledWithArgs(4, c.logger.info, expInfoMsg[0]); + assertMockCalledWithArgs(5, c.logger.info, expInfoMsg[1]); + }); + + it('should return default value when frequencyMs is negative', () => { + // GIVEN + const c = new TestContext(); + const options = { + frequencyMS: -100, + logger: c.logger + }; + const frequnecyMsDefault = 5000; + + // WHEN + HealthCache(options); + + // THEN + assert.deepStrictEqual(options.frequencyMS, frequnecyMsDefault); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/koaHealthRouterTest.js b/QuoteVerification/QuoteVerificationService/test/koa/koaHealthRouterTest.js new file mode 100644 index 00000000..ee18715f --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/koaHealthRouterTest.js @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('chai').assert; + +const { assertMockCalledOnceWithArgs } = require('../mocks/helpers'); + +class RouterStub { + constructor() { + this.get = sinon.spy(); + } +} + +class TestContext { + constructor() { + this.healthHandler = sinon.stub(); + } + + getTarget() { + return proxyquire('../../src/koa/koaHealthRouter', { + 'koa-router': RouterStub + }); + } +} + +describe('koa health cache router', () => { + it('should return router with prefix', async() => { + const c = new TestContext(); + const target = c.getTarget(); + const router = target.createHealthRouter(c.healthHandler, '/test'); + assert.instanceOf(router, RouterStub); + assertMockCalledOnceWithArgs(router.get, '/test/:component', c.healthHandler); + assertMockCalledOnceWithArgs(router.get, '/test', c.healthHandler); + }); + + it('should return router with default prefix', async() => { + const c = new TestContext(); + const target = c.getTarget(); + const router = target.createHealthRouter(c.healthHandler); + assert.instanceOf(router, RouterStub); + assertMockCalledOnceWithArgs(router.get, '/:component', c.healthHandler); + assertMockCalledOnceWithArgs(router.get, '/', c.healthHandler); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/maxClientsHandlerTest.js b/QuoteVerification/QuoteVerificationService/test/koa/maxClientsHandlerTest.js new file mode 100644 index 00000000..6bc21f31 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/maxClientsHandlerTest.js @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('chai').assert; +const _ = require('lodash'); +const events = require('events'); +const { + STATUS_OK, + STATUS_SERVICE_UNAVAILABLE +} = require('../../src/koa/response').STATUSES; + +const stubs = { + response: { + send() { + return this; + }, + end() { + return this; + }, + setHeader() { + return this; + }, + status() { + return this; + } + } +}; + +class TestContext { + constructor() { + this.ctx = { + status: STATUS_OK.httpCode, + reqId: {}, + body: {}, + log: { + error: sinon.stub(), + info: sinon.stub(), + debug: sinon.stub(), + trace: sinon.stub() + }, + state: {}, + params: {}, + req: _.extend(stubs.request), + request: _.extend(stubs.request), + set: sinon.stub(), + get() {} + }; + } + + getTarget() { + return proxyquire('../../src/koa/maxClientsHandler', {}); + } +} + +describe('decorators', () => { + + it('test_maxClientsHandler_too_many_clients_negative', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + // WHEN + const serviceName = 'AS'; + const taskType = 'as_task_type_name'; + const maxClients = 4; + global.currentClients = 100; + + await target.createRequestManager(serviceName, maxClients).manageRequest(taskType, () => {})(c.ctx, () => {}); + + // THEN + assert.strictEqual(c.ctx.status, STATUS_SERVICE_UNAVAILABLE.httpCode); + assert.deepEqual(c.ctx.body, { code: 'KO.', message: 'Server too busy.' }); + }); + + async function testEventEmmitCommon(inputData) { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + // WHEN + const taskType = 'as_task_type_name'; + const maxClients = 4; + + const ctx = { + res: new events.EventEmitter(), + log: { + error: sinon.stub(), + info: sinon.stub(), + debug: sinon.stub(), + trace: sinon.stub() + }, + }; + + ctx.res.writableFinished = inputData.writableFinished; + + await target.createRequestManager(inputData.serviceName, maxClients).manageRequest(taskType, () => {})(ctx, () => {}); + + assert.strictEqual(global.currentClients, inputData.expectedClients.beforeEmit); + ctx.res.emit(inputData.event); + // THEN + assert.strictEqual(global.currentClients, inputData.expectedClients.afterEmit); + } + + it('test_maxClientsHandler_event_finish_positive', async() => { + const inputData = { + event: 'finish', + serviceName: 'AS', + writableFinished: false, + expectedClients: { beforeEmit: 1, afterEmit: 0 } + }; + + return testEventEmmitCommon(inputData); + }); + + it('test_maxClientsHandler_event_close_positive', async() => { + const inputData = { + event: 'close', + serviceName: 'PS', + writableFinished: false, + expectedClients: { beforeEmit: 1, afterEmit: 0 } + }; + + return testEventEmmitCommon(inputData); + }); + + it('test_maxClientsHandler_event_close_writable_not_finished_negative', async() => { + const inputData = { + event: 'close', + serviceName: 'AS', + writableFinished: true, + expectedClients: { beforeEmit: 1, afterEmit: 1 } + }; + + return testEventEmmitCommon(inputData); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/bodyParserTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/bodyParserTest.js new file mode 100644 index 00000000..ba8060eb --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/bodyParserTest.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const sinon = require('sinon'); +const proxyquire = require('proxyquire'); + +const assertMockFirstCalledWithArgs = require('../../mocks/helpers').assertMockFirstCalledWithArgs; + +class TestContext { + constructor() { + this.koaBodyParser = sinon.stub(); + } + + getTarget() { + return proxyquire('../../../src/koa/middleware/bodyParser', { + 'koa-bodyparser': this.koaBodyParser + }); + } +} + +describe('body parser middleware tests', () => { + + it('default limits', async() => { + // GIVEN + const c = new TestContext(); + const getConfiguredBodyParser = c.getTarget().getConfiguredBodyParser; + // WHEN + getConfiguredBodyParser(); + // THEN + assertMockFirstCalledWithArgs(c.koaBodyParser, { enableTypes: ['json', 'text', 'form'] }); + }); + + it('specific limits', async() => { + // GIVEN + const c = new TestContext(); + const getConfiguredBodyParser = c.getTarget().getConfiguredBodyParser; + const bodySizeLimits = { + json: '192mb', + text: '192mb', + form: '64kb' + }; + // WHEN + getConfiguredBodyParser({ + service: { bodySizeLimits } + }); + // THEN + assertMockFirstCalledWithArgs(c.koaBodyParser, { + enableTypes: ['json', 'text', 'form'], + jsonLimit: bodySizeLimits.json, + textLimit: bodySizeLimits.text, + formLimit: bodySizeLimits.form + }); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/configureCtxLoggerAndLogResponseTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/configureCtxLoggerAndLogResponseTest.js new file mode 100644 index 00000000..309ded02 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/configureCtxLoggerAndLogResponseTest.js @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('chai').assert; +const events = require('events'); +const helpers = require('../../mocks/helpers'); +const { + STATUS_OK, + STATUS_BAD_REQUEST, + STATUS_INTERNAL_ERROR +} = require('../../../src/koa/response').STATUSES; + + +class TestContext { + constructor() { + this.ctx = { + status: STATUS_OK.httpCode, + request: { + method: 'POST', + originalUrl: 'dummyUrl' + }, + response: { + headers: { + dummyHeaders: 'value' + }, + body: 'dummy body' + }, + res: new events.EventEmitter(), + }; + this.log = { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + }; + this.requestLogFormatter = { + formatResponseMessage: sinon.stub().returns(['msg to log']) + }; + } + + getTarget() { + return proxyquire('../../../src/koa/middleware/configureCtxLoggerAndLogResponse.js', { + '../../common/requestLogFormatter': this.requestLogFormatter + }); + } +} + +describe('configureCtxLoggerAndLogResponse middleware', () => { + + it('should log on info level when finished emitted and status < 400', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + // WHEN + await target(c.log)(c.ctx, () => {}); + c.ctx.res.emit('finish'); + + // THEN + assert.ok(c.requestLogFormatter.formatResponseMessage.calledOnce); + helpers.assertMockCalledOnceWithArgs(c.ctx.log.info, 'msg to log'); + assert.ok(c.ctx.log.warn.notCalled); + assert.ok(c.ctx.log.error.notCalled); + }); + + it('should log on warn level when finished emitted and status >= 400', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + // WHEN + c.ctx.status = STATUS_BAD_REQUEST.httpCode; + await target(c.log)(c.ctx, () => {}); + c.ctx.res.emit('finish'); + + // THEN + assert.ok(c.requestLogFormatter.formatResponseMessage.calledOnce); + assert.ok(c.ctx.log.info.notCalled); + helpers.assertMockCalledOnceWithArgs(c.ctx.log.warn, 'msg to log'); + assert.ok(c.ctx.log.error.notCalled); + }); + + it('should log on error level when finished emitted and status >= 500', async() => { + // GIVEN + const c = new TestContext(); + const target = c.getTarget(); + // WHEN + c.ctx.status = STATUS_INTERNAL_ERROR.httpCode; + await target(c.log)(c.ctx, () => {}); + c.ctx.res.emit('finish'); + + // THEN + assert.ok(c.requestLogFormatter.formatResponseMessage.calledOnce); + assert.ok(c.ctx.log.info.notCalled); + assert.ok(c.ctx.log.warn.notCalled); + helpers.assertMockCalledOnceWithArgs(c.ctx.log.error, 'msg to log'); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/errorHandlerTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/errorHandlerTest.js new file mode 100644 index 00000000..0e4be361 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/errorHandlerTest.js @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const sinon = require('sinon'); +const assert = require('assert'); + +const assertMockFirstCalledWithArgs = require('../../mocks/helpers').assertMockFirstCalledWithArgs; + +class TestContext { + getTarget() { + return require('../../../src/koa/middleware/errorHandler'); + } +} + +describe('error handler middleware tests', () => { + function getContext(errStatus) { + return { + 'log': { + error: sinon.stub() + }, + 'set': sinon.stub(), + 'response': { + get: sinon.stub().returns('application/json; charset=utf-8') + }, + 'throw': sinon.stub().throws({ status: (errStatus || 404) }) + }; + } + + it('positive', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assertMockFirstCalledWithArgs(ctx.set, 'Content-Type', 'application/json'); + assert(ctx.log.error.notCalled); + }); + + it('positive when status created', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(); + const body = 'test'; + ctx.status = 201; + ctx.body = body; + const errorHandler = new TestContext().getTarget().withNoBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assert.strictEqual(ctx.body, body); + assert(ctx.log.error.notCalled); + }); + + it('positive when status bad request and body', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(400); + const body = 'errorReason'; + ctx.status = 400; + ctx.body = body; + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assert.strictEqual(ctx.body, body); + assert(ctx.log.error.notCalled); + }); + + it('no body when status not allowed', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(405); + ctx.status = 405; + ctx.body = 'NotAllowed'; + const errorHandler = new TestContext().getTarget().withNoBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assert.strictEqual(ctx.body, ''); + assert(ctx.log.error.notCalled); + }); + + + it('clears body on error withNoBodyOnError', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(401); + ctx.status = 401; + ctx.body = 'unauthorized'; + const errorHandler = new TestContext().getTarget().withNoBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assert.strictEqual(ctx.body, ''); + assert(ctx.log.error.notCalled); + }); + + it('content-type without charset is kept untouched', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(); + const contentType = 'application/x-pem-file'; + ctx.response.get = sinon.stub().returns(contentType); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assertMockFirstCalledWithArgs(ctx.set, 'Content-Type', contentType); + assert(ctx.log.error.notCalled); + }); + + it('no content-type is not causing issue', async() => { + // GIVEN + const next = sinon.stub().returns(0); + const ctx = getContext(); + ctx.response.get = sinon.stub().returns(undefined); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.response.get.calledOnce); + assert(ctx.log.error.notCalled); + }); + + it('catch http error with status 404', async() => { + // GIVEN + const err = new Error('Not found'); + err.status = 404; + const next = sinon.stub().throws(err); + const ctx = getContext(); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.log.error.notCalled); + }); + + it('catch http error with statusCode', async() => { + // GIVEN + const err = new Error('Not found'); + err.statusCode = 404; + const next = sinon.stub().throws(err); + const ctx = getContext(); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.log.error.notCalled); + }); + + it('catch other error and log stacktrace', async() => { + // GIVEN + const err = new Error('Other error'); + const next = sinon.stub().throws(err); + const ctx = getContext(); + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assertMockFirstCalledWithArgs(ctx.log.error, err); + }); + + it('Default NotFound text is not returned in body when status is 404', async() => { + // GIVEN + const err = new Error('Not found'); + err.statusCode = 404; + const next = sinon.stub().throws(err); + const ctx = getContext(); + ctx.body = ctx.body = 'NotFound'; + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.log.error.notCalled); + assert.strictEqual(ctx.body, ''); + }); + + it('BadRequest passes reason to the user', async() => { + // GIVEN + const err = new Error('Bad Request'); + const reason = 'EMPTY_NAME'; + err.statusCode = 400; + const next = sinon.stub().throws(err); + const ctx = getContext(400); + ctx.body = reason; + const errorHandler = new TestContext().getTarget().withBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assertMockFirstCalledWithArgs(ctx.set, 'Content-Type', 'application/json'); + assert(ctx.log.error.notCalled); + assert.strictEqual(ctx.body.status, err.message); + }); + + it("BadRequest should not pass body if 'withNoBodyOnError' handler is used", async() => { + // GIVEN + const err = new Error('Bad Request'); + const reason = 'EMPTY_NAME'; + err.statusCode = 400; + const next = sinon.stub().throws(err); + const ctx = getContext(400); + ctx.body = reason; + const errorHandler = new TestContext().getTarget().withNoBodyOnError; + // WHEN + await errorHandler(ctx, next); + // THEN + assert(next.calledOnce); + assert(ctx.log.error.notCalled); + assert.strictEqual(ctx.body, ''); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/rawBodyParserTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/rawBodyParserTest.js new file mode 100644 index 00000000..1ce2ea1b --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/rawBodyParserTest.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +const helpers = require('../../mocks/helpers'); + +class TestContext { + constructor() { + this.rawBodyParser = sinon.stub().returns(Buffer.from('body')); + this.ctx = { + 'log': { + error: sinon.stub() + }, + 'request': { + req: { + on: () => { // simulate wrapping stream + return { + pipe: () => sinon.stub(), + }; + } + }, + headers: { + 'content-length': 123 + } + }, + 'body': '', + 'throw': sinon.stub(), + 'set': sinon.stub(), + }; + this.config = { + service: { + bodySizeLimits: { + blob: '32kb' + } + } + }; + this.next = sinon.stub(); + } + + getTarget() { + return proxyquire('../../../src/koa/middleware/rawBodyParser', { + 'raw-body': this.rawBodyParser + }); + } +} + +describe('raw body parser middleware tests', () => { + + it('positive', async() => { + // GIVEN + const c = new TestContext(); + const getConfiguredRawBodyParser = c.getTarget().getConfiguredRawBodyParser(c.config); + // WHEN + await getConfiguredRawBodyParser(c.ctx, c.next); + // THEN + assert.deepEqual(c.ctx.request.body, Buffer.from('body')); + assert.ok(c.next.calledOnce); + assert.ok(c.ctx.log.error.notCalled); + assert.ok(c.ctx.throw.notCalled); + }); + + it('negative with error code', async() => { + // GIVEN + const c = new TestContext(); + const error = { statusCode: 413 }; + c.rawBodyParser = sinon.stub().throws(error); + const getConfiguredRawBodyParser = c.getTarget().getConfiguredRawBodyParser(c.config); + // WHEN + await getConfiguredRawBodyParser(c.ctx, c.next); + // THEN + assert.deepEqual(c.ctx.body, ''); + assert.ok(c.next.notCalled); + helpers.assertMockCalledOnceWithArgs(c.ctx.log.error, error); + helpers.assertMockCalledOnceWithArgs(c.ctx.throw, 413); + }); + + it('negative without error code', async() => { + // GIVEN + const c = new TestContext(); + const error = new Error('someError'); + c.rawBodyParser = sinon.stub().throws(error); + const getConfiguredRawBodyParser = c.getTarget().getConfiguredRawBodyParser(c.config); + // WHEN + await getConfiguredRawBodyParser(c.ctx, c.next); + // THEN + assert.deepEqual(c.ctx.body, ''); + assert.ok(c.next.notCalled); + helpers.assertMockCalledOnceWithArgs(c.ctx.log.error, error); + helpers.assertMockCalledOnceWithArgs(c.ctx.throw, 500); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestBodyLoggerTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestBodyLoggerTest.js new file mode 100644 index 00000000..03b2d774 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestBodyLoggerTest.js @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +const assertMockFirstCalledWithArgs = require('../../mocks/helpers').assertMockFirstCalledWithArgs; + +class TestContext { + constructor() { + this.ctx = { + log: { + info: sinon.stub() + }, + request: { + body: 'requestBody' + } + }; + this.next = sinon.stub(); + } + + getTarget() { + return proxyquire('../../../src/koa/middleware/requestBodyLogger', {}); + } +} + +describe('requestBodyLoggerTest', () => { + describe('Body printed', () => { + it('text', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + await c.getTarget()(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.ctx.log.info, ' [body=\'requestBody\']'); + assert(c.next.calledOnce); + }); + + it('json', async() => { + // GIVEN + const c = new TestContext(); + c.ctx.request.body = { key: 'value' }; + // WHEN + await c.getTarget()(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.ctx.log.info, ' [body={"key":"value"}]'); + assert(c.next.calledOnce); + }); + }); + + describe('Empty body', () => { + it('no logs', async() => { + // GIVEN + const c = new TestContext(); + c.ctx.request.body = undefined; + // WHEN + await c.getTarget()(c.ctx, c.next); + // THEN + assert(c.ctx.log.info.notCalled); + assert(c.next.calledOnce); + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestControlTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestControlTest.js new file mode 100644 index 00000000..1bc5d627 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestControlTest.js @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const sinon = require('sinon'); +const assert = require('assert'); + +const assertMockCalledOnceWithArgs = require('../../mocks/helpers').assertMockCalledOnceWithArgs; + +class TestContext { + + constructor() { + this.next = sinon.stub().returns(0); + this.set = sinon.stub(); + this.log = { + warn: sinon.stub() + }; + } + + getTarget() { + return require('../../../src/koa/middleware/requestControl'); + } + + getContext(input, router) { + return { + 'req': { + method: input.method + }, + 'is': sinon.stub().returns(input.isResult), + 'set': this.set, + 'log': this.log, + 'path': '/', + 'throw': (err) => { throw (err); }, + 'app': { middleware: [{ name: 'dispatch', router }] }, + }; + } + + getRouter(otp) { + const output = otp || { + path: [{ path: '/', methods: ['GET', 'POST'] }], + route: true, + contentType: 'application/json' + }; + + return { + match: sinon.stub().returns(output) + }; + } +} + +describe('request body control middleware tests', () => { + + it('should call next when POST request contain application/json content type', async() => { + // GIVEN + const ctx = new TestContext(); + const ctxInput = { + method: 'POST', + isResult: true + }; + + // WHEN + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter()), ctx.next); + // THEN + assert(ctx.next.calledOnce); + assert(ctx.log.warn.notCalled); + }); + + it('should call next when GET request not contain content type', async() => { + // GIVEN + const ctx = new TestContext(); + const ctxInput = { + method: 'GET', + isResult: null + }; + + // WHEN + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter()), ctx.next); + // THEN + assert(ctx.next.calledOnce); + assert(ctx.log.warn.notCalled); + }); + + it('should call throw 415 when POST request contain wrong content type', async() => { + // GIVEN + const ctx = new TestContext(); + const expWarnMsg = 'Only application/json content type is supported. Return 415 Unsupported Media Type.'; + const ctxInput = { + method: 'POST', + isResult: false + }; + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter()), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 415); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); + + it('should throw 415 when GET request contain content type', async() => { + // GIVEN + const expWarnMsg = 'Incoming request has not empty body for GET method. Return 415 Unsupported Media Type.'; + const ctx = new TestContext(); + const ctxInput = { + method: 'GET' + }; + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter()), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 415); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); + + it('should throw 405 when incoming method is not allowed', async() => { + const ctx = new TestContext(); + const ctxInput = { + method: 'TRACE', + }; + // GIVEN + const expWarnMsg = `Method: ${ctxInput.method} is not allowed.`; + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter()), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 405); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); + + it('should call throw 404 when route does not match', async() => { + // GIVEN + const ctx = new TestContext(); + const expWarnMsg = 'Path: / is not allowed.'; + const ctxInput = { + method: 'POST', + isResult: false + }; + const match = { + path: [], + route: false, + contentType: 'application/json' + }; + + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter(match)), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 404); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); + + it('should call throw 405 when method does not match', async() => { + // GIVEN + const ctx = new TestContext(); + const expWarnMsg = 'Method: POST is not allowed.'; + const ctxInput = { + method: 'POST', + isResult: false + }; + const match = { + path: [{ path: '/', methods: ['GET'] }], + route: false, + contentType: 'application/json' + }; + + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter(match)), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 405); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.set, 'Allow', 'GET'); // assert that 'Allowed' header has been set + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); + + it('should call throw 404 when router has no allowed methods', async() => { + // GIVEN + const ctx = new TestContext(); + const expWarnMsg = 'Path: / is not allowed.'; + const ctxInput = { + method: 'POST', + isResult: false + }; + const match = { + path: [{ path: '/', methods: [] }], + route: false, + contentType: 'application/json' + }; + + + // WHEN + try { + await ctx.getTarget().requestControl(ctx.rbcInput)(ctx.getContext(ctxInput, ctx.getRouter(match)), ctx.next); + } + // THEN + catch (err) { + assert.equal(err, 404); + } + assert(ctx.next.notCalled); + assert(ctx.log.warn.calledOnce); + assertMockCalledOnceWithArgs(ctx.log.warn, expWarnMsg); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestIdTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestIdTest.js new file mode 100644 index 00000000..bcabe1a4 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/requestIdTest.js @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const Buffer = require('safe-buffer').Buffer; + +const assertMockFirstCalledWithArgs = require('../../mocks/helpers').assertMockFirstCalledWithArgs; + +class TestContext { + constructor() { + this.uuid = { + bin: sinon.stub().returns(Buffer.from('aaaaaaaabbbbccccddddeeeeeeeeeeee', 'hex')) + }; + this.logger = { + error: sinon.stub(), + warn: sinon.stub(), + info: sinon.stub(), + debug: sinon.stub() + }; + this.ctx = { + log: { + scoped: sinon.stub().returns(this.logger), + error: sinon.stub(), + warn: sinon.stub(), + info: sinon.stub(), + debug: sinon.stub() + }, + set: sinon.stub(), + req: { + headers: [] + }, + request: {} + }; + this.next = sinon.stub(); + this.serverLevel = { isTopLevel: false }; + } + + getTarget() { + return proxyquire('../../../src/koa/middleware/requestId', { + 'uuid-random': this.uuid + }); + } +} + +describe('requestIdTest', () => { + it('generated reqId is in correct format, without dashes', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assert.strictEqual(c.ctx.reqId, 'aaaaaaaabbbbccccddddeeeeeeeeeeee'); + assert.strictEqual(c.ctx.request.reqId, 'aaaaaaaabbbbccccddddeeeeeeeeeeee'); + }); + + it('Request-ID header is set correctly', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.ctx.set, 'Request-ID', 'aaaaaaaabbbbccccddddeeeeeeeeeeee'); + }); + + it('Request has no Request-ID header', async() => { + // GIVEN + const expectedMsg = 'No Request-ID header. New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.debug, expectedMsg); + assert(c.next.calledOnce); + }); + + it('Request has empty Request-ID header', async() => { + // GIVEN + const expectedMsg = 'No Request-ID header. New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = ''; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.debug, expectedMsg); + assert(c.next.calledOnce); + }); + + it('Request has valid Request-ID header but not top level', async() => { + // GIVEN + const expectedMsg = 'Using received Request-ID: [00000000111122223333444444444444]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = '00000000111122223333444444444444'; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.debug, expectedMsg); + assert(c.next.calledOnce); + }); + + it('Request-ID header is not hexadecimal format and not top level', async() => { + // GIVEN + const expectedMsg = 'Received own Request-ID header: [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX] has incorrect format (must be 32-sign hexadecimal). New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.warn, expectedMsg); + assert(c.next.calledOnce); + }); + + it('Request-ID header is too short and not top level', async() => { + // GIVEN + const expectedMsg = 'Received own Request-ID header: [aaaaaaaabbbbccccddddeeeeeeeeeee] has incorrect format (must be 32-sign hexadecimal). New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = 'aaaaaaaabbbbccccddddeeeeeeeeeee'; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.warn, expectedMsg); + assert(c.next.calledOnce); + }); + + it('Request-ID header is too long and not top level', async() => { + // GIVEN + const expectedMsg = 'Received own Request-ID header: [aaaaaaaabbbbccccddddeeeeeeeeeeeee] has incorrect format (must be 32-sign hexadecimal). New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = 'aaaaaaaabbbbccccddddeeeeeeeeeeeee'; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.warn, expectedMsg); + assert(c.next.calledOnce); + }); + + + it('Request has Request-ID header and top level', async() => { + // GIVEN + const expectedMsg = 'Received own Request-ID header: [00000000111122223333444444444444]. New Request-ID is: [aaaaaaaabbbbccccddddeeeeeeeeeeee]'; + const c = new TestContext(); + c.ctx.req.headers['request-id'] = '00000000111122223333444444444444'; + c.serverLevel.isTopLevel = true; + // WHEN + await c.getTarget()(c.serverLevel)(c.ctx, c.next); + // THEN + assertMockFirstCalledWithArgs(c.logger.info, expectedMsg); + assert(c.next.calledOnce); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/middleware/responseHandlerTest.js b/QuoteVerification/QuoteVerificationService/test/koa/middleware/responseHandlerTest.js new file mode 100644 index 00000000..7bf4fe55 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/middleware/responseHandlerTest.js @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); +const _ = require('lodash'); +global.configPath = './configuration-default/'; + +const assertMockCalledOnceWithArgs = require('../../mocks/helpers').assertMockCalledOnceWithArgs; + +class TestContext { + getTarget() { + return proxyquire('../../../src/koa/middleware/responseHandler', {}); + } +} + +function createContextStub() { + return { + log: { + info: sinon.stub(), + warn: sinon.stub(), + error: sinon.stub() + }, + set: sinon.stub() + + }; +} + +function createResponseStub(status, body, headers) { + return { + httpCode: status, + jsonBody: body, + headers + }; +} + +function getHandlerFromTarget(target, hasBody) { + if (hasBody) { + return target.withBodyOnError; + } + return target.withNoBodyOnError; +} + +describe('requestHandlerTest', () => { + + let ctx = null; + let target = null; + + beforeEach(() => { + target = new TestContext().getTarget(); + ctx = createContextStub(); + }); + + describe('BaseResponseHandler', () => { + it('should initialize BaseResponseHandler with expected status and body', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, true); + await handler(ctx, () => {}); + + // WHEN + ctx.setBadRequest(); + + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(_.isUndefined(ctx.body), false); + }); + + it('should throw ErrorResponse when required response params are missing', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, true); + await handler(ctx, () => {}); + + // WHEN + const responseStub = createResponseStub(undefined, undefined); + + // THEN + assert.throws(() => { + ctx.setResponse(responseStub); + }, Object); + + }); + + it('should setInternalError with expected status and set body', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, true); + await handler(ctx, () => {}); + + // WHEN + ctx.setInternalError(); + + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(_.isUndefined(ctx.body), false); + + }); + + it('should initialize BaseResponseHandler with expected headers', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, true); + await handler(ctx, () => {}); + const responseStub = createResponseStub(200, {}, new Map([['header_1', 'value_1'], ['header_2', 'value_2']])); + + // WHEN + ctx.setResponse(responseStub); + + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepStrictEqual(ctx.body, {}); + + assertMockCalledOnceWithArgs(ctx.set, 'header_1', 'value_1'); + assertMockCalledOnceWithArgs(ctx.set, 'header_2', 'value_2'); + + }); + + }); + + describe('NoBodyOnErrorResponseHandler', () => { + + it('should initialize NoBodyOnErrorResponseHandler with expected body', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, false); + await handler(ctx, () => {}); + + // WHEN + ctx.setBadRequest(); + + // THEN + assert.strictEqual(ctx.status, 400); + assert.strictEqual(ctx.body, ''); + + }); + + it('should initialize NoBodyOnErrorResponseHandler with response body', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, false); + await handler(ctx, () => {}); + const jsonBodyStub = { + test: 'case' + }; + const responseStub = createResponseStub(200, jsonBodyStub); + + // WHEN + ctx.setResponse(responseStub); + + // THEN + assert.strictEqual(ctx.status, 200); + assert.deepStrictEqual(ctx.body, jsonBodyStub); + + }); + + + it('should throw ErrorResponse required response params are missing', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, false); + await handler(ctx, () => {}); + + const responseStub = createResponseStub(undefined, undefined); + + // WHEN + const callSetResponse = () => { + ctx.setResponse(responseStub); + }; + + // THEN + assert.throws(callSetResponse, Object); + + }); + + it('should throw ErrorResponse when response body is missing', async() => { + + // GIVEN + const handler = getHandlerFromTarget(target, false); + await handler(ctx, () => {}); + + const responseStub = createResponseStub(200, undefined); + + // THEN + assert.throws(() => { + // WHEN + ctx.setResponse(responseStub); + }, Object); + + }); + + it('should extend context with expected body and status', async() => { + // GIVEN + const handler = getHandlerFromTarget(target, false); + await handler(ctx, () => {}); + + // WHEN + ctx.setInternalError(); + + // THEN + assert.strictEqual(ctx.status, 500); + assert.strictEqual(ctx.body, ''); + + }); + + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/koa/mocks/loggerMock.js b/QuoteVerification/QuoteVerificationService/test/koa/mocks/loggerMock.js new file mode 100644 index 00000000..d42187fd --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/mocks/loggerMock.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const sinon = require('sinon'); + +/** + * Create mocked logger object; to be used in UTs only + * @return Object - mocked logger object + * @exports + */ +function createLoggerMock() { + const logger = {}; + logger.warn = sinon.spy(); + logger.debug = sinon.spy(); + logger.info = sinon.spy(); + logger.error = sinon.spy(); + return logger; +} + +module.exports.create = createLoggerMock; diff --git a/QuoteVerification/QuoteVerificationService/test/koa/responseTest.js b/QuoteVerification/QuoteVerificationService/test/koa/responseTest.js new file mode 100644 index 00000000..c5f1f2fb --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/koa/responseTest.js @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const sinon = require('sinon'); +const assert = require('assert'); +global.configPath = './configuration-default/'; + +const response = require('../../src/koa/response'); +const { InternalError, InternalErrorResponse, BadRequestResponse, GenericResponse } = response; + +const loggerStub = { + error: sinon.stub() +}; + + +describe('responseTest', () => { + + it('should execute properly', () => { + // GIVEN + const statusStub = { httpCode: 1024, name: 'test' }; + + // WHEN + const genResponse = new GenericResponse(statusStub, loggerStub); + + // THEN + assert.equal(genResponse.httpCode, statusStub.httpCode); + assert.equal(genResponse.jsonBody.status, statusStub.name); + + }); + + it('should throw error when required status params missing', () => { + // GIVEN / WHEN + const statusStub = { httpCode: undefined, name: undefined }; + + // THEN + assert.throws(() => { + return new GenericResponse(statusStub, loggerStub); + }, InternalError); + + }); + + it('should properly init BadRequestResponse', () => { + // GIVEN / WHEN + const badRequestResponse = new BadRequestResponse(loggerStub); + // THEN + assert.equal(badRequestResponse.httpCode, 400); + assert.equal(badRequestResponse.jsonBody.status, 'STATUS_BAD_REQUEST'); + + }); + + it('should properly init InternalErrorResponse', () => { + // GIVEN / WHEN + const internalErrorResponse = new InternalErrorResponse(loggerStub); + // THEN + assert.equal(internalErrorResponse.httpCode, 500); + assert.equal(internalErrorResponse.jsonBody.status, 'STATUS_INTERNAL_ERROR'); + + }); + + describe('setResponse', () => { + + it('should help setting BadRequestResponse in current context', () => { + // GIVEN + const ctx = {}; + const exampleResponse = new BadRequestResponse(loggerStub); + // WHEN + response.setResponse(exampleResponse, ctx); + // THEN + assert.strictEqual(ctx.body, exampleResponse.jsonBody); + assert.strictEqual(ctx.status, exampleResponse.httpCode); + }); + + it('should help setting InternalErrorResponse in current context', () => { + // GIVEN + const ctx = {}; + const exampleResponse = new InternalErrorResponse(loggerStub); + // WHEN + response.setResponse(exampleResponse, ctx); + // THEN + assert.strictEqual(ctx.body, exampleResponse.jsonBody); + assert.strictEqual(ctx.status, exampleResponse.httpCode); + }); + + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/loggerTest.js b/QuoteVerification/QuoteVerificationService/test/loggerTest.js new file mode 100644 index 00000000..f211c49d --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/loggerTest.js @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru(); +const sinon = require('sinon'); +const assert = require('assert'); + +class TestContext { + constructor() { + this.config = { + logger: { + category: 'category', + fileName: 'logfile.log', + levelFile: 'trace', + levelConsole: 'trace', + isMultilineLogEnabled: true + } + }; + this.logger = { createLogger: sinon.stub() }; + this.qvl = { loggerSetup: sinon.stub() }; + this.qvlFileNameExpected = 'logfile-qvl.log'; + this.qvlPatternExpected = '[%Y-%m-%dT%H:%M:%S.%eZ] [%l] [%n %@] [pid:%P]%r %v'; + } + + getTarget() { + return proxyquire('../src/logger', { + './common/logger': this.logger, + './qvl': this.qvl + }); + } +} + +describe('loggerTest', () => { + it('positive', () => { + // GIVEN + const c = new TestContext(); + const loggerInstance = { logger: 'logger' }; + + c.logger.createLogger.returns(loggerInstance); + + // WHEN + const target = c.getTarget()(c.config); + + // THEN + assert.equal(c.qvl.loggerSetup.callCount, 1); + assert.ok(c.qvl.loggerSetup.calledWithExactly( + c.config.logger.category, + c.config.logger.levelConsole.toUpperCase(), + c.config.logger.levelFile.toUpperCase(), + c.qvlFileNameExpected, + c.qvlPatternExpected + )); + + assert.equal(c.logger.createLogger.callCount, 1); + assert.ok(c.logger.createLogger.calledWithExactly( + c.config.logger.category, + c.config.logger.fileName, + c.config.logger.levelFile, + c.config.logger.levelConsole, + c.config.logger.isMultilineLogEnabled)); + + assert.deepEqual(target, loggerInstance); + }); + + it('positive - proper QVL filename when complex filename', () => { + // GIVEN + const c = new TestContext(); + const loggerInstance = { logger: 'logger' }; + + c.logger.createLogger.returns(loggerInstance); + // WHEN + const config = { + logger: { + category: 'category', + fileName: '/some/dir/for/logs/logfile.log', + levelFile: 'trace', + levelConsole: 'trace', + isMultilineLogEnabled: true + } + }; + c.getTarget()(config); + + // THEN + assert.equal(c.qvl.loggerSetup.callCount, 1); + assert.ok(c.qvl.loggerSetup.calledWithExactly( + config.logger.category, + config.logger.levelConsole.toUpperCase(), + config.logger.levelFile.toUpperCase(), + '/some/dir/for/logs/logfile-qvl.log', + c.qvlPatternExpected + )); + + }); + + it('positive - proper QVL filename when no file extension', () => { + // GIVEN + const c = new TestContext(); + const loggerInstance = { logger: 'logger' }; + + c.logger.createLogger.returns(loggerInstance); + // WHEN + const config = { + logger: { + category: 'category', + fileName: 'logfile', + levelFile: 'trace', + levelConsole: 'trace', + isMultilineLogEnabled: true + } + }; + c.getTarget()(config); + + // THEN + assert.equal(c.qvl.loggerSetup.callCount, 1); + assert.ok(c.qvl.loggerSetup.calledWithExactly( + config.logger.category, + config.logger.levelConsole.toUpperCase(), + config.logger.levelFile.toUpperCase(), + 'logfile-qvl', + c.qvlPatternExpected + )); + + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/mocks/helpers.js b/QuoteVerification/QuoteVerificationService/test/mocks/helpers.js new file mode 100644 index 00000000..793c5358 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/mocks/helpers.js @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const assert = require('assert'); +const util = require('util'); +const _ = require('lodash'); + +function assertMockFirstCalledWithArgs(mock, ...args) { + assertMockCalledWithArgs(0, mock, ...args); +} + +function assertMockCalledWithArgs(call, mock, ...args) { + assert(call < mock.callCount, 'Mock not called enough times'); + assert.strictEqual(mock.args[call].length, args.length); + for (const [index, value] of args.entries()) { + if (util.isObject(value)) { + assert.deepStrictEqual(mock.args[call][index], value); + } + else { + assert.strictEqual(mock.args[call][index], value); + } + } +} + +function getCallCountWithArgs(mock, ...args) { + let callCount = 0; + for (let callIndex = 0; callIndex < mock.callCount; callIndex++) { + let argsFound = true; + for (const [index, value] of args.entries()) { + argsFound = argsFound && _.isEqual(mock.args[callIndex][index], value); + if (!argsFound) { + break; + } + } + if (argsFound) { + callCount++; + } + } + + return callCount; +} + +function assertMockCalledNTimesWithArgs(expectedAllCount, mock, ...args) { + const times = getCallCountWithArgs(mock, ...args); + assert(expectedAllCount === times, + `Mock called with args (${args}) ${times} out of ${expectedAllCount} times.`); +} + +function assertMockCalledOnceWithArgs(mock, ...args) { + assertMockCalledNTimesWithArgs(1, mock, ...args); +} + +// regular assert.throws would not work with async, hence this helper +async function assertThrowsAsync(testPromise, expectedError) { + try { + await testPromise(); + } + catch (err) { + if (err instanceof expectedError) { + return; + } + else { + assert.fail(util.format( + 'Assertion failed: exception of invalid type was thrown: [%s], required type: [%s]', + err.name, + expectedError.name)); + } + + } + assert.fail(util.format('Assertion failed: no exception was thrown')); +} + +module.exports = { + assertMockCalledWithArgs, + assertMockFirstCalledWithArgs, + assertMockCalledNTimesWithArgs, + assertMockCalledOnceWithArgs, + assertThrowsAsync +}; diff --git a/QuoteVerification/QuoteVerificationService/test/package.json b/QuoteVerification/QuoteVerificationService/test/package.json new file mode 100644 index 00000000..fc3c404a --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/package.json @@ -0,0 +1,41 @@ +{ + "name": "quoteverificationservice-test", + "scripts": { + "install": "cmake-js build -d qvl/ -O qvl/cmake-build-release" + }, + "version": "1.0.0", + "license": "SEE LICENSE IN RESPECTIVE FILES", + "main": "bootstrap.js", + "dependencies": { + "bluebird": "^3.7.2", + "fast-url-parser": "^1.1.3", + "https-proxy-agent": "^5.0.1", + "js-yaml": "^4.1.0", + "koa": "^2.13.4", + "koa-bodyparser": "^4.3.0", + "koa-router": "^12.0.0", + "lodash": "^4.17.21", + "log4js": "^6.7.0", + "moment": "^2.29.4", + "node-addon-api": "^5.0.0", + "node-cache": "^5.1.2", + "raw-body": "^2.5.1", + "retry": "^0.13.1", + "safe-buffer": "^5.2.1", + "serialize-error": "^8.1.0", + "uuid-random": "^1.3.2", + "validator": "^13.7.0", + "z-schema": "^5.0.4" + }, + "devDependencies": { + "chai": "^4.3.6", + "cmake-js": "^7.0.0", + "eslint": "^8.21.0", + "mocha": "^10.0.0", + "mocha-junit-reporter": "^2.0.2", + "mocha-multi-reporters": "^1.5.1", + "nyc": "^15.1.0", + "proxyquire": "^2.1.3", + "sinon": "^14.0.0" + } +} diff --git a/QuoteVerification/QuoteVerificationService/test/qvl/qvlTest.js b/QuoteVerification/QuoteVerificationService/test/qvl/qvlTest.js new file mode 100644 index 00000000..57f16942 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/qvl/qvlTest.js @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const qvlStatus = require('../../src/qvl/status'); +const errorSource = require('../../src/qvl/verifyQuoteErrorSource'); +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const assert = require('assert'); +const sinon = require('sinon'); +const _ = require('lodash'); + +class TestContext { + + constructor() { + this.logger = { + error: sinon.stub() + }; + this.wrapper = { + version: sinon.stub() + }; + + this.quote = 'BAACAAAAAAAAAAAAk5pyM/ecTKmUCg2zlX8GB4VC77s22/LQN/6qS0tygL20OfXhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARyIAAImd7xJIU1ZY31DHknZNLDVVCYYTLqauDIAd7UuqwqLG79PizV+K9NF45gedm83AlDVpIJrihAIWwOE55oXm4xzHkm45u1XQk84Ek+zcqFGYVfAjkM79pyJbyHSd1Ssw7kjhpB+5HplZTG2a0zTtCufKpzW3/ePqCKTEPGOQltT6BgDBIQAAFy0s//WImSkoHl35Qs/q2STP7j4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHcG6nlffDFL42gquGa0DiEYBQQIDTVkKVYNKE14O4PObaZqaqON2rjm65Tj2aes4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOrIRU0pvEloEwM1dWvA66dGWArJoIuiH3suEVu8DLxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHolmsoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2Ve5c2F6uAh7/j5zkBVRtNoKw/6YOejtrGzICgIb2EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJIwFrxPZY3k+f/lJ/RLzrhuP1K0oBGnjKy6KJqocz8FyLvF9mP+b/ztOC96T3/3qEJPATnZCxO9i7vUuBWEfxwAABQD5HwAAWwpbCiAgVmVyc2lvbjogVjMKICBTdWJqZWN0OiBDPVVTLCBTVD1DQSwgTD1TYW50YSBDbGFyYSwgTz1JbnRlbCBDb3Jwb3JhdGlvbiwgQ049SW50ZWwgU0dYIFJvb3QgQ0EKICBTaWduYXR1cmUgQWxnb3JpdGhtOiBTSEEyNTZ3aXRoRUNEU0EsIE9JRCA9IDEuMi44NDAuMTAwNDUuNC4zLjIKCiAgS2V5OiAgU3VuIEVDIHB1YmxpYyBrZXksIDI1NiBiaXRzCiAgcHVibGljIHggY29vcmQ6IDU4MDA0NzQyNzc0MDg3NzExMTg5OTM0NDk4NjU3NTA0ODc0MzQwNDcyMTA5OTUyNzQyNDUwNzQ4MjM3NjMxNjg1MjExMTU4MTU5MTcyCiAgcHVibGljIHkgY29vcmQ6IDY1NjQ5NTA4Nzc0Mzg5Nzg5MzM5NjQ3Nzc5NjgyNzIxNzY4MzYyMTU0NzQwMTA2MjI0NjA2NjA1NDEzMzQ1OTkxNTkzOTY1OTQ1OTQzCiAgcGFyYW1ldGVyczogc2VjcDI1NnIxIFtOSVNUIFAtMjU2LCBYOS42MiBwcmltZTI1NnYxXSAoMS4yLjg0MC4xMDA0NS4zLjEuNykKICBWYWxpZGl0eTogW0Zyb206IFRodSBNYXkgMTkgMTA6NDk6MTUgQ0VTVCAyMDIyLAogICAgICAgICAgICAgICBUbzogU2F0IEphbiAwMSAwMDo1OTo1OSBDRVQgMjA1MF0KICBJc3N1ZXI6IEM9VVMsIFNUPUNBLCBMPVNhbnRhIENsYXJhLCBPPUludGVsIENvcnBvcmF0aW9uLCBDTj1JbnRlbCBTR1ggUm9vdCBDQQogIFNlcmlhbE51bWJlcjogWyAgICAzMTQzOTAzYyA3MmFjZWZjOCBjODgwMjc1NiBmYjk5NGRlNCAwODg4YjUxYV0KCkNlcnRpZmljYXRlIEV4dGVuc2lvbnM6IDUKWzFdOiBPYmplY3RJZDogMi41LjI5LjM1IENyaXRpY2FsaXR5PWZhbHNlCkF1dGhvcml0eUtleUlkZW50aWZpZXIgWwpLZXlJZGVudGlmaWVyIFsKMDAwMDogMzEgNDMgOTAgM0MgNzIgQUMgRUYgQzggICBDOCA4MCAyNyA1NiBGQiA5OSA0RCBFNCAgMUMuPHIuLi4uLidWLi5NLgowMDEwOiAwOCA4OCBCNSAxQSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4uCl0KXQoKWzJdOiBPYmplY3RJZDogMi41LjI5LjE5IENyaXRpY2FsaXR5PXRydWUKQmFzaWNDb25zdHJhaW50czpbCiAgQ0E6dHJ1ZQogIFBhdGhMZW46MQpdCgpbM106IE9iamVjdElkOiAyLjUuMjkuMzEgQ3JpdGljYWxpdHk9ZmFsc2UKQ1JMRGlzdHJpYnV0aW9uUG9pbnRzIFsKICBbRGlzdHJpYnV0aW9uUG9pbnQ6CiAgICAgW1VSSU5hbWU6IGh0dHA6Ly9ub24tZXhpc3RpbmctZGVidWctb25seS5pbnRlbC5jb20vSW50ZWxTR1hSb290Q0EuY3JsXQpdXQoKWzRdOiBPYmplY3RJZDogMi41LjI5LjE1IENyaXRpY2FsaXR5PXRydWUKS2V5VXNhZ2UgWwogIEtleV9DZXJ0U2lnbgogIENybF9TaWduCl0KCls1XTogT2JqZWN0SWQ6IDIuNS4yOS4xNCBDcml0aWNhbGl0eT1mYWxzZQpTdWJqZWN0S2V5SWRlbnRpZmllciBbCktleUlkZW50aWZpZXIgWwowMDAwOiAzMSA0MyA5MCAzQyA3MiBBQyBFRiBDOCAgIEM4IDgwIDI3IDU2IEZCIDk5IDREIEU0ICAxQy48ci4uLi4uJ1YuLk0uCjAwMTA6IDA4IDg4IEI1IDFBICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLi4KXQpdCgpdCiAgQWxnb3JpdGhtOiBbU0hBMjU2d2l0aEVDRFNBXQogIFNpZ25hdHVyZToKMDAwMDogMzAgNDUgMDIgMjEgMDAgOUMgOEQgQTYgICBDOCA2NCAwMCAzOCBBRCA4RCBCRCAwOSAgMEUuIS4uLi4uZC44Li4uLgowMDEwOiAzQSA2MyA1NSBERCBDQSA2RiA5RiAxOCAgIDA0IDQyIDVBIDg1IEQzIEY5IDhBIDU4ICA6Y1UuLm8uLi5CWi4uLi5YCjAwMjA6IDkyIEZFIEU0IEY3IDQ4IDAyIDIwIDVBICAgRkUgNUIgNUYgQzQgRDggMkQgMzkgRDAgIC4uLi5ILiBaLltfLi4tOS4KMDAzMDogNDYgQjkgM0EgNEEgRDEgMjAgMzIgQTkgICA0NiBDMyAxQyBCMSA3RSBDNCBBNyA4QiAgRi46Si4gMi5GLi4uLi4uLgowMDQwOiBDNyAwRiAxNiBFQiA2NCA5MiBDRSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4uZC4uCgpdWwpbCiAgVmVyc2lvbjogVjMKICBTdWJqZWN0OiBDPVVTLCBTVD1DQSwgTD1TYW50YSBDbGFyYSwgTz1JbnRlbCBDb3Jwb3JhdGlvbiwgQ049SW50ZWwgU0dYIFBDSyBQcm9jZXNzb3IgQ0EKICBTaWduYXR1cmUgQWxnb3JpdGhtOiBTSEEyNTZ3aXRoRUNEU0EsIE9JRCA9IDEuMi44NDAuMTAwNDUuNC4zLjIKCiAgS2V5OiAgU3VuIEVDIHB1YmxpYyBrZXksIDI1NiBiaXRzCiAgcHVibGljIHggY29vcmQ6IDIwMjUyNjI5NDkxMjM0NzY5MDEwNDEzODM0MzQwMjE1NDgwMjkyODQ4MTU1MjgzMDUwNDczODIxMDY1OTAyODMzMTQ3NzU0NDM3MDUxCiAgcHVibGljIHkgY29vcmQ6IDY2OTQ0OTg3OTk4MTY3NTUzMjMxMzY1OTU5MjYwNjI2NjUyNzEyNjIzMDQ2ODExNzMzMTQwNTI5NDI2ODM5Njc2MTM3MDA1NjEzNjEzCiAgcGFyYW1ldGVyczogc2VjcDI1NnIxIFtOSVNUIFAtMjU2LCBYOS42MiBwcmltZTI1NnYxXSAoMS4yLjg0MC4xMDA0NS4zLjEuNykKICBWYWxpZGl0eTogW0Zyb206IFRodSBNYXkgMTkgMTA6NDk6MTUgQ0VTVCAyMDIyLAogICAgICAgICAgICAgICBUbzogU3VuIE1heSAxOSAxMDo0OToxNSBDRVNUIDIwNTJdCiAgSXNzdWVyOiBDPVVTLCBTVD1DQSwgTD1TYW50YSBDbGFyYSwgTz1JbnRlbCBDb3Jwb3JhdGlvbiwgQ049SW50ZWwgU0dYIFJvb3QgQ0EKICBTZXJpYWxOdW1iZXI6IFsgICAgMjg4OTEwZmIgZTAwNDE4YTAgMzgzZjUyYjUgM2YxZDQ3YzAgMzJlMmIyZjNdCgpDZXJ0aWZpY2F0ZSBFeHRlbnNpb25zOiA1ClsxXTogT2JqZWN0SWQ6IDIuNS4yOS4zNSBDcml0aWNhbGl0eT1mYWxzZQpBdXRob3JpdHlLZXlJZGVudGlmaWVyIFsKS2V5SWRlbnRpZmllciBbCjAwMDA6IDMxIDQzIDkwIDNDIDcyIEFDIEVGIEM4ICAgQzggODAgMjcgNTYgRkIgOTkgNEQgRTQgIDFDLjxyLi4uLi4nVi4uTS4KMDAxMDogMDggODggQjUgMUEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLi4uLgpdCl0KClsyXTogT2JqZWN0SWQ6IDIuNS4yOS4xOSBDcml0aWNhbGl0eT10cnVlCkJhc2ljQ29uc3RyYWludHM6WwogIENBOnRydWUKICBQYXRoTGVuOjAKXQoKWzNdOiBPYmplY3RJZDogMi41LjI5LjMxIENyaXRpY2FsaXR5PWZhbHNlCkNSTERpc3RyaWJ1dGlvblBvaW50cyBbCiAgW0Rpc3RyaWJ1dGlvblBvaW50OgogICAgIFtVUklOYW1lOiBodHRwOi8vbm9uLWV4aXN0aW5nLWRlYnVnLW9ubHkuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybF0KXV0KCls0XTogT2JqZWN0SWQ6IDIuNS4yOS4xNSBDcml0aWNhbGl0eT10cnVlCktleVVzYWdlIFsKICBLZXlfQ2VydFNpZ24KICBDcmxfU2lnbgpdCgpbNV06IE9iamVjdElkOiAyLjUuMjkuMTQgQ3JpdGljYWxpdHk9ZmFsc2UKU3ViamVjdEtleUlkZW50aWZpZXIgWwpLZXlJZGVudGlmaWVyIFsKMDAwMDogMjggODkgMTAgRkIgRTAgMDQgMTggQTAgICAzOCAzRiA1MiBCNSAzRiAxRCA0NyBDMCAgKC4uLi4uLi44P1IuPy5HLgowMDEwOiAzMiBFMiBCMiBGMyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyLi4uCl0KXQoKXQogIEFsZ29yaXRobTogW1NIQTI1NndpdGhFQ0RTQV0KICBTaWduYXR1cmU6CjAwMDA6IDMwIDQ0IDAyIDIwIDQ3IDA4IDJBIEExICAgRjYgQTkgOEUgOUIgNTggNDggMTggOUMgIDBELiBHLiouLi4uLlhILi4KMDAxMDogRjYgNUIgQzkgMTAgNTYgODcgQjcgODYgICA3QyBCMiAwMiAxNiA3NyA1RiAxNiBBRCAgLlsuLlYuLi4uLi4ud18uLgowMDIwOiA2RiBGOSAzQyBFOSAwMiAyMCA3NSA1NiAgIEM0IEIxIDRBIDUzIDJCIDE2IDM0IDBFICBvLjwuLiB1Vi4uSlMrLjQuCjAwMzA6IDNBIDM3IDlCIEQwIDJGIDYwIDk2IDk1ICAgMUEgQkMgMDggQ0EgMjYgOEQgQ0EgQzMgIDo3Li4vYC4uLi4uLiYuLi4KMDA0MDogNkQgQ0YgQTcgRUYgMEIgOEIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbS4uLi4uCgpdWwpbCiAgVmVyc2lvbjogVjMKICBTdWJqZWN0OiBDPVVTLCBTVD1DQSwgTD1TYW50YSBDbGFyYSwgTz1JbnRlbCBDb3Jwb3JhdGlvbiwgQ049SW50ZWwgU0dYIFBDSyBDZXJ0aWZpY2F0ZQogIFNpZ25hdHVyZSBBbGdvcml0aG06IFNIQTI1NndpdGhFQ0RTQSwgT0lEID0gMS4yLjg0MC4xMDA0NS40LjMuMgoKICBLZXk6ICBTdW4gRUMgcHVibGljIGtleSwgMjU2IGJpdHMKICBwdWJsaWMgeCBjb29yZDogMTAwOTM1MjYxNzEwMTIyMDQ3NzExNTE5MTAwNTYxOTcwNjYwNTUxMTA0MDk1NjY4ODYzNjI2NzkxODUxMDI5NTg4MzU0OTYxMzY0MjMyCiAgcHVibGljIHkgY29vcmQ6IDc0MDI0NTcyNzQ2NTA0MjIwNTg4NTEyMzM2OTAwMjU2NDAzMzkxNzQzODc5MzA0MjgyNTI1MTYyMDAzNDg4NjI5ODM1NTAzMTU0ODk5CiAgcGFyYW1ldGVyczogc2VjcDI1NnIxIFtOSVNUIFAtMjU2LCBYOS42MiBwcmltZTI1NnYxXSAoMS4yLjg0MC4xMDA0NS4zLjEuNykKICBWYWxpZGl0eTogW0Zyb206IFRodSBNYXkgMTkgMTA6NDk6MTUgQ0VTVCAyMDIyLAogICAgICAgICAgICAgICBUbzogU3VuIE1heSAxOSAxMDo0OToxNSBDRVNUIDIwNTJdCiAgSXNzdWVyOiBDPVVTLCBTVD1DQSwgTD1TYW50YSBDbGFyYSwgTz1JbnRlbCBDb3Jwb3JhdGlvbiwgQ049SW50ZWwgU0dYIFBDSyBQcm9jZXNzb3IgQ0EKICBTZXJpYWxOdW1iZXI6IFsgICAgYzk3ZDM2ZDEgMzgxMTgwMmIgZDFjNmQ5MDkgNThlYjU1MzIgNDRmMTRmODFdCgpDZXJ0aWZpY2F0ZSBFeHRlbnNpb25zOiA2ClsxXTogT2JqZWN0SWQ6IDEuMi44NDAuMTEzNzQxLjEuMTMuMSBDcml0aWNhbGl0eT1mYWxzZQpFeHRlbnNpb24gdW5rbm93bjogREVSIGVuY29kZWQgT0NURVQgc3RyaW5nID0KMDAwMDogMDQgODIgMDEgQ0UgMzAgODIgMDEgQ0EgICAzMCAxRSAwNiAwQSAyQSA4NiA0OCA4NiAgLi4uLjAuLi4wLi4uKi5ILgowMDEwOiBGOCA0RCAwMSAwRCAwMSAwMSAwNCAxMCAgIDlCIDJCIDgyIDM1IDUwIDU5IDIzIDEzICAuTS4uLi4uLi4rLjVQWSMuCjAwMjA6IEMzIDhEIEE0IEUzIDdGIDFDIDExIDQxICAgMzAgODIgMDEgNkQgMDYgMEEgMkEgODYgIC4uLi4uLi5BMC4ubS4uKi4KMDAzMDogNDggODYgRjggNEQgMDEgMEQgMDEgMDIgICAzMCA4MiAwMSA1RCAzMCAxMCAwNiAwQiAgSC4uTS4uLi4wLi5dMC4uLgowMDQwOiAyQSA4NiA0OCA4NiBGOCA0RCAwMSAwRCAgIDAxIDAyIDAxIDAyIDAxIDE3IDMwIDEwICAqLkguLk0uLi4uLi4uLjAuCjAwNTA6IDA2IDBCIDJBIDg2IDQ4IDg2IEY4IDREICAgMDEgMEQgMDEgMDIgMDIgMDIgMDEgMkQgIC4uKi5ILi5NLi4uLi4uLi0KMDA2MDogMzAgMTAgMDYgMEIgMkEgODYgNDggODYgICBGOCA0RCAwMSAwRCAwMSAwMiAwMyAwMiAgMC4uLiouSC4uTS4uLi4uLgowMDcwOiAwMSAyQyAzMCAxMSAwNiAwQiAyQSA4NiAgIDQ4IDg2IEY4IDREIDAxIDBEIDAxIDAyICAuLDAuLi4qLkguLk0uLi4uCjAwODA6IDA0IDAyIDAyIDAwIEZGIDMwIDExIDA2ICAgMEIgMkEgODYgNDggODYgRjggNEQgMDEgIC4uLi4uMC4uLiouSC4uTS4KMDA5MDogMEQgMDEgMDIgMDUgMDIgMDIgMDAgRjUgICAzMCAxMSAwNiAwQiAyQSA4NiA0OCA4NiAgLi4uLi4uLi4wLi4uKi5ILgowMEEwOiBGOCA0RCAwMSAwRCAwMSAwMiAwNiAwMiAgIDAyIDAwIDg4IDMwIDExIDA2IDBCIDJBICAuTS4uLi4uLi4uLjAuLi4qCjAwQjA6IDg2IDQ4IDg2IEY4IDREIDAxIDBEIDAxICAgMDIgMDcgMDIgMDIgMDAgOTkgMzAgMTAgIC5ILi5NLi4uLi4uLi4uMC4KMDBDMDogMDYgMEIgMkEgODYgNDggODYgRjggNEQgICAwMSAwRCAwMSAwMiAwOCAwMiAwMSAyOSAgLi4qLkguLk0uLi4uLi4uKQowMEQwOiAzMCAxMCAwNiAwQiAyQSA4NiA0OCA4NiAgIEY4IDREIDAxIDBEIDAxIDAyIDA5IDAyICAwLi4uKi5ILi5NLi4uLi4uCjAwRTA6IDAxIDI4IDMwIDEwIDA2IDBCIDJBIDg2ICAgNDggODYgRjggNEQgMDEgMEQgMDEgMDIgIC4oMC4uLiouSC4uTS4uLi4KMDBGMDogMEEgMDIgMDEgMUUgMzAgMTAgMDYgMEIgICAyQSA4NiA0OCA4NiBGOCA0RCAwMSAwRCAgLi4uLjAuLi4qLkguLk0uLgowMTAwOiAwMSAwMiAwQiAwMiAwMSA1RCAzMCAxMSAgIDA2IDBCIDJBIDg2IDQ4IDg2IEY4IDREICAuLi4uLl0wLi4uKi5ILi5NCjAxMTA6IDAxIDBEIDAxIDAyIDBDIDAyIDAyIDAwICAgRjkgMzAgMTAgMDYgMEIgMkEgODYgNDggIC4uLi4uLi4uLjAuLi4qLkgKMDEyMDogODYgRjggNEQgMDEgMEQgMDEgMDIgMEQgICAwMiAwMSA0MiAzMCAxMSAwNiAwQiAyQSAgLi5NLi4uLi4uLkIwLi4uKgowMTMwOiA4NiA0OCA4NiBGOCA0RCAwMSAwRCAwMSAgIDAyIDBFIDAyIDAyIDAwIENGIDMwIDExICAuSC4uTS4uLi4uLi4uLjAuCjAxNDA6IDA2IDBCIDJBIDg2IDQ4IDg2IEY4IDREICAgMDEgMEQgMDEgMDIgMEYgMDIgMDIgMDAgIC4uKi5ILi5NLi4uLi4uLi4KMDE1MDogRUEgMzAgMTEgMDYgMEIgMkEgODYgNDggICA4NiBGOCA0RCAwMSAwRCAwMSAwMiAxMCAgLjAuLi4qLkguLk0uLi4uLgowMTYwOiAwMiAwMiAwMCBEOSAzMCAxMiAwNiAwQiAgIDJBIDg2IDQ4IDg2IEY4IDREIDAxIDBEICAuLi4uMC4uLiouSC4uTS4uCjAxNzA6IDAxIDAyIDExIDAyIDAzIDAwIDlGIEI3ICAgMzAgMUYgMDYgMEIgMkEgODYgNDggODYgIC4uLi4uLi4uMC4uLiouSC4KMDE4MDogRjggNEQgMDEgMEQgMDEgMDIgMTIgMDQgICAxMCAxNyAyRCAyQyBGRiBGNSA4OCA5OSAgLk0uLi4uLi4uLi0sLi4uLgowMTkwOiAyOSAyOCAxRSA1RCBGOSA0MiBDRiBFQSAgIEQ5IDMwIDEwIDA2IDBBIDJBIDg2IDQ4ICApKC5dLkIuLi4wLi4uKi5ICjAxQTA6IDg2IEY4IDREIDAxIDBEIDAxIDAzIDA0ICAgMDIgNzIgRTggMzAgMTQgMDYgMEEgMkEgIC4uTS4uLi4uLnIuMC4uLioKMDFCMDogODYgNDggODYgRjggNEQgMDEgMEQgMDEgICAwNCAwNCAwNiA4QyBCMiBGNyBDMCBCNCAgLkguLk0uLi4uLi4uLi4uLgowMUMwOiA2OSAzMCAwRiAwNiAwQSAyQSA4NiA0OCAgIDg2IEY4IDREIDAxIDBEIDAxIDA1IDBBICBpMC4uLiouSC4uTS4uLi4uCjAxRDA6IDAxIDAwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uCgoKWzJdOiBPYmplY3RJZDogMi41LjI5LjM1IENyaXRpY2FsaXR5PWZhbHNlCkF1dGhvcml0eUtleUlkZW50aWZpZXIgWwpLZXlJZGVudGlmaWVyIFsKMDAwMDogMjggODkgMTAgRkIgRTAgMDQgMTggQTAgICAzOCAzRiA1MiBCNSAzRiAxRCA0NyBDMCAgKC4uLi4uLi44P1IuPy5HLgowMDEwOiAzMiBFMiBCMiBGMyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyLi4uCl0KXQoKWzNdOiBPYmplY3RJZDogMi41LjI5LjE5IENyaXRpY2FsaXR5PXRydWUKQmFzaWNDb25zdHJhaW50czpbCiAgQ0E6ZmFsc2UKICBQYXRoTGVuOiB1bmRlZmluZWQKXQoKWzRdOiBPYmplY3RJZDogMi41LjI5LjMxIENyaXRpY2FsaXR5PWZhbHNlCkNSTERpc3RyaWJ1dGlvblBvaW50cyBbCiAgW0Rpc3RyaWJ1dGlvblBvaW50OgogICAgIFtVUklOYW1lOiBodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50ZWwuY29tL0ludGVsU0dYUENLUHJvY2Vzc29yLmNybF0KXV0KCls1XTogT2JqZWN0SWQ6IDIuNS4yOS4xNSBDcml0aWNhbGl0eT10cnVlCktleVVzYWdlIFsKICBEaWdpdGFsU2lnbmF0dXJlCiAgTm9uX3JlcHVkaWF0aW9uCl0KCls2XTogT2JqZWN0SWQ6IDIuNS4yOS4xNCBDcml0aWNhbGl0eT1mYWxzZQpTdWJqZWN0S2V5SWRlbnRpZmllciBbCktleUlkZW50aWZpZXIgWwowMDAwOiA5NCAwRiAxQiA0QyAwRCAyMyA0NyA4MiAgIEM1IEZFIEYzIDkzIDcyIENCIDE3IDQyICAuLi5MLiNHLi4uLi5yLi5CCjAwMTA6IEFDIEFFIDBFIDY2ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4uLmYKXQpdCgpdCiAgQWxnb3JpdGhtOiBbU0hBMjU2d2l0aEVDRFNBXQogIFNpZ25hdHVyZToKMDAwMDogMzAgNDYgMDIgMjEgMDAgRkIgNDEgREMgICA3NyBEOSA1MiAyNyA5MiAxNiBBMCBBNSAgMEYuIS4uQS53LlInLi4uLgowMDEwOiAzMCA2OCA1MyBEQiAzNSA2QiA5OCBFNyAgIDdGIDU5IENEIEI1IEY4IDdCIDcyIDRFICAwaFMuNWsuLi5ZLi4uLnJOCjAwMjA6IEZDIDQ5IDdGIDkzIEU4IDAyIDIxIDAwICAgRkEgNzYgRUUgNzMgMkYgMkQgQTUgOEUgIC5JLi4uLiEuLnYucy8tLi4KMDAzMDogODcgRTYgQUYgMDggNjYgQjUgQjcgMEYgICAwMCAzNCAxNyBGOSA0QSA1MiA5MyA1NiAgLi4uLmYuLi4uNC4uSlIuVgowMDQwOiAwOSAxOSA3NyA4QiBCMyA3NSA2RSA2QyAgICAgICAgICAgICAgICAgICAgICAgICAgICAuLncuLnVubAoKXQ=='; + this.standardPckCertificate = `-----BEGIN CERTIFICATE----- +MIIEhjCCBCugAwIBAgIVAMl9NtE4EYAr0cbZCVjrVTJE8U+BMAoGCCqGSM49BAMC +MHExIzAhBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQK +DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV +BAgMAkNBMQswCQYDVQQGEwJVUzAgFw0yMjA1MTkwODQ5MTVaGA8yMDUyMDUxOTA4 +NDkxNVowcDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBDZXJ0aWZpY2F0ZTEaMBgG +A1UECgwRSW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQsw +CQYDVQQIDAJDQTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AATfJ1VnkHV9M8yDNH4JkPHv4WMQ85ocJqknmEJm1sHVCKOobGVVWvYAVAx0Qv+P +8oAjVIz0u8sgN1d7Zm2wPrbTo4ICnTCCApkwHwYDVR0jBBgwFoAUKIkQ++AEGKA4 +P1K1Px1HwDLisvMwWAYDVR0fBFEwTzBNoEugSYZHaHR0cHM6Ly9jZXJ0aWZpY2F0 +ZXMudHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFBDS1Byb2Nlc3Nv +ci5jcmwwHQYDVR0OBBYEFJQPG0wNI0eCxf7zk3LLF0Ksrg5mMA4GA1UdDwEB/wQE +AwIGwDAMBgNVHRMBAf8EAjAAMIIB3QYJKoZIhvhNAQ0BBIIBzjCCAcowHgYKKoZI +hvhNAQ0BAQQQmyuCNVBZIxPDjaTjfxwRQTCCAW0GCiqGSIb4TQENAQIwggFdMBAG +CyqGSIb4TQENAQIBAgEXMBAGCyqGSIb4TQENAQICAgEtMBAGCyqGSIb4TQENAQID +AgEsMBEGCyqGSIb4TQENAQIEAgIA/zARBgsqhkiG+E0BDQECBQICAPUwEQYLKoZI +hvhNAQ0BAgYCAgCIMBEGCyqGSIb4TQENAQIHAgIAmTAQBgsqhkiG+E0BDQECCAIB +KTAQBgsqhkiG+E0BDQECCQIBKDAQBgsqhkiG+E0BDQECCgIBHjAQBgsqhkiG+E0B +DQECCwIBXTARBgsqhkiG+E0BDQECDAICAPkwEAYLKoZIhvhNAQ0BAg0CAUIwEQYL +KoZIhvhNAQ0BAg4CAgDPMBEGCyqGSIb4TQENAQIPAgIA6jARBgsqhkiG+E0BDQEC +EAICANkwEgYLKoZIhvhNAQ0BAhECAwCftzAfBgsqhkiG+E0BDQECEgQQFy0s//WI +mSkoHl35Qs/q2TAQBgoqhkiG+E0BDQEDBAJy6DAUBgoqhkiG+E0BDQEEBAaMsvfA +tGkwDwYKKoZIhvhNAQ0BBQoBADAKBggqhkjOPQQDAgNJADBGAiEA+0Hcd9lSJ5IW +oKUwaFPbNWuY539ZzbX4e3JO/El/k+gCIQD6du5zLy2ljofmrwhmtbcPADQX+UpS +k1YJGXeLs3VubA== +-----END CERTIFICATE-----`; + + this.scalablePckCertificate = `-----BEGIN CERTIFICATE----- +MIIFADCCBKWgAwIBAgIUDqW3QZNJGlU7Z64xmJ/Arv7rUk8wCgYIKoZIzj0EAwIw +cDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBQbGF0Zm9ybSBDQTEaMBgGA1UECgwR +SW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQI +DAJDQTELMAkGA1UEBhMCVVMwHhcNMjIwNTEyMDg0MzI2WhcNMjkwNTEyMDg0MzI2 +WjBwMSIwIAYDVQQDDBlJbnRlbCBTR1ggUENLIENlcnRpZmljYXRlMRowGAYDVQQK +DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV +BAgMAkNBMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM1j +Mok/JG5aF5dmEN3yU+UWtHFKjia89b5vvaIZD3oUjcoKTY8sf6NYE1PPJTqPTFHx +dua8vly1pxSBDTVSe9KjggMbMIIDFzAfBgNVHSMEGDAWgBTtuYIDdPNuVuxFlJO5 +vsEacMQ9tDBvBgNVHR8EaDBmMGSgYqBghl5odHRwczovL3ByZTEzLWdyZWVuLXBj +cy5zZ3hucC5hZHNkY3NwLmNvbS9zZ3gvY2VydGlmaWNhdGlvbi92My9wY2tjcmw/ +Y2E9cGxhdGZvcm0mZW5jb2Rpbmc9ZGVyMB0GA1UdDgQWBBTezqyoFXPa8PcSAUDD +lZqttLIyuTAOBgNVHQ8BAf8EBAMCBsAwDAYDVR0TAQH/BAIwADCCAkQGCSqGSIb4 +TQENAQSCAjUwggIxMB4GCiqGSIb4TQENAQEEEGN47uJQUYj5rpm5A7tfzvUwggFu +BgoqhkiG+E0BDQECMIIBXjAQBgsqhkiG+E0BDQECAQIBADARBgsqhkiG+E0BDQEC +AgICANgwEQYLKoZIhvhNAQ0BAgMCAgD7MBEGCyqGSIb4TQENAQIEAgIAwDAQBgsq +hkiG+E0BDQECBQIBADARBgsqhkiG+E0BDQECBgICAPgwEQYLKoZIhvhNAQ0BAgcC +AgDBMBEGCyqGSIb4TQENAQIIAgIA3zARBgsqhkiG+E0BDQECCQICAMUwEAYLKoZI +hvhNAQ0BAgoCAR0wEAYLKoZIhvhNAQ0BAgsCAVswEAYLKoZIhvhNAQ0BAgwCATUw +EQYLKoZIhvhNAQ0BAg0CAgDtMBAGCyqGSIb4TQENAQIOAgEjMBEGCyqGSIb4TQEN +AQIPAgIAujARBgsqhkiG+E0BDQECEAICALQwEQYLKoZIhvhNAQ0BAhECAi6GMB8G +CyqGSIb4TQENAQISBBAA2PvAAPjB38UdWzXtI7q0MBAGCiqGSIb4TQENAQMEAgAA +MBQGCiqGSIb4TQENAQQEBs19ugQAADAPBgoqhkiG+E0BDQEFCgEBMB4GCiqGSIb4 +TQENAQYEEE1IunY4uzprOPjsRxFkF7wwRAYKKoZIhvhNAQ0BBzA2MBAGCyqGSIb4 +TQENAQcBAQH/MBAGCyqGSIb4TQENAQcCAQH/MBAGCyqGSIb4TQENAQcDAQEAMAoG +CCqGSM49BAMCA0kAMEYCIQCCA5avU/QqQ/W28BCUoWsZUpG0Ly+uxzAkQ/3rcYEt +lwIhAKh9jYja0prJNGYfhC4mFv1mbphhgfB75Ni9gWA0uGJs +-----END CERTIFICATE-----`; + + this.scalableWihIntegrityPckCertificate = `-----BEGIN CERTIFICATE----- +MIIE8jCCBJigAwIBAgIUDqW3QZNJGlU7Z64xmJ/Arv7rUk8wCgYIKoZIzj0EAwIw +cDEiMCAGA1UEAwwZSW50ZWwgU0dYIFBDSyBQbGF0Zm9ybSBDQTEaMBgGA1UECgwR +SW50ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQI +DAJDQTELMAkGA1UEBhMCVVMwHhcNMjIwNTEyMDg0MzI3WhcNMjkwNTEyMDg0MzI3 +WjBwMSIwIAYDVQQDDBlJbnRlbCBTR1ggUENLIENlcnRpZmljYXRlMRowGAYDVQQK +DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV +BAgMAkNBMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABM1j +Mok/JG5aF5dmEN3yU+UWtHFKjia89b5vvaIZD3oUjcoKTY8sf6NYE1PPJTqPTFHx +dua8vly1pxSBDTVSe9KjggMOMIIDCjAfBgNVHSMEGDAWgBTtuYIDdPNuVuxFlJO5 +vsEacMQ9tDBiBgNVHR8EWzBZMFegVaBThlFodHRwczovL3ByZTEzLWdyZWVuLXBj +cy5zZ3hucC5hZHNkY3NwLmNvbS9zZ3gvY2VydGlmaWNhdGlvbi92Mi9wY2tjcmw/ +Y2E9cGxhdGZvcm0wHQYDVR0OBBYEFN7OrKgVc9rw9xIBQMOVmq20sjK5MA4GA1Ud +DwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMIICRAYJKoZIhvhNAQ0BBIICNTCCAjEw +HgYKKoZIhvhNAQ0BAQQQY3ju4lBRiPmumbkDu1/O9TCCAW4GCiqGSIb4TQENAQIw +ggFeMBAGCyqGSIb4TQENAQIBAgEAMBEGCyqGSIb4TQENAQICAgIA2DARBgsqhkiG ++E0BDQECAwICAPswEQYLKoZIhvhNAQ0BAgQCAgDAMBAGCyqGSIb4TQENAQIFAgEA +MBEGCyqGSIb4TQENAQIGAgIA+DARBgsqhkiG+E0BDQECBwICAMEwEQYLKoZIhvhN +AQ0BAggCAgDfMBEGCyqGSIb4TQENAQIJAgIAxTAQBgsqhkiG+E0BDQECCgIBHTAQ +BgsqhkiG+E0BDQECCwIBWzAQBgsqhkiG+E0BDQECDAIBNTARBgsqhkiG+E0BDQEC +DQICAO0wEAYLKoZIhvhNAQ0BAg4CASMwEQYLKoZIhvhNAQ0BAg8CAgC6MBEGCyqG +SIb4TQENAQIQAgIAtDARBgsqhkiG+E0BDQECEQICLoYwHwYLKoZIhvhNAQ0BAhIE +EADY+8AA+MHfxR1bNe0jurQwEAYKKoZIhvhNAQ0BAwQCAAAwFAYKKoZIhvhNAQ0B +BAQGzX26BAAAMA8GCiqGSIb4TQENAQUKAQEwHgYKKoZIhvhNAQ0BBgQQTUi6dji7 +Oms4+OxHEWQXvDBEBgoqhkiG+E0BDQEHMDYwEAYLKoZIhvhNAQ0BBwEBAf8wEAYL +KoZIhvhNAQ0BBwIBAf8wEAYLKoZIhvhNAQ0BBwMBAQAwCgYIKoZIzj0EAwIDSAAw +RQIgBme4bAQyOHVIM+4/hMbJegEMiXSLSH9UZ5bloaTP2MECIQCujD8VLLhJ31nj +m0nxa0ExVWWrSBSL2uSM81uKftgmrw== +-----END CERTIFICATE-----`; + + this.tcbSigningCert = `-----BEGIN CERTIFICATE----- +MIICiDCCAi6gAwIBAgIUOGmGYZPE2lFghsw8+siUsai+2yEwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMCAXDTIyMDUxOTA4NDkxNVoYDzIwNTIwNTE5MDg0OTE1WjBsMR4w +HAYDVQQDDBVJbnRlbCBTR1ggVENCIFNpZ25pbmcxGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXYVFKEWN6t8kISYy +Sy0MnWA5uhMmc2/DkPhDjrcXOJBL7J41hU44IccEtMDeLtC6tZS4a5fsf1BsVBnP +lcD0LKOBrzCBrDAfBgNVHSMEGDAWgBQxQ5A8cqzvyMiAJ1b7mU3kCIi1GjBMBgNV +HR8ERTBDMEGgP6A9hjtodHRwOi8vbm9uLWV4aXN0aW5nLWRlYnVnLW9ubHkuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUOGmGYZPE2lFghsw8 ++siUsai+2yEwDgYDVR0PAQH/BAQDAgbAMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0E +AwIDSAAwRQIhANeHlhzC5Lp4EnRSQUQfS2hFbG5P6OM0IsVjRvIIWs78AiA7hXqH +qwb1ASfXtioQB5XXC2O46KRaGiwpvz/oAOD/rg== +-----END CERTIFICATE-----`; + this.intermediateCert = `-----BEGIN CERTIFICATE----- +MIICkjCCAjmgAwIBAgIUKIkQ++AEGKA4P1K1Px1HwDLisvMwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMCAXDTIyMDUxOTA4NDkxNVoYDzIwNTIwNTE5MDg0OTE1WjBxMSMw +IQYDVQQDDBpJbnRlbCBTR1ggUENLIFByb2Nlc3NvciBDQTEaMBgGA1UECgwRSW50 +ZWwgQ29ycG9yYXRpb24xFDASBgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJD +QTELMAkGA1UEBhMCVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQsxpUCw8rk +6HAhIMk+5fda2Qft0yGFazF8oHAd593Zu5QBhTxnrTlNKG6lgBF440/Kom1jrYMc +0ldzKwIZn4Ito4G1MIGyMB8GA1UdIwQYMBaAFDFDkDxyrO/IyIAnVvuZTeQIiLUa +MEwGA1UdHwRFMEMwQaA/oD2GO2h0dHA6Ly9ub24tZXhpc3RpbmctZGVidWctb25s +eS5pbnRlbC5jb20vSW50ZWxTR1hSb290Q0EuY3JsMB0GA1UdDgQWBBQoiRD74AQY +oDg/UrU/HUfAMuKy8zAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIB +ADAKBggqhkjOPQQDAgNHADBEAiBHCCqh9qmOm1hIGJz2W8kQVoe3hnyyAhZ3Xxat +b/k86QIgdVbEsUpTKxY0Djo3m9AvYJaVGrwIyiaNysNtz6fvC4s= +-----END CERTIFICATE-----`; + + this.rootCaCert = `-----BEGIN CERTIFICATE----- +MIICiDCCAi6gAwIBAgIUMUOQPHKs78jIgCdW+5lN5AiItRowCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTIyMDUxOTA4NDkxNVoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgD2FXqq4lPgCVgFx0+Y+YvFt +78HeveFaQsoQ8DIHH0SRJE4hWxIRjBklmkh/GkOzeNIU8UdS1GcwHz5OUBCEV6OB +tTCBsjAfBgNVHSMEGDAWgBQxQ5A8cqzvyMiAJ1b7mU3kCIi1GjBMBgNVHR8ERTBD +MEGgP6A9hjtodHRwOi8vbm9uLWV4aXN0aW5nLWRlYnVnLW9ubHkuaW50ZWwuY29t +L0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUMUOQPHKs78jIgCdW+5lN5AiI +tRowDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYIKoZIzj0E +AwIDSAAwRQIhAJyNpshkADitjb0JOmNV3cpvnxgEQlqF0/mKWJL+5PdIAiBa/ltf +xNgtOdBGuTpK0SAyqUbDHLF+xKeLxw8W62SSzg== +-----END CERTIFICATE-----`; + + this.rootCaCertNotTrusted = `-----BEGIN CERTIFICATE----- +MIICkjCCAjegAwIBAgIUVs38UHvRdwK2UoHC07LcgvA5+3owCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE5MDgyODExMzUxNVoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5OrBrf+Fu4b8cZB0kCHhTBAH +eo/NX9IWlGVfIRLlNEa+k8v5ahmfFlkBbHPN/EKcX5BW5HDidc9dYG8fRWoStaOB +vjCBuzAfBgNVHSMEGDAWgBRWzfxQe9F3ArZSgcLTstyC8Dn7ejBVBgNVHR8ETjBM +MEqgSKBGhkRodHRwczovL2Zha2UtY3JsLWRpc3RyaWJ1dGlvbi1wb2ludC11cmwu +aW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmNybDAdBgNVHQ4EFgQUVs38UHvRdwK2 +UoHC07LcgvA5+3owDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEw +CgYIKoZIzj0EAwIDSQAwRgIhAKz1aFTu1JcXhbVOyxnuqxO84wKeRRc1+1vi+q2a +JsrtAiEA4/5hDa8ri0X4kBaFfe9d0oCqaN8tXA0fuslGoZVhzmU= +-----END CERTIFICATE-----`; + + this.tcbInfo = '{"tcbInfo":{"version":3,"issueDate":"2022-05-19T08:49:16Z","nextUpdate":"2049-10-04T08:49:16Z","fmspc":"8cb2f7c0b469","pceId":"72e8","tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":23},{"svn":45},{"svn":44},{"svn":255},{"svn":245},{"svn":136},{"svn":153},{"svn":41},{"svn":40},{"svn":30},{"svn":93},{"svn":249},{"svn":66},{"svn":207},{"svn":234},{"svn":217}],"pcesvn":40887},"tcbStatus":"UpToDate","tcbDate":"2022-05-19T08:49:16Z","advisoryIDs":["INTEL-SA-38861","INTEL-SA-68515"]}],"id":"SGX","tcbType":0,"tcbEvaluationDataNumber":0},"signature":"db060eb4aa81465703331311547b8396ee7c68d2ace96242da976dea35b1929aaecd993af9f24df1ed0dbd10bbabd764f64580992439cc5bbcf315be0307739b"}'; + this.qeIdentity = '{"enclaveIdentity":{"id":"QE","version":2,"issueDate":"2022-05-19T08:49:16Z","nextUpdate":"2049-10-04T08:49:16Z","tcbEvaluationDataNumber":0,"miscselect":"24CFEE3E","miscselectMask":"FFFFFFFF","attributes":"1dc1ba9e57df0c52f8da0aae19ad0388","attributesMask":"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF","mrsigner":"ceac8454d29bc4968130335756bc0eba746580ac9a08ba21f7b2e115bbc0cbc4","isvprodid":9594,"tcbLevels":[{"tcb":{"isvsvn":51866},"tcbDate":"2022-05-19T08:49:16Z","tcbStatus":"UpToDate"}]},"signature":"de1a7221811d2990d3c6bed38cbf42ca07456f72fc0743f7dd9e27bf51b78d552c9f42cc5147859c39a7e4654cc01d027e1465b32e440757bb57055d3eaef566"}'; + this.tcbSigningCertChain = `${this.rootCaCert}\n${this.tcbSigningCert}`; + this.pckCertIssuerCertChain = `${this.rootCaCert}\n${this.intermediateCert}`; + this.intermediateCrl = '308201293081d1020101300a06082a8648ce3d04030230713123302106035504030c1a496e74656c205347582050434b2050726f636573736f72204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3232303531393038343931355a170d3439313030343038343931355aa02f302d300a0603551d140403020101301f0603551d23041830168014288910fbe00418a0383f52b53f1d47c032e2b2f3300a06082a8648ce3d04030203470030440220619b3d28a4d71d4932da0fa18c66968917f89fa65ebf8ca1281e9ea6f973b87c02202a6c92f5d447f35d765fad76cdfc1da10e106c09488a59bee92d69bad8f1315c'; + this.rootCrl = '308201213081c8020101300a06082a8648ce3d0403023068311a301806035504030c11496e74656c2053475820526f6f74204341311a3018060355040a0c11496e74656c20436f72706f726174696f6e3114301206035504070c0b53616e746120436c617261310b300906035504080c024341310b3009060355040613025553170d3232303531393038343931355a170d3439313030343038343931355aa02f302d300a0603551d140403020101301f0603551d230418301680143143903c72acefc8c8802756fb994de40888b51a300a06082a8648ce3d0403020348003045022100bc0980401119231caca34a76bbfd47e44127aa5f7f6e66bac8144554ef91165902203091aaa5900c90eabc8a617ad03a41b91f8e19bab3258a75198c10a652b1f522'; + } + + async getTarget() { + return require('../../src/qvl'); + } + + async getMock() { + return proxyquire('../../src/qvl', { + '../../native/QuoteVerificationLibraryWrapper.node': this.wrapper, + '../../native/QuoteVerificationLibraryWrapperd.node': this.wrapper, + '../qvl/cmake-build-debug/QuoteVerificationLibraryWrapperd': this.wrapper + }); + } +} + +// Only basic wrapper tests without QVL logic specific tests as it's tested in QVL UTs. +describe('qvlTest', () => { + it('get version - positive', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + const wrapper = await c.getTarget(); + const version = await wrapper.getVersion('test-request-id', c.logger); + const expected = { body: { status: 'OK', version: '1.0.0' } }; + // THEN + assert(_.isEqual(version, expected), + `\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(version)}`); + }); + + it('get version - negative', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + c.wrapper.version.rejects(); + const wrapper = await c.getMock(); + const version = await wrapper.getVersion('test-request-id', c.logger); + const expected = { body: { status: 'FAILED', version: 'NA' } }; + // THEN + assert(_.isEqual(version, expected), + `\nExpected: ${JSON.stringify(expected)}\nActual: ${JSON.stringify(version)}`); + }); + + it('Get Certification Data - positive', async() => { + // GIVEN + const c = new TestContext(); + const quote = Buffer.from(c.quote, 'base64'); + const wrapper = await c.getTarget(); + + // WHEN + const result = await wrapper.getCertificationData('test-request-id', quote); + + // THEN + assert.equal(result.type, 5); + }); + + it('Get Certification Data - negative', async() => { + // GIVEN + const c = new TestContext(); + const quote = Buffer.from('AwACAAAAAAAEAAkAk5pyM/ec', 'base64'); // wrong quote + const wrapper = await c.getTarget(); + + // WHEN + const resultPromise = wrapper.getCertificationData('test-request-id', quote); + + // THEN + await assert.rejects(resultPromise, { + error: 'sgxAttestationGetQECertificationDataSize failed', + status: 37 + }); + }); + + it('Get PCK Certificate Data - positive - Standard', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const result = await wrapper.getPckCertificateData('test-request-id', c.standardPckCertificate); + + // THEN + assert(result.fmspc, '00906EA10000'); + assert(result.sgxType, 'Standard'); + assert(result.dynamicPlatform === undefined); + assert(result.cachedKeys === undefined); + assert(result.smtEnabled === undefined); + }); + + it('Get PCK Certificate Data - positive - Scalable', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const result = await wrapper.getPckCertificateData('test-request-id', c.scalablePckCertificate); + + // THEN + assert(result.fmspc, '00906EA10000'); + assert(result.sgxType, 'Scalable'); + assert(result.dynamicPlatform === true); + assert(result.cachedKeys === true); + assert(result.smtEnabled === false); + }); + + it('Get PCK Certificate Data - positive - ScalableWithIntegrity', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const result = await wrapper.getPckCertificateData('test-request-id', c.scalableWihIntegrityPckCertificate); + + // THEN + assert(result.fmspc, '00906EA10000'); + assert(result.sgxType, 'ScalableWithIntegrity'); + assert(result.dynamicPlatform === true); + assert(result.cachedKeys === true); + assert(result.smtEnabled === false); + }); + + it('Get PCK Certificate Data - negative', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const resultPromise = wrapper.getPckCertificateData('test-request-id', 'wrong cert'); + + // THEN + await assert.rejects(resultPromise, { + error: 'Error getting data from PCK certificate: PEM_read_bio_X509 failed error:0909006C:PEM routines:get_name:no start line' + }); + }); + + it('Get CRL distribution point - positive', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const result = await wrapper.getCrlDistributionPoint('test-request-id', c.standardPckCertificate); + // THEN + assert.equal(result, 'Full Name: URI:https://certificates.trustedservices.intel.com/IntelSGXPCKProcessor.crl'); + }); + + it('Get CRL distribution point - negative', async() => { + // GIVEN + const c = new TestContext(); + const wrapper = await c.getTarget(); + + // WHEN + const resultPromise = wrapper.getCrlDistributionPoint('test-request-id', 'wrong cert'); + + // THEN + await assert.rejects(resultPromise, { + error: 'Error getting CRL distribution point: PEM_read_bio_X509 failed error:0909006C:PEM routines:get_name:no start line' + }); + }); + + it('Verify quote - positive but TCB out of date', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_OK + }); + + }); + + it('Verify quote - wrong pck certificate', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, 'GARBAGE', c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); + + it('Verify quote - wrong tcb info', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, 'GARBAGE', c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_TCB_INFO_UNSUPPORTED_FORMAT, + errorSource: errorSource.VERIFY_TCB_INFO, + error: 'TCB info verification failed' + }); + }); + + it('Verify quote - wrong QE Identity', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, 'GARBAGE', c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_ENCLAVE_IDENTITY_UNSUPPORTED_FORMAT, + errorSource: errorSource.VERIFY_ENCLAVE_IDENTITY, + error: 'Enclave identity verification failed' + }); + }); + + it('Verify quote - wrong pck cert chain', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, 'GARBAGE', c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); + + it('Verify quote - wrong tcb signing chain', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, 'GARBAGE', + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT, + errorSource: errorSource.VERIFY_TCB_INFO, + error: 'TCB info verification failed' + }); + }); + + it('Verify quote - wrong intermediate CRL', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + 'GARBAGE', c.rootCrl, c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_CRL_UNSUPPORTED_FORMAT, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); + + it('Verify quote - wrong root CRL', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, 'GARBAGE', c.rootCaCert, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_CRL_UNSUPPORTED_FORMAT, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); + + it('Verify quote - wrong root CA cert', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, 'GARBAGE', c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_TRUSTED_ROOT_CA_UNSUPPORTED_FORMAT, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); + + it('Verify quote - wrong TCB Signing root CA cert', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, 'GARBAGE'); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_UNSUPPORTED_CERT_FORMAT, + errorSource: errorSource.VERIFY_TCB_INFO, + error: 'TCB info verification failed' + }); + }); + + it('Verify quote - correct PCK Cert chain Root and not correct TCB Signing Chain root', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCert, c.rootCaCertNotTrusted); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_TCB_SIGNING_CERT_CHAIN_UNTRUSTED, + errorSource: errorSource.VERIFY_TCB_INFO, + error: 'TCB info verification failed' + }); + }); + + it('Verify quote - correct TCB Signing chain Root and not correct PCK cert Chain root', async() => { + const c = new TestContext(); + const wrapper = await c.getTarget(); + const quote = Buffer.from(c.quote, 'base64'); + + const result = await wrapper.verifyQuote('test-request-id', quote, c.standardPckCertificate, c.tcbInfo, c.qeIdentity, c.pckCertIssuerCertChain, c.tcbSigningCertChain, + c.intermediateCrl, c.rootCrl, c.rootCaCertNotTrusted, c.rootCaCert); + + assert.deepEqual(result, { + status: qvlStatus.STATUS_SGX_PCK_CERT_CHAIN_UNTRUSTED, + errorSource: errorSource.VERIFY_PCK_CERTIFICATE, + error: 'PCK certificate verification failed' + }); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/routes/KoaRouterMock.js b/QuoteVerification/QuoteVerificationService/test/routes/KoaRouterMock.js new file mode 100644 index 00000000..30773472 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/routes/KoaRouterMock.js @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +module.exports = (use, routes, get, post) => function KoaRouterMock() { + this.use = use; + this.routes = routes; + this.get = get; + this.post = post; +}; diff --git a/QuoteVerification/QuoteVerificationService/test/routes/healthTest.js b/QuoteVerification/QuoteVerificationService/test/routes/healthTest.js new file mode 100644 index 00000000..11e56db1 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/routes/healthTest.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +class TestContext { + constructor() { + this.checkHealth = sinon.stub(); + } + + getTarget() { + return proxyquire('../../src/routes/health', { + '../handlers/health': { + checkHealth: this.checkHealth + } + }); + } +} + +describe('health router', () => { + it('should return router with prefix', async() => { + const c = new TestContext(); + const target = c.getTarget(); + const router = target.createRouter('/test'); + assert.equal(router.stack[1].path, '/test/:component', c.checkHealth); + assert.equal(router.stack[1].stack[0], c.checkHealth); + assert.equal(router.stack[0].path, '/test'); + assert.equal(router.stack[0].stack[0], c.checkHealth); + }); + + it('should return router', async() => { + const c = new TestContext(); + const target = c.getTarget(); + const router = target.createRouter(); + assert.equal(router.stack[1].path, '/:component', c.checkHealth); + assert.equal(router.stack[1].stack[0], c.checkHealth); + assert.equal(router.stack[0].path, '/'); + assert.equal(router.stack[0].stack[0], c.checkHealth); + }); +}); diff --git a/QuoteVerification/QuoteVerificationService/test/routes/routesTest.js b/QuoteVerification/QuoteVerificationService/test/routes/routesTest.js new file mode 100644 index 00000000..24f31be3 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/routes/routesTest.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('chai').assert; + +const createKoaRouterMock = require('./KoaRouterMock'); +const assertMockCalledOnceWithArgs = require('../mocks/helpers').assertMockCalledOnceWithArgs; + + +class TestContext { + constructor() { + this.koaRouter = { + use: sinon.spy(), + routes: sinon.spy() + }; + + this.routes = 'routes'; + this.allowedMethods = 'allowedMethods'; + + this.routerMock = { + createRouter: sinon.stub().returns({ + routes: sinon.stub().returns(this.routes), + allowedMethods: sinon.stub().returns(this.allowedMethods) + }) + }; + } + + async getTarget() { + const self = this; + return proxyquire('../../src/routes', { + 'koa-router': createKoaRouterMock(self.koaRouter.use, self.koaRouter.routes), + './health': this.routerMock, + './v1/verifyAttestationEvidence': this.routerMock + }); + } +} + +describe('routesTest', () => { + + it('should create router', async() => { + // GIVEN + const c = new TestContext(); + + // WHEN + await c.getTarget(); + + // THEN + assert.strictEqual(c.koaRouter.use.callCount, 2); + assertMockCalledOnceWithArgs(c.koaRouter.use, '/health', c.routes, c.allowedMethods); + assertMockCalledOnceWithArgs(c.koaRouter.use, '/attestation/sgx/dcap/v1/report', c.routes, c.allowedMethods); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/routes/verifyAttestationEvidenceTest.js b/QuoteVerification/QuoteVerificationService/test/routes/verifyAttestationEvidenceTest.js new file mode 100644 index 00000000..c61aec44 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/routes/verifyAttestationEvidenceTest.js @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const createKoaRouterMock = require('./KoaRouterMock'); + +const assertMockCalledOnceWithArgs = require('../mocks/helpers').assertMockCalledOnceWithArgs; + +class TestContext { + constructor() { + this.koaRouter = { + use: sinon.spy(), + routes: sinon.spy(), + get: sinon.spy(), + post: sinon.spy() + }; + this.verifyAttestationEvidenceHandler = { + verifyAttestationEvidence: sinon.stub() + }; + } + + async getTarget() { + const self = this; + return proxyquire('../../src/routes/v1/verifyAttestationEvidence', { + '../../handlers/verifyAttestationEvidence': this.verifyAttestationEvidenceHandler, + 'koa-router': + createKoaRouterMock( + self.koaRouter.use, + self.koaRouter.routes, + self.koaRouter.get, + self.koaRouter.post + ) + }); + } +} + +describe('verify attestation evidence routes', () => { + + it('should create router', async() => { + // GIVEN + const c = new TestContext(); + const target = await c.getTarget(); + // WHEN + await target.createRouter(); + // THEN + assertMockCalledOnceWithArgs(c.koaRouter.post, '/', c.verifyAttestationEvidenceHandler.verifyAttestationEvidence); + }); + +}); diff --git a/QuoteVerification/QuoteVerificationService/test/serverTest.js b/QuoteVerification/QuoteVerificationService/test/serverTest.js new file mode 100644 index 00000000..3890c9e4 --- /dev/null +++ b/QuoteVerification/QuoteVerificationService/test/serverTest.js @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +'use strict'; + +const proxyquire = require('proxyquire').noCallThru().noPreserveCache(); +const sinon = require('sinon'); +const assert = require('assert'); + +class TestContext { + constructor() { + this.config = { + logger: { + levelFile: 'trace', + levelConsole: 'off', + fileName: 'qvs.log', + category: 'qvs' + }, + service: { + port: 8799, + certFile: '', + keyFile: '', + caCertDirectories: [''], + tlsServerType: 'TLS', + bodySizeLimits: { + json: '256kb' + } + }, + healthCheck: { extraHttpPort: 8798 } + }; + + this.configLoader = { + getConfig: () => this.config + }; + + this.routes = sinon.stub(); + + this.healthRoutes = { + createRouter: () => { + return { routes: () => { return () => {}; } }; + } + }; + + this.HealthCache = { + run: sinon.stub() + }; + + this.process = { + on: sinon.stub(), + exit: sinon.stub() + }; + + this.version = { + version: 'UNDEFINED' + }; + + + this.init = sinon.stub(); + this.init.prototype.startHTTPServer = sinon.stub(); + this.init.prototype.startHTTPSServer = sinon.stub(); + } + + async getTarget() { + this.logger = proxyquire('../src/logger', { + './config': this.config + }); + return proxyquire('../src/server', { + 'process': this.process, + './configLoader': this.configLoader, + './logger': this.logger, + './healthChecker': this.HealthCache, + '../version': this.version, + './routes': this.routes, + './routes/health': this.healthRoutes, + './koa/init/serverInit': this.init + }); + } +} + +describe('serverTest', () => { + + describe('positive', () => { + it('both https and http server started', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + await c.getTarget(); + // THEN + assert(c.init.prototype.startHTTPServer.notCalled); + assert(c.init.prototype.startHTTPSServer.calledOnce); + assert(c.HealthCache.run.calledOnce); + }); + + it('without http healthcheck, only https', async() => { + // GIVEN + const c = new TestContext(); + // WHEN + c.config.healthCheck = {}; + await c.getTarget(); + // THEN + assert(c.init.prototype.startHTTPSServer.calledOnce); + assert(c.init.prototype.startHTTPServer.notCalled); + assert(c.HealthCache.run.calledOnce); + }); + }); + + describe('negative', () => { + it('no key or cert available', async() => { + // GIVEN + const c = new TestContext(); + c.init.prototype.startHTTPSServer.throws('fatal error'); + // WHEN + await c.getTarget(); + // THEN + assert(c.init.prototype.startHTTPSServer.calledOnce); + assert(c.init.prototype.startHTTPServer.notCalled); + assert(c.HealthCache.run.notCalled); + }); + }); +}); diff --git a/QuoteVerification/QvE/AttestationLibrary/AttestationLibrary.vcxproj b/QuoteVerification/QvE/AttestationLibrary/AttestationLibrary.vcxproj index fa6f021e..7b586d9e 100644 --- a/QuoteVerification/QvE/AttestationLibrary/AttestationLibrary.vcxproj +++ b/QuoteVerification/QvE/AttestationLibrary/AttestationLibrary.vcxproj @@ -144,7 +144,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -166,7 +166,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -188,7 +188,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -209,7 +209,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -231,7 +231,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -254,7 +254,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - NDEBUG;SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS;OPENSSL_NO_FILENAMES + NDEBUG;SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;OPENSSL_NO_FILENAMES true Guard 4101 @@ -282,7 +282,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -298,7 +298,7 @@ - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 diff --git a/QuoteVerification/QvE/AttestationParsers/AttestationParsers.vcxproj b/QuoteVerification/QvE/AttestationParsers/AttestationParsers.vcxproj index 46247743..f3b62565 100644 --- a/QuoteVerification/QvE/AttestationParsers/AttestationParsers.vcxproj +++ b/QuoteVerification/QvE/AttestationParsers/AttestationParsers.vcxproj @@ -144,7 +144,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -166,7 +166,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -188,7 +188,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -209,7 +209,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -231,7 +231,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -254,7 +254,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - NDEBUG;SGX_TRUSTED;_WINDOWS;OPENSSL_NO_FILENAMES + NDEBUG;SGX_TRUSTED;_WINDOWS;OPENSSL_NO_FILENAMES;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -282,7 +282,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Enclave;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -298,7 +298,7 @@ - SGX_TRUSTED;_WINDOWS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 diff --git a/QuoteVerification/QvE/Enclave/qve.cpp b/QuoteVerification/QvE/Enclave/qve.cpp index 407dec81..8a44a18a 100644 --- a/QuoteVerification/QvE/Enclave/qve.cpp +++ b/QuoteVerification/QvE/Enclave/qve.cpp @@ -165,10 +165,11 @@ static quote3_error_t status_error_to_quote3_error(Status status_err) { case STATUS_SGX_ROOT_CA_UNTRUSTED: return SGX_QL_PCK_CERT_CHAIN_ERROR; case STATUS_UNSUPPORTED_TCB_INFO_FORMAT: - case STATUS_TCB_NOT_SUPPORTED: case STATUS_SGX_TCB_INFO_UNSUPPORTED_FORMAT: case STATUS_SGX_TCB_INFO_INVALID: return SGX_QL_TCBINFO_UNSUPPORTED_FORMAT; + case STATUS_TCB_NOT_SUPPORTED: + return SGX_QL_TCB_NOT_SUPPORTED; case STATUS_TCB_INFO_MISMATCH: return SGX_QL_TCBINFO_MISMATCH; case STATUS_SGX_QE_IDENTITY_UNSUPPORTED_FORMAT: diff --git a/QuoteVerification/QvE/Enclave/qve.vcxproj b/QuoteVerification/QvE/Enclave/qve.vcxproj index 774a91f9..ae1399f3 100644 --- a/QuoteVerification/QvE/Enclave/qve.vcxproj +++ b/QuoteVerification/QvE/Enclave/qve.vcxproj @@ -187,7 +187,7 @@ Guard - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) sgx_trts.lib;sgx_tstdc.lib;sgx_tservice.lib;sgx_tcxx.lib;sgx_tcrypto.lib;AttestationParsers.lib;AttestationLibrary.lib;libsgx_tsgxssl.lib;libsgx_tsgxssl_crypto.lib @@ -223,7 +223,7 @@ Guard - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) true /d2FH4- %(AdditionalOptions) @@ -259,7 +259,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QvE\Include;$(ProjectDir)..\..\../QuoteGeneration/pce_wrapper/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\Qve\Enclave;%(AdditionalIncludeDirectories) - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) true @@ -297,7 +297,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\package\include;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QvE\Include;$(ProjectDir)..\..\../QuoteGeneration/pce_wrapper/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(SGXSDKInstallPath)include;$(SGXSDKInstallPath)include\tlibc;$(SGXSDKInstallPath)include\libc++;$(SGXSDKInstallPath)include\epid;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\Qve\Enclave;%(AdditionalIncludeDirectories) - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) true @@ -339,7 +339,7 @@ true true Guard - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) sgx_trts.lib;sgx_tstdc.lib;sgx_tservice.lib;sgx_tcxx.lib;sgx_tcrypto.lib;AttestationParsers.lib;AttestationLibrary.lib;libsgx_tsgxssl.lib;libsgx_tsgxssl_crypto.lib @@ -375,7 +375,7 @@ true true Guard - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) sgx_trts.lib;sgx_tstdc.lib;sgx_tservice.lib;sgx_tcxx.lib;sgx_tcrypto.lib;AttestationParsers.lib;AttestationLibrary.lib;libsgx_tsgxssl.lib;libsgx_tsgxssl_crypto.lib @@ -412,7 +412,7 @@ true true Guard - NDEBUG;ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + NDEBUG;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) /d2FH4- %(AdditionalOptions) false ProgramDatabase @@ -453,7 +453,7 @@ true true Guard - ATTESTATIONLIBRARY_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) + ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;SGX_TRUSTED;_WINDOWS;_WINDLL;%(PreprocessorDefinitions) sgx_trts.lib;sgx_tstdc.lib;sgx_tservice.lib;sgx_tcxx.lib;sgx_tcrypto.lib;AttestationParsers.lib;AttestationLibrary.lib;libsgx_tsgxssl.lib;libsgx_tsgxssl_crypto.lib diff --git a/QuoteVerification/QvE/Makefile b/QuoteVerification/QvE/Makefile index aa97ad88..d89dbe34 100644 --- a/QuoteVerification/QvE/Makefile +++ b/QuoteVerification/QvE/Makefile @@ -105,7 +105,6 @@ $(BUILD_DIR): PREPARE_SGXSSL := ../prepare_sgxssl.sh SGXSSL_HEADER_CHECK := $(SGXSSL_PACKAGE_PATH)/include/openssl/opensslconf.h PREPARE_SGX_SSL: - @chmod 755 $(PREPARE_SGXSSL) @test -f $(SGXSSL_PACKAGE_PATH)/lib64/lib$(SGXSSL_TCRYPTO).a && test -f $(SGXSSL_PACKAGE_PATH)/lib64/lib$(SGXSSL_TLIB).a && test -f $(SGXSSL_HEADER_CHECK) || $(PREPARE_SGXSSL) $(SGXSSL_HEADER_CHECK): PREPARE_SGX_SSL diff --git a/QuoteVerification/dcap_quoteverify/AttestationLibrary_untrusted/AttestationLibrary_untrusted.vcxproj b/QuoteVerification/dcap_quoteverify/AttestationLibrary_untrusted/AttestationLibrary_untrusted.vcxproj index d4fbfd5c..949e6120 100644 --- a/QuoteVerification/dcap_quoteverify/AttestationLibrary_untrusted/AttestationLibrary_untrusted.vcxproj +++ b/QuoteVerification/dcap_quoteverify/AttestationLibrary_untrusted/AttestationLibrary_untrusted.vcxproj @@ -87,7 +87,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) true - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -108,7 +108,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) - _WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + _WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 @@ -131,7 +131,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/src;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(VCToolsInstallDir)\include;$(ProjectDir)..\..\QvE\Include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) true - NDEBUG;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS;OPENSSL_NO_FILENAMES + NDEBUG;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;OPENSSL_NO_FILENAMES true Guard 4101 @@ -160,7 +160,7 @@ - SGX_TRUSTED;_WINDOWS;_ATTESTATIONLIBRARY_EXPORTS + SGX_TRUSTED;_WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC true Guard 4101 diff --git a/QuoteVerification/dcap_quoteverify/AttestationParsers_untrusted/AttestationParsers_untrusted.vcxproj b/QuoteVerification/dcap_quoteverify/AttestationParsers_untrusted/AttestationParsers_untrusted.vcxproj index 3645ffba..5cd4f6ad 100644 --- a/QuoteVerification/dcap_quoteverify/AttestationParsers_untrusted/AttestationParsers_untrusted.vcxproj +++ b/QuoteVerification/dcap_quoteverify/AttestationParsers_untrusted/AttestationParsers_untrusted.vcxproj @@ -85,7 +85,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(VCToolsInstallDir)\include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) true - _WINDOWS; + _WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -106,7 +106,7 @@ Disabled Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(VCToolsInstallDir)\include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) - _WINDOWS; + _WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -128,7 +128,7 @@ Level4 $(ProjectDir)..\..\QVL/Src/;$(ProjectDir)..\..\QVL/Src/ThirdParty/rapidjson/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include;$(ProjectDir)..\..\QVL/Src/AttestationCommons/include/Utils;$(ProjectDir)..\..\QVL/Src/AttestationParsers/include/;$(ProjectDir)..\..\QVL/Src/AttestationParsers/src;$(ProjectDir)..\..\QVL/Src/AttestationLibrary/include/;..\Include;$(ProjectDir)..\..\../QuoteGeneration/quote_wrapper/common/inc;$(ProjectDir)..\..\../QuoteGeneration/common/inc/internal;$(VCToolsInstallDir)\include;$(SolutionDir)prebuilt\openssl\inc;%(AdditionalIncludeDirectories) true - NDEBUG;_WINDOWS;OPENSSL_NO_FILENAMES + NDEBUG;_WINDOWS;OPENSSL_NO_FILENAMES;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 @@ -155,7 +155,7 @@ - _WINDOWS; + _WINDOWS;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC Guard true 4101 diff --git a/QuoteVerification/dcap_quoteverify/inc/sgx_dcap_quoteverify.h b/QuoteVerification/dcap_quoteverify/inc/sgx_dcap_quoteverify.h index 4531ce6c..abd6d1a3 100644 --- a/QuoteVerification/dcap_quoteverify/inc/sgx_dcap_quoteverify.h +++ b/QuoteVerification/dcap_quoteverify/inc/sgx_dcap_quoteverify.h @@ -306,7 +306,23 @@ quote3_error_t tee_verify_quote( sgx_ql_qe_report_info_t *p_qve_report_info, tee_supp_data_descriptor_t *p_supp_data_descriptor); - +/** + * Extrace FMSPC from a given quote + * @param p_quote[IN] - Pointer to a quote buffer. + * @param quote_size[IN] - Size of input quote buffer. + * @param p_fmspc_from_quote[IN/OUT] - Pointer to a buffer to write fmspc to. + * @param fmspc_from_quote_size[IN] - Size of fmspc buffer. + * + * @return Status code of the operation, one of: + * - SGX_QL_SUCCESS + * - SGX_QL_ERROR_INVALID_PARAMETER + * - SGX_QL_ERROR_UNEXPECTED + * - SGX_QL_PCK_CERT_CHAIN_ERROR + * - SGX_QL_QUOTE_CERTIFICATION_DATA_UNSUPPORTED + */ +quote3_error_t tee_get_fmspc_from_quote(const uint8_t* p_quote, uint32_t quote_size, + uint8_t* p_fmspc_from_quote, uint32_t fmspc_from_quote_size); + #if defined(__cplusplus) } #endif diff --git a/QuoteVerification/dcap_quoteverify/inc/tee_qv_class.h b/QuoteVerification/dcap_quoteverify/inc/tee_qv_class.h index 7ee744c0..d64fccbe 100644 --- a/QuoteVerification/dcap_quoteverify/inc/tee_qv_class.h +++ b/QuoteVerification/dcap_quoteverify/inc/tee_qv_class.h @@ -147,7 +147,7 @@ class sgx_qv : public tee_qv_base { class sgx_qv_trusted : public sgx_qv { public: sgx_qv_trusted(sgx_enclave_id_t id) : m_qve_id(id) {}; - ~sgx_qv_trusted() { m_qve_id = 0; }; + virtual ~sgx_qv_trusted() { m_qve_id = 0; }; virtual quote3_error_t tee_verify_evidence( const uint8_t *p_quote, @@ -193,7 +193,7 @@ class tdx_qv : public sgx_qv { class tdx_qv_trusted : public tdx_qv { public: tdx_qv_trusted(sgx_enclave_id_t id) : m_qve_id(id) {}; - ~tdx_qv_trusted() { m_qve_id = 0; }; + virtual ~tdx_qv_trusted() { m_qve_id = 0; }; virtual quote3_error_t tee_verify_evidence( const uint8_t *p_quote, diff --git a/QuoteVerification/dcap_quoteverify/linux/config.cpp b/QuoteVerification/dcap_quoteverify/linux/config.cpp index c1e5c2a0..eb77f600 100644 --- a/QuoteVerification/dcap_quoteverify/linux/config.cpp +++ b/QuoteVerification/dcap_quoteverify/linux/config.cpp @@ -282,7 +282,10 @@ bool sgx_dcap_load_urts() //try to load urts v2 g_urts_handle = dlopen(SGX_URTS_LIB_FILE_NAME_V2, RTLD_LAZY); if (g_urts_handle == NULL) { - fputs(dlerror(), stderr); + char *cerror = dlerror(); + if(cerror != NULL){ + fputs(cerror, stderr); + } SE_TRACE(SE_TRACE_DEBUG, "Couldn't find urts library: %s, %s\n", SGX_URTS_LIB_FILE_NAME, SGX_URTS_LIB_FILE_NAME_V2); break; } diff --git a/QuoteVerification/dcap_quoteverify/linux/sgx_dcap_quoteverify.lds b/QuoteVerification/dcap_quoteverify/linux/sgx_dcap_quoteverify.lds index 9652a931..fbe15d8b 100644 --- a/QuoteVerification/dcap_quoteverify/linux/sgx_dcap_quoteverify.lds +++ b/QuoteVerification/dcap_quoteverify/linux/sgx_dcap_quoteverify.lds @@ -12,6 +12,7 @@ global: tee_qv_free_collateral; tee_get_supplemental_data_version_and_size; tee_verify_quote; + tee_get_fmspc_from_quote; local: *; }; diff --git a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/Cargo.toml b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/Cargo.toml index 6aed5a90..775e3538 100644 --- a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/Cargo.toml +++ b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/Cargo.toml @@ -1,7 +1,9 @@ [package] name = "sgx-dcap-quoteverify-rs" -version = "0.2.0" +version = "0.2.1" edition = "2021" +license = "BSD-3-Clause" +description = "Rust wrapper for SGX DCAP Quote Verification Library" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/src/lib.rs b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/src/lib.rs index e60c6415..c6860204 100644 --- a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/src/lib.rs +++ b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs/src/lib.rs @@ -34,8 +34,6 @@ //! //! This is a safe wrapper for **sgx-dcap-quoteverify-sys**. -use std::ffi::CString; -use std::slice; use sgx_dcap_quoteverify_sys as qvl_sys; pub use qvl_sys::quote3_error_t; @@ -123,14 +121,14 @@ pub fn sgx_qv_get_quote_supplemental_data_size() -> Result /// - **qve_report_info**\ /// This parameter can be used in 2 ways.\ /// - If qve_report_info is NOT None, the API will use Intel QvE to perform quote verification, and QvE will generate a report using the target_info in sgx_ql_qe_report_info_t structure.\ -/// - if qve_report_info is None, the API will use QVL library to perform quote verification, not that the results can not be cryptographically authenticated in this mode. +/// - if qve_report_info is None, the API will use QVL library to perform quote verification, note that the results can not be cryptographically authenticated in this mode. /// - **supplemental_data_size**\ /// Size of the supplemental data (in bytes). /// - **supplemental_data**\ /// The parameter is optional. If it is None, supplemental_data_size must be 0. /// /// # Return -/// Result type of (collateral_expiration_status, verification_result) +/// Result type of (collateral_expiration_status, verification_result). /// /// Status code of the operation, one of: /// - *SGX_QL_ERROR_INVALID_PARAMETER* @@ -225,14 +223,14 @@ pub fn tdx_qv_get_quote_supplemental_data_size() -> Result /// - **qve_report_info**\ /// This parameter can be used in 2 ways.\ /// - If qve_report_info is NOT None, the API will use Intel QvE to perform quote verification, and QvE will generate a report using the target_info in sgx_ql_qe_report_info_t structure.\ -/// - if qve_report_info is None, the API will use QVL library to perform quote verification, not that the results can not be cryptographically authenticated in this mode. +/// - if qve_report_info is None, the API will use QVL library to perform quote verification, note that the results can not be cryptographically authenticated in this mode. /// - **supplemental_data_size**\ /// Size of the supplemental data (in bytes). /// - **supplemental_data**\ /// The parameter is optional. If it is None, supplemental_data_size must be 0. /// /// # Return -/// Result type of (collateral_expiration_status, verification_result) +/// Result type of (collateral_expiration_status, verification_result). /// /// Status code of the operation, one of: /// - *SGX_QL_ERROR_INVALID_PARAMETER* @@ -287,7 +285,7 @@ pub fn tdx_qv_verify_quote( } } -/// Set the full path of QVE and QPL library. +/// Set the full path of QVE and QPL library.\ /// The function takes the enum and the corresponding full path. /// /// # Param @@ -300,11 +298,11 @@ pub fn tdx_qv_verify_quote( /// - ***SGX_QL_SUCCESS***\ /// Successfully set the full path. /// - ***SGX_QL_ERROR_INVALID_PARAMETER***\ -/// path is not a valid full path or the path is too long. +/// Path is not a valid full path or the path is too long. /// #[cfg(target_os = "linux")] pub fn sgx_qv_set_path(path_type: sgx_qv_path_type_t, path: &str) -> quote3_error_t { - match CString::new(path) { + match std::ffi::CString::new(path) { Ok(path) => unsafe { qvl_sys::sgx_qv_set_path(path_type, path.as_ptr()) } _ => quote3_error_t::SGX_QL_ERROR_INVALID_PARAMETER, } @@ -317,7 +315,7 @@ pub fn sgx_qv_set_path(path_type: sgx_qv_path_type_t, path: &str) -> quote3_erro /// SGX/TDX Quote, presented as u8 vector. /// /// # Return -/// Result type of quote_collecteral +/// Result type of quote_collecteral. /// /// - **quote_collateral**\ /// This is the Quote Certification Collateral retrieved based on Quote. @@ -344,7 +342,58 @@ pub fn tee_qv_get_collateral(quote: &[u8]) -> Result, quote3_error_t> { &mut buf_len, ) { quote3_error_t::SGX_QL_SUCCESS => { - let collateral = slice::from_raw_parts(buf, buf_len as usize).to_vec(); + assert!(!buf.is_null()); + assert!(buf_len > 0); + let mut collateral = vec![0u8; buf_len as usize]; + let orig_collateral = &*(buf as *const sgx_ql_qve_collateral_t); + + // copy the original collateral + let mut index = 0usize; + collateral[..std::mem::size_of::()] + .copy_from_slice(std::slice::from_raw_parts(buf, std::mem::size_of::())); + + // copy pck_crl_issuer_chain field + index += std::mem::size_of::(); + collateral[index..(index + orig_collateral.pck_crl_issuer_chain_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.pck_crl_issuer_chain as *const u8, orig_collateral.pck_crl_issuer_chain_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).pck_crl_issuer_chain = &mut collateral[index] as *mut u8 as *mut i8; + + // copy root_ca_crl field + index += orig_collateral.pck_crl_issuer_chain_size as usize; + collateral[index..(index + orig_collateral.root_ca_crl_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.root_ca_crl as *const u8, orig_collateral.root_ca_crl_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).root_ca_crl = &mut collateral[index] as *mut u8 as *mut i8; + + // copy pck_crl field + index += orig_collateral.root_ca_crl_size as usize; + collateral[index..(index + orig_collateral.pck_crl_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.pck_crl as *const u8, orig_collateral.pck_crl_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).pck_crl = &mut collateral[index] as *mut u8 as *mut i8; + + // copy tcb_info_issuer_chain field + index += orig_collateral.pck_crl_size as usize; + collateral[index..(index + orig_collateral.tcb_info_issuer_chain_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.tcb_info_issuer_chain as *const u8, orig_collateral.tcb_info_issuer_chain_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).tcb_info_issuer_chain = &mut collateral[index] as *mut u8 as *mut i8; + + // copy tcb_info field + index += orig_collateral.tcb_info_issuer_chain_size as usize; + collateral[index..(index + orig_collateral.tcb_info_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.tcb_info as *const u8, orig_collateral.tcb_info_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).tcb_info = &mut collateral[index] as *mut u8 as *mut i8; + + // copy qe_identity_issuer_chain field + index += orig_collateral.tcb_info_size as usize; + collateral[index..(index + orig_collateral.qe_identity_issuer_chain_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.qe_identity_issuer_chain as *const u8, orig_collateral.qe_identity_issuer_chain_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).qe_identity_issuer_chain = &mut collateral[index] as *mut u8 as *mut i8; + + // copy qe_identity field + index += orig_collateral.qe_identity_issuer_chain_size as usize; + collateral[index..(index + orig_collateral.qe_identity_size as usize)] + .copy_from_slice(std::slice::from_raw_parts(orig_collateral.qe_identity as *const u8, orig_collateral.qe_identity_size as usize)); + (*(collateral.as_mut_ptr() as *mut sgx_ql_qve_collateral_t)).qe_identity = &mut collateral[index] as *mut u8 as *mut i8; + match qvl_sys::tee_qv_free_collateral(buf) { quote3_error_t::SGX_QL_SUCCESS => Ok(collateral), error_code => Err(error_code), @@ -355,14 +404,14 @@ pub fn tee_qv_get_collateral(quote: &[u8]) -> Result, quote3_error_t> { } } -/// Get supplemental data latest version and required size, support both SGX and TDX +/// Get supplemental data latest version and required size, support both SGX and TDX. /// /// # Param /// - **quote**\ /// SGX/TDX Quote, presented as u8 vector. /// /// # Return -/// Result type of (version, data_size) tuple +/// Result type of (version, data_size) tuple. /// /// - **version**\ /// Latest version of the supplemental data. @@ -388,8 +437,8 @@ pub fn tee_get_supplemental_data_version_and_size( } } -/// Perform quote verification for SGX and TDX -/// This API works the same as the old one, but takes a new parameter to describe the supplemental data (p_supp_data_descriptor) +/// Perform quote verification for SGX and TDX.\ +/// This API works the same as the old one, but takes a new parameter to describe the supplemental data (supp_data_descriptor). /// /// # Param /// - **quote**\ @@ -401,17 +450,17 @@ pub fn tee_get_supplemental_data_version_and_size( /// - **qve_report_info**\ /// This parameter can be used in 2 ways.\ /// - If qve_report_info is NOT None, the API will use Intel QvE to perform quote verification, and QvE will generate a report using the target_info in sgx_ql_qe_report_info_t structure.\ -/// - if qve_report_info is None, the API will use QVL library to perform quote verification, not that the results can not be cryptographically authenticated in this mode. +/// - if qve_report_info is None, the API will use QVL library to perform quote verification, note that the results can not be cryptographically authenticated in this mode. /// - **supp_datal_descriptor**\ -/// *tee_supp_data_descriptor_t* structure. -/// You can specify the major version of supplemental data by setting supp_datal_descriptor.major_version -/// If supp_datal_descriptor is None, no supplemental data is returned. -/// If supp_datal_descriptor.major_version == 0, then return the latest version of the *sgx_ql_qv_supplemental_t* structure. -/// If supp_datal_descriptor <= latest supported version, return the latest minor version associated with that major version. -/// If supp_datal_descriptor > latest supported version, return an error *SGX_QL_SUPPLEMENTAL_DATA_VERSION_NOT_SUPPORTED*. +/// *tee_supp_data_descriptor_t* structure.\ +/// You can specify the major version of supplemental data by setting supp_datal_descriptor.major_version.\ +/// If supp_datal_descriptor is None, no supplemental data is returned.\ +/// If supp_datal_descriptor.major_version == 0, then return the latest version of the *sgx_ql_qv_supplemental_t* structure.\ +/// If supp_datal_descriptor.major_version <= latest supported version, return the latest minor version associated with that major version.\ +/// If supp_datal_descriptor.major_version > latest supported version, return an error *SGX_QL_SUPPLEMENTAL_DATA_VERSION_NOT_SUPPORTED*. /// /// # Return -/// Result type of (collateral_expiration_status, verification_result) +/// Result type of (collateral_expiration_status, verification_result). /// /// Status code of the operation, one of: /// - *SGX_QL_ERROR_INVALID_PARAMETER* diff --git a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-sys/Cargo.toml b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-sys/Cargo.toml index befc08b3..df8286d3 100644 --- a/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-sys/Cargo.toml +++ b/QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-sys/Cargo.toml @@ -3,6 +3,8 @@ name = "sgx-dcap-quoteverify-sys" version = "0.2.0" edition = "2021" links = "sgx_dcap_quoteverify" +license = "BSD-3-Clause" +description = "Rust raw FFI bindings for SGX DCAP Quote Verification Library" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/QuoteVerification/dcap_quoteverify/sgx_dcap_quoteverify.cpp b/QuoteVerification/dcap_quoteverify/sgx_dcap_quoteverify.cpp index f8662ec5..117e947e 100644 --- a/QuoteVerification/dcap_quoteverify/sgx_dcap_quoteverify.cpp +++ b/QuoteVerification/dcap_quoteverify/sgx_dcap_quoteverify.cpp @@ -204,22 +204,11 @@ static sgx_status_t load_qve(sgx_enclave_id_t *p_qve_eid, return SGX_ERROR_FEATURE_NOT_SUPPORTED; } - int rc = se_mutex_lock(&g_qve_status.m_qve_mutex); - if (rc != 1) - { - SE_TRACE(SE_TRACE_ERROR, "Failed to lock mutex\n"); - return SGX_ERROR_UNEXPECTED; // SGX_QvE_INTERFACE_UNAVAILABLE; - } - // Load the QvE if (g_qve_status.m_qve_eid == 0) { if (!get_qve_path(qve_enclave_path, MAX_PATH)) { - rc = se_mutex_unlock(&g_qve_status.m_qve_mutex); - if (rc != 1) - { - SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex\n"); - } + return SGX_ERROR_UNEXPECTED; //SGX_QvE_INTERFACE_UNAVAILABLE; } do @@ -246,12 +235,6 @@ static sgx_status_t load_qve(sgx_enclave_id_t *p_qve_eid, } while (SGX_ERROR_ENCLAVE_LOST == sgx_status && enclave_lost_retry_time--); if (sgx_status != SGX_SUCCESS) { - rc = se_mutex_unlock(&g_qve_status.m_qve_mutex); - if (rc != 1) - { - SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex\n"); - return SGX_ERROR_UNEXPECTED; - } if (sgx_status == SGX_ERROR_OUT_OF_EPC) return SGX_ERROR_OUT_OF_EPC; else @@ -264,12 +247,7 @@ static sgx_status_t load_qve(sgx_enclave_id_t *p_qve_eid, *p_qve_eid = g_qve_status.m_qve_eid; memcpy_s(p_qve_attributes, sizeof(sgx_misc_attribute_t), &g_qve_status.m_qve_attributes, sizeof(sgx_misc_attribute_t)); } - rc = se_mutex_unlock(&g_qve_status.m_qve_mutex); - if (rc != 1) - { - SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex\n"); - return SGX_ERROR_UNEXPECTED; - } + return SGX_SUCCESS; } @@ -363,8 +341,16 @@ static quote3_error_t get_verification_supplemental_data_size_and_version( quote3_error_t qve_ret = SGX_QL_ERROR_INVALID_PARAMETER; tee_qv_base *p_trusted_qv = NULL; tee_qv_base *p_untrusted_qv = NULL; + int mutex_locked = 0; do { + mutex_locked = se_mutex_lock(&g_qve_status.m_qve_mutex); + if (mutex_locked == 0) + { + SE_TRACE(SE_TRACE_ERROR, "Failed to lock mutex\n"); + return SGX_QL_ERROR_UNEXPECTED; + } + //create and initialize QvE // load_ret = initialize_enclave(&qve_eid); @@ -379,13 +365,7 @@ static quote3_error_t get_verification_supplemental_data_size_and_version( } if (load_ret != SGX_SUCCESS) { - if (load_ret == SGX_ERROR_FEATURE_NOT_SUPPORTED) { - qve_ret = SGX_QL_PSW_NOT_AVAILABLE; - } - else { - SE_TRACE(SE_TRACE_DEBUG, "Warning: failed to load QvE.\n"); - qve_ret = SGX_QL_ENCLAVE_LOAD_ERROR; - } + SE_TRACE(SE_TRACE_DEBUG, "Warning: failed to load QvE.\n"); break; } @@ -458,6 +438,13 @@ static quote3_error_t get_verification_supplemental_data_size_and_version( delete p_trusted_qv; delete p_untrusted_qv; + if (mutex_locked != 0) { + mutex_locked = se_mutex_unlock(&g_qve_status.m_qve_mutex); + if (mutex_locked == 0) { + SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex\n"); + } + } + return qve_ret; } @@ -471,7 +458,8 @@ quote3_error_t tee_get_supplemental_data_version_and_size( uint32_t *p_version, uint32_t *p_data_size) { - if (p_quote == NULL || quote_size == 0 || + if (CHECK_MANDATORY_PARAMS(p_quote, quote_size) || + quote_size < QUOTE_MIN_SIZE || (p_version == NULL && p_data_size == NULL)) return SGX_QL_ERROR_INVALID_PARAMETER; @@ -508,6 +496,7 @@ quote3_error_t tee_verify_evidence( //validate input parameters // if (CHECK_MANDATORY_PARAMS(p_quote, quote_size) || + quote_size < QUOTE_MIN_SIZE || NULL_POINTER(p_collateral_expiration_status) || expiration_check_date == 0 || NULL_POINTER(p_quote_verification_result) || @@ -557,9 +546,17 @@ quote3_error_t tee_verify_evidence( unsigned char ca_from_quote[CA_SIZE] = { 0 }; struct _sgx_ql_qve_collateral_t* qve_collaterals_from_qp = NULL; tee_qv_base *p_tee_qv = NULL; + int mutex_locked = 0; do { if (p_qve_report_info) { + mutex_locked = se_mutex_lock(&g_qve_status.m_qve_mutex); + if (mutex_locked == 0) + { + SE_TRACE(SE_TRACE_ERROR, "Failed to lock mutex\n"); + return SGX_QL_ERROR_UNEXPECTED; + } + //try to load QvE for trusted quote verification // load_ret = initialize_enclave(&qve_eid); @@ -672,6 +669,13 @@ quote3_error_t tee_verify_evidence( if (p_tee_qv) delete p_tee_qv; + if (mutex_locked != 0) { + mutex_locked = se_mutex_unlock(&g_qve_status.m_qve_mutex); + if (mutex_locked == 0) { + SE_TRACE(SE_TRACE_ERROR, "Failed to unlock mutex\n"); + } + } + return qve_ret; } @@ -832,7 +836,11 @@ quote3_error_t tee_qv_get_collateral( quote3_error_t ret = SGX_QL_SUCCESS; unsigned char fmspc_from_quote[FMSPC_SIZE] = {0}; unsigned char ca_from_quote[CA_SIZE] = {0}; - if (p_quote == NULL || quote_size < QUOTE_MIN_SIZE || pp_quote_collateral == NULL || *pp_quote_collateral != NULL || p_collateral_size == NULL) + if (CHECK_MANDATORY_PARAMS(p_quote, quote_size) || + quote_size < QUOTE_MIN_SIZE || + pp_quote_collateral == NULL || + *pp_quote_collateral != NULL || + p_collateral_size == NULL) return SGX_QL_ERROR_INVALID_PARAMETER; // skip version and att_key_type in SGX or TDX quote uint32_t quote_type = *((uint32_t *)(p_quote + sizeof(uint16_t) + sizeof(uint16_t))); @@ -962,7 +970,8 @@ quote3_error_t tee_verify_quote( uint8_t *p_supp_data = NULL; // only check quote, other parameters will be checked in internal functions - if (p_quote == NULL || quote_size == 0) + if (CHECK_MANDATORY_PARAMS(p_quote, quote_size) || + quote_size < QUOTE_MIN_SIZE) return SGX_QL_ERROR_INVALID_PARAMETER; ret = tee_get_supplemental_data_version_and_size(p_quote, quote_size, &latest_version.version, &tmp_size); @@ -1016,7 +1025,7 @@ quote3_error_t tee_verify_quote( supp_data_size, p_supp_data); - if (ret != SGX_QL_SUCCESS && p_supp_data_descriptor->p_data != NULL) { + if (ret != SGX_QL_SUCCESS && p_supp_data_descriptor != NULL && p_supp_data_descriptor->p_data != NULL) { // defense in depth memset(p_supp_data_descriptor->p_data, 0, sizeof(sgx_ql_qve_collateral_t)); } @@ -1024,13 +1033,47 @@ quote3_error_t tee_verify_quote( return ret; } +/** + * @brief Extrace FMSPC from a given quote with cert type 5 + * @param p_quote[IN] - Pointer to a quote buffer. + * @param quote_size[IN] - Size of input quote buffer. + * @param p_fmspc_from_quote[IN/OUT] - Pointer to a buffer to write fmspc to. + * @param fmspc_from_quote_size[IN] - Size of fmspc buffer. + * + * @return Status code of the operation, one of: + * - SGX_QL_SUCCESS + * - SGX_QL_ERROR_INVALID_PARAMETER + * - SGX_QL_ERROR_UNEXPECTED + * - SGX_QL_PCK_CERT_CHAIN_ERROR + * - SGX_QL_QUOTE_CERTIFICATION_DATA_UNSUPPORTED + */ +quote3_error_t tee_get_fmspc_from_quote(const uint8_t *p_quote, + uint32_t quote_size, + uint8_t *p_fmspc_from_quote, + uint32_t fmspc_from_quote_size) { + if (CHECK_MANDATORY_PARAMS(p_quote, quote_size) || + quote_size < QUOTE_MIN_SIZE || + p_fmspc_from_quote == NULL || + fmspc_from_quote_size < FMSPC_SIZE) { + return SGX_QL_ERROR_INVALID_PARAMETER; + } + + unsigned char ca_from_quote[CA_SIZE] = {0}; + return qvl_get_fmspc_ca_from_quote( + p_quote, + quote_size, + p_fmspc_from_quote, + FMSPC_SIZE, + ca_from_quote, + CA_SIZE); +} + #ifndef _MSC_VER #include #include - /** * This API can be used to set the full path of QVE and QPL library. * @@ -1049,15 +1092,19 @@ quote3_error_t sgx_qv_set_path( { quote3_error_t ret = SGX_QL_SUCCESS; bool temp_ret = false; + struct stat info; - if (!p_path) + if (!p_path){ return(SGX_QL_ERROR_INVALID_PARAMETER); + } - if(stat(p_path, &info) != 0) + if(stat(p_path, &info) != 0){ return(SGX_QL_ERROR_INVALID_PARAMETER); - else if((info.st_mode & S_IFREG) == 0) + } + else if((info.st_mode & S_IFREG) == 0){ return(SGX_QL_ERROR_INVALID_PARAMETER); + } switch(path_type) { diff --git a/QuoteVerification/dcap_quoteverify/win/dcap_quoteverify.vcxproj b/QuoteVerification/dcap_quoteverify/win/dcap_quoteverify.vcxproj index bc6a4a0b..d38417cb 100644 --- a/QuoteVerification/dcap_quoteverify/win/dcap_quoteverify.vcxproj +++ b/QuoteVerification/dcap_quoteverify/win/dcap_quoteverify.vcxproj @@ -100,7 +100,7 @@ Level4 Disabled true - DEBUG_MODE=1;_DEBUG;QVL_EXPORTS;_USRDLL;_WINDOWS;_DCAP_QUOTEVERIFY_LIB;ATTESTATIONLIBRARY_STATIC;%(PreprocessorDefinitions) + DEBUG_MODE=1;_DEBUG;QVL_EXPORTS;_USRDLL;_WINDOWS;_DCAP_QUOTEVERIFY_LIB;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true @@ -132,7 +132,7 @@ Level4 Disabled true - DEBUG_MODE=1;WIN32;_DEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + DEBUG_MODE=1;WIN32;_DEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true @@ -163,7 +163,7 @@ true true true - WIN32;NDEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + WIN32;NDEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true @@ -195,7 +195,7 @@ true true true - NDEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;_DCAP_QUOTEVERIFY_LIB;ATTESTATIONLIBRARY_STATIC;%(PreprocessorDefinitions) + NDEBUG;QVL_EXPORTS;_WINDOWS;_USRDLL;_DCAP_QUOTEVERIFY_LIB;ATTESTATIONLIBRARY_STATIC;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true @@ -276,4 +276,4 @@ - + \ No newline at end of file diff --git a/QuoteVerification/dcap_quoteverify/win/sgx_dcap_quoteverify.def b/QuoteVerification/dcap_quoteverify/win/sgx_dcap_quoteverify.def index f87da73f..6d47d419 100644 --- a/QuoteVerification/dcap_quoteverify/win/sgx_dcap_quoteverify.def +++ b/QuoteVerification/dcap_quoteverify/win/sgx_dcap_quoteverify.def @@ -9,4 +9,5 @@ EXPORTS tee_qv_get_collateral @6 tee_qv_free_collateral @7 tee_get_supplemental_data_version_and_size @8 - tee_verify_quote @9 \ No newline at end of file + tee_verify_quote @9 + tee_get_fmspc_from_quote @10 diff --git a/QuoteVerification/prepare_sgxssl.cmd b/QuoteVerification/prepare_sgxssl.cmd index 2f3c15fb..0498c79c 100644 --- a/QuoteVerification/prepare_sgxssl.cmd +++ b/QuoteVerification/prepare_sgxssl.cmd @@ -39,17 +39,17 @@ set top_dir=%~dp0 set sgxssl_dir=%top_dir%\sgxssl set openssl_out_dir=%sgxssl_dir%\openssl_source -set openssl_ver_name=openssl-1.1.1q +set openssl_ver_name=openssl-1.1.1t set sgxssl_github_archive=https://github.com/intel/intel-sgx-ssl/archive -set sgxssl_ver_name=win_2.17_1.1.1q +set sgxssl_ver_name=win_2.18_1.1.1t set sgxssl_ver=%sgxssl_ver_name% set build_script=%sgxssl_dir%\Windows\build_package.cmd set server_url_path=https://www.openssl.org/source/ set full_openssl_url=%server_url_path%/%openssl_ver_name%.tar.gz -set sgxssl_chksum=6BCC533BD89FFC4E0A5E7FB7B4FB8D8CC2BFF6B421B9D7EF1F0156B99BABF1D9 -set openssl_chksum=D7939CE614029CDFF0B6C20F0E2E5703158A489A72B2507B8BD51BF8C8FD10CA +set sgxssl_chksum=665ADEC6329A8EAE40E9F45AF2EDBF4FB2F2BB5CED172813CF6A237F894AAEED +set openssl_chksum=8DEE9B24BDB1DCBF0C3D1E9B02FB8F6BF22165E807F45ADEB7C9677536859D3B if not exist %sgxssl_dir% ( diff --git a/QuoteVerification/prepare_sgxssl.sh b/QuoteVerification/prepare_sgxssl.sh index 00a2c7ec..60ff2b1e 100755 --- a/QuoteVerification/prepare_sgxssl.sh +++ b/QuoteVerification/prepare_sgxssl.sh @@ -34,16 +34,16 @@ ARG1=${1:-build} top_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" sgxssl_dir=$top_dir/sgxssl openssl_out_dir=$sgxssl_dir/openssl_source -openssl_ver_name=openssl-1.1.1q +openssl_ver_name=openssl-1.1.1t sgxssl_github_archive=https://github.com/intel/intel-sgx-ssl/archive -sgxssl_file_name=lin_2.18_1.1.1q +sgxssl_file_name=lin_2.19_1.1.1t build_script=$sgxssl_dir/Linux/build_openssl.sh server_url_path=https://www.openssl.org/source/ full_openssl_url=$server_url_path/$openssl_ver_name.tar.gz full_openssl_url_old=$server_url_path/old/1.1.1/$openssl_ver_name.tar.gz -sgxssl_chksum=6c33d2178b6b01bdbb1f97804ae14aec13544b0cb45902a0906c20ef7b4032bc -openssl_chksum=d7939ce614029cdff0b6c20f0e2e5703158a489a72b2507b8bd51bf8c8fd10ca +sgxssl_chksum=bff5a9059911846e27447acb402c4690346abf46da8e1c26b66d406e8abb1588 +openssl_chksum=8dee9b24bdb1dcbf0c3d1e9b02fb8f6bf22165e807f45adeb7c9677536859d3b rm -f check_sum_sgxssl.txt check_sum_openssl.txt if [ ! -f $build_script ]; then wget $sgxssl_github_archive/$sgxssl_file_name.zip -P $sgxssl_dir/ || exit 1 diff --git a/SGX_DCAP.sln b/SGX_DCAP.sln index 5bfc67b9..25535a07 100644 --- a/SGX_DCAP.sln +++ b/SGX_DCAP.sln @@ -71,6 +71,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tdqe", "QuoteGeneration\quote_wrapper\tdx_quote\enclave\win\tdqe.vcxproj", "{FA74E16C-93BA-4722-8B06-434F57C0E6D0}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tdx_logic", "QuoteGeneration\quote_wrapper\tdx_quote\win\tdx_logic.vcxproj", "{DF76228C-AA18-41D2-ABF6-E340AE2B6591}" + ProjectSection(ProjectDependencies) = postProject + {7A7054A9-BBEC-45A8-A1E5-0F0998CB3A9C} = {7A7054A9-BBEC-45A8-A1E5-0F0998CB3A9C} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/SampleCode/JavaQuoteVerificationSample/Makefile b/SampleCode/JavaQuoteVerificationSample/Makefile index c823974b..6af49969 100644 --- a/SampleCode/JavaQuoteVerificationSample/Makefile +++ b/SampleCode/JavaQuoteVerificationSample/Makefile @@ -59,6 +59,7 @@ jni_dcap_quoteverify_lib := libjni_dcap_quoteverify.so QvL_BUILD_PATH := ../../QuoteVerification/dcap_quoteverify/linux +DISTDIR := src/main/c/lib JNI_C_SRCS := src/main/c/jni/sgx_dcap_quoteverify_wrap.c JNI_C_SRCS += src/main/c/jni/jni_native_lib_init.c JNI_C_OBJS := $(JNI_C_SRCS:.c=.o) @@ -69,7 +70,8 @@ CFLAGS += $(INCLUDE_DIR) CFLAGS += -fvisibility=hidden lib_jni_dcap_quoteverify: $(JNI_C_OBJS) - $(CC) -shared -Wl,-soname=$(jni_dcap_quoteverify_lib) -o src/main/c/lib/$(jni_dcap_quoteverify_lib).$(JNI_WRAPPER_VERSION) $^ + test -d "$(DISTDIR)" || mkdir "$(DISTDIR)" + $(CC) -shared -Wl,-soname=$(jni_dcap_quoteverify_lib) -o $(DISTDIR)/$(jni_dcap_quoteverify_lib).$(JNI_WRAPPER_VERSION) $^ ldconfig -n src/main/c/lib %.o : %.c diff --git a/SampleCode/JavaQuoteVerificationSample/README.md b/SampleCode/JavaQuoteVerificationSample/README.md index a9cadd86..194dc18e 100644 --- a/SampleCode/JavaQuoteVerificationSample/README.md +++ b/SampleCode/JavaQuoteVerificationSample/README.md @@ -20,6 +20,8 @@ Prerequisite: *Please refer to SGX DCAP Linux installation guide "https://download.01.org/intel-sgx/sgx-dcap/#version#/linux/docs/Intel_SGX_DCAP_Linux_SW_Installation_Guide.pdf" to install above dependencies*
*Note that you need to change **\#version\#** to actual version number in URL, such as 1.4.* +*Please make sure the config file of qcnl is configured properly + 1. Generate an ECDSA quote with certification data of type 5 using QuoteGenerationSample ``` $ cd SampleCode/QuoteGenerationSample/ diff --git a/SampleCode/JavaQuoteVerificationSample/pom.xml b/SampleCode/JavaQuoteVerificationSample/pom.xml index fe31c364..49fa13c1 100644 --- a/SampleCode/JavaQuoteVerificationSample/pom.xml +++ b/SampleCode/JavaQuoteVerificationSample/pom.xml @@ -5,7 +5,7 @@ com.intel.sgx JavaQuoteVerificationSample - 1.0 + 1.0.1 JavaQuoteVerificationSample https://github.com/intel/SGXDataCenterAttestationPrimitives @@ -20,7 +20,7 @@ junit junit - 4.11 + 4.13.1 test @@ -157,4 +157,4 @@ - \ No newline at end of file + diff --git a/SampleCode/JavaQuoteVerificationSample/src/main/java/com/intel/sgx/Demo.java b/SampleCode/JavaQuoteVerificationSample/src/main/java/com/intel/sgx/Demo.java index a855b16e..3a4712f3 100644 --- a/SampleCode/JavaQuoteVerificationSample/src/main/java/com/intel/sgx/Demo.java +++ b/SampleCode/JavaQuoteVerificationSample/src/main/java/com/intel/sgx/Demo.java @@ -65,10 +65,11 @@ public static void main(String[] args) throws IOException { } catch (ArgumentParserException e) { parser.handleError(e); } + System.out.println("Do quote verfication:\n"); SgxDcapVerifyQuoteJNI Verifer = new SgxDcapVerifyQuoteJNI(); SgxDcapQuoteVerifyResult result = Verifer.sgxQvVerifyQuote(quoteBytes); - System.out.println("verify result is " + result.getVerifyResult()); + System.out.println("Quote verify result is " + result.getVerifyResult()); if (result.getSupplement() != null) { byte[] cpu_svn = result.getSupplement().getTcb_cpusvn().getSvn(); for (int i = 0; i < cpu_svn.length; i++) { @@ -76,6 +77,7 @@ public static void main(String[] args) throws IOException { } } + System.out.println("Get quote collateral:\n"); Collateral collat = Verifer.teeQvGetCollateral(quoteBytes); System.out.print(collat.toString()); TeeDcapQuoteVerifyResult teeReult = Verifer.tee_verify_quote(quoteBytes, (short) 3, 0); diff --git a/SampleCode/QuoteGenerationSample/App/App.cpp b/SampleCode/QuoteGenerationSample/App/App.cpp index f8b1d8e8..9589f086 100644 --- a/SampleCode/QuoteGenerationSample/App/App.cpp +++ b/SampleCode/QuoteGenerationSample/App/App.cpp @@ -35,6 +35,7 @@ * Description: Sample application to * demonstrate the usage of quote generation. */ + #if defined(_MSC_VER) #define _CRT_SECURE_NO_WARNINGS #endif @@ -45,6 +46,10 @@ #include #endif +#include +#include + +#include #include "sgx_urts.h" #include "sgx_report.h" #include "sgx_dcap_ql_wrapper.h" @@ -61,6 +66,8 @@ #define ENCLAVE_PATH "enclave.signed.so" #endif +using namespace std; + bool create_app_enclave_report(sgx_target_info_t qe_target_info, sgx_report_t *app_report) { bool ret = true; @@ -97,10 +104,38 @@ bool create_app_enclave_report(sgx_target_info_t qe_target_info, sgx_report_t *a sgx_destroy_enclave(eid); return ret; } + + +vector readBinaryContent(const string& filePath) +{ + ifstream file(filePath, ios::binary); + if (!file.is_open()) + { + printf("Error: Unable to open file %s\n", filePath.c_str()); + return {}; + } + + file.seekg(0, ios_base::end); + streampos fileSize = file.tellg(); + + file.seekg(0, ios_base::beg); + vector retVal(fileSize); + file.read(reinterpret_cast(retVal.data()), fileSize); + file.close(); + return retVal; +} + +void usage() +{ + printf("\nOption:\n"); + printf("\tTargetInfo \"\n"); + printf("\t\tUse target_info in the file instead of generating it by `sgx_qe_get_target_info` fucntion.\n\n"); +} + + int main(int argc, char* argv[]) { - (void)(argc); - (void)(argv); + int ret = 0; quote3_error_t qe3_ret = SGX_QL_SUCCESS; @@ -114,6 +149,13 @@ int main(int argc, char* argv[]) sgx_ql_certification_data_t *p_cert_data; FILE *fptr = NULL; bool is_out_of_proc = false; + + //Just for sample use, better to change solid command line args solution in production env + if (argc != 1 && argc != 3) { + usage(); + return 0; + } + char *out_of_proc = getenv(SGX_AESM_ADDR); if(out_of_proc) is_out_of_proc = true; @@ -168,10 +210,29 @@ int main(int argc, char* argv[]) qe3_ret = sgx_qe_get_target_info(&qe_target_info); if (SGX_QL_SUCCESS != qe3_ret) { printf("Error in sgx_qe_get_target_info. 0x%04x\n", qe3_ret); - ret = -1; + ret = -1; goto CLEANUP; } printf("succeed!"); + + if (argv[1] && !strcmp(argv[1], "TargetInfo")) { + printf("\nRead target_info:"); + vector target_info = readBinaryContent(argv[2]); + if (target_info.empty()) { + usage(); + ret = -1; + goto CLEANUP; + } + printf(" path: %s:", argv[2]); + if (sizeof(qe_target_info) != target_info.size()) { + printf("Error: Invalid target info file."); + ret = -1; + goto CLEANUP; + } + memcpy(&qe_target_info, target_info.data(), sizeof(qe_target_info)); + } + printf("succeed!"); + printf("\nStep2: Call create_app_report:"); if(true != create_app_enclave_report(qe_target_info, &app_report)) { printf("\nCall to create_app_report() failed\n"); @@ -180,6 +241,17 @@ int main(int argc, char* argv[]) } printf("succeed!"); + +#if _WIN32 + fopen_s(&fptr, "report.dat", "wb"); +#else + fptr = fopen("report.dat","wb"); +#endif + if( fptr ) { + fwrite(&app_report, sizeof(app_report), 1, fptr); + fclose(fptr); + } + printf("\nStep3: Call sgx_qe_get_quote_size:"); qe3_ret = sgx_qe_get_quote_size("e_size); if (SGX_QL_SUCCESS != qe3_ret) { diff --git a/SampleCode/QuoteGenerationSample/Makefile b/SampleCode/QuoteGenerationSample/Makefile index 6b15633b..b8060d20 100644 --- a/SampleCode/QuoteGenerationSample/Makefile +++ b/SampleCode/QuoteGenerationSample/Makefile @@ -35,7 +35,6 @@ SGX_DEBUG ?= 0 SGX_SDK ?= /opt/intel/sgxsdk SGX_MODE ?= HW SGX_ARCH ?= x64 -OUT_OF_PROC ?= 0 SGX_COMMON_CFLAGS := -m64 @@ -53,7 +52,6 @@ endif DCAP_Library_Name := sgx_dcap_ql Urts_Library_Name := sgx_urts -Quote_ex_Library_Name := sgx_quote_ex App_Cpp_Files := App/App.cpp App_Include_Paths := -I$(SGX_SDK)/include -I../../QuoteGeneration/quote_wrapper/common/inc @@ -72,9 +70,6 @@ endif App_Cpp_Flags := $(App_C_Flags) -std=c++11 App_Link_Flags := $(SGX_COMMON_CFLAGS) -l$(DCAP_Library_Name) -l$(Urts_Library_Name) -ldl -lpthread -ifeq ($(OUT_OF_PROC),1) - App_Link_Flags += -l$(Quote_ex_Library_Name) -endif App_Cpp_Objects := $(App_Cpp_Files:.cpp=.o) diff --git a/SampleCode/QuoteGenerationSample/README.md b/SampleCode/QuoteGenerationSample/README.md index c459be3a..9b20723b 100644 --- a/SampleCode/QuoteGenerationSample/README.md +++ b/SampleCode/QuoteGenerationSample/README.md @@ -49,7 +49,7 @@ Prepare Enclave test key(two options): 1. Install openssl first, then the project will generate a test key `Enclave_private_sample.pem` automatically when you build the project. 2. Rename your test key(3072-bit RSA private key) to `Enclave_private_sample.pem` and put it under the `Enclave` folder. -"in-porc" Release build: +Release build: ``` $ make ``` @@ -61,16 +61,6 @@ Run application in "in-proc" mode: ``` $ ./app ``` -"out-of-proc" Release build: -``` -$ make OUT_OF_PROC=1 -``` -Or Debug build: -``` -$ make SGX_DEBUG=1 OUT_OF_PROC=1 -``` -**Note**: you need to install `libsgx-quote-ex-dev` or `libsgx-quote-ex-devel` package. - Run application in "out-of-proc" mode: ``` $ SGX_AESM_ADDR=1 ./app @@ -79,7 +69,6 @@ $ SGX_AESM_ADDR=1 ./app # Windows ## Supported operating systems: -* Windows* Server 2016 (Long-Term Servicing Channel) * Windows* Server 2019 (Long-Term Servicing Channel) ## Requirements: diff --git a/SampleCode/QuoteVerificationSample/App/App.vcxproj b/SampleCode/QuoteVerificationSample/App/App.vcxproj index 6359cd91..d1711f3a 100644 --- a/SampleCode/QuoteVerificationSample/App/App.vcxproj +++ b/SampleCode/QuoteVerificationSample/App/App.vcxproj @@ -50,7 +50,7 @@
true - sgx_dcap_quoteverify.lib;sgx_urts.lib;sgx_uae_service.lib;%(AdditionalDependencies) + sgx_dcap_quoteverify.lib;sgx_urts.lib;%(AdditionalDependencies) $(SGXSDKInstallPath)bin\$(Platform)\Debug;$(SGXDCAPComponentsPath)lib\native\Libraries;%(AdditionalLibraryDirectories) true libcmtd.lib @@ -72,7 +72,7 @@ true true true - sgx_dcap_quoteverify.lib;sgx_urts.lib;sgx_uae_service.lib;%(AdditionalDependencies) + sgx_dcap_quoteverify.lib;sgx_urts.lib;%(AdditionalDependencies) $(SGXSDKInstallPath)bin\$(Platform)\Release;$(SGXDCAPComponentsPath)lib\native\Libraries;%(AdditionalLibraryDirectories) true libcmt.lib diff --git a/SampleCode/RustQuoteVerificationSample/Cargo.toml b/SampleCode/RustQuoteVerificationSample/Cargo.toml index d1d5bfd0..66798315 100644 --- a/SampleCode/RustQuoteVerificationSample/Cargo.toml +++ b/SampleCode/RustQuoteVerificationSample/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] clap = { version = "4.0", features = ["derive"] } -sgx-dcap-quoteverify-rs = { version = "0.2.0", path = "../../QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs"} +sgx-dcap-quoteverify-rs = { version = "0.2.1", path = "../../QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-rs"} sgx-dcap-quoteverify-sys = { version = "0.2.0", path = "../../QuoteVerification/dcap_quoteverify/sgx-dcap-quoteverify-sys"} [features] diff --git a/SampleCode/RustQuoteVerificationSample/README.md b/SampleCode/RustQuoteVerificationSample/README.md index 32d4f1a0..015f027a 100644 --- a/SampleCode/RustQuoteVerificationSample/README.md +++ b/SampleCode/RustQuoteVerificationSample/README.md @@ -23,8 +23,7 @@ Prerequisite: * Intel(R) SGX DCAP Packages * Intel(R) SGX DCAP PCCS (Provisioning Certificate Caching Service) -*Please refer to SGX DCAP Linux installation guide "https://download.01.org/intel-sgx/sgx-dcap/#version#/linux/docs/Intel_SGX_DCAP_Linux_SW_Installation_Guide.pdf" to install above dependencies*
-*Note that you need to change **\#version\#** to actual version number in URL, such as 1.4.*
+*Please refer to SGX DCAP Linux installation guide "https://download.01.org/intel-sgx/latest/linux-latest/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf" to install above dependencies*
*Note that you need to install **libsgx-dcap-quote-verify-dev** for this package.* 1. Generate an ECDSA quote with certification data of type 5 using *QuoteGenerationSample* diff --git a/SampleCode/RustQuoteVerificationSample/src/main.rs b/SampleCode/RustQuoteVerificationSample/src/main.rs index a7f92b2f..81f4e61e 100644 --- a/SampleCode/RustQuoteVerificationSample/src/main.rs +++ b/SampleCode/RustQuoteVerificationSample/src/main.rs @@ -186,6 +186,23 @@ fn ecdsa_quote_verification(quote: &[u8], use_qve: bool) { Err(e) => println!("\tError: tee_get_quote_supplemental_data_size failed: {:#04x}", e as u32), } + // get collateral + let collateral = match tee_qv_get_collateral(quote) { + Ok(c) => { + println!("\tInfo: tee_qv_get_collateral successfully returned."); + Some(c) + } + Err(e) => { + println!("\tError: tee_qv_get_collateral failed: {:#04x}", e as u32); + None + } + }; + + let p_collateral: Option<&[u8]> = None; + // uncomment the next 2 lines, if you want to use the collateral provided by the caller in the verification + // let collateral = collateral.unwrap(); + // let p_collateral = Some(&collateral[..]); + // set current time. This is only for sample purposes, in production mode a trusted time should be used. // let current_time = SystemTime::now() @@ -205,7 +222,7 @@ fn ecdsa_quote_verification(quote: &[u8], use_qve: bool) { // if '&qve_report_info' is NULL, this API will call 'untrusted quote verify lib' to verify quote, this mode doesn't rely on SGX capable system, but the results can not be cryptographically authenticated match tee_verify_quote( quote, - None, + p_collateral, current_time, Some(&mut qve_report_info), p_supplemental_data, @@ -287,6 +304,23 @@ fn ecdsa_quote_verification(quote: &[u8], use_qve: bool) { Err(e) => println!("\tError: tee_get_quote_supplemental_data_size failed: {:#04x}", e as u32), } + // get collateral + let collateral = match tee_qv_get_collateral(quote) { + Ok(c) => { + println!("\tInfo: tee_qv_get_collateral successfully returned."); + Some(c) + } + Err(e) => { + println!("\tError: tee_qv_get_collateral failed: {:#04x}", e as u32); + None + } + }; + + let p_collateral: Option<&[u8]> = None; + // uncomment the next 2 lines, if you want to use the collateral provided by the caller in the verification + // let collateral = collateral.unwrap(); + // let p_collateral = Some(&collateral[..]); + // set current time. This is only for sample purposes, in production mode a trusted time should be used. // let current_time = SystemTime::now() @@ -306,7 +340,7 @@ fn ecdsa_quote_verification(quote: &[u8], use_qve: bool) { // if '&qve_report_info' is NULL, this API will call 'untrusted quote verify lib' to verify quote, this mode doesn't rely on SGX capable system, but the results can not be cryptographically authenticated match tee_verify_quote( quote, - None, + p_collateral, current_time, None, p_supplemental_data, diff --git a/SampleCode/RustSGXQuoteGenerationSample/.gitignore b/SampleCode/RustSGXQuoteGenerationSample/.gitignore new file mode 100644 index 00000000..0ec9f5bc --- /dev/null +++ b/SampleCode/RustSGXQuoteGenerationSample/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*.dat diff --git a/SampleCode/RustSGXQuoteGenerationSample/Cargo.toml b/SampleCode/RustSGXQuoteGenerationSample/Cargo.toml new file mode 100644 index 00000000..20e3e235 --- /dev/null +++ b/SampleCode/RustSGXQuoteGenerationSample/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "app" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sgx-dcap-ql-rs = { path = "../../QuoteGeneration/quote_wrapper/sgx-dcap-ql-rs"} +structopt = "0.3" diff --git a/SampleCode/RustSGXQuoteGenerationSample/README.md b/SampleCode/RustSGXQuoteGenerationSample/README.md new file mode 100644 index 00000000..53813950 --- /dev/null +++ b/SampleCode/RustSGXQuoteGenerationSample/README.md @@ -0,0 +1,73 @@ +Intel(R) Software Guard Extensions Data Center Attestation Primitives (Intel(R) SGX DCAP) Rust SGX Quote Generation SampleCode +================================================ +# Linux +## Supported operating systems: +* Ubuntu\* 18.04 LTS Desktop 64bits +* Ubuntu\* 18.04 LTS Server 64bits +* Ubuntu\* 20.04 LTS Desktop 64bits +* Ubuntu\* 20.04 LTS Server 64bits +* Ubuntu\* 22.04 LTS Server 64bits +* Red Hat Enterprise Linux Server release 8.6 64bits +* CentOS Stream 8 64bits +* CentOS 8.3 64bits +* SUSE Linux Enterprise Server 15.4 64bits +* Anolis OS 8.6 64bits +* Debian 10 64bits +## Requirements: +* make +* gcc +* g++ +* bash shell +* clang +* Rust and Cargo +## Prerequisite: +* Linux 5.11 and above, or Intel(R) SGX DCAP Driver +* Intel(R) SGX SDK +* Intel(R) SGX DCAP Development Packages + +`libsgx-enclave-common-dev`, `libsgx-dcap-ql-dev` and `libsgx-dcap-default-qpl-dev`. Or `libsgx-enclave-common-devel`, `libsgx-dcap-ql-devel` and `libsgx-dcap-default-qpl-devel` +* Intel(R) SGX DCAP PCCS (Provisioning Certificate Caching Service) +* If you want to use "out-of-process" quote generation, you need to install `libsgx-quote-ex-dev` or `libsgx-quote-ex-devel` package in Intel(R) SGX PSW Packages + +*Please refer to [SGX DCAP Linux installation guide](https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf) to install above dependencies* + +## Apps with "in-process" quote +If your app uses Intel(R) SGX AESM service for "out-of-process" quote generation (quote generated in AESM process using Intel(R) signed PCE and QE), then the AESM installer will do the configuration described here and you can ignore this section. + +If your app is doing so-called "in-process" quote generation, i.e., it loads provisioning/quoting enclaves by itself including Intel(R) signed PCE, QE, then the app needs to be run with an uid in `sgx_prv` group. + +Use below command to add the user running the process to `sgx_prv` group, then run app again: +``` +$ sudo usermod -a -G sgx_prv +``` +Note that you need to open another terminal to make above command take effect. + +Details please refer to driver [README](https://github.com/intel/SGXDataCenterAttestationPrimitives/tree/master/driver/linux#launching-an-enclave-with-provision-bit-set). + +*Note:* Without proper access, the app will fail on loading the provisioning enclaves with error. e.g. `SGX_ERROR_SERVICE_INVALID_PRIVILEGE(0x4004)` from enclave loader. + +## Build and run RustSGXQuoteGenerationSample to generate SGX ECDSA quote +An enclave is required to create a report that will be converted to quote. In this sample, we use [QuoteGenerationSample](../QuoteGenerationSample) to create the report. Users can use a 3rd party Rust SGX SDK to develop an enclave and create the report. + +Build and run *RustTDQuoteGenerationSample* to "in-process" generate a TD quote + +``` +$ cargo build +$ ./target/debug/app target-info +$ pushd ../QuoteGenerationSample +$ ./app TargetInfo ../RustSGXQuoteGenerationSample/target_info.dat +$ popd +$ ./target/debug/app quote ../QuoteGenerationSample/report.dat +``` + +Build and run *RustTDQuoteGenerationSample* to "out-of-process" generate a TD quote + +``` +$ cargo build +$ export SGX_AESM_ADDR=1 +$ ./target/debug/app target-info +$ pushd ../QuoteGenerationSample +$ ./app TargetInfo ../RustSGXQuoteGenerationSample/target_info.dat +$ popd +$ ./target/debug/app quote ../QuoteGenerationSample/report.dat +``` \ No newline at end of file diff --git a/SampleCode/RustSGXQuoteGenerationSample/src/main.rs b/SampleCode/RustSGXQuoteGenerationSample/src/main.rs new file mode 100644 index 00000000..de5bbffb --- /dev/null +++ b/SampleCode/RustSGXQuoteGenerationSample/src/main.rs @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2011-2022 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +use std::io::{self, BufReader, Read}; +use std::fs::{self, File}; +use std::slice; +use structopt::StructOpt; +use sgx_dcap_ql_rs; + +#[derive(StructOpt)] +/// demostrate the usage of sgx_dcap_ql_rs Crate +enum Sample { + /// write QE target_info that used for an enclave to generate report + TargetInfo { + /// The path to the file to write target_info + #[structopt(parse(from_os_str), default_value = "target_info.dat")] + target_info: std::path::PathBuf, + }, + /// read app enclave report and cervert it to quote + Quote { + /// The path to the file to read app enclave report + #[structopt(parse(from_os_str), default_value = "report.dat")] + report: std::path::PathBuf, + }, +} + +fn read_struct(path: std::path::PathBuf) -> io::Result { + let struct_size = ::std::mem::size_of::(); + let mut reader = BufReader::new(File::open(path).expect("Unable to open report file.")); + unsafe { + let mut r = core::mem::MaybeUninit::::uninit(); + let buffer = slice::from_raw_parts_mut(r.as_mut_ptr() as *mut u8, struct_size); + reader.read_exact(buffer).expect("Unable to read report file."); + Ok(r.assume_init()) + } +} + +fn get_target_info(path: std::path::PathBuf) { + let mut target_info: sgx_dcap_ql_rs::sgx_target_info_t = Default::default(); + let result = sgx_dcap_ql_rs::sgx_qe_get_target_info(&mut target_info); + if result != sgx_dcap_ql_rs::quote3_error_t::SGX_QL_SUCCESS { + println!("Failed to get the target_info."); + return; + } + println!("Successfully get the target_info."); + + unsafe { + fs::write(path, + ::std::slice::from_raw_parts( + &target_info as *const sgx_dcap_ql_rs::sgx_target_info_t as *const u8, + ::std::mem::size_of::())) + .expect("Unable to write target_info file."); + } + println!("Successfully write the target_info."); +} + +fn get_quote(path: std::path::PathBuf) { + let sgx_report = read_struct::(path) + .expect("Unable to read report file."); + println!("Successfully read the report."); + match std::env::var("SGX_AESM_ADDR") { + Ok(_) => (), + _ => { + println!("Need to call sgx_qe_get_target_info first in out-of-proc mode."); + let mut target_info: sgx_dcap_ql_rs::sgx_target_info_t = Default::default(); + sgx_dcap_ql_rs::sgx_qe_get_target_info(&mut target_info); + }, + } + let (result, quote) = sgx_dcap_ql_rs::sgx_qe_get_quote(&sgx_report); + if result != sgx_dcap_ql_rs::quote3_error_t::SGX_QL_SUCCESS { + println!("Failed to get the quote. Error code: {:?}", result); + return; + } + match quote { + Some(q) => { + println!("quote data: {:?}", q); + println!("Successfully get the SGX Quote."); + fs::write("quote.dat", q).expect("Unable to write quote file."); + }, + None => { + return; + }, + } +} + +fn main() { + match Sample::from_args() { + Sample::TargetInfo{target_info} => { + get_target_info(target_info); + }, + Sample::Quote{report} => { + get_quote(report); + }, + } +} diff --git a/driver/win/PLE/Makefile b/driver/win/PLE/Makefile new file mode 100644 index 00000000..488300db --- /dev/null +++ b/driver/win/PLE/Makefile @@ -0,0 +1,166 @@ +# +# Copyright (C) 2011-2021 Intel Corporation. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# + +# This file is provided under a dual BSD/GPLv2 license. When using or +# redistributing this file, you may do so under either license. +# +# GPL LICENSE SUMMARY +# +# Copyright(c) 2016-2018 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of version 2 of the GNU General Public License as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# Contact Information: +# Jarkko Sakkinen +# Intel Finland Oy - BIC 0357606-4 - Westendinkatu 7, 02160 Espoo +# +# BSD LICENSE +# +# Copyright(c) 2016-2018 Intel Corporation. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +.DEFAULT_GOAL := sign + + +TARGET_DIR = build + +ifeq ($(INTEL_SIGNED),1) + SIGN_EXTRA := intel_signed + TARGET_DIR = build-intel_signed +endif + +TARGET := $(shell realpath $(TARGET_DIR)) + +CC = gcc +LD = ld +CFLAGS = -Wall -Werror -static -nostdlib -nostartfiles -fPIE -fno-stack-protector -mrdrnd +LDFLAGS = -m elf_x86_64 -z max-page-size=0x200000 +INCLUDES = -I./include + +ifneq ($(SIG_FILE),) + CSS_SIG_FILE = $(shell realpath $(SIG_FILE)) +endif + +ifneq ($(PUBKEY_FILE),) + CSS_PUBKEY_FILE = $(shell realpath $(PUBKEY_FILE)) +endif + +VERBOSE := @ +ifeq ($(V),1) + VERBOSE := +endif + +SGX_LE_SIGNING_KEY_PATH := sgx_signing_key.pem +SGX_LE_PUBLIC_KEY_PATH := sgx_public_key.pem +SGX_LE_SIGNING_MATERIAL := signing_material.bin + +SIGNING_KEY_PATH := $(shell realpath $(SGX_LE_SIGNING_KEY_PATH)) +PUBLIC_KEY_PATH := $(shell realpath $(SGX_LE_PUBLIC_KEY_PATH)) +SIGNING_MATERIAL := $(shell realpath $(SGX_LE_SIGNING_MATERIAL)) + +$(SIGNING_KEY_PATH): + $(VERBOSE) openssl genrsa -3 -out $(SIGNING_KEY_PATH) 3072 + +$(PUBLIC_KEY_PATH): $(SIGNING_KEY_PATH) + $(VERBOSE) openssl rsa -in $(SIGNING_KEY_PATH) -outform PEM -pubout -out $(PUBLIC_KEY_PATH) + +SGX_LE_C_OBJS := $(addprefix $(TARGET)/,main.o string.o cmac.o) +SGX_LE_S_OBJS := $(addprefix $(TARGET)/,encl_bootstrap.o) + +$(TARGET): + $(VERBOSE) mkdir $@ + +$(SGX_LE_C_OBJS): $(TARGET)/%.o: %.c | $(TARGET) + $(VERBOSE) $(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@ + +$(SGX_LE_S_OBJS): $(TARGET)/%.o: %.S | $(TARGET) + $(VERBOSE) $(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@ + +$(TARGET)/sgx_le.elf: sgx_le.lds $(SGX_LE_C_OBJS) $(SGX_LE_S_OBJS) + $(VERBOSE) $(LD) $(LDFLAGS) -T $^ -o $@ + +$(TARGET)/sgx_le.bin: $(TARGET)/sgx_le.elf + $(VERBOSE) objcopy --remove-section=.got.plt -O binary $< $@ + +$(TARGET)/sgxsign: sgxsign.c | $(TARGET) + $(VERBOSE) $(CC) -Wall $(INCLUDES) -o $@ $< -lcrypto + +$(TARGET)/bin2c: bin2c.c | $(TARGET) + $(VERBOSE) $(CC) -Wall $(INCLUDES) -o $@ $< + +sign: $(SIGNING_KEY_PATH) $(TARGET)/sgx_le.bin $(TARGET)/sgxsign $(TARGET)/bin2c + $(VERBOSE) $(TARGET)/sgxsign sign $(SIGNING_KEY_PATH) $(TARGET)/sgx_le.bin $(TARGET)/sgx_le.ss $(SIGN_EXTRA) + $(VERBOSE) $(TARGET)/bin2c $(TARGET)/sgx_le.bin $(TARGET)/sgx_le_blob.h sgx_le_blob + $(VERBOSE) $(TARGET)/bin2c $(TARGET)/sgx_le.ss $(TARGET)/sgx_le_ss.h sgx_le_ss + +gendata: $(TARGET)/sgx_le.bin $(TARGET)/sgxsign + $(VERBOSE) $(TARGET)/sgxsign gendata $(TARGET)/sgx_le.bin $(SIGNING_MATERIAL) $(SIGN_EXTRA) + +usesig: $(TARGET)/sgx_le.bin $(TARGET)/sgxsign $(TARGET)/bin2c + $(VERBOSE) $(TARGET)/sgxsign usesig $(CSS_PUBKEY_FILE) $(TARGET)/sgx_le.bin $(CSS_SIG_FILE) $(TARGET)/sgx_le.ss $(SIGN_EXTRA) + $(VERBOSE) $(TARGET)/bin2c $(TARGET)/sgx_le.bin $(TARGET)/sgx_le_blob.h sgx_le_blob + $(VERBOSE) $(TARGET)/bin2c $(TARGET)/sgx_le.ss $(TARGET)/sgx_le_ss.h sgx_le_ss + +clean: + $(VERBOSE) rm -vrf $(TARGET) $(SIGNING_MATERIAL) diff --git a/driver/win/PLE/include/asm/sgx_arch.h b/driver/win/PLE/include/asm/sgx_arch.h index abf414f2..2a8f272e 100644 --- a/driver/win/PLE/include/asm/sgx_arch.h +++ b/driver/win/PLE/include/asm/sgx_arch.h @@ -67,14 +67,15 @@ enum sgx_misc { #define SGX_MISC_RESERVED_MASK 0xFFFFFFFFFFFFFFFEL enum sgx_attribute { - SGX_ATTR_DEBUG = 0x02, - SGX_ATTR_MODE64BIT = 0x04, - SGX_ATTR_PROVISIONKEY = 0x10, - SGX_ATTR_EINITTOKENKEY = 0x20, - SGX_ATTR_KSS = 0x80, + SGX_ATTR_DEBUG = 0x002, + SGX_ATTR_MODE64BIT = 0x004, + SGX_ATTR_PROVISIONKEY = 0x010, + SGX_ATTR_EINITTOKENKEY = 0x020, + SGX_ATTR_KSS = 0x080, + SGX_ATTR_AEX_NOTIFY = 0x400, }; -#define SGX_ATTR_RESERVED_MASK 0xFFFFFFFFFFFFFF49L +#define SGX_ATTR_RESERVED_MASK 0xFFFFFFFFFFFFFB49L #define SGX_SECS_RESERVED1_SIZE 24 #define SGX_SECS_RESERVED2_SIZE 32 diff --git a/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_blob.h b/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_blob.h index 3e7d0710..a7a7d4bb 100644 --- a/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_blob.h +++ b/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_blob.h @@ -31,14 +31,14 @@ #pragma once -#define LE_BLOB_LEN 36864 -const int sgx_le_blob_length = LE_BLOB_LEN; +#define SGX_LE_BLOB_LEN 36864 +const int sgx_le_blob_length = SGX_LE_BLOB_LEN; -const unsigned char sgx_le_blob[LE_BLOB_LEN] = +const unsigned char sgx_le_blob[SGX_LE_BLOB_LEN] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, - 0x84,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x37,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -292,81 +292,84 @@ const unsigned char sgx_le_blob[LE_BLOB_LEN] = 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0xc7,0x45,0xfc,0x00,0x00,0x00,0x00,0xeb, - 0x20,0x48,0x8b,0x55,0xe8,0x0f,0xc7,0xf0,0x89,0x02,0xba,0x01,0x00,0x00,0x00,0x0f, - 0x42,0xc2,0x85,0xc0,0x74,0x07,0xb8,0x01,0x00,0x00,0x00,0xeb,0x0f,0x83,0x45,0xfc, - 0x01,0x83,0x7d,0xfc,0x09,0x7e,0xda,0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0x4c,0x8d, - 0x54,0x24,0x08,0x48,0x81,0xe4,0x00,0xfe,0xff,0xff,0x41,0xff,0x72,0xf8,0x55,0x48, - 0x89,0xe5,0x41,0x52,0x48,0x81,0xec,0xe8,0x07,0x00,0x00,0x48,0x89,0xbd,0xf8,0xf9, - 0xff,0xff,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x48,0x05,0x00,0x01,0x00,0x00,0x48, - 0x89,0x85,0x00,0xfe,0xff,0xff,0xc7,0x85,0x0c,0xfe,0xff,0xff,0x00,0x00,0x00,0x00, - 0xeb,0x3a,0x8b,0x85,0x0c,0xfe,0xff,0xff,0x48,0x98,0x48,0x8d,0x14,0x85,0x00,0x00, - 0x00,0x00,0x48,0x8b,0x85,0x00,0xfe,0xff,0xff,0x48,0x01,0xd0,0x48,0x89,0xc7,0xe8, - 0x5c,0xff,0xff,0xff,0x83,0xf0,0x01,0x84,0xc0,0x74,0x0a,0xb8,0x03,0x00,0x00,0x40, - 0xe9,0xb8,0x01,0x00,0x00,0x83,0x85,0x0c,0xfe,0xff,0xff,0x01,0x8b,0x85,0x0c,0xfe, - 0xff,0xff,0x83,0xf8,0x07,0x76,0xbb,0x48,0x8d,0x85,0x10,0xfa,0xff,0xff,0xba,0x00, - 0x02,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x2c,0x06,0x00,0x00, - 0x66,0xc7,0x85,0x10,0xfa,0xff,0xff,0x00,0x00,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff, - 0x48,0x8d,0x88,0x00,0x01,0x00,0x00,0x48,0x8b,0x01,0x48,0x8b,0x51,0x08,0x48,0x89, - 0x85,0x38,0xfa,0xff,0xff,0x48,0x89,0x95,0x40,0xfa,0xff,0xff,0x48,0x8b,0x41,0x10, - 0x48,0x8b,0x51,0x18,0x48,0x89,0x85,0x48,0xfa,0xff,0xff,0x48,0x89,0x95,0x50,0xfa, - 0xff,0xff,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x48,0x05,0xc0,0x00,0x00,0x00,0x48, - 0x8b,0x50,0x08,0x48,0x8b,0x00,0x48,0x89,0x85,0x18,0xfa,0xff,0xff,0x48,0x89,0x95, - 0x20,0xfa,0xff,0xff,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x48,0x05,0xd2,0x00,0x00, - 0x00,0x0f,0xb7,0x00,0x66,0x89,0x85,0x14,0xfa,0xff,0xff,0x48,0xc7,0x85,0x28,0xfa, - 0xff,0xff,0xfb,0xff,0xff,0xff,0x48,0xc7,0x85,0x30,0xfa,0xff,0xff,0x00,0x00,0x00, - 0x00,0xc7,0x85,0x58,0xfa,0xff,0xff,0xff,0xff,0xff,0xff,0x48,0x8b,0x85,0xf8,0xf9, - 0xff,0xff,0x8b,0x90,0xec,0x00,0x00,0x00,0x8b,0x85,0x58,0xfa,0xff,0xff,0x21,0xc2, - 0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x89,0x90,0xec,0x00,0x00,0x00,0x48,0x8b,0x85, - 0xf8,0xf9,0xff,0xff,0x48,0x8b,0x90,0xf0,0x00,0x00,0x00,0x48,0x8b,0x85,0x28,0xfa, - 0xff,0xff,0x48,0x21,0xc2,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x48,0x89,0x90,0xf0, - 0x00,0x00,0x00,0x48,0x8b,0x85,0xf8,0xf9,0xff,0xff,0x48,0x8b,0x90,0xf8,0x00,0x00, - 0x00,0x48,0x8b,0x85,0x30,0xfa,0xff,0xff,0x48,0x21,0xc2,0x48,0x8b,0x85,0xf8,0xf9, - 0xff,0xff,0x48,0x89,0x90,0xf8,0x00,0x00,0x00,0x48,0x8d,0x95,0x00,0xfa,0xff,0xff, - 0x48,0x8d,0x85,0x10,0xfa,0xff,0xff,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0xf7,0x04, - 0x00,0x00,0x85,0xc0,0x74,0x07,0xb8,0x00,0x00,0x00,0x00,0xeb,0x60,0x48,0x8b,0x85, - 0xf8,0xf9,0xff,0xff,0x48,0x8d,0x90,0x20,0x01,0x00,0x00,0x48,0x8b,0xb5,0xf8,0xf9, - 0xff,0xff,0x48,0x8d,0x85,0x00,0xfa,0xff,0xff,0x48,0x89,0xd1,0xba,0xc0,0x00,0x00, - 0x00,0x48,0x89,0xc7,0xe8,0xc7,0x0d,0x00,0x00,0x89,0x85,0xfc,0xfd,0xff,0xff,0x48, - 0x8d,0x85,0x00,0xfa,0xff,0xff,0xba,0x10,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00, - 0x48,0x89,0xc7,0xe8,0xb4,0x04,0x00,0x00,0x83,0xbd,0xfc,0xfd,0xff,0xff,0x00,0x74, - 0x07,0xb8,0x03,0x00,0x00,0x40,0xeb,0x05,0xb8,0x00,0x00,0x00,0x00,0x48,0x81,0xc4, - 0xe8,0x07,0x00,0x00,0x41,0x5a,0x5d,0x49,0x8d,0x62,0xf8,0xc3,0x4c,0x8d,0x54,0x24, - 0x08,0x48,0x81,0xe4,0x00,0xfe,0xff,0xff,0x41,0xff,0x72,0xf8,0x55,0x48,0x89,0xe5, - 0x41,0x52,0x48,0x81,0xec,0xe8,0x07,0x00,0x00,0x48,0x89,0xbd,0x88,0xf9,0xff,0xff, - 0x48,0x89,0xb5,0x80,0xf9,0xff,0xff,0x48,0x89,0x95,0x78,0xf9,0xff,0xff,0x48,0x89, - 0x8d,0x70,0xf9,0xff,0xff,0x4c,0x89,0x85,0x68,0xf9,0xff,0xff,0x48,0x8b,0x85,0x78, - 0xf9,0xff,0xff,0x24,0x49,0x48,0x85,0xc0,0x74,0x0a,0xb8,0x02,0x00,0x00,0x00,0xe9, - 0xa3,0x01,0x00,0x00,0x48,0x8d,0x85,0x10,0xfc,0xff,0xff,0xba,0x00,0x02,0x00,0x00, - 0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x1f,0x04,0x00,0x00,0x48,0x8d,0x85, - 0x90,0xf9,0xff,0xff,0xba,0x40,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89, - 0xc7,0xe8,0x06,0x04,0x00,0x00,0x48,0x8d,0x85,0x10,0xfa,0xff,0xff,0xba,0xb0,0x01, - 0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0xed,0x03,0x00,0x00,0x48, - 0x8d,0x95,0x10,0xfa,0xff,0xff,0x48,0x8d,0x8d,0x90,0xf9,0xff,0xff,0x48,0x8d,0x85, - 0x10,0xfc,0xff,0xff,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0xab,0x03,0x00,0x00,0x85, - 0xc0,0x74,0x0a,0xb8,0x03,0x00,0x00,0x40,0xe9,0x2a,0x01,0x00,0x00,0x48,0x8b,0x85, - 0x68,0xf9,0xff,0xff,0xba,0x30,0x01,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89, - 0xc7,0xe8,0xa6,0x03,0x00,0x00,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0xc7,0x00,0x01, - 0x00,0x00,0x00,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x48,0x8d,0x48,0x40,0x48,0x8b, - 0x85,0x88,0xf9,0xff,0xff,0xba,0x20,0x00,0x00,0x00,0x48,0x89,0xc6,0x48,0x89,0xcf, - 0xe8,0xb5,0x03,0x00,0x00,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x48,0x8d,0x88,0x80, - 0x00,0x00,0x00,0x48,0x8b,0x85,0x80,0xf9,0xff,0xff,0xba,0x20,0x00,0x00,0x00,0x48, - 0x89,0xc6,0x48,0x89,0xcf,0xe8,0x90,0x03,0x00,0x00,0x48,0x8b,0x85,0x68,0xf9,0xff, - 0xff,0x48,0x8b,0x95,0x78,0xf9,0xff,0xff,0x48,0x89,0x50,0x30,0x48,0x8b,0x85,0x68, - 0xf9,0xff,0xff,0x48,0x8b,0x95,0x70,0xf9,0xff,0xff,0x48,0x89,0x50,0x38,0x48,0x8b, - 0x85,0x68,0xf9,0xff,0xff,0x48,0x8d,0x88,0xc0,0x00,0x00,0x00,0x48,0x8b,0x85,0x10, - 0xfa,0xff,0xff,0x48,0x8b,0x95,0x18,0xfa,0xff,0xff,0x48,0x89,0x01,0x48,0x89,0x51, - 0x08,0x0f,0xb7,0x95,0x12,0xfb,0xff,0xff,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x66, - 0x89,0x90,0xd2,0x00,0x00,0x00,0x0f,0xb7,0x95,0x10,0xfb,0xff,0xff,0x48,0x8b,0x85, - 0x68,0xf9,0xff,0xff,0x66,0x89,0x90,0xd0,0x00,0x00,0x00,0x48,0x8b,0x95,0x40,0xfa, - 0xff,0xff,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x48,0x89,0x90,0xf0,0x00,0x00,0x00, - 0x48,0x8b,0x95,0x48,0xfa,0xff,0xff,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x48,0x89, - 0x90,0xf8,0x00,0x00,0x00,0x8b,0x95,0x20,0xfa,0xff,0xff,0x48,0x8b,0x85,0x68,0xf9, - 0xff,0xff,0x89,0x90,0xec,0x00,0x00,0x00,0x48,0x8b,0x85,0x68,0xf9,0xff,0xff,0x48, - 0x89,0xc7,0xe8,0xc7,0xfb,0xff,0xff,0x48,0x81,0xc4,0xe8,0x07,0x00,0x00,0x41,0x5a, - 0x5d,0x49,0x8d,0x62,0xf8,0xc3,0x55,0x48,0x89,0xe5,0x48,0x81,0xec,0xa0,0x01,0x00, + 0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0xc7,0x45,0xfc,0x00, + 0x00,0x00,0x00,0xeb,0x20,0x48,0x8b,0x55,0xe8,0x0f,0xc7,0xf0,0x89,0x02,0xba,0x01, + 0x00,0x00,0x00,0x0f,0x42,0xc2,0x85,0xc0,0x74,0x07,0xb8,0x01,0x00,0x00,0x00,0xeb, + 0x0f,0x83,0x45,0xfc,0x01,0x83,0x7d,0xfc,0x09,0x7e,0xda,0xb8,0x00,0x00,0x00,0x00, + 0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x81,0xe4,0x00,0xfe,0xff, + 0xff,0x48,0x81,0xec,0x00,0x06,0x00,0x00,0x48,0x89,0xbc,0x24,0xe8,0x01,0x00,0x00, + 0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x05,0x00,0x01,0x00,0x00,0x48,0x89, + 0x84,0x24,0xf0,0x05,0x00,0x00,0xc7,0x84,0x24,0xfc,0x05,0x00,0x00,0x00,0x00,0x00, + 0x00,0xeb,0x3d,0x8b,0x84,0x24,0xfc,0x05,0x00,0x00,0x48,0x98,0x48,0x8d,0x14,0x85, + 0x00,0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0xf0,0x05,0x00,0x00,0x48,0x01,0xd0,0x48, + 0x89,0xc7,0xe8,0x59,0xff,0xff,0xff,0x83,0xf0,0x01,0x84,0xc0,0x74,0x0a,0xb8,0x03, + 0x00,0x00,0x40,0xe9,0xda,0x01,0x00,0x00,0x83,0x84,0x24,0xfc,0x05,0x00,0x00,0x01, + 0x8b,0x84,0x24,0xfc,0x05,0x00,0x00,0x83,0xf8,0x07,0x76,0xb7,0x48,0x8d,0x84,0x24, + 0x00,0x02,0x00,0x00,0xba,0x00,0x02,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89, + 0xc7,0xe8,0xcc,0x05,0x00,0x00,0x66,0xc7,0x84,0x24,0x00,0x02,0x00,0x00,0x00,0x00, + 0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x8d,0x88,0x00,0x01,0x00,0x00,0x48, + 0x8b,0x01,0x48,0x8b,0x51,0x08,0x48,0x89,0x84,0x24,0x28,0x02,0x00,0x00,0x48,0x89, + 0x94,0x24,0x30,0x02,0x00,0x00,0x48,0x8b,0x41,0x10,0x48,0x8b,0x51,0x18,0x48,0x89, + 0x84,0x24,0x38,0x02,0x00,0x00,0x48,0x89,0x94,0x24,0x40,0x02,0x00,0x00,0x48,0x8b, + 0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x05,0xc0,0x00,0x00,0x00,0x48,0x8b,0x50,0x08, + 0x48,0x8b,0x00,0x48,0x89,0x84,0x24,0x08,0x02,0x00,0x00,0x48,0x89,0x94,0x24,0x10, + 0x02,0x00,0x00,0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x05,0xd2,0x00,0x00, + 0x00,0x0f,0xb7,0x00,0x66,0x89,0x84,0x24,0x04,0x02,0x00,0x00,0x48,0xc7,0x84,0x24, + 0x18,0x02,0x00,0x00,0xfb,0xff,0xff,0xff,0x48,0xc7,0x84,0x24,0x20,0x02,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc7,0x84,0x24,0x48,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x48, + 0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x8b,0x90,0xec,0x00,0x00,0x00,0x8b,0x84,0x24, + 0x48,0x02,0x00,0x00,0x21,0xc2,0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x89,0x90, + 0xec,0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x8b,0x90,0xf0, + 0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0x18,0x02,0x00,0x00,0x48,0x21,0xc2,0x48,0x8b, + 0x84,0x24,0xe8,0x01,0x00,0x00,0x48,0x89,0x90,0xf0,0x00,0x00,0x00,0x48,0x8b,0x84, + 0x24,0xe8,0x01,0x00,0x00,0x48,0x8b,0x90,0xf8,0x00,0x00,0x00,0x48,0x8b,0x84,0x24, + 0x20,0x02,0x00,0x00,0x48,0x21,0xc2,0x48,0x8b,0x84,0x24,0xe8,0x01,0x00,0x00,0x48, + 0x89,0x90,0xf8,0x00,0x00,0x00,0x48,0x8d,0x94,0x24,0xf0,0x01,0x00,0x00,0x48,0x8d, + 0x84,0x24,0x00,0x02,0x00,0x00,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x8b,0x0f,0x00, + 0x00,0x85,0xc0,0x74,0x07,0xb8,0x00,0x00,0x00,0x00,0xeb,0x66,0x48,0x8b,0x84,0x24, + 0xe8,0x01,0x00,0x00,0x48,0x8d,0x90,0x20,0x01,0x00,0x00,0x48,0x8b,0xb4,0x24,0xe8, + 0x01,0x00,0x00,0x48,0x8d,0x84,0x24,0xf0,0x01,0x00,0x00,0x48,0x89,0xd1,0xba,0xc0, + 0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x75,0x0d,0x00,0x00,0x89,0x84,0x24,0xec,0x05, + 0x00,0x00,0x48,0x8d,0x84,0x24,0xf0,0x01,0x00,0x00,0xba,0x10,0x00,0x00,0x00,0xbe, + 0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x36,0x04,0x00,0x00,0x83,0xbc,0x24,0xec, + 0x05,0x00,0x00,0x00,0x74,0x07,0xb8,0x03,0x00,0x00,0x40,0xeb,0x05,0xb8,0x00,0x00, + 0x00,0x00,0xc9,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x81,0xe4,0x00, + 0xfe,0xff,0xff,0x48,0x81,0xec,0x00,0x06,0x00,0x00,0x48,0x89,0xbc,0x24,0x78,0x01, + 0x00,0x00,0x48,0x89,0xb4,0x24,0x70,0x01,0x00,0x00,0x48,0x89,0x94,0x24,0x68,0x01, + 0x00,0x00,0x48,0x89,0x8c,0x24,0x60,0x01,0x00,0x00,0x4c,0x89,0x84,0x24,0x58,0x01, + 0x00,0x00,0x48,0x8b,0x84,0x24,0x68,0x01,0x00,0x00,0x48,0x25,0x49,0xfb,0xff,0xff, + 0x48,0x85,0xc0,0x74,0x0a,0xb8,0x02,0x00,0x00,0x00,0xe9,0xc1,0x01,0x00,0x00,0x48, + 0x8d,0x84,0x24,0x00,0x04,0x00,0x00,0xba,0x00,0x02,0x00,0x00,0xbe,0x00,0x00,0x00, + 0x00,0x48,0x89,0xc7,0xe8,0xa9,0x03,0x00,0x00,0x48,0x8d,0x84,0x24,0x80,0x01,0x00, + 0x00,0xba,0x40,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x8f, + 0x03,0x00,0x00,0x48,0x8d,0x84,0x24,0x00,0x02,0x00,0x00,0xba,0xb0,0x01,0x00,0x00, + 0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x75,0x03,0x00,0x00,0x48,0x8d,0x94, + 0x24,0x00,0x02,0x00,0x00,0x48,0x8d,0x8c,0x24,0x80,0x01,0x00,0x00,0x48,0x8d,0x84, + 0x24,0x00,0x04,0x00,0x00,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x3d,0x0e,0x00,0x00, + 0x85,0xc0,0x74,0x0a,0xb8,0x03,0x00,0x00,0x40,0xe9,0x42,0x01,0x00,0x00,0x48,0x8b, + 0x84,0x24,0x58,0x01,0x00,0x00,0xba,0x30,0x01,0x00,0x00,0xbe,0x00,0x00,0x00,0x00, + 0x48,0x89,0xc7,0xe8,0x2a,0x03,0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00, + 0xc7,0x00,0x01,0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x48,0x8d, + 0x48,0x40,0x48,0x8b,0x84,0x24,0x78,0x01,0x00,0x00,0xba,0x20,0x00,0x00,0x00,0x48, + 0x89,0xc6,0x48,0x89,0xcf,0xe8,0x3a,0x03,0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01, + 0x00,0x00,0x48,0x8d,0x88,0x80,0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0x70,0x01,0x00, + 0x00,0xba,0x20,0x00,0x00,0x00,0x48,0x89,0xc6,0x48,0x89,0xcf,0xe8,0x13,0x03,0x00, + 0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x48,0x8b,0x94,0x24,0x68,0x01,0x00, + 0x00,0x48,0x89,0x50,0x30,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x48,0x8b,0x94, + 0x24,0x60,0x01,0x00,0x00,0x48,0x89,0x50,0x38,0x48,0x8b,0x84,0x24,0x58,0x01,0x00, + 0x00,0x48,0x8d,0x88,0xc0,0x00,0x00,0x00,0x48,0x8b,0x84,0x24,0x00,0x02,0x00,0x00, + 0x48,0x8b,0x94,0x24,0x08,0x02,0x00,0x00,0x48,0x89,0x01,0x48,0x89,0x51,0x08,0x0f, + 0xb7,0x94,0x24,0x02,0x03,0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x66, + 0x89,0x90,0xd2,0x00,0x00,0x00,0x0f,0xb7,0x94,0x24,0x00,0x03,0x00,0x00,0x48,0x8b, + 0x84,0x24,0x58,0x01,0x00,0x00,0x66,0x89,0x90,0xd0,0x00,0x00,0x00,0x48,0x8b,0x94, + 0x24,0x30,0x02,0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x48,0x89,0x90, + 0xf0,0x00,0x00,0x00,0x48,0x8b,0x94,0x24,0x38,0x02,0x00,0x00,0x48,0x8b,0x84,0x24, + 0x58,0x01,0x00,0x00,0x48,0x89,0x90,0xf8,0x00,0x00,0x00,0x8b,0x94,0x24,0x10,0x02, + 0x00,0x00,0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x89,0x90,0xec,0x00,0x00,0x00, + 0x48,0x8b,0x84,0x24,0x58,0x01,0x00,0x00,0x48,0x89,0xc7,0xe8,0x92,0xfb,0xff,0xff, + 0xc9,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x81,0xec,0xa0,0x01,0x00, 0x00,0x48,0x89,0xbd,0x68,0xfe,0xff,0xff,0x48,0x83,0xbd,0x68,0xfe,0xff,0xff,0x00, - 0x0f,0x84,0xdb,0x01,0x00,0x00,0x48,0x8b,0x8d,0x68,0xfe,0xff,0xff,0x48,0x8b,0x01, + 0x0f,0x84,0xd9,0x01,0x00,0x00,0x48,0x8b,0x8d,0x68,0xfe,0xff,0xff,0x48,0x8b,0x01, 0x48,0x8b,0x51,0x08,0x48,0x89,0x85,0xa0,0xfe,0xff,0xff,0x48,0x89,0x95,0xa8,0xfe, 0xff,0xff,0x48,0x8b,0x41,0x10,0x48,0x8b,0x51,0x18,0x48,0x89,0x85,0xb0,0xfe,0xff, 0xff,0x48,0x89,0x95,0xb8,0xfe,0xff,0xff,0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x48, @@ -376,206 +379,203 @@ const unsigned char sgx_le_blob[LE_BLOB_LEN] = 0x85,0x68,0xfe,0xff,0xff,0x48,0x83,0xc0,0x40,0x48,0x8b,0x00,0x48,0x89,0x85,0x78, 0xfe,0xff,0xff,0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x48,0x83,0xc0,0x48,0x48,0x8b, 0x00,0x48,0x89,0x85,0x70,0xfe,0xff,0xff,0x48,0x8d,0x85,0xc0,0xfe,0xff,0xff,0xba, - 0x30,0x01,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0xbb,0x01,0x00, + 0x30,0x01,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x31,0x01,0x00, 0x00,0x48,0x8b,0x85,0x78,0xfe,0xff,0xff,0x83,0xe0,0x10,0x48,0x85,0xc0,0x74,0x74, 0xc7,0x45,0xfc,0x00,0x00,0x00,0x00,0xeb,0x36,0x8b,0x45,0xfc,0x48,0x98,0x48,0xc1, - 0xe0,0x05,0x48,0x89,0xc2,0x48,0x8d,0x05,0x84,0x1a,0x00,0x00,0x48,0x8d,0x0c,0x02, + 0xe0,0x05,0x48,0x89,0xc2,0x48,0x8d,0x05,0x54,0x1a,0x00,0x00,0x48,0x8d,0x0c,0x02, 0x48,0x8d,0x85,0x80,0xfe,0xff,0xff,0xba,0x20,0x00,0x00,0x00,0x48,0x89,0xc6,0x48, - 0x89,0xcf,0xe8,0xfd,0x01,0x00,0x00,0x85,0xc0,0x74,0x0e,0x83,0x45,0xfc,0x01,0x8b, + 0x89,0xcf,0xe8,0x7b,0x01,0x00,0x00,0x85,0xc0,0x74,0x0e,0x83,0x45,0xfc,0x01,0x8b, 0x45,0xfc,0x83,0xf8,0x02,0x76,0xc2,0xeb,0x01,0x90,0x83,0x7d,0xfc,0x03,0x75,0x24, 0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0xc7,0x40,0x50,0x00,0x00,0x00,0x00,0x48,0x8b, 0x85,0x68,0xfe,0xff,0xff,0xc7,0x80,0x80,0x01,0x00,0x00,0x02,0x00,0x00,0x40,0xe9, - 0xae,0x00,0x00,0x00,0x48,0x8b,0x8d,0x70,0xfe,0xff,0xff,0x48,0x8b,0x95,0x78,0xfe, + 0xac,0x00,0x00,0x00,0x48,0x8b,0x8d,0x70,0xfe,0xff,0xff,0x48,0x8b,0x95,0x78,0xfe, 0xff,0xff,0x48,0x8d,0xbd,0xc0,0xfe,0xff,0xff,0x48,0x8d,0xb5,0x80,0xfe,0xff,0xff, - 0x48,0x8d,0x85,0xa0,0xfe,0xff,0xff,0x49,0x89,0xf8,0x48,0x89,0xc7,0xe8,0x7a,0xfc, - 0xff,0xff,0x89,0xc2,0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x89,0x90,0x80,0x01,0x00, - 0x00,0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x8b,0x80,0x80,0x01,0x00,0x00,0x85,0xc0, - 0x75,0x60,0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x48,0x83,0xc0,0x50,0x48,0x89,0xc2, - 0x48,0x8d,0x85,0xc0,0xfe,0xff,0xff,0xb9,0x30,0x01,0x00,0x00,0x48,0x8b,0x30,0x48, - 0x89,0x32,0x89,0xce,0x48,0x01,0xd6,0x48,0x8d,0x7e,0x08,0x89,0xce,0x48,0x01,0xc6, - 0x48,0x83,0xc6,0x08,0x48,0x8b,0x76,0xf0,0x48,0x89,0x77,0xf0,0x48,0x8d,0x7a,0x08, - 0x48,0x83,0xe7,0xf8,0x48,0x29,0xfa,0x48,0x29,0xd0,0x01,0xd1,0x83,0xe1,0xf8,0xc1, - 0xe9,0x03,0x89,0xca,0x89,0xd2,0x48,0x89,0xc6,0x48,0x89,0xd1,0xf3,0x48,0xa5,0xeb, - 0x01,0x90,0xc9,0xc3,0x48,0x8d,0x83,0x00,0x90,0x00,0x00,0x48,0x94,0x50,0x51,0x53, - 0xe8,0xf1,0xfd,0xff,0xff,0x5b,0x48,0xb8,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, - 0x48,0xba,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x48,0x8d,0xbb,0x00,0x50,0x00, - 0x00,0x0f,0xae,0x0f,0x48,0x31,0xc9,0x48,0x31,0xd2,0x48,0x31,0xff,0x48,0x31,0xf6, - 0x4d,0x31,0xc0,0x4d,0x31,0xc9,0x4d,0x31,0xd2,0x4d,0x31,0xdb,0x4d,0x31,0xe4,0x4d, - 0x31,0xed,0x4d,0x31,0xf6,0x4d,0x31,0xff,0x48,0x01,0xd2,0x5b,0x58,0x48,0x89,0xc4, - 0x48,0xc7,0xc0,0x04,0x00,0x00,0x00,0x0f,0x01,0xd7,0x53,0x48,0x31,0xc0,0x48,0x89, - 0xfb,0x48,0x89,0xf1,0x0f,0x01,0xd7,0x5b,0xc3,0x53,0x48,0xc7,0xc0,0x01,0x00,0x00, - 0x00,0x48,0x89,0xfb,0x48,0x89,0xf1,0x0f,0x01,0xd7,0x5b,0xc3,0x55,0x48,0x89,0xe5, - 0x48,0x89,0x7d,0xe8,0x89,0x75,0xe4,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00, - 0x00,0x00,0x00,0xeb,0x15,0x48,0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0, - 0x8b,0x55,0xe4,0x88,0x10,0x48,0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48,0x3b, - 0x45,0xd8,0x72,0xe1,0x48,0x8b,0x45,0xe8,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89, - 0x7d,0xe8,0x48,0x89,0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00, - 0x00,0x00,0xeb,0x20,0x48,0x8b,0x55,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x48, - 0x8b,0x4d,0xe8,0x48,0x8b,0x55,0xf8,0x48,0x01,0xca,0x0f,0xb6,0x00,0x88,0x02,0x48, - 0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45,0xd8,0x72,0xd6,0x48,0x8b, - 0x45,0xe8,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x48,0x89,0x75,0xe0, - 0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x4d,0x48,0x8b, - 0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x10,0x48,0x8b,0x4d,0xe0, - 0x48,0x8b,0x45,0xf8,0x48,0x01,0xc8,0x0f,0xb6,0x00,0x38,0xc2,0x74,0x28,0x48,0x8b, - 0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x00,0x0f,0xbe,0xd0,0x48, - 0x8b,0x4d,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x01,0xc8,0x0f,0xb6,0x00,0x0f,0xbe,0xc0, - 0x29,0xc2,0x89,0xd0,0xeb,0x14,0x48,0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48, - 0x3b,0x45,0xd8,0x72,0xa9,0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0x55,0x48,0x89,0xe5, - 0x48,0x89,0x7d,0xd8,0x48,0x89,0x75,0xd0,0x48,0x8b,0x45,0xd8,0x48,0x89,0x45,0xf8, - 0x48,0x8b,0x45,0xd0,0x48,0x89,0x45,0xf0,0xc7,0x45,0xec,0x00,0x00,0x00,0x00,0xeb, - 0x30,0x48,0x8b,0x45,0xf8,0x0f,0xb6,0x00,0x0f,0xb6,0xd0,0x48,0x8b,0x45,0xf0,0x0f, - 0xb6,0x00,0x0f,0xb6,0xc0,0x29,0xc2,0x89,0xd0,0x89,0x45,0xec,0x83,0x7d,0xec,0x00, - 0x74,0x05,0x8b,0x45,0xec,0xeb,0x25,0x48,0x83,0x45,0xf8,0x01,0x48,0x83,0x45,0xf0, - 0x01,0x48,0x8b,0x45,0xf8,0x0f,0xb6,0x00,0x84,0xc0,0x75,0xc5,0x48,0x8b,0x45,0xf0, - 0x0f,0xb6,0x00,0x84,0xc0,0x75,0xba,0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0x55,0x48, - 0x89,0xe5,0x48,0x89,0x7d,0xf8,0x48,0x89,0x75,0xf0,0x48,0x89,0x55,0xe8,0x48,0x8b, - 0x45,0xf8,0x48,0x8b,0x40,0x08,0x48,0x8b,0x55,0xf0,0x48,0x8b,0x75,0xe8,0xf3,0x0f, - 0x6f,0x02,0xf3,0x0f,0x6f,0x08,0x66,0x0f,0xef,0xc1,0x48,0x83,0xc0,0x10,0x48,0xc7, - 0xc1,0x09,0x00,0x00,0x00,0xf3,0x0f,0x6f,0x08,0x66,0x0f,0x38,0xdc,0xc1,0x48,0x83, - 0xc0,0x10,0x48,0x83,0xe9,0x01,0x75,0xed,0xf3,0x0f,0x6f,0x08,0x66,0x0f,0x38,0xdd, - 0xc1,0xf3,0x0f,0x7f,0x06,0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0x55,0x48,0x89,0xe5, - 0x48,0x89,0x7d,0xf8,0x48,0x89,0x75,0xf0,0x48,0x8b,0x55,0xf8,0x48,0x8b,0x4d,0xf0, - 0xf3,0x0f,0x6f,0x01,0xf3,0x0f,0x7f,0x02,0xeb,0x2d,0x66,0x0f,0x70,0xc9,0xff,0x66, - 0x0f,0xef,0xc8,0x66,0x0f,0x73,0xf8,0x04,0x66,0x0f,0xef,0xc8,0x66,0x0f,0x73,0xf8, - 0x04,0x66,0x0f,0xef,0xc8,0x66,0x0f,0x73,0xf8,0x04,0x66,0x0f,0xef,0xc1,0x48,0x83, - 0xc2,0x10,0xf3,0x0f,0x7f,0x02,0xc3,0x66,0x0f,0x3a,0xdf,0xc8,0x01,0xe8,0xc8,0xff, - 0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x02,0xe8,0xbd,0xff,0xff,0xff,0x66,0x0f,0x3a, - 0xdf,0xc8,0x04,0xe8,0xb2,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x08,0xe8,0xa7, - 0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x10,0xe8,0x9c,0xff,0xff,0xff,0x66,0x0f, - 0x3a,0xdf,0xc8,0x20,0xe8,0x91,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x40,0xe8, - 0x86,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x80,0xe8,0x7b,0xff,0xff,0xff,0x66, - 0x0f,0x3a,0xdf,0xc8,0x1b,0xe8,0x70,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x36, - 0xe8,0x65,0xff,0xff,0xff,0x90,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8, + 0x48,0x8d,0x85,0xa0,0xfe,0xff,0xff,0x49,0x89,0xf8,0x48,0x89,0xc7,0xe8,0x62,0xfc, + 0xff,0xff,0x48,0x8b,0x95,0x68,0xfe,0xff,0xff,0x89,0x82,0x80,0x01,0x00,0x00,0x48, + 0x8b,0x85,0x68,0xfe,0xff,0xff,0x8b,0x80,0x80,0x01,0x00,0x00,0x85,0xc0,0x75,0x60, + 0x48,0x8b,0x85,0x68,0xfe,0xff,0xff,0x48,0x83,0xc0,0x50,0x48,0x89,0xc2,0x48,0x8d, + 0x85,0xc0,0xfe,0xff,0xff,0xb9,0x30,0x01,0x00,0x00,0x48,0x8b,0x30,0x48,0x89,0x32, + 0x89,0xce,0x48,0x01,0xd6,0x48,0x8d,0x7e,0x08,0x89,0xce,0x48,0x01,0xc6,0x48,0x83, + 0xc6,0x08,0x48,0x8b,0x76,0xf0,0x48,0x89,0x77,0xf0,0x48,0x8d,0x7a,0x08,0x48,0x83, + 0xe7,0xf8,0x48,0x29,0xfa,0x48,0x29,0xd0,0x01,0xd1,0x83,0xe1,0xf8,0xc1,0xe9,0x03, + 0x89,0xca,0x89,0xd2,0x48,0x89,0xc6,0x48,0x89,0xd1,0xf3,0x48,0xa5,0xeb,0x01,0x90, + 0xc9,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x89,0x75, + 0xe4,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x15,0x48, + 0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x8b,0x55,0xe4,0x88,0x10,0x48, + 0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45,0xd8,0x72,0xe1,0x48,0x8b, + 0x45,0xe8,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8, + 0x48,0x89,0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00, + 0xeb,0x20,0x48,0x8b,0x55,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x48,0x8b,0x4d, + 0xe8,0x48,0x8b,0x55,0xf8,0x48,0x01,0xca,0x0f,0xb6,0x00,0x88,0x02,0x48,0x83,0x45, + 0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45,0xd8,0x72,0xd6,0x48,0x8b,0x45,0xe8, + 0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x48,0x89, + 0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x4b, + 0x48,0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x10,0x48,0x8b, + 0x4d,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x01,0xc8,0x0f,0xb6,0x00,0x38,0xc2,0x74,0x26, + 0x48,0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x00,0x0f,0xbe, + 0xc0,0x48,0x8b,0x4d,0xe0,0x48,0x8b,0x55,0xf8,0x48,0x01,0xca,0x0f,0xb6,0x12,0x0f, + 0xbe,0xd2,0x29,0xd0,0xeb,0x14,0x48,0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48, + 0x3b,0x45,0xd8,0x72,0xab,0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa, + 0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xd8,0x48,0x89,0x75,0xd0,0x48,0x8b,0x45,0xd8, + 0x48,0x89,0x45,0xf8,0x48,0x8b,0x45,0xd0,0x48,0x89,0x45,0xf0,0xc7,0x45,0xec,0x00, + 0x00,0x00,0x00,0xeb,0x30,0x48,0x8b,0x45,0xf8,0x0f,0xb6,0x00,0x0f,0xb6,0xd0,0x48, + 0x8b,0x45,0xf0,0x0f,0xb6,0x00,0x0f,0xb6,0xc8,0x89,0xd0,0x29,0xc8,0x89,0x45,0xec, + 0x83,0x7d,0xec,0x00,0x74,0x05,0x8b,0x45,0xec,0xeb,0x25,0x48,0x83,0x45,0xf8,0x01, + 0x48,0x83,0x45,0xf0,0x01,0x48,0x8b,0x45,0xf8,0x0f,0xb6,0x00,0x84,0xc0,0x75,0xc5, + 0x48,0x8b,0x45,0xf0,0x0f,0xb6,0x00,0x84,0xc0,0x75,0xba,0xb8,0x00,0x00,0x00,0x00, + 0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xf8,0x48,0x89, + 0x75,0xf0,0x48,0x89,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x8b,0x40,0x08,0x48,0x8b, + 0x55,0xf0,0x48,0x8b,0x75,0xe8,0xf3,0x0f,0x6f,0x02,0xf3,0x0f,0x6f,0x08,0x66,0x0f, + 0xef,0xc1,0x48,0x83,0xc0,0x10,0x48,0xc7,0xc1,0x09,0x00,0x00,0x00,0xf3,0x0f,0x6f, + 0x08,0x66,0x0f,0x38,0xdc,0xc1,0x48,0x83,0xc0,0x10,0x48,0x83,0xe9,0x01,0x75,0xed, + 0xf3,0x0f,0x6f,0x08,0x66,0x0f,0x38,0xdd,0xc1,0xf3,0x0f,0x7f,0x06,0xb8,0x00,0x00, + 0x00,0x00,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xf8, + 0x48,0x89,0x75,0xf0,0x48,0x8b,0x55,0xf8,0x48,0x8b,0x4d,0xf0,0xf3,0x0f,0x6f,0x01, + 0xf3,0x0f,0x7f,0x02,0xeb,0x2d,0x66,0x0f,0x70,0xc9,0xff,0x66,0x0f,0xef,0xc8,0x66, + 0x0f,0x73,0xf8,0x04,0x66,0x0f,0xef,0xc8,0x66,0x0f,0x73,0xf8,0x04,0x66,0x0f,0xef, + 0xc8,0x66,0x0f,0x73,0xf8,0x04,0x66,0x0f,0xef,0xc1,0x48,0x83,0xc2,0x10,0xf3,0x0f, + 0x7f,0x02,0xc3,0x66,0x0f,0x3a,0xdf,0xc8,0x01,0xe8,0xc8,0xff,0xff,0xff,0x66,0x0f, + 0x3a,0xdf,0xc8,0x02,0xe8,0xbd,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x04,0xe8, + 0xb2,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x08,0xe8,0xa7,0xff,0xff,0xff,0x66, + 0x0f,0x3a,0xdf,0xc8,0x10,0xe8,0x9c,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x20, + 0xe8,0x91,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x40,0xe8,0x86,0xff,0xff,0xff, + 0x66,0x0f,0x3a,0xdf,0xc8,0x80,0xe8,0x7b,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8, + 0x1b,0xe8,0x70,0xff,0xff,0xff,0x66,0x0f,0x3a,0xdf,0xc8,0x36,0xe8,0x65,0xff,0xff, + 0xff,0x90,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8, 0x48,0x89,0x75,0xe0,0x48,0x8b,0x45,0xe8,0x48,0x89,0x45,0xf8,0xeb,0x0f,0x48,0x8b, 0x45,0xf8,0x48,0x8d,0x50,0x01,0x48,0x89,0x55,0xf8,0xc6,0x00,0x00,0x48,0x8b,0x45, - 0xe0,0x48,0x8d,0x50,0xff,0x48,0x89,0x55,0xe0,0x48,0x85,0xc0,0x75,0xe0,0x90,0x5d, - 0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xd8,0x48,0x89,0x75,0xd0,0xc6,0x45,0xf7, - 0x87,0xc6,0x45,0xff,0x00,0x48,0xc7,0x45,0xe8,0x10,0x00,0x00,0x00,0x0f,0xb6,0x45, - 0xf7,0x88,0x45,0xe7,0x48,0x8b,0x45,0xe8,0x83,0xe8,0x01,0x89,0x45,0xf8,0xeb,0x4a, - 0x8b,0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd0,0x48,0x01,0xd0,0x0f,0xb6,0x00, - 0x0f,0xb6,0xc0,0x01,0xc0,0x89,0xc2,0x0f,0xb6,0x45,0xff,0x89,0xd1,0x09,0xc1,0x8b, - 0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd8,0x48,0x01,0xd0,0x89,0xca,0x88,0x10, - 0x8b,0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd0,0x48,0x01,0xd0,0x0f,0xb6,0x00, - 0xc0,0xe8,0x07,0x88,0x45,0xff,0x83,0x6d,0xf8,0x01,0x83,0x7d,0xf8,0x00,0x79,0xb0, - 0x48,0x8b,0x45,0xd0,0x0f,0xb6,0x00,0xc0,0xf8,0x07,0x88,0x45,0xe6,0x48,0x8b,0x45, - 0xe8,0x48,0x8d,0x50,0xff,0x48,0x8b,0x45,0xd8,0x48,0x01,0xd0,0x0f,0xb6,0x30,0x0f, - 0xb6,0x45,0xe7,0x22,0x45,0xe6,0x89,0xc1,0x48,0x8b,0x45,0xe8,0x48,0x8d,0x50,0xff, - 0x48,0x8b,0x45,0xd8,0x48,0x01,0xd0,0x31,0xce,0x89,0xf2,0x88,0x10,0xb8,0x00,0x00, - 0x00,0x00,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x83,0xec,0x38,0x48,0x89,0x7d,0xd8, - 0x48,0x89,0x75,0xd0,0x48,0x89,0x55,0xc8,0x48,0x8d,0x45,0xe0,0xbe,0x10,0x00,0x00, - 0x00,0x48,0x89,0xc7,0xe8,0xdf,0xfe,0xff,0xff,0x48,0x8b,0x45,0xd8,0x48,0x8b,0x40, - 0x18,0x48,0x8d,0x55,0xe0,0x48,0x8d,0x4d,0xe0,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8, - 0xaa,0xfd,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x38,0x48,0x8d,0x55, - 0xe0,0x48,0x8b,0x45,0xd0,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0xe1,0xfe,0xff,0xff, - 0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x1f,0x48,0x8b,0x55,0xd0,0x48,0x8b,0x45, - 0xc8,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0xc5,0xfe,0xff,0xff,0x89,0x45,0xfc,0x83, - 0x7d,0xfc,0x00,0xeb,0x04,0x90,0xeb,0x01,0x90,0x48,0x8d,0x45,0xe0,0xbe,0x10,0x00, - 0x00,0x00,0x48,0x89,0xc7,0xe8,0x6e,0xfe,0xff,0xff,0x8b,0x45,0xfc,0xc9,0xc3,0x55, - 0x48,0x89,0xe5,0x48,0x83,0xec,0x20,0x48,0x89,0x7d,0xe8,0x48,0x89,0x75,0xe0,0x48, - 0x83,0x7d,0xe8,0x00,0x75,0x07,0xb8,0x00,0x9f,0xff,0xff,0xeb,0x38,0x48,0x8b,0x45, - 0xe8,0x48,0x8b,0x40,0x18,0x48,0x89,0x45,0xf8,0x48,0x8b,0x45,0xf8,0x48,0x8d,0x50, - 0x10,0x48,0x8b,0x45,0xf8,0x48,0x89,0x50,0x08,0x48,0x8b,0x45,0xf8,0x48,0x8b,0x40, - 0x08,0x48,0x8b,0x55,0xe0,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x5c,0xfd,0xff,0xff, - 0xb8,0x00,0x00,0x00,0x00,0xc9,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x48, - 0x89,0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb, - 0x32,0x48,0x8b,0x55,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x30,0x48, - 0x8b,0x55,0xd8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x08,0x48,0x8b,0x55, - 0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x31,0xce,0x89,0xf2,0x88,0x10,0x48,0x83, - 0x45,0xf8,0x01,0x48,0x83,0x7d,0xf8,0x0f,0x76,0xc7,0x90,0x5d,0xc3,0x55,0x48,0x89, - 0xe5,0x48,0x89,0x7d,0xe8,0x48,0x89,0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0x89,0x4d, - 0xd0,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x54,0x48,0x8b,0x45,0xf8,0x48, - 0x3b,0x45,0xd0,0x73,0x1d,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0, - 0x48,0x8b,0x4d,0xe8,0x48,0x8b,0x55,0xf8,0x48,0x01,0xca,0x0f,0xb6,0x00,0x88,0x02, - 0xeb,0x28,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45,0xd0,0x75,0x10,0x48,0x8b,0x55,0xe8, - 0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0xc6,0x00,0x80,0xeb,0x0e,0x48,0x8b,0x55,0xe8, - 0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0xc6,0x00,0x00,0x48,0x83,0x45,0xf8,0x01,0x48, - 0x8b,0x45,0xf8,0x48,0x3b,0x45,0xe0,0x72,0xa2,0x90,0x5d,0xc3,0x55,0x48,0x89,0xe5, - 0x48,0x83,0xec,0x50,0x48,0x89,0x7d,0xc8,0x48,0x89,0x75,0xc0,0x48,0x89,0x55,0xb8, - 0xc7,0x45,0xfc,0x00,0x00,0x00,0x00,0x48,0x83,0x7d,0xc8,0x00,0x74,0x14,0x48,0x83, - 0x7d,0xc0,0x00,0x74,0x0d,0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x20,0x48,0x85,0xc0, - 0x75,0x0a,0xb8,0x00,0x9f,0xff,0xff,0xe9,0xc4,0x01,0x00,0x00,0x48,0x8b,0x45,0xc8, - 0x48,0x8b,0x40,0x20,0x48,0x89,0x45,0xe8,0x48,0xc7,0x45,0xe0,0x10,0x00,0x00,0x00, - 0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x20,0x48,0x89,0x45,0xd8,0x48,0x8b,0x45,0xe8, - 0x48,0x8b,0x40,0x20,0x48,0x85,0xc0,0x0f,0x84,0xc2,0x00,0x00,0x00,0x48,0x8b,0x45, - 0xe8,0x48,0x8b,0x40,0x20,0x48,0x8b,0x55,0xe0,0x48,0x29,0xc2,0x48,0x89,0xd0,0x48, - 0x39,0x45,0xb8,0x0f,0x86,0xa6,0x00,0x00,0x00,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40, - 0x20,0x48,0x8b,0x55,0xe0,0x48,0x29,0xc2,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20, - 0x48,0x8d,0x48,0x10,0x48,0x8b,0x45,0xe8,0x48,0x01,0xc1,0x48,0x8b,0x45,0xc0,0x48, - 0x89,0xc6,0x48,0x89,0xcf,0xe8,0x30,0xfa,0xff,0xff,0x48,0x8b,0x45,0xe8,0x48,0x8d, - 0x48,0x10,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x45,0xd8,0x48,0x89,0xce,0x48,0x89,0xc7, - 0xe8,0x52,0xfe,0xff,0xff,0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x18,0x48,0x8b,0x55, - 0xd8,0x48,0x8b,0x4d,0xd8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x2e,0xfb,0xff,0xff, - 0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x0f,0x85,0xf9,0x00,0x00,0x00,0x48,0x8b,0x45, - 0xe8,0x48,0x8b,0x40,0x20,0x48,0x8b,0x55,0xe0,0x48,0x29,0xc2,0x48,0x89,0xd0,0x48, - 0x01,0x45,0xc0,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20,0x48,0x2b,0x45,0xe0,0x48, - 0x01,0x45,0xb8,0x48,0x8b,0x45,0xe8,0x48,0xc7,0x40,0x20,0x00,0x00,0x00,0x00,0x48, - 0x8b,0x55,0xb8,0x48,0x8b,0x45,0xe0,0x48,0x01,0xd0,0x48,0x83,0xe8,0x01,0xba,0x00, - 0x00,0x00,0x00,0x48,0xf7,0x75,0xe0,0x48,0x89,0x45,0xd0,0x48,0xc7,0x45,0xf0,0x01, - 0x00,0x00,0x00,0xeb,0x50,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x4d,0xc0,0x48,0x8b,0x45, - 0xd8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0xbb,0xfd,0xff,0xff,0x48,0x8b,0x45,0xc8, - 0x48,0x8b,0x40,0x18,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x4d,0xd8,0x48,0x89,0xce,0x48, - 0x89,0xc7,0xe8,0x97,0xfa,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x69, - 0x48,0x8b,0x45,0xe0,0x48,0x29,0x45,0xb8,0x48,0x8b,0x45,0xe0,0x48,0x01,0x45,0xc0, - 0x48,0x83,0x45,0xf0,0x01,0x48,0x8b,0x45,0xf0,0x48,0x3b,0x45,0xd0,0x72,0xa6,0x48, - 0x83,0x7d,0xb8,0x00,0x74,0x46,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20,0x48,0x8d, - 0x50,0x10,0x48,0x8b,0x45,0xe8,0x48,0x8d,0x0c,0x02,0x48,0x8b,0x55,0xb8,0x48,0x8b, - 0x45,0xc0,0x48,0x89,0xc6,0x48,0x89,0xcf,0xe8,0x0d,0xf9,0xff,0xff,0x48,0x8b,0x45, - 0xe8,0x48,0x8b,0x50,0x20,0x48,0x8b,0x45,0xb8,0x48,0x01,0xc2,0x48,0x8b,0x45,0xe8, - 0x48,0x89,0x50,0x20,0xeb,0x07,0x90,0xeb,0x04,0x90,0xeb,0x01,0x90,0x8b,0x45,0xfc, - 0xc9,0xc3,0x55,0x48,0x89,0xe5,0x48,0x83,0xec,0x70,0x48,0x89,0x7d,0x98,0x48,0x89, + 0xe0,0x48,0x8d,0x50,0xff,0x48,0x89,0x55,0xe0,0x48,0x85,0xc0,0x75,0xe0,0x90,0x90, + 0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xd8,0x48,0x89, + 0x75,0xd0,0xc6,0x45,0xf7,0x87,0xc6,0x45,0xff,0x00,0x48,0xc7,0x45,0xe8,0x10,0x00, + 0x00,0x00,0x0f,0xb6,0x45,0xf7,0x88,0x45,0xe7,0x48,0x8b,0x45,0xe8,0x83,0xe8,0x01, + 0x89,0x45,0xf8,0xeb,0x4a,0x8b,0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd0,0x48, + 0x01,0xd0,0x0f,0xb6,0x00,0x0f,0xb6,0xc0,0x01,0xc0,0x89,0xc2,0x0f,0xb6,0x45,0xff, + 0x89,0xd1,0x09,0xc1,0x8b,0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd8,0x48,0x01, + 0xd0,0x89,0xca,0x88,0x10,0x8b,0x45,0xf8,0x48,0x63,0xd0,0x48,0x8b,0x45,0xd0,0x48, + 0x01,0xd0,0x0f,0xb6,0x00,0xc0,0xe8,0x07,0x88,0x45,0xff,0x83,0x6d,0xf8,0x01,0x83, + 0x7d,0xf8,0x00,0x79,0xb0,0x48,0x8b,0x45,0xd0,0x0f,0xb6,0x00,0xc0,0xf8,0x07,0x88, + 0x45,0xe6,0x48,0x8b,0x45,0xe8,0x48,0x8d,0x50,0xff,0x48,0x8b,0x45,0xd8,0x48,0x01, + 0xd0,0x0f,0xb6,0x08,0x0f,0xb6,0x45,0xe7,0x22,0x45,0xe6,0x89,0xc2,0x48,0x8b,0x45, + 0xe8,0x48,0x8d,0x70,0xff,0x48,0x8b,0x45,0xd8,0x48,0x01,0xf0,0x31,0xca,0x88,0x10, + 0xb8,0x00,0x00,0x00,0x00,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48, + 0x83,0xec,0x40,0x48,0x89,0x7d,0xd8,0x48,0x89,0x75,0xd0,0x48,0x89,0x55,0xc8,0x48, + 0x8d,0x45,0xe0,0xbe,0x10,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0xd4,0xfe,0xff,0xff, + 0x48,0x8b,0x45,0xd8,0x48,0x8b,0x40,0x18,0x48,0x8d,0x55,0xe0,0x48,0x8d,0x4d,0xe0, + 0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x97,0xfd,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d, + 0xfc,0x00,0x75,0x38,0x48,0x8d,0x55,0xe0,0x48,0x8b,0x45,0xd0,0x48,0x89,0xd6,0x48, + 0x89,0xc7,0xe8,0xdb,0xfe,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x1f, + 0x48,0x8b,0x55,0xd0,0x48,0x8b,0x45,0xc8,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0xbf, + 0xfe,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0xeb,0x04,0x90,0xeb,0x01,0x90, + 0x48,0x8d,0x45,0xe0,0xbe,0x10,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x63,0xfe,0xff, + 0xff,0x8b,0x45,0xfc,0xc9,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x83, + 0xec,0x20,0x48,0x89,0x7d,0xe8,0x48,0x89,0x75,0xe0,0x48,0x83,0x7d,0xe8,0x00,0x75, + 0x07,0xb8,0x00,0x9f,0xff,0xff,0xeb,0x38,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x18, + 0x48,0x89,0x45,0xf8,0x48,0x8b,0x45,0xf8,0x48,0x8d,0x50,0x10,0x48,0x8b,0x45,0xf8, + 0x48,0x89,0x50,0x08,0x48,0x8b,0x45,0xf8,0x48,0x8b,0x40,0x08,0x48,0x8b,0x55,0xe0, + 0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x49,0xfd,0xff,0xff,0xb8,0x00,0x00,0x00,0x00, + 0xc9,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x48,0x89,0x75,0xe0,0x48,0x89, + 0x55,0xd8,0x48,0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x30,0x48,0x8b,0x55,0xe0, + 0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x08,0x48,0x8b,0x55,0xd8,0x48,0x8b, + 0x45,0xf8,0x48,0x01,0xd0,0x0f,0xb6,0x10,0x48,0x8b,0x75,0xe8,0x48,0x8b,0x45,0xf8, + 0x48,0x01,0xf0,0x31,0xca,0x88,0x10,0x48,0x83,0x45,0xf8,0x01,0x48,0x83,0x7d,0xf8, + 0x0f,0x76,0xc9,0x90,0x90,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x89,0x7d,0xe8,0x48, + 0x89,0x75,0xe0,0x48,0x89,0x55,0xd8,0x48,0x89,0x4d,0xd0,0x48,0xc7,0x45,0xf8,0x00, + 0x00,0x00,0x00,0xeb,0x54,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45,0xd0,0x73,0x1d,0x48, + 0x8b,0x55,0xd8,0x48,0x8b,0x45,0xf8,0x48,0x01,0xd0,0x48,0x8b,0x4d,0xe8,0x48,0x8b, + 0x55,0xf8,0x48,0x01,0xca,0x0f,0xb6,0x00,0x88,0x02,0xeb,0x28,0x48,0x8b,0x45,0xf8, + 0x48,0x3b,0x45,0xd0,0x75,0x10,0x48,0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01, + 0xd0,0xc6,0x00,0x80,0xeb,0x0e,0x48,0x8b,0x55,0xe8,0x48,0x8b,0x45,0xf8,0x48,0x01, + 0xd0,0xc6,0x00,0x00,0x48,0x83,0x45,0xf8,0x01,0x48,0x8b,0x45,0xf8,0x48,0x3b,0x45, + 0xe0,0x72,0xa2,0x90,0x90,0x5d,0xc3,0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48, + 0x83,0xec,0x50,0x48,0x89,0x7d,0xc8,0x48,0x89,0x75,0xc0,0x48,0x89,0x55,0xb8,0xc7, + 0x45,0xfc,0x00,0x00,0x00,0x00,0x48,0x83,0x7d,0xc8,0x00,0x74,0x14,0x48,0x83,0x7d, + 0xc0,0x00,0x74,0x0d,0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x20,0x48,0x85,0xc0,0x75, + 0x0a,0xb8,0x00,0x9f,0xff,0xff,0xe9,0xc1,0x01,0x00,0x00,0x48,0x8b,0x45,0xc8,0x48, + 0x8b,0x40,0x20,0x48,0x89,0x45,0xe8,0x48,0xc7,0x45,0xe0,0x10,0x00,0x00,0x00,0x48, + 0x8b,0x45,0xc8,0x48,0x8b,0x40,0x20,0x48,0x89,0x45,0xd8,0x48,0x8b,0x45,0xe8,0x48, + 0x8b,0x40,0x20,0x48,0x85,0xc0,0x0f,0x84,0xbf,0x00,0x00,0x00,0x48,0x8b,0x45,0xe8, + 0x48,0x8b,0x50,0x20,0x48,0x8b,0x45,0xe0,0x48,0x29,0xd0,0x48,0x39,0x45,0xb8,0x0f, + 0x86,0xa6,0x00,0x00,0x00,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x48,0x20,0x48,0x8b,0x45, + 0xe0,0x48,0x29,0xc8,0x48,0x89,0xc2,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20,0x48, + 0x8d,0x48,0x10,0x48,0x8b,0x45,0xe8,0x48,0x01,0xc1,0x48,0x8b,0x45,0xc0,0x48,0x89, + 0xc6,0x48,0x89,0xcf,0xe8,0x0b,0xfa,0xff,0xff,0x48,0x8b,0x45,0xe8,0x48,0x8d,0x48, + 0x10,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x45,0xd8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8, + 0x4e,0xfe,0xff,0xff,0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x18,0x48,0x8b,0x55,0xd8, + 0x48,0x8b,0x4d,0xd8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x13,0xfb,0xff,0xff,0x89, + 0x45,0xfc,0x83,0x7d,0xfc,0x00,0x0f,0x85,0xf6,0x00,0x00,0x00,0x48,0x8b,0x45,0xe8, + 0x48,0x8b,0x50,0x20,0x48,0x8b,0x45,0xe0,0x48,0x29,0xd0,0x48,0x01,0x45,0xc0,0x48, + 0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20,0x48,0x2b,0x45,0xe0,0x48,0x01,0x45,0xb8,0x48, + 0x8b,0x45,0xe8,0x48,0xc7,0x40,0x20,0x00,0x00,0x00,0x00,0x48,0x8b,0x55,0xb8,0x48, + 0x8b,0x45,0xe0,0x48,0x01,0xd0,0x48,0x83,0xe8,0x01,0xba,0x00,0x00,0x00,0x00,0x48, + 0xf7,0x75,0xe0,0x48,0x89,0x45,0xd0,0x48,0xc7,0x45,0xf0,0x01,0x00,0x00,0x00,0xeb, + 0x50,0x48,0x8b,0x55,0xd8,0x48,0x8b,0x4d,0xc0,0x48,0x8b,0x45,0xd8,0x48,0x89,0xce, + 0x48,0x89,0xc7,0xe8,0xba,0xfd,0xff,0xff,0x48,0x8b,0x45,0xc8,0x48,0x8b,0x40,0x18, + 0x48,0x8b,0x55,0xd8,0x48,0x8b,0x4d,0xd8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x7f, + 0xfa,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x69,0x48,0x8b,0x45,0xe0, + 0x48,0x29,0x45,0xb8,0x48,0x8b,0x45,0xe0,0x48,0x01,0x45,0xc0,0x48,0x83,0x45,0xf0, + 0x01,0x48,0x8b,0x45,0xf0,0x48,0x3b,0x45,0xd0,0x72,0xa6,0x48,0x83,0x7d,0xb8,0x00, + 0x74,0x46,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x40,0x20,0x48,0x8d,0x50,0x10,0x48,0x8b, + 0x45,0xe8,0x48,0x8d,0x0c,0x02,0x48,0x8b,0x55,0xb8,0x48,0x8b,0x45,0xc0,0x48,0x89, + 0xc6,0x48,0x89,0xcf,0xe8,0xeb,0xf8,0xff,0xff,0x48,0x8b,0x45,0xe8,0x48,0x8b,0x50, + 0x20,0x48,0x8b,0x45,0xb8,0x48,0x01,0xc2,0x48,0x8b,0x45,0xe8,0x48,0x89,0x50,0x20, + 0xeb,0x07,0x90,0xeb,0x04,0x90,0xeb,0x01,0x90,0x8b,0x45,0xfc,0xc9,0xc3,0xf3,0x0f, + 0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x83,0xec,0x70,0x48,0x89,0x7d,0x98,0x48,0x89, 0x75,0x90,0x48,0x83,0x7d,0x98,0x00,0x74,0x14,0x48,0x8b,0x45,0x98,0x48,0x8b,0x40, 0x20,0x48,0x85,0xc0,0x74,0x07,0x48,0x83,0x7d,0x90,0x00,0x75,0x0a,0xb8,0x00,0x9f, 0xff,0xff,0xe9,0x67,0x01,0x00,0x00,0x48,0x8b,0x45,0x98,0x48,0x8b,0x40,0x20,0x48, 0x89,0x45,0xf8,0x48,0xc7,0x45,0xf0,0x10,0x00,0x00,0x00,0x48,0x8b,0x45,0xf8,0x48, 0x89,0x45,0xe8,0x48,0x8d,0x45,0xc0,0xbe,0x10,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8, - 0xd4,0xfa,0xff,0xff,0x48,0x8d,0x45,0xb0,0xbe,0x10,0x00,0x00,0x00,0x48,0x89,0xc7, - 0xe8,0xc3,0xfa,0xff,0xff,0x48,0x8d,0x55,0xb0,0x48,0x8d,0x4d,0xc0,0x48,0x8b,0x45, - 0x98,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0xa8,0xfb,0xff,0xff,0x48,0x8b,0x45,0xf8, + 0xc0,0xfa,0xff,0xff,0x48,0x8d,0x45,0xb0,0xbe,0x10,0x00,0x00,0x00,0x48,0x89,0xc7, + 0xe8,0xaf,0xfa,0xff,0xff,0x48,0x8d,0x55,0xb0,0x48,0x8d,0x4d,0xc0,0x48,0x8b,0x45, + 0x98,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x9b,0xfb,0xff,0xff,0x48,0x8b,0x45,0xf8, 0x48,0x83,0xc0,0x10,0x48,0x89,0x45,0xe0,0x48,0x8b,0x45,0xf8,0x48,0x8b,0x40,0x20, 0x48,0x39,0x45,0xf0,0x76,0x35,0x48,0x8b,0x45,0xf8,0x48,0x8b,0x48,0x20,0x48,0x8b, - 0x55,0xe0,0x48,0x8b,0x75,0xf0,0x48,0x8d,0x45,0xa0,0x48,0x89,0xc7,0xe8,0xbb,0xfc, + 0x55,0xe0,0x48,0x8b,0x75,0xf0,0x48,0x8d,0x45,0xa0,0x48,0x89,0xc7,0xe8,0xb5,0xfc, 0xff,0xff,0x48,0x8d,0x55,0xb0,0x48,0x8d,0x4d,0xa0,0x48,0x8d,0x45,0xa0,0x48,0x89, - 0xce,0x48,0x89,0xc7,0xe8,0x4e,0xfc,0xff,0xff,0xeb,0x17,0x48,0x8d,0x55,0xc0,0x48, - 0x8b,0x4d,0xe0,0x48,0x8d,0x45,0xa0,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x35,0xfc, + 0xce,0x48,0x89,0xc7,0xe8,0x49,0xfc,0xff,0xff,0xeb,0x17,0x48,0x8d,0x55,0xc0,0x48, + 0x8b,0x4d,0xe0,0x48,0x8d,0x45,0xa0,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x30,0xfc, 0xff,0xff,0x48,0x8b,0x55,0xe8,0x48,0x8d,0x4d,0xa0,0x48,0x8b,0x45,0xe8,0x48,0x89, - 0xce,0x48,0x89,0xc7,0xe8,0x1e,0xfc,0xff,0xff,0x48,0x8b,0x45,0x98,0x48,0x8b,0x40, + 0xce,0x48,0x89,0xc7,0xe8,0x19,0xfc,0xff,0xff,0x48,0x8b,0x45,0x98,0x48,0x8b,0x40, 0x18,0x48,0x8b,0x55,0xe8,0x48,0x8b,0x4d,0xe8,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8, - 0xfa,0xf8,0xff,0xff,0x89,0x45,0xdc,0x83,0x7d,0xdc,0x00,0x75,0x19,0x48,0x8b,0x55, + 0xde,0xf8,0xff,0xff,0x89,0x45,0xdc,0x83,0x7d,0xdc,0x00,0x75,0x19,0x48,0x8b,0x55, 0xf0,0x48,0x8b,0x4d,0xe8,0x48,0x8b,0x45,0x90,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8, - 0xa6,0xf7,0xff,0xff,0xeb,0x01,0x90,0x48,0x8d,0x45,0xc0,0xbe,0x10,0x00,0x00,0x00, - 0x48,0x89,0xc7,0xe8,0xe0,0xf9,0xff,0xff,0x48,0x8d,0x45,0xb0,0xbe,0x10,0x00,0x00, - 0x00,0x48,0x89,0xc7,0xe8,0xcf,0xf9,0xff,0xff,0x48,0x8b,0x45,0xf8,0x48,0xc7,0x40, + 0x80,0xf7,0xff,0xff,0xeb,0x01,0x90,0x48,0x8d,0x45,0xc0,0xbe,0x10,0x00,0x00,0x00, + 0x48,0x89,0xc7,0xe8,0xcc,0xf9,0xff,0xff,0x48,0x8d,0x45,0xb0,0xbe,0x10,0x00,0x00, + 0x00,0x48,0x89,0xc7,0xe8,0xbb,0xf9,0xff,0xff,0x48,0x8b,0x45,0xf8,0x48,0xc7,0x40, 0x20,0x00,0x00,0x00,0x00,0x48,0x8b,0x45,0xf8,0x48,0x83,0xc0,0x10,0xbe,0x10,0x00, - 0x00,0x00,0x48,0x89,0xc7,0xe8,0xae,0xf9,0xff,0xff,0x48,0x8b,0x45,0xe8,0xbe,0x10, - 0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x9d,0xf9,0xff,0xff,0x8b,0x45,0xdc,0xc9,0xc3, - 0x55,0x48,0x89,0xe5,0x48,0x81,0xec,0xa0,0x01,0x00,0x00,0x48,0x89,0xbd,0x78,0xfe, - 0xff,0xff,0x48,0x89,0xb5,0x70,0xfe,0xff,0xff,0x48,0x89,0x95,0x68,0xfe,0xff,0xff, - 0x48,0x89,0x8d,0x60,0xfe,0xff,0xff,0x48,0x83,0xbd,0x78,0xfe,0xff,0xff,0x00,0x74, - 0x14,0x48,0x83,0xbd,0x70,0xfe,0xff,0xff,0x00,0x74,0x0a,0x48,0x83,0xbd,0x60,0xfe, - 0xff,0xff,0x00,0x75,0x0a,0xb8,0x00,0x9f,0xff,0xff,0xe9,0x12,0x01,0x00,0x00,0x48, - 0x8d,0x45,0xd0,0xba,0x28,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7, - 0xe8,0xa7,0xf6,0xff,0xff,0x48,0x8d,0x85,0xb0,0xfe,0xff,0xff,0xba,0x20,0x01,0x00, - 0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x8e,0xf6,0xff,0xff,0x48,0x8d, - 0x85,0xb0,0xfe,0xff,0xff,0x48,0x89,0x45,0xe8,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff, - 0xba,0x28,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x6a,0xf6, - 0xff,0xff,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff,0xbe,0x10,0x00,0x00,0x00,0x48,0x89, - 0xc7,0xe8,0xe2,0xf8,0xff,0xff,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff,0x48,0x89,0x45, - 0xf0,0x48,0x8b,0x95,0x78,0xfe,0xff,0xff,0x48,0x8d,0x45,0xd0,0x48,0x89,0xd6,0x48, - 0x89,0xc7,0xe8,0x58,0xfa,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x41, - 0x48,0x8b,0x95,0x68,0xfe,0xff,0xff,0x48,0x8b,0x8d,0x70,0xfe,0xff,0xff,0x48,0x8d, - 0x45,0xd0,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x5f,0xfb,0xff,0xff,0x89,0x45,0xfc, - 0x83,0x7d,0xfc,0x00,0x75,0x1e,0x48,0x8b,0x95,0x60,0xfe,0xff,0xff,0x48,0x8d,0x45, - 0xd0,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x46,0xfd,0xff,0xff,0x89,0x45,0xfc,0xeb, - 0x04,0x90,0xeb,0x01,0x90,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff,0xbe,0x28,0x00,0x00, - 0x00,0x48,0x89,0xc7,0xe8,0x5f,0xf8,0xff,0xff,0x48,0x8d,0x85,0xb0,0xfe,0xff,0xff, - 0xbe,0x20,0x01,0x00,0x00,0x48,0x89,0xc7,0xe8,0x4b,0xf8,0xff,0xff,0x48,0x8d,0x45, - 0xd0,0xbe,0x28,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x3a,0xf8,0xff,0xff,0x8b,0x45, - 0xfc,0xc9,0xc3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x48,0x89,0xc7,0xe8,0x9a,0xf9,0xff,0xff,0x48,0x8b,0x45,0xe8,0xbe,0x10, + 0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x89,0xf9,0xff,0xff,0x8b,0x45,0xdc,0xc9,0xc3, + 0xf3,0x0f,0x1e,0xfa,0x55,0x48,0x89,0xe5,0x48,0x81,0xec,0xa0,0x01,0x00,0x00,0x48, + 0x89,0xbd,0x78,0xfe,0xff,0xff,0x48,0x89,0xb5,0x70,0xfe,0xff,0xff,0x48,0x89,0x95, + 0x68,0xfe,0xff,0xff,0x48,0x89,0x8d,0x60,0xfe,0xff,0xff,0x48,0x83,0xbd,0x78,0xfe, + 0xff,0xff,0x00,0x74,0x14,0x48,0x83,0xbd,0x70,0xfe,0xff,0xff,0x00,0x74,0x0a,0x48, + 0x83,0xbd,0x60,0xfe,0xff,0xff,0x00,0x75,0x0a,0xb8,0x00,0x9f,0xff,0xff,0xe9,0x12, + 0x01,0x00,0x00,0x48,0x8d,0x45,0xd0,0xba,0x28,0x00,0x00,0x00,0xbe,0x00,0x00,0x00, + 0x00,0x48,0x89,0xc7,0xe8,0x79,0xf6,0xff,0xff,0x48,0x8d,0x85,0xb0,0xfe,0xff,0xff, + 0xba,0x20,0x01,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x60,0xf6, + 0xff,0xff,0x48,0x8d,0x85,0xb0,0xfe,0xff,0xff,0x48,0x89,0x45,0xe8,0x48,0x8d,0x85, + 0x80,0xfe,0xff,0xff,0xba,0x28,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x48,0x89, + 0xc7,0xe8,0x3c,0xf6,0xff,0xff,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff,0xbe,0x10,0x00, + 0x00,0x00,0x48,0x89,0xc7,0xe8,0xca,0xf8,0xff,0xff,0x48,0x8d,0x85,0x80,0xfe,0xff, + 0xff,0x48,0x89,0x45,0xf0,0x48,0x8b,0x95,0x78,0xfe,0xff,0xff,0x48,0x8d,0x45,0xd0, + 0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x4b,0xfa,0xff,0xff,0x89,0x45,0xfc,0x83,0x7d, + 0xfc,0x00,0x75,0x41,0x48,0x8b,0x95,0x68,0xfe,0xff,0xff,0x48,0x8b,0x8d,0x70,0xfe, + 0xff,0xff,0x48,0x8d,0x45,0xd0,0x48,0x89,0xce,0x48,0x89,0xc7,0xe8,0x56,0xfb,0xff, + 0xff,0x89,0x45,0xfc,0x83,0x7d,0xfc,0x00,0x75,0x1e,0x48,0x8b,0x95,0x60,0xfe,0xff, + 0xff,0x48,0x8d,0x45,0xd0,0x48,0x89,0xd6,0x48,0x89,0xc7,0xe8,0x3e,0xfd,0xff,0xff, + 0x89,0x45,0xfc,0xeb,0x04,0x90,0xeb,0x01,0x90,0x48,0x8d,0x85,0x80,0xfe,0xff,0xff, + 0xbe,0x28,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x47,0xf8,0xff,0xff,0x48,0x8d,0x85, + 0xb0,0xfe,0xff,0xff,0xbe,0x20,0x01,0x00,0x00,0x48,0x89,0xc7,0xe8,0x33,0xf8,0xff, + 0xff,0x48,0x8d,0x45,0xd0,0xbe,0x28,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x22,0xf8, + 0xff,0xff,0x8b,0x45,0xfc,0xc9,0xc3,0x48,0x8d,0x83,0x00,0x90,0x00,0x00,0x48,0x94, + 0x50,0x51,0x53,0xe8,0x6a,0xf3,0xff,0xff,0x5b,0x48,0xb8,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x48,0xba,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x48,0x8d,0xbb, + 0x00,0x50,0x00,0x00,0x0f,0xae,0x0f,0x48,0x31,0xc9,0x48,0x31,0xd2,0x48,0x31,0xff, + 0x48,0x31,0xf6,0x4d,0x31,0xc0,0x4d,0x31,0xc9,0x4d,0x31,0xd2,0x4d,0x31,0xdb,0x4d, + 0x31,0xe4,0x4d,0x31,0xed,0x4d,0x31,0xf6,0x4d,0x31,0xff,0x48,0x01,0xd2,0x5b,0x58, + 0x48,0x89,0xc4,0x48,0xc7,0xc0,0x04,0x00,0x00,0x00,0x0f,0x01,0xd7,0x53,0x48,0x31, + 0xc0,0x48,0x89,0xfb,0x48,0x89,0xf1,0x0f,0x01,0xd7,0x5b,0xc3,0x53,0x48,0xc7,0xc0, + 0x01,0x00,0x00,0x00,0x48,0x89,0xfb,0x48,0x89,0xf1,0x0f,0x01,0xd7,0x5b,0xc3,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, diff --git a/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_ss.h b/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_ss.h index ba803a44..2116e173 100644 --- a/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_ss.h +++ b/driver/win/WinLeProxyUMDF/FLC_Enclave/sgx_le_ss.h @@ -30,13 +30,14 @@ */ #pragma once -#define LE_SS_LEN 1808 -const int sgx_le_ss_length = LE_SS_LEN; -const unsigned char sgx_le_ss[LE_SS_LEN] = +#define SGX_LE_SS_LEN 1808 +const int sgx_le_ss_length = SGX_LE_SS_LEN; + +const unsigned char sgx_le_ss[SGX_LE_SS_LEN] = { 0x06,0x00,0x00,0x00,0xe1,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00, - 0x86,0x80,0x00,0x00,0x14,0x06,0x19,0x20,0x01,0x01,0x00,0x00,0x60,0x00,0x00,0x00, + 0x86,0x80,0x00,0x00,0x16,0x02,0x23,0x20,0x01,0x01,0x00,0x00,0x60,0x00,0x00,0x00, 0x60,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -67,85 +68,85 @@ const unsigned char sgx_le_ss[LE_SS_LEN] = 0x8f,0xc6,0xaf,0xff,0x2d,0x93,0x34,0x23,0x4c,0x4e,0x82,0x2e,0xfc,0x03,0x22,0xb1, 0x9c,0x0c,0xab,0x9b,0x98,0xfb,0xc7,0x8d,0x74,0x0e,0x0e,0xc3,0x5c,0xf2,0x81,0x98, 0x90,0x09,0x42,0xff,0xba,0x22,0xf0,0xd8,0x6f,0x2e,0xcd,0x4e,0x0f,0xdd,0x74,0x9f, - 0x03,0x00,0x00,0x00,0x6d,0x08,0xfa,0x01,0xb5,0x6f,0xd7,0xef,0x8e,0xda,0xcb,0x43, - 0xba,0xd6,0x57,0xc8,0xa2,0xa2,0xbc,0xd0,0x3d,0x20,0xe7,0x5a,0xb0,0xb1,0xd4,0xee, - 0x6a,0xce,0xb2,0x9a,0xb5,0x7e,0x3d,0x9a,0xf1,0xf9,0x66,0x32,0xa1,0x20,0x4a,0xfe, - 0xd4,0xac,0xa9,0xe9,0xfe,0x1b,0x60,0x54,0xbc,0x40,0x3b,0xdb,0x73,0x1c,0x2f,0xd1, - 0xe3,0xa9,0x3c,0x75,0x23,0xf3,0x3e,0xfc,0x72,0x55,0x40,0x26,0xe0,0x53,0x28,0x4a, - 0x6b,0xce,0xcc,0xb1,0x76,0xd6,0x47,0x74,0x51,0x30,0x2a,0x3e,0x94,0x99,0xa2,0x9e, - 0x2a,0xc7,0x4f,0x5a,0x41,0xad,0xa1,0xfa,0x8f,0xe5,0x95,0x3b,0xc6,0xac,0x58,0xa1, - 0x45,0x92,0xa5,0x60,0xe1,0x94,0xf5,0xf9,0xd7,0xe8,0xbd,0x11,0x41,0xc7,0x43,0xb0, - 0x93,0x5c,0xb6,0x6f,0xe0,0xca,0xca,0xee,0x43,0xa5,0x2c,0x79,0x91,0x4a,0xc4,0x5c, - 0x1e,0x90,0x69,0xaa,0x08,0xdb,0xa3,0x63,0x85,0xc0,0x5f,0x0b,0xd4,0x85,0x69,0xc2, - 0x0d,0x97,0xb2,0xe8,0x80,0x69,0x72,0xb6,0x34,0x59,0x7e,0xa9,0x73,0xea,0xbb,0xd7, - 0x80,0xfb,0x57,0x9e,0xf3,0x14,0x97,0xd9,0xbb,0xbe,0xc7,0x8f,0xbd,0x74,0x5f,0xf9, - 0x5b,0x0f,0x3b,0x56,0xe0,0x78,0x97,0x95,0x57,0x03,0x56,0x3b,0xb7,0x37,0x57,0x20, - 0xfb,0x59,0x69,0x0d,0x2d,0xcc,0x80,0xcb,0x61,0x4b,0x13,0xba,0x2c,0x43,0x24,0x7a, - 0x34,0xdd,0x33,0xe3,0x2d,0xf0,0x53,0xb3,0xaa,0xf4,0xc8,0xe1,0x13,0xd3,0x88,0x77, - 0x7a,0x63,0xe3,0xb7,0xa4,0xf9,0x08,0x90,0xd2,0xf9,0x71,0xd2,0xb5,0xf9,0x83,0xf3, - 0x98,0x0a,0x33,0x83,0xcd,0x3c,0xc3,0xc2,0x24,0x36,0xaa,0xfc,0xd4,0x71,0xf9,0xf0, - 0xf7,0x33,0xb6,0x0c,0x8c,0xb6,0x97,0xee,0xf5,0xdb,0xaa,0x7a,0xd3,0x68,0x0d,0xc6, - 0x44,0x6f,0x22,0xaf,0xdb,0x37,0x8c,0xfa,0x44,0x3c,0x35,0x36,0x94,0x04,0xa0,0xc9, - 0x26,0x92,0x2d,0xbe,0x63,0x7c,0x8f,0x61,0xd2,0x9e,0xaf,0x8b,0xc9,0x5a,0xbd,0x9b, - 0x57,0xe7,0x47,0x57,0xb0,0x20,0x14,0x4d,0x06,0x45,0x0e,0xea,0xdf,0xe4,0xaa,0x62, - 0x4d,0x00,0x52,0xaf,0x2c,0x04,0xb1,0x24,0x5a,0x6e,0xf9,0x54,0x33,0x51,0x40,0x97, - 0x01,0xd0,0x97,0x77,0x46,0xf0,0x65,0xce,0x73,0x47,0x1b,0x0a,0x22,0xe0,0xfe,0x30, - 0x7e,0x2d,0x54,0x35,0x13,0xbe,0xb0,0xbc,0xe2,0x96,0x46,0x69,0xe9,0x74,0x59,0x8e, - 0x29,0xf6,0xcc,0x2c,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x00,0x00,0x00,0x7f,0xa0,0x93,0x8b,0x62,0x54,0x04,0x58,0xea,0x85,0x02,0xca, + 0xbf,0x40,0x82,0xb3,0x68,0x6f,0x1c,0xf2,0x0d,0xc6,0x1c,0x06,0x9c,0xcb,0x4b,0xcc, + 0x22,0x27,0x3e,0x0e,0x39,0xe7,0xc5,0xc5,0x23,0x68,0xaf,0x75,0xad,0x0f,0x99,0x78, + 0x23,0x87,0x2c,0x19,0x09,0xdd,0xd0,0xcd,0x60,0x16,0xbf,0x54,0x23,0xe8,0x47,0x4e, + 0x06,0x11,0x02,0x6d,0x09,0xf1,0x18,0x36,0x21,0x05,0xdd,0xd7,0x92,0xf2,0x8e,0x1c, + 0xe9,0xc5,0x2a,0x5b,0x94,0x2a,0x2f,0x27,0x97,0x79,0x3d,0x54,0xc0,0xf3,0xcc,0x94, + 0xce,0xdd,0xc0,0x3f,0x46,0x8c,0xa8,0x09,0xc7,0xf1,0xe6,0x79,0x97,0x5f,0xaa,0xae, + 0x26,0xe5,0x26,0x5e,0xc3,0x2f,0xdd,0x2d,0x7c,0xdd,0x01,0x3e,0x39,0x1b,0xd0,0xe8, + 0xc9,0x0f,0x2a,0x23,0x05,0x54,0xec,0x55,0xdf,0xe9,0x93,0x83,0x79,0x1e,0xb0,0xa5, + 0x6c,0xde,0xf3,0x11,0x94,0xa1,0x3e,0x35,0x58,0xa6,0x2d,0xb3,0x04,0xf9,0x73,0x01, + 0x2d,0x44,0x71,0xf8,0xf5,0x3a,0x9c,0x3f,0xfb,0xe4,0xc9,0xbd,0x0c,0x58,0x73,0x59, + 0x8c,0xa9,0x9b,0x6d,0x1b,0x56,0x0b,0xd8,0xe5,0x0c,0xc7,0x89,0xf5,0x49,0x3b,0xbe, + 0xde,0x53,0x76,0x3c,0x9e,0xf4,0xed,0x35,0x56,0x9a,0x09,0x2b,0x25,0x54,0x6c,0xd7, + 0x53,0xd4,0xfe,0xe7,0x75,0x05,0x51,0x1c,0x09,0x53,0x4c,0x00,0x66,0x20,0x13,0xbe, + 0x94,0x09,0x11,0x8d,0x10,0xa3,0xa3,0x6f,0xd8,0xc6,0x0a,0x96,0xf4,0x80,0xd5,0x74, + 0x5f,0x08,0xa6,0x6b,0x03,0xd3,0x35,0x9b,0x53,0x52,0xe7,0x9c,0x38,0x74,0xc0,0x10, + 0xe2,0xdf,0x89,0xca,0x7c,0x57,0x53,0x6d,0x9f,0x78,0xca,0x7a,0x26,0xfb,0x81,0x95, + 0x30,0x39,0x30,0xa4,0x7b,0x54,0x77,0x86,0xae,0x47,0xb3,0xf4,0xd5,0x67,0xa5,0x79, + 0x8b,0x3a,0xea,0xe6,0xc3,0xbd,0x58,0xfb,0xf9,0x47,0x90,0x8e,0xa0,0x70,0x0d,0x00, + 0x66,0xc6,0xfe,0xcb,0x38,0x1c,0x86,0x5c,0x18,0x26,0x61,0x3e,0x89,0x12,0x18,0x04, + 0x36,0x15,0xd2,0x6a,0x9c,0xd1,0x6e,0x0e,0x93,0x11,0xd8,0xcf,0xa9,0x58,0x96,0x7b, + 0x9a,0xb0,0xa8,0xb5,0xda,0x39,0x86,0x2c,0x14,0x1f,0x0a,0xf9,0x87,0x61,0xd3,0x67, + 0xd1,0xb5,0xd2,0x9e,0x97,0xe1,0xa5,0xb1,0xb4,0x3f,0x5a,0x6f,0x07,0x14,0xe8,0xf0, + 0x74,0x87,0xa0,0x46,0xd4,0xff,0xa1,0xa8,0xfe,0xde,0x61,0x6d,0xa0,0xed,0xad,0x58, + 0xb4,0xda,0x58,0x06,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, - 0xda,0xa8,0x00,0x28,0x8f,0x0d,0x88,0xb0,0x78,0x99,0x94,0x77,0x53,0x8b,0xb9,0xc0, - 0x88,0x8d,0x1e,0x0e,0xaf,0xe0,0xbe,0x20,0x2c,0x49,0x3d,0x3e,0x27,0x4e,0xd9,0x80, + 0xb8,0x93,0xd2,0x89,0x25,0xd1,0x97,0x2a,0x91,0x64,0xb1,0x7a,0x32,0x6c,0xa2,0xaf, + 0xb0,0xb6,0xea,0x85,0x6e,0x17,0xdf,0xe1,0x21,0x7a,0xb1,0x17,0x4f,0x38,0x2f,0xeb, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0xa4,0x6b,0x8b,0xb2,0x71,0x19,0x62,0x38,0xba,0x8d,0x96,0x19,0xbf,0x63,0x6a,0xaf, - 0xbb,0x75,0x3c,0xb5,0xa7,0x2f,0x1c,0xad,0x36,0x9d,0xdf,0x9e,0x23,0xd3,0x9b,0x2d, - 0x3d,0x96,0xda,0x7e,0x36,0x3e,0xf0,0x37,0x1b,0xc0,0x3a,0xfb,0x4c,0xad,0x36,0xe4, - 0xaf,0x7a,0x80,0x77,0x87,0xaf,0x43,0x7b,0xe8,0x3a,0x0b,0x8d,0xef,0x38,0x7c,0x1b, - 0x14,0x3f,0x7c,0xf8,0x5f,0xb7,0x93,0x13,0xb9,0xd2,0x10,0xb6,0x6e,0x02,0x32,0x7f, - 0xeb,0xd9,0x93,0x73,0x66,0x2d,0x42,0xd7,0x2e,0xaf,0xe1,0x3d,0x8c,0x9e,0x75,0x51, - 0x30,0xad,0x3f,0xa6,0xd8,0xbb,0x8f,0xd2,0xdc,0x6a,0xb8,0xa0,0x86,0x25,0xde,0xfe, - 0xd1,0x7b,0x72,0x9f,0x6b,0x66,0xc7,0xd7,0x25,0x61,0x27,0x52,0x58,0x50,0xc4,0xa5, - 0xee,0x88,0xc5,0x47,0x6d,0xaf,0x50,0xaf,0x3f,0xac,0xfb,0xdc,0x36,0x00,0xa2,0x91, - 0xf7,0xb3,0x3a,0x08,0x87,0x33,0xd5,0xb0,0xb2,0x05,0xcd,0x0a,0xa3,0x16,0xc6,0x28, - 0x26,0x41,0xc1,0xea,0x73,0x41,0x29,0xb8,0x02,0xe2,0x08,0x4d,0xff,0xba,0x3c,0x18, - 0x3e,0xc5,0x40,0x21,0xa9,0x2f,0x7c,0x32,0x4f,0x4f,0x8b,0x3a,0x29,0x14,0xa6,0x90, - 0x3c,0x0a,0x60,0x70,0x7f,0xc1,0x6b,0x35,0x7f,0xf5,0x2c,0x4d,0xe3,0x06,0x60,0xe3, - 0xa8,0x52,0xb3,0xc9,0x3c,0x0e,0x70,0xca,0xee,0x00,0xf9,0x9c,0xaa,0xb0,0x9a,0x52, - 0xd0,0xfe,0x3d,0x22,0xb5,0x50,0xf3,0x8a,0x25,0x39,0x3a,0x89,0x9f,0x7e,0x73,0xc8, - 0x30,0xf5,0x15,0x0f,0xf6,0x1f,0xfb,0x86,0x52,0x20,0xec,0x5d,0x45,0x5d,0x04,0x3f, - 0xb1,0x25,0x73,0x49,0x42,0x65,0xfe,0xa3,0xe3,0xcd,0xe8,0x01,0x7c,0xee,0x6b,0xf6, - 0xb5,0x6d,0x2c,0x23,0xd1,0x1a,0x21,0x25,0x11,0xba,0xb7,0xc5,0xf8,0xcb,0xae,0xff, - 0x80,0xb7,0x0d,0xa1,0x77,0x98,0xba,0x33,0x75,0x06,0x81,0xd1,0x79,0x0f,0x2e,0x33, - 0x2c,0x51,0x02,0xda,0xba,0xbb,0xb3,0x61,0x1e,0xa2,0x0f,0x3f,0x1b,0x41,0x6b,0xf8, - 0x59,0xae,0x4a,0xdb,0x86,0x0c,0x7a,0x1d,0xeb,0x0e,0xb2,0xea,0x10,0x00,0x99,0xd1, - 0x92,0x77,0xa8,0x3b,0x5e,0xce,0x2c,0x64,0x9e,0xb1,0x82,0x4c,0xc5,0x72,0x9c,0x9c, - 0x5c,0x8c,0x34,0x06,0xb3,0x43,0xd9,0x74,0xcc,0x78,0x45,0x02,0x3d,0xae,0x50,0x69, - 0x6f,0xb8,0x9e,0x85,0xac,0x0f,0xb1,0x8a,0xef,0x2c,0xcf,0x80,0xde,0x4c,0x96,0x0c, - 0xb1,0xdf,0x8b,0x04,0x4e,0xf0,0xd7,0x2b,0x17,0x6d,0x2b,0x23,0x31,0x35,0x69,0xa0, - 0x7b,0x0a,0xba,0x4a,0xba,0xd9,0x31,0xdf,0x9a,0x7a,0x1b,0x0b,0x71,0xa9,0x07,0x4d, - 0xee,0x32,0x0a,0x5f,0x41,0xbe,0x93,0xd8,0xac,0xf7,0xf2,0x0e,0xfb,0xae,0x64,0x5a, - 0x87,0xb8,0x9c,0x31,0x75,0xaa,0x17,0x3c,0x32,0xc2,0x75,0xf5,0xd1,0x14,0xb7,0xbc, - 0xf2,0xba,0x6a,0xf2,0x83,0xb1,0xe6,0xc1,0x1d,0x97,0x6e,0x99,0x5a,0x80,0x40,0x1b, - 0xfa,0xe2,0x9d,0xeb,0xd4,0x38,0xe5,0x75,0xd3,0xf0,0xe3,0x83,0xb2,0x8b,0x1f,0x21, - 0x13,0xd4,0x7e,0x38,0x8b,0xbd,0xcd,0xba,0xea,0x34,0x69,0x8d,0x74,0x8f,0x08,0xb3, - 0x82,0x3c,0xda,0xb2,0xf1,0x90,0x63,0x24,0x45,0xba,0x25,0x83,0xfe,0x7a,0x6b,0xc0, - 0xdc,0x07,0xe6,0x8d,0xf0,0xc9,0x9d,0xb8,0x47,0x47,0xf2,0xa5,0x9a,0xba,0xaf,0x80, - 0x9e,0x04,0xc8,0xf6,0x9c,0x69,0x39,0x22,0xf9,0x95,0x71,0x13,0xf8,0xfb,0xb9,0xb8, - 0x11,0x57,0xd9,0xd1,0xdf,0x11,0x28,0xe5,0xc5,0x23,0x37,0xfb,0x0a,0xfc,0x0d,0x7c, - 0xa4,0x36,0xbe,0x03,0x4b,0xc7,0xa5,0x2a,0x67,0xed,0xd7,0x58,0x7c,0x08,0xf2,0x3a, - 0x63,0x7d,0x9f,0x3a,0x65,0xb1,0xbf,0x3b,0x55,0xe7,0x13,0x28,0x2a,0x16,0x2e,0xe3, - 0x80,0x39,0x9a,0x3b,0x7a,0x89,0xa2,0x0c,0x9c,0xc6,0xaf,0xbd,0x6b,0xb5,0x8d,0x90, - 0xf7,0x4a,0xbb,0xd7,0x69,0x19,0xe0,0xbe,0x3c,0xd4,0xab,0x41,0x60,0xf5,0x74,0x92, - 0xf1,0xce,0x15,0xd2,0xcf,0x85,0xfb,0x70,0x40,0x30,0xe0,0xef,0x1b,0x8a,0x76,0x3e, - 0x67,0xcc,0x37,0x59,0x39,0x7d,0x1e,0x06,0x78,0x3f,0x95,0xd6,0xb2,0x6f,0x50,0xa8, - 0x3b,0x60,0x6a,0x28,0xb2,0x90,0xbd,0x23,0xd4,0xc7,0xeb,0xef,0x6b,0xe8,0x83,0x91, - 0x9d,0xb9,0x42,0x58,0xd8,0xff,0x5e,0x0a,0xc3,0x9b,0x01,0x3e,0xa1,0xd6,0x3b,0xcf, - 0x51,0xd7,0xec,0x65,0xb7,0x56,0xbc,0x23,0x01,0x46,0xd5,0xf0,0x72,0x0d,0xa7,0xe8, - 0x6c,0x61,0x43,0x21,0xd4,0x89,0x4a,0xe0,0x25,0x74,0xd8,0xa9,0x66,0xbd,0xc2,0xb5, - 0x50,0x46,0xc0,0xb3,0x90,0x51,0xbf,0xa5,0x3f,0xa3,0x3e,0xbb,0xdd,0x95,0x9f,0xb6, - 0xd8,0xcc,0x7d,0x65,0xb6,0x5d,0xfd,0x01,0x69,0x3c,0xcf,0xf3,0x90,0x73,0x71,0x1a, - 0xab,0xc0,0xcf,0x06,0x7a,0x16,0xfa,0x08,0x00,0xcb,0x09,0xb2,0x79,0x1f,0x01,0x16, + 0x88,0x34,0x78,0xff,0xf4,0x63,0x53,0x40,0x1e,0x27,0xf2,0x2b,0x46,0xb3,0x86,0xa6, + 0x92,0x85,0x9b,0x5a,0xf8,0x3a,0xda,0x0d,0x2a,0xb6,0x85,0xca,0xb4,0xf2,0x4f,0x84, + 0x34,0xc2,0x7c,0xbe,0x37,0x0e,0x0c,0xa4,0xca,0xd5,0x3a,0x85,0xbc,0xcb,0x22,0x3a, + 0xd9,0x41,0xd4,0x93,0x0e,0x34,0x79,0xd2,0x9a,0xba,0xd7,0x3f,0xa4,0x87,0xa6,0x4e, + 0x1e,0x2f,0xc3,0x55,0x2e,0x9b,0xee,0x18,0x42,0x5d,0x86,0x9d,0xb4,0x56,0x6e,0xed, + 0x6f,0x75,0x8d,0xff,0x9f,0xf8,0x5d,0x5f,0xdd,0xb3,0x7e,0x51,0x0b,0x11,0xb1,0xac, + 0x55,0xcb,0x76,0x68,0x37,0x95,0xff,0x0e,0x18,0x69,0xb4,0x11,0x41,0xa7,0xc2,0x9c, + 0xfa,0xcb,0xea,0xe0,0x70,0x0e,0x66,0x06,0xae,0x4d,0x72,0xdd,0xe6,0x4b,0xe8,0x78, + 0xa8,0x32,0xbd,0x73,0xf0,0x07,0x0a,0xe4,0x45,0xd7,0xc5,0x07,0xe1,0xf0,0xaf,0x15, + 0x3a,0x10,0x09,0x1c,0x85,0x8e,0xc1,0xf3,0x57,0x5f,0xb3,0x27,0x49,0x11,0x0b,0x8e, + 0xcf,0xa2,0x3b,0xf4,0xa7,0x7a,0xb0,0xe5,0x58,0x15,0xab,0xbc,0x12,0x9b,0x21,0xdc, + 0xe5,0x1d,0xc7,0xcb,0x92,0x9c,0xd7,0x5d,0x6e,0x64,0xd2,0x63,0x82,0xdf,0x0a,0x8b, + 0x7f,0x85,0x4d,0x57,0x91,0xa7,0xed,0x79,0x95,0x70,0xd3,0x84,0x37,0x73,0xe0,0x93, + 0x69,0xaf,0x1b,0x91,0x67,0xe0,0xd4,0x94,0xb6,0xcd,0x71,0xb1,0xa0,0x11,0x8a,0xa4, + 0x76,0x64,0x18,0xb0,0xdd,0xcb,0x28,0xe3,0x72,0x50,0xe3,0x54,0x61,0x69,0xce,0x61, + 0x13,0x89,0xc6,0x94,0xbf,0x25,0x92,0xe9,0xb4,0x9c,0x3c,0xf1,0x06,0xc3,0x19,0x8d, + 0xaa,0x4f,0xf9,0x8f,0xa4,0x52,0x27,0xe9,0x07,0x36,0x18,0x59,0xad,0xce,0x2e,0x2d, + 0xed,0x93,0x73,0xd7,0x6f,0xe7,0xd1,0x80,0x94,0xd7,0x57,0x51,0xe5,0x12,0x8e,0xe0, + 0xbf,0x3b,0xd9,0xc7,0x02,0x1f,0x49,0x1f,0x16,0x45,0x90,0x00,0x54,0xd1,0x59,0x12, + 0x0a,0xd4,0xb8,0x63,0xc9,0xa1,0x9b,0x43,0xf0,0x64,0xed,0x2c,0x56,0x1c,0x7c,0x67, + 0x94,0x3a,0x20,0x93,0xbe,0x33,0xe3,0xfa,0xa6,0x71,0x9c,0x25,0xf7,0x63,0x54,0x20, + 0x76,0x0b,0xf4,0xb0,0x70,0xf9,0x73,0x71,0xa5,0xd8,0xa9,0xea,0x22,0xc5,0xa2,0xc7, + 0xfe,0xe0,0x99,0x27,0xaa,0xfa,0x81,0x53,0x91,0x20,0xcd,0xb4,0x57,0x4c,0xd0,0x36, + 0x40,0xdb,0xc9,0x06,0x02,0x1d,0x59,0x4f,0x7d,0x3d,0xbe,0xa6,0x30,0xad,0x40,0x00, + 0x7f,0xc6,0xe2,0xb2,0xd7,0x54,0xab,0x1a,0xaf,0xda,0x0b,0x35,0x0d,0xcc,0x6f,0x14, + 0x85,0x08,0xe9,0x19,0x5a,0xd1,0x9d,0x39,0x9b,0xc1,0x66,0xd3,0x99,0x17,0xd7,0xf1, + 0x24,0xf2,0x9c,0x3c,0xf9,0xe8,0xc7,0x50,0xba,0x2d,0x56,0x61,0x62,0xca,0x8d,0x65, + 0xff,0xa5,0x83,0x39,0x50,0x2f,0x2d,0x23,0x1b,0xa1,0x9e,0xfd,0x28,0x1c,0x80,0x17, + 0x10,0x71,0xf6,0x1b,0x9b,0xfe,0xdf,0x8c,0xb0,0xd4,0x84,0x05,0x75,0xea,0x90,0xa7, + 0x21,0xdc,0xf6,0x92,0xa3,0xdb,0x9c,0x5e,0x54,0xf1,0xd3,0x62,0x9f,0xe3,0x28,0x46, + 0x18,0xb6,0xaf,0xf7,0x14,0xb6,0x5c,0xf8,0xe6,0xba,0x5a,0x87,0x3e,0x53,0x63,0xa6, + 0x18,0xd0,0xa0,0xf8,0x12,0xfb,0x15,0xa5,0x15,0x89,0x31,0xfe,0x75,0x79,0xe7,0x97, + 0x47,0x95,0xb4,0xe0,0xa0,0x52,0x23,0x69,0x54,0x5b,0xfa,0xc6,0xec,0x74,0xd3,0x3c, + 0x38,0xda,0xca,0xbb,0xf5,0x6f,0xfe,0xfb,0x47,0x58,0xba,0xfd,0x03,0xa9,0x59,0xc8, + 0xb4,0xdc,0x00,0xbc,0xbd,0x09,0x7f,0x44,0xce,0xeb,0x8e,0x39,0x57,0x88,0x55,0x8a, + 0x29,0xaf,0xae,0x48,0x36,0x4a,0xd1,0x44,0x38,0x79,0x76,0x01,0x8c,0xa6,0xc2,0xf3, + 0xe7,0xf5,0x8d,0x1a,0x8f,0xbd,0xf6,0xc5,0x20,0xca,0x20,0x6a,0x50,0x30,0x97,0x6b, + 0x50,0x3f,0x80,0x9b,0x9a,0xa0,0x48,0xdf,0x1a,0xa7,0xb6,0x9a,0x03,0xd8,0x83,0xcb, + 0x74,0xab,0xbe,0xa1,0x21,0x1b,0x01,0x13,0x18,0xc5,0x1c,0x14,0xea,0xd7,0xa0,0xc6, + 0x03,0x91,0x73,0xa6,0x7e,0x89,0xad,0x1c,0x79,0x42,0x17,0xd0,0x25,0x2f,0x6d,0xe9, + 0xf0,0x71,0x7a,0x51,0x4f,0xd4,0x67,0x9e,0x45,0x2a,0x3a,0xe1,0x7c,0x8e,0xee,0xf9, + 0x31,0x9a,0x05,0x60,0x2c,0xab,0x7d,0x37,0x24,0x6e,0x0d,0x3c,0x1c,0xd4,0x70,0xd1, + 0x87,0xce,0x29,0x79,0xdc,0x25,0xd3,0xfb,0x4e,0x90,0x1b,0xc5,0xcd,0xcd,0x78,0xbe, + 0x55,0x63,0x73,0xe0,0x96,0xbe,0x3b,0xcb,0x14,0x2c,0x59,0xc9,0x6a,0x34,0x60,0x4f, + 0xd0,0x61,0x02,0xeb,0x26,0x9c,0xce,0xe5,0x48,0x29,0x02,0x6d,0xfa,0x37,0x83,0x5b, + 0x25,0xe5,0x68,0x65,0xc9,0xb8,0x21,0xb8,0x08,0x74,0x82,0xaf,0x07,0x88,0xc3,0x8c, + 0xff,0x49,0x2f,0x6e,0x32,0x43,0x99,0x0a,0xa9,0x0b,0x49,0x6d,0x5d,0x73,0x32,0xe3, + 0xd8,0x50,0x16,0x14,0x27,0xcd,0xf9,0x1f,0xb1,0x88,0x87,0xce,0x23,0x4c,0xa7,0x04, }; diff --git a/prebuilt/openssl/inc/openssl/bnerr.h b/prebuilt/openssl/inc/openssl/bnerr.h index 9f3c7cfa..5c83777f 100644 --- a/prebuilt/openssl/inc/openssl/bnerr.h +++ b/prebuilt/openssl/inc/openssl/bnerr.h @@ -1,6 +1,6 @@ /* * Generated by util/mkerr.pl DO NOT EDIT - * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1995-2023 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -72,6 +72,7 @@ int ERR_load_BN_strings(void); # define BN_F_BN_SET_WORDS 144 # define BN_F_BN_STACK_PUSH 148 # define BN_F_BN_USUB 115 +# define BN_F_OSSL_BN_RSA_DO_UNBLIND 151 /* * BN reason codes. diff --git a/prebuilt/openssl/inc/openssl/cmserr.h b/prebuilt/openssl/inc/openssl/cmserr.h index 7dbc13dc..d589f592 100644 --- a/prebuilt/openssl/inc/openssl/cmserr.h +++ b/prebuilt/openssl/inc/openssl/cmserr.h @@ -187,6 +187,7 @@ int ERR_load_CMS_strings(void); # define CMS_R_UNKNOWN_DIGEST_ALGORITHM 149 # define CMS_R_UNKNOWN_ID 150 # define CMS_R_UNSUPPORTED_COMPRESSION_ALGORITHM 151 +# define CMS_R_UNSUPPORTED_CONTENT_ENCRYPTION_ALGORITHM 194 # define CMS_R_UNSUPPORTED_CONTENT_TYPE 152 # define CMS_R_UNSUPPORTED_KEK_ALGORITHM 153 # define CMS_R_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM 179 diff --git a/prebuilt/openssl/inc/openssl/opensslv.h b/prebuilt/openssl/inc/openssl/opensslv.h index fec5f579..7b6c212f 100644 --- a/prebuilt/openssl/inc/openssl/opensslv.h +++ b/prebuilt/openssl/inc/openssl/opensslv.h @@ -39,8 +39,8 @@ extern "C" { * (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for * major minor fix final patch/beta) */ -# define OPENSSL_VERSION_NUMBER 0x1010111fL -# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1q 5 Jul 2022" +# define OPENSSL_VERSION_NUMBER 0x1010114fL +# define OPENSSL_VERSION_TEXT "OpenSSL 1.1.1t 7 Feb 2023" /*- * The macros below are to be used for shared library (.so, .dll, ...) diff --git a/prebuilt/openssl/inc/openssl/x509v3.h b/prebuilt/openssl/inc/openssl/x509v3.h index 90fa3592..3a4f04c1 100644 --- a/prebuilt/openssl/inc/openssl/x509v3.h +++ b/prebuilt/openssl/inc/openssl/x509v3.h @@ -1,5 +1,5 @@ /* - * Copyright 1999-2021 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 1999-2023 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -136,7 +136,7 @@ typedef struct GENERAL_NAME_st { OTHERNAME *otherName; /* otherName */ ASN1_IA5STRING *rfc822Name; ASN1_IA5STRING *dNSName; - ASN1_TYPE *x400Address; + ASN1_STRING *x400Address; X509_NAME *directoryName; EDIPARTYNAME *ediPartyName; ASN1_IA5STRING *uniformResourceIdentifier; diff --git a/prebuilt/openssl/lib/linux64/libcrypto.a b/prebuilt/openssl/lib/linux64/libcrypto.a index 917eb3d2..9ee5f5c9 100644 Binary files a/prebuilt/openssl/lib/linux64/libcrypto.a and b/prebuilt/openssl/lib/linux64/libcrypto.a differ diff --git a/prebuilt/openssl/lib/win64/libcrypto.lib b/prebuilt/openssl/lib/win64/libcrypto.lib index a3245b76..10d25001 100644 Binary files a/prebuilt/openssl/lib/win64/libcrypto.lib and b/prebuilt/openssl/lib/win64/libcrypto.lib differ diff --git a/tools/PCKCertSelection/PCKCertSelectionLib/PCKCertSelectionLib.vcxproj b/tools/PCKCertSelection/PCKCertSelectionLib/PCKCertSelectionLib.vcxproj index 25f68e3c..a0cb2f13 100644 --- a/tools/PCKCertSelection/PCKCertSelectionLib/PCKCertSelectionLib.vcxproj +++ b/tools/PCKCertSelection/PCKCertSelectionLib/PCKCertSelectionLib.vcxproj @@ -125,7 +125,7 @@ Level4 Disabled true - WIN32;_DEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + WIN32;_DEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true $(ProjectDir)\..\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\src;$(ProjectDir)..\..\..\prebuilt\openssl\inc;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\ThirdParty\rapidjson\include;$(ProjectDir)..\..\..\QuoteGeneration\common\inc\internal SyncCThrow @@ -145,7 +145,7 @@ Level4 Disabled true - _DEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + _DEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true $(ProjectDir)\..\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\src;$(ProjectDir)..\..\..\prebuilt\openssl\inc;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\ThirdParty\rapidjson\include;$(ProjectDir)..\..\..\QuoteGeneration\common\inc\internal;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationCommons/include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationCommons/include/Utils SyncCThrow @@ -168,7 +168,7 @@ true true true - WIN32;NDEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + WIN32;NDEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true $(ProjectDir)\..\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\src;$(ProjectDir)..\..\..\prebuilt\openssl\inc;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\ThirdParty\rapidjson\include;$(ProjectDir)..\..\..\QuoteGeneration\common\inc\internal SyncCThrow @@ -192,7 +192,7 @@ true true true - NDEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + NDEBUG;PCKCertSelectionLIB_EXPORTS;_WINDOWS;_USRDLL;ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) true $(ProjectDir)\..\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationParsers\src;$(ProjectDir)..\..\..\prebuilt\openssl\inc;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\ThirdParty\rapidjson\include;$(ProjectDir)..\..\..\QuoteGeneration\common\inc\internal;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationCommons/include;$(ProjectDir)\..\..\..\QuoteVerification\QVL\Src\AttestationCommons/include/Utils true @@ -209,6 +209,16 @@ true
+ + + ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) + + + + + ATTESTATIONPARSERS_STATIC;%(PreprocessorDefinitions) + + diff --git a/tools/PCKRetrievalTool/App/linux/network_wrapper.cpp b/tools/PCKRetrievalTool/App/linux/network_wrapper.cpp index 17e9edb5..7d1e3ae6 100644 --- a/tools/PCKRetrievalTool/App/linux/network_wrapper.cpp +++ b/tools/PCKRetrievalTool/App/linux/network_wrapper.cpp @@ -188,6 +188,9 @@ static network_post_error_t curl_error_to_network_post_error(CURLcode curl_error static bool process_configuration_setting(const char *config_file_name, string& url, string &proxy_type, string &proxy_url, string &user_token) { bool ret = true; + bool config_file_exist = true; + bool config_file_provide_pccs_url=false; + ifstream ifs(config_file_name); string line; if (ifs.is_open()) { @@ -207,6 +210,7 @@ static bool process_configuration_setting(const char *config_file_name, string& else { url = server_url_string + "/sgx/certification/v4/platforms"; } + config_file_provide_pccs_url = true; } else if (name.compare("USE_SECURE_CERT") == 0) { if (use_secure_cert_string.empty() == true) { @@ -241,13 +245,25 @@ static bool process_configuration_setting(const char *config_file_name, string& } } else { + config_file_exist = false; + if (use_secure_cert_string.compare("FALSE") == 0 || use_secure_cert_string.compare("false") == 0) { g_use_secure_cert = false; } - url = server_url_string + "/sgx/certification/v2/platforms"; + + if(server_url_string.empty() == false) { + url = server_url_string + "/sgx/certification/v4/platforms"; + } ret = false; } + + if(config_file_exist && config_file_provide_pccs_url == false) { + if(server_url_string.empty() == false) { + url = server_url_string + "/sgx/certification/v4/platforms"; + } + } + return ret; } @@ -270,6 +286,11 @@ static void network_configuration(string &url, string &proxy_type, string &proxy if (ret){ process_configuration_setting(local_configuration_file_path,url, proxy_type, proxy_url, user_token); } + else { + if(server_url_string.empty() == false) { + url = server_url_string + "/sgx/certification/v4/platforms"; + } + } } diff --git a/tools/PCKRetrievalTool/App/win/network_wrapper.cpp b/tools/PCKRetrievalTool/App/win/network_wrapper.cpp index 812a3cdc..533dcf7b 100644 --- a/tools/PCKRetrievalTool/App/win/network_wrapper.cpp +++ b/tools/PCKRetrievalTool/App/win/network_wrapper.cpp @@ -101,6 +101,8 @@ static network_post_error_t windows_last_error_to_network_post_error(void) static bool process_configuration_setting(const char *config_file_name, string& url, ProxyType &proxy_type, string &proxy_url, string& user_token) { bool ret = true; + bool config_file_exist = true; + bool config_file_provide_pccs_url=false; ifstream ifs(config_file_name); string line; if (ifs.is_open()) { @@ -120,6 +122,7 @@ static bool process_configuration_setting(const char *config_file_name, string& else { url = server_url_string + "/sgx/certification/v4/platforms"; } + config_file_provide_pccs_url = true; } else if (name.compare("USE_SECURE_CERT") == 0) { if (use_secure_cert_string.empty() == true) { @@ -156,7 +159,7 @@ static bool process_configuration_setting(const char *config_file_name, string& proxy_type = PROXY_TYPE_MANUAL_PROXY; } else if (proxy_type_string.compare("AUTO") == 0 || proxy_type_string.compare("auto") == 0) { - proxy_type = PROXY_TYPE_DIRECT_ACCESS; + proxy_type = PROXY_TYPE_AUTOMATIC; } else { proxy_type = PROXY_TYPE_DEFAULT_PROXY; @@ -179,8 +182,9 @@ static bool process_configuration_setting(const char *config_file_name, string& } } else { + config_file_exist = false; if (use_secure_cert_string.compare("FALSE") == 0 || use_secure_cert_string.compare("false") == 0) { - g_use_secure_cert = true; + g_use_secure_cert = false; } if (proxy_type_string.compare("DIRECT") == 0 || proxy_type_string.compare("direct") == 0) { @@ -195,9 +199,19 @@ static bool process_configuration_setting(const char *config_file_name, string& else { proxy_type = PROXY_TYPE_DEFAULT_PROXY; } - url = server_url_string + "/sgx/certification/v2/platforms"; + + + if(server_url_string.empty() == false) { + url = server_url_string + "/sgx/certification/v4/platforms"; + } ret = false; } + + if(config_file_exist && config_file_provide_pccs_url == false) { + if(server_url_string.empty() == false) { + url = server_url_string + "/sgx/certification/v4/platforms"; + } + } return ret; } @@ -355,7 +369,7 @@ network_post_error_t network_https_post(const uint8_t* raw_data, const uint32_t return POST_INVALID_PARAMETER_ERROR; } - network_post_error_t ret = POST_UNEXPECTED_ERROR; + network_post_error_t ret = POST_UNEXPECTED_ERROR; string strJson(""); ret = generate_json_message_body(raw_data, raw_data_size, platform_id_length, non_enclave_mode, strJson); if (ret != POST_SUCCESS) { @@ -365,30 +379,30 @@ network_post_error_t network_https_post(const uint8_t* raw_data, const uint32_t ret = POST_UNEXPECTED_ERROR; - // initialize https request url - string url(server_url_string); + // initialize https request url + string url(server_url_string); ProxyType proxy_type = PROXY_TYPE_DEFAULT_PROXY; string proxy_url(proxy_url_string); string user_token(user_token_string); - // initialize network configuration - network_configuration(url, proxy_type, proxy_url, user_token); + // initialize network configuration + network_configuration(url, proxy_type, proxy_url, user_token); HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; - //WinHTTP API explicitly use UNICODE so that we should use WCHAR instead of TCHAR - WCHAR wurl[MAX_PATH]; - WCHAR wproxyurl[MAX_PATH]; - WCHAR whostname[MAX_PATH]; - size_t count = 0; - if (mbstowcs_s(&count, wurl, url.c_str(), url.size()) != 0) { - return POST_UNEXPECTED_ERROR; - } + //WinHTTP API explicitly use UNICODE so that we should use WCHAR instead of TCHAR + WCHAR wurl[MAX_PATH]; + WCHAR wproxyurl[MAX_PATH]; + WCHAR whostname[MAX_PATH]; + size_t count = 0; + if (mbstowcs_s(&count, wurl, url.c_str(), url.size()) != 0) { + return POST_UNEXPECTED_ERROR; + } do { //WinHTTP API explicitly use UNICODE so that we should use WCHAR instead of TCHAR URL_COMPONENTS urlCompccs; ZeroMemory(&urlCompccs, sizeof(urlCompccs)); - URL_COMPONENTS proxyurlCompccs; - ZeroMemory(&urlCompccs, sizeof(proxyurlCompccs)); + URL_COMPONENTS proxyurlCompccs; + ZeroMemory(&urlCompccs, sizeof(proxyurlCompccs)); urlCompccs.dwStructSize = sizeof(urlCompccs); urlCompccs.lpszHostName = whostname;//we will only crack hostname, urlpath urlCompccs.dwHostNameLength = MAX_PATH;//copy hostname to a buffer to get 0-terminated string diff --git a/tools/PCKRetrievalTool/README_standalone.txt b/tools/PCKRetrievalTool/README_standalone.txt index a9b6b9d9..d0bbbf58 100644 --- a/tools/PCKRetrievalTool/README_standalone.txt +++ b/tools/PCKRetrievalTool/README_standalone.txt @@ -68,4 +68,6 @@ And the retrieved data can also be uploaded to cache server if user provide the $ sudo usermod -a -G sgx_prv 3. If you are using this tool in Ubuntu 20.04, please execute the following command: $ sudo mount -o remount,exec /dev + 4. If you are using this tool in Ubuntu 22.04, please execute this tool with the following command: + $ LD_LIBRARY_PATH=. ./PCKIDRetrievalTool [OPTION] diff --git a/tools/PCKRetrievalTool/gen_release.bat b/tools/PCKRetrievalTool/gen_release.bat index ee79ba95..351ce14f 100644 --- a/tools/PCKRetrievalTool/gen_release.bat +++ b/tools/PCKRetrievalTool/gen_release.bat @@ -52,6 +52,7 @@ CALL :COPY_FILE ..\SGXPlatformRegistration\x64\release\mp_uefi.dll %rel_dir_name CALL :COPY_FILE ..\..\x64\release\sgx_dcap_ql.dll %rel_dir_name% CALL :COPY_FILE ..\..\QuoteGeneration\psw\ae\data\prebuilt\win\pce.signed.dll %rel_dir_name% CALL :COPY_FILE ..\..\QuoteGeneration\psw\ae\data\prebuilt\win\qe3.signed.dll %rel_dir_name% +CALL :COPY_FILE ..\..\QuoteGeneration\psw\ae\data\prebuilt\win\id_enclave.signed.dll %rel_dir_name% CALL :COPY_FILE network_setting.conf %rel_dir_name% CALL :COPY_FILE README_standalone.txt %rel_dir_name%\README.txt CALL :COPY_FILE License.txt %rel_dir_name% @@ -68,8 +69,6 @@ call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\sgx_launch.dll call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\sgx_enclave_common.dll call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\mp_uefi.dll call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\sgx_dcap_ql.dll -call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\pce.signed.dll -call "%TOOLSFOLDER%\Sign.bat" %rel_dir_name%\qe3.signed.dll diff --git a/tools/PCKRetrievalTool/installer/deb/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool-1.0/debian/control b/tools/PCKRetrievalTool/installer/deb/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool-1.0/debian/control index 9527a580..0818e91c 100644 --- a/tools/PCKRetrievalTool/installer/deb/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool-1.0/debian/control +++ b/tools/PCKRetrievalTool/installer/deb/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool-1.0/debian/control @@ -9,6 +9,6 @@ Homepage: https://github.com/intel/SGXDataCenterAttestationPrimitives Package: sgx-pck-id-retrieval-tool Architecture: amd64 Depends: ${shlibs:Depends}, ${misc:Depends} -Recommends: libsgx-urts (>= 2.18), libsgx-ae-pce (>= @dep_version@),libsgx-ae-id-enclave (>=@dep_version@), libsgx-ra-uefi (>= @dep_version@) +Recommends: libsgx-urts (>= 2.19), libsgx-ae-pce (>= @dep_version@),libsgx-ae-id-enclave (>=@dep_version@), libsgx-ra-uefi (>= @dep_version@) Description: Intel(R) Software Guard Extensions: this tool is used to collect the platform information to retrieve the PCK certs from PCS(Provisioning Certification Server) diff --git a/tools/PCKRetrievalTool/installer/rpm/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool.spec b/tools/PCKRetrievalTool/installer/rpm/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool.spec index 6203fd5e..7ac2b1d8 100644 --- a/tools/PCKRetrievalTool/installer/rpm/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool.spec +++ b/tools/PCKRetrievalTool/installer/rpm/sgx-pck-id-retrieval-tool/sgx-pck-id-retrieval-tool.spec @@ -37,7 +37,7 @@ Version: @version@ Release: 1%{?dist} Summary: Intel(R) Software Guard Extensions:this tool is used to collect the platform information to retrieve the PCK certs from PCS(Provisioning Certification Server) Group: Development/System -Recommends: libsgx-urts >= 2.18, libsgx-ae-pce >= %{version}-%{release}, libsgx-ae-id-enclave >= %{version}-%{release},libsgx-ra-uefi >= %{version}-%{release} +Recommends: libsgx-urts >= 2.19, libsgx-ae-pce >= %{version}-%{release}, libsgx-ae-id-enclave >= %{version}-%{release},libsgx-ra-uefi >= %{version}-%{release} License: BSD License URL: https://github.com/intel/SGXDataCenterAttestationPrimitives diff --git a/tools/PccsAdminTool/lib/intelsgx/pcs.py b/tools/PccsAdminTool/lib/intelsgx/pcs.py index e6bcc62a..cf549102 100644 --- a/tools/PccsAdminTool/lib/intelsgx/pcs.py +++ b/tools/PccsAdminTool/lib/intelsgx/pcs.py @@ -514,6 +514,35 @@ def get_pck_crl(self, target, dec=None): return [crl_str, response.headers['SGX-PCK-CRL-Issuer-Chain']] +#---------------------------------------------------------------------------- +# PCS: Get FMSPC List +#---------------------------------------------------------------------------- + + def get_fmspcs(self, platform, dec=None): + self.clear_errors() + if ( platform not in ['all', 'client', 'E3', 'E5'] ): + self.error('Invalid argument') + return None + + url= self._geturl('fmspcs') + if self.ApiVersion<4: + self.error('API /fmspcs not supported') + return None + else: + url+= "?platform={:s}".format(platform) + + response= self._get_request(url, False) + if response.status_code != 200: + self.error(response.status_code) + return None + + # Verify expected headers + if not response.headers['Request-ID']: + self.error("Response missing Request-ID header") + return None + + return response.json() + #---------------------------------------------------------------------------- # PCS: Get TCB Info #---------------------------------------------------------------------------- diff --git a/tools/PccsAdminTool/pccsadmin.py b/tools/PccsAdminTool/pccsadmin.py index 72942c06..331e1c8d 100755 --- a/tools/PccsAdminTool/pccsadmin.py +++ b/tools/PccsAdminTool/pccsadmin.py @@ -42,6 +42,7 @@ def main(): parser_fetch.add_argument("-u", "--url", help="The URL of the Intel PCS service; default: https://api.trustedservices.intel.com/sgx/certification/v4/") parser_fetch.add_argument("-i", "--input_file", help="The input file name for platform list; default: platform_list.json") parser_fetch.add_argument("-o", "--output_file", help="The output file name for platform collaterals; default: platform_collaterals.json") + parser_fetch.add_argument("-p", "--platform", help="Specify what kind of platform you want to fetch FMSPCs and tcbinfos for; default: all", choices=['all','client','E3','E5']) parser_fetch.set_defaults(func=pcs_fetch) # subparser for collect @@ -171,6 +172,9 @@ def pcs_fetch(args): output_file = "platform_collaterals.json" if args.output_file: output_file = args.output_file + fmspc_platform = "all" + if args.platform: + fmspc_platform = args.platform # Get PCS ApiKey from keyring credential = Credentials() @@ -272,6 +276,12 @@ def pcs_fetch(args): break if save_to_file.lower() == "n": break + + # Get fmspcs for specified platform + fmspcs = pcsclient.get_fmspcs(fmspc_platform, 'ascii') + if fmspcs != None: + for fmspc in fmspcs: + fmspc_set.add(fmspc['fmspc']) # output.collaterals.tcbinfos for fmspc in fmspc_set: