From b518cc23933a93ec934ff18d0bff8c750835e704 Mon Sep 17 00:00:00 2001 From: Takuro Sato Date: Mon, 9 Sep 2024 15:36:20 +0100 Subject: [PATCH] Add Azure support Co-authored-by: Dominic Ayre Co-authored-by: Joe Powell Co-authored-by: Kapil Vaswani Co-authored-by: Ken Gordon Co-authored-by: Mahati Chamarthy Co-authored-by: Ronny Bjones --- .bazelrc | 11 + BUILD.bazel | 19 + src/BUILD.bazel | 1 + src/azure/BUILD.bazel | 20 + src/azure/attestation/BUILD.bazel | 30 + src/azure/attestation/src/BUILD.bazel | 51 ++ src/azure/attestation/src/attestation.h | 151 ++++ src/azure/attestation/src/endorsed_tcb.cc | 45 ++ src/azure/attestation/src/endorsements.cc | 63 ++ src/azure/attestation/src/evidence.cc | 63 ++ src/azure/attestation/src/fake_report.cc | 376 ++++++++++ src/azure/attestation/src/print_report.cc | 36 + src/azure/attestation/src/report.cc | 39 + src/azure/attestation/src/sev.h | 93 +++ src/azure/attestation/src/sev_guest.h | 84 +++ src/azure/attestation/src/snp.cc | 35 + src/azure/attestation/src/utils/BUILD.bazel | 34 + .../attestation/src/utils/host_amd_certs.cc | 36 + .../attestation/src/utils/host_amd_certs.h | 29 + .../attestation/src/utils/security_context.cc | 38 + .../attestation/src/utils/security_context.h | 29 + src/azure/attestation/src/uvm_endorsements.cc | 32 + src/azure/attestation/test/BUILD.bazel | 26 + .../attestation/test/attestation_test.cc | 67 ++ src/azure/encrypt_payload/BUILD.bazel | 38 + src/azure/encrypt_payload/encrypt_payload.cc | 303 ++++++++ src/azure/fetch_auth_token/BUILD.bazel | 29 + .../fetch_auth_token/fetch_auth_token.cc | 87 +++ .../auth_token_provider/BUILD.bazel | 6 +- .../auth_token_provider/azure/BUILD.bazel | 70 ++ .../azure/azure_auth_token_provider.cc | 228 ++++++ .../azure/azure_auth_token_provider.h | 69 ++ .../azure/azure_auth_token_provider_test.cc | 143 ++++ .../auth_token_provider/azure/error_codes.h | 46 ++ .../blob_storage_client_provider/BUILD.bazel | 6 +- .../azure/BUILD.bazel | 31 + .../azure_blob_storage_client_provider.cc | 128 ++++ .../azure_blob_storage_client_provider.h | 87 +++ .../cloud_initializer/BUILD.bazel | 6 +- .../cloud_initializer/azure/BUILD.bazel | 27 + .../azure/no_op_initializer.cc | 34 + .../azure/no_op_initializer.h | 34 + .../instance_client_provider/BUILD.bazel | 5 +- .../azure/BUILD.bazel | 46 ++ .../azure/azure_instance_client_provider.cc | 138 ++++ .../azure/azure_instance_client_provider.h | 71 ++ .../azure_instance_client_provider_test.cc | 146 ++++ .../kms_client_provider/BUILD.bazel | 8 +- .../kms_client_provider/azure/BUILD.bazel | 77 ++ .../azure/azure_kms_client_provider.cc | 377 ++++++++++ .../azure/azure_kms_client_provider.h | 87 +++ .../azure/azure_kms_client_provider_test.cc | 273 +++++++ .../azure/azure_kms_client_provider_utils.cc | 666 ++++++++++++++++++ .../azure/azure_kms_client_provider_utils.h | 223 ++++++ .../azure_kms_client_provider_utils_test.cc | 108 +++ .../kms_client_provider/azure/error_codes.h | 93 +++ .../parameter_client_provider/BUILD.bazel | 6 +- .../azure/BUILD.bazel | 53 ++ .../azure/azure_parameter_client_provider.cc | 138 ++++ .../azure/azure_parameter_client_provider.h | 58 ++ .../azure_parameter_client_provider_test.cc | 132 ++++ .../azure/error_codes.h | 41 ++ .../private_key_fetcher_provider/BUILD.bazel | 4 + .../azure/BUILD.bazel | 71 ++ .../azure_private_key_fetcher_provider.cc | 267 +++++++ .../azure_private_key_fetcher_provider.h | 103 +++ ...azure_private_key_fetcher_provider_test.cc | 266 +++++++ ...zure_private_key_fetcher_provider_utils.cc | 57 ++ ...azure_private_key_fetcher_provider_utils.h | 49 ++ ...private_key_fetcher_provider_utils_test.cc | 52 ++ .../azure/error_codes.h | 35 + .../role_credentials_provider/BUILD.bazel | 5 +- .../azure/BUILD.bazel | 30 + .../azure/azure_role_credentials_provider.cc | 45 ++ .../azure/azure_role_credentials_provider.h | 32 + src/public/core/interface/cloud_platform.h | 2 + src/public/cpio/interface/BUILD.bazel | 17 + src/telemetry/BUILD.bazel | 1 + 78 files changed, 6555 insertions(+), 7 deletions(-) create mode 100644 src/azure/BUILD.bazel create mode 100644 src/azure/attestation/BUILD.bazel create mode 100644 src/azure/attestation/src/BUILD.bazel create mode 100644 src/azure/attestation/src/attestation.h create mode 100644 src/azure/attestation/src/endorsed_tcb.cc create mode 100644 src/azure/attestation/src/endorsements.cc create mode 100644 src/azure/attestation/src/evidence.cc create mode 100644 src/azure/attestation/src/fake_report.cc create mode 100644 src/azure/attestation/src/print_report.cc create mode 100644 src/azure/attestation/src/report.cc create mode 100644 src/azure/attestation/src/sev.h create mode 100644 src/azure/attestation/src/sev_guest.h create mode 100644 src/azure/attestation/src/snp.cc create mode 100644 src/azure/attestation/src/utils/BUILD.bazel create mode 100644 src/azure/attestation/src/utils/host_amd_certs.cc create mode 100644 src/azure/attestation/src/utils/host_amd_certs.h create mode 100644 src/azure/attestation/src/utils/security_context.cc create mode 100644 src/azure/attestation/src/utils/security_context.h create mode 100644 src/azure/attestation/src/uvm_endorsements.cc create mode 100644 src/azure/attestation/test/BUILD.bazel create mode 100644 src/azure/attestation/test/attestation_test.cc create mode 100644 src/azure/encrypt_payload/BUILD.bazel create mode 100644 src/azure/encrypt_payload/encrypt_payload.cc create mode 100644 src/azure/fetch_auth_token/BUILD.bazel create mode 100644 src/azure/fetch_auth_token/fetch_auth_token.cc create mode 100644 src/cpio/client_providers/auth_token_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.cc create mode 100644 src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.h create mode 100644 src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider_test.cc create mode 100644 src/cpio/client_providers/auth_token_provider/azure/error_codes.h create mode 100644 src/cpio/client_providers/blob_storage_client_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.cc create mode 100644 src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.h create mode 100644 src/cpio/client_providers/cloud_initializer/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.cc create mode 100644 src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.h create mode 100644 src/cpio/client_providers/instance_client_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.cc create mode 100644 src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.h create mode 100644 src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider_test.cc create mode 100644 src/cpio/client_providers/kms_client_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.cc create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.h create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_test.cc create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.cc create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.h create mode 100644 src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils_test.cc create mode 100644 src/cpio/client_providers/kms_client_provider/azure/error_codes.h create mode 100644 src/cpio/client_providers/parameter_client_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.cc create mode 100644 src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.h create mode 100644 src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider_test.cc create mode 100644 src/cpio/client_providers/parameter_client_provider/azure/error_codes.h create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.cc create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.h create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_test.cc create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.cc create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.h create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils_test.cc create mode 100644 src/cpio/client_providers/private_key_fetcher_provider/azure/error_codes.h create mode 100644 src/cpio/client_providers/role_credentials_provider/azure/BUILD.bazel create mode 100644 src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.cc create mode 100644 src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.h diff --git a/.bazelrc b/.bazelrc index 887ffa802..6c4463fca 100644 --- a/.bazelrc +++ b/.bazelrc @@ -142,8 +142,12 @@ build:instance_gcp --//:instance=gcp build:instance_aws --//:instance=aws +build:instance_azure --//:instance=azure + build:platform_aws --//:platform=aws +build:platform_azure --//:platform=azure + build:platform_gcp --//:platform=gcp build:local_aws --config=instance_local @@ -159,5 +163,12 @@ build:gcp_gcp --config=platform_gcp build:aws_aws --config=instance_aws build:aws_aws --config=platform_aws + +build:local_azure --config=instance_local +build:local_azure --config=platform_azure + +build:azure_azure --config=instance_azure +build:azure_azure --config=platform_azure + build:non_prod --//:build_flavor=non_prod build:prod --//:build_flavor=prod diff --git a/BUILD.bazel b/BUILD.bazel index 3a97df9da..6edac5791 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2023 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +21,7 @@ string_flag( build_setting_default = "aws", values = [ "aws", + "azure", "gcp", "local", ], @@ -34,6 +36,14 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "azure_platform", + flag_values = { + ":platform": "azure", + }, + visibility = ["//visibility:public"], +) + config_setting( name = "gcp_platform", flag_values = { @@ -55,6 +65,7 @@ string_flag( build_setting_default = "aws", values = [ "aws", + "azure", "gcp", "local", ], @@ -69,6 +80,14 @@ config_setting( visibility = ["//visibility:public"], ) +config_setting( + name = "azure_instance", + flag_values = { + ":instance": "azure", + }, + visibility = ["//visibility:public"], +) + config_setting( name = "gcp_instance", flag_values = { diff --git a/src/BUILD.bazel b/src/BUILD.bazel index d70e5b625..44f4c26da 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -20,6 +20,7 @@ package_group( name = "scp_internal_pkg", packages = [ "//src/aws/...", + "//src/azure/...", "//src/core/...", "//src/cpio/...", "//src/public/...", diff --git a/src/azure/BUILD.bazel b/src/azure/BUILD.bazel new file mode 100644 index 000000000..5e388ea9e --- /dev/null +++ b/src/azure/BUILD.bazel @@ -0,0 +1,20 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +alias( + name = "attestation", + actual = "//src/azure/attestation:src", +) diff --git a/src/azure/attestation/BUILD.bazel b/src/azure/attestation/BUILD.bazel new file mode 100644 index 000000000..b5acdced8 --- /dev/null +++ b/src/azure/attestation/BUILD.bazel @@ -0,0 +1,30 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +alias( + name = "src", + actual = "//src/azure/attestation/src:attestation", +) + +alias( + name = "utils", + actual = "//src/azure/attestation/src/utils:attestation_utils", +) + +alias( + name = "print_report", + actual = "//src/azure/attestation/src:print_report", +) diff --git a/src/azure/attestation/src/BUILD.bazel b/src/azure/attestation/src/BUILD.bazel new file mode 100644 index 000000000..cdf29b1dd --- /dev/null +++ b/src/azure/attestation/src/BUILD.bazel @@ -0,0 +1,51 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "attestation", + srcs = [ + "endorsed_tcb.cc", + "endorsements.cc", + "evidence.cc", + "fake_report.cc", + "report.cc", + "snp.cc", + "uvm_endorsements.cc", + ], + hdrs = [ + "attestation.h", + "sev.h", + "sev_guest.h", + ], + deps = [ + "//src/azure/attestation:utils", + "//src/core/utils:core_utils", + "@com_github_google_glog//:glog", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@nlohmann_json//:lib", + ], +) + +cc_binary( + name = "print_report", + srcs = ["print_report.cc"], + deps = [ + "attestation", + ], +) diff --git a/src/azure/attestation/src/attestation.h b/src/azure/attestation/src/attestation.h new file mode 100644 index 000000000..b30d820f6 --- /dev/null +++ b/src/azure/attestation/src/attestation.h @@ -0,0 +1,151 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AZURE_ATTESTATION_H +#define AZURE_ATTESTATION_H + +#include +#include +#include + +#include + +namespace google::scp::azure::attestation { + +/* from SEV-SNP Firmware ABI Specification Table 20 */ +struct SnpRequest { + uint8_t report_data[64]; + uint32_t vmpl; + uint8_t reserved[28]; // needs to be zero +}; + +enum SNP_MSG_TYPE { + SNP_MSG_TYPE_INVALID = 0, + SNP_MSG_CPUID_REQ, + SNP_MSG_CPUID_RSP, + SNP_MSG_KEY_REQ, + SNP_MSG_KEY_RSP, + SNP_MSG_REPORT_REQ, + SNP_MSG_REPORT_RSP, + SNP_MSG_EXPORT_REQ, + SNP_MSG_EXPORT_RSP, + SNP_MSG_IMPORT_REQ, + SNP_MSG_IMPORT_RSP, + SNP_MSG_ABSORB_REQ, + SNP_MSG_ABSORB_RSP, + SNP_MSG_VMRK_REQ, + SNP_MSG_VMRK_RSP, + SNP_MSG_TYPE_MAX +}; + +/* from SEV-SNP Firmware ABI Specification from Table 21 */ +struct SnpReport { + uint32_t version; // version no. of this attestation report. + // Set to 1 for this specification. + uint32_t guest_svn; // The guest SVN + uint64_t policy; // see table 8 - various settings + __uint128_t family_id; // as provided at launch + __uint128_t image_id; // as provided at launch + uint32_t vmpl; // the request VMPL for the attestation + // report + uint32_t signature_algo; + uint64_t platform_version; // The install version of the firmware + uint64_t platform_info; // information about the platform see table + // 22 + // not going to try to use bit fields for + // this next one. Too confusing as to which + // bit of the byte will be used. Make a mask + // if you need it + uint32_t author_key_en; // 31 bits of reserved, must be zero, bottom + // bit indicates that the digest of the + // author key is present in + // AUTHOR_KEY_DIGEST. Set to the value of + // GCTX.AuthorKeyEn. + uint32_t reserved1; // must be zero + uint8_t report_data[64]; // Guest provided data. + uint8_t measurement[48]; // measurement calculated at launch + uint8_t host_data[32]; // data provided by the hypervisor at launch + uint8_t id_key_digest[48]; // SHA-384 digest of the ID public key that + // signed the ID block provided in + // SNP_LAUNCH_FINISH + uint8_t author_key_digest[48]; // SHA-384 digest of the Author public key + // that certified the ID key, if provided in + // SNP_LAUNCH_FINISH. Zeros if author_key_en + // is 1 (sounds backwards to me). + uint8_t report_id[32]; // Report ID of this guest. + uint8_t report_id_ma[32]; // Report ID of this guest's mmigration + // agent. + uint64_t reported_tcb; // Reported TCB version used to derive the + // VCEK that signed this report + uint8_t reserved2[24]; // reserved + uint8_t chip_id[64]; // Identifier unique to the chip + uint8_t committed_svn[8]; // The current commited SVN of the firware + // (version 2 report feature) + uint8_t committed_version[8]; // The current commited version of the + // firware + uint8_t launch_svn[8]; // The SVN that this guest was launched or + // migrated at + uint8_t reserved3[168]; // reserved + uint8_t signature[512]; // Signature of this attestation report. + // See table 23. +}; + +/* from SEV-SNP Firmware ABI Specification Table 22 */ +struct SnpResponse { + uint32_t status; + uint32_t report_size; + uint8_t reserved[24]; + SnpReport report; + uint8_t padding[64]; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ + // (i.e., 1280 bytes) +}; + +enum SnpType { SEV, SEV_GUEST, NONE }; + +struct AttestationReport { + std::string evidence; + std::string endorsements; + std::string uvm_endorsements; + std::string endorsed_tcb; + + operator nlohmann::json() const { + return nlohmann::json{{"evidence", evidence}, + {"endorsements", endorsements}, + {"uvm_endorsements", uvm_endorsements}, + {"endorsed_tcb", endorsed_tcb}}; + } +}; + +SnpType getSnpType(); + +bool hasSnp(); + +std::optional fetchSnpAttestation( + const std::string report_data = ""); + +std::optional fetchFakeSnpAttestation(); + +std::string getSnpEvidence(const std::string report_data); + +std::string getSnpEndorsements(); + +std::string getSnpUvmEndorsements(); + +std::string getSnpEndorsedTcb(); + +} // namespace google::scp::azure::attestation + +#endif // AZURE_ATTESTATION_H diff --git a/src/azure/attestation/src/endorsed_tcb.cc b/src/azure/attestation/src/endorsed_tcb.cc new file mode 100644 index 000000000..9c118e6c2 --- /dev/null +++ b/src/azure/attestation/src/endorsed_tcb.cc @@ -0,0 +1,45 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ATTESTATION_ENDORSED_TCB_H +#define ATTESTATION_ENDORSED_TCB_H + +#include + +#include "utils/host_amd_certs.h" + +using google::scp::azure::attestation::utils::getHostAmdCerts; + +namespace google::scp::azure::attestation { + +std::string getSnpEndorsedTcb() { + auto host_certs_json = getHostAmdCerts(); + + // Extract the endorsed TCB from the JSON + std::string endorsed_tcb_reversed_endian = host_certs_json["tcbm"]; + + // Reverse the endianess of the endorsed TCB + std::string endorsed_tcb = ""; + for (int i = endorsed_tcb_reversed_endian.length() - 2; i >= 0; i -= 2) { + endorsed_tcb += endorsed_tcb_reversed_endian.substr(i, 2); + } + + return endorsed_tcb; +} + +} // namespace google::scp::azure::attestation + +#endif // ATTESTATION_ENDORSED_TCB_H diff --git a/src/azure/attestation/src/endorsements.cc b/src/azure/attestation/src/endorsements.cc new file mode 100644 index 000000000..e9de5a727 --- /dev/null +++ b/src/azure/attestation/src/endorsements.cc @@ -0,0 +1,63 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ATTESTATION_ENDORSEMENTS_H +#define ATTESTATION_ENDORSEMENTS_H + +#include + +#include "src/core/utils/base64.h" +#include "utils/host_amd_certs.h" + +using google::scp::azure::attestation::utils::getHostAmdCerts; +using google::scp::core::utils::Base64Encode; + +namespace google::scp::azure::attestation { + +std::string replace(const std::string& input, const std::string toReplace, + const std::string replaceWith) { + std::string output = input; + size_t pos = 0; + while ((pos = output.find(toReplace, pos)) != std::string::npos) { + output.replace(pos, toReplace.length(), replaceWith); + pos += replaceWith.length(); + } + return output; +} + +std::string getSnpEndorsements() { + auto host_certs_json = getHostAmdCerts(); + + // Extract the certs from the JSON + std::string vcekCert = host_certs_json["vcekCert"].dump(); + std::string certificateChain = host_certs_json["certificateChain"].dump(); + + // Combine the certs into a chain and cleanup characters that shouldn't be + // there + std::string endorsementCerts = vcekCert + certificateChain; + endorsementCerts.erase( + std::remove(endorsementCerts.begin(), endorsementCerts.end(), '\"'), + endorsementCerts.cend()); + endorsementCerts = replace(endorsementCerts, "\\n", "\n"); + + // Base64 encode the certificate chain + Base64Encode(endorsementCerts, endorsementCerts); + return endorsementCerts; +} + +} // namespace google::scp::azure::attestation + +#endif // ATTESTATION_ENDORSEMENTS_H diff --git a/src/azure/attestation/src/evidence.cc b/src/azure/attestation/src/evidence.cc new file mode 100644 index 000000000..00239b091 --- /dev/null +++ b/src/azure/attestation/src/evidence.cc @@ -0,0 +1,63 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ATTESTATION_EVIDENCE_H +#define ATTESTATION_EVIDENCE_H + +#include + +#include + +#include "absl/log/check.h" +#include "src/core/utils/base64.h" + +#include "attestation.h" +#include "sev.h" +#include "sev_guest.h" + +namespace google::scp::azure::attestation { + +std::string base64EncodeBytes(const uint8_t* decoded, size_t size) { + size_t required_len = 0; + EVP_EncodedLength(&required_len, size); + auto buffer = std::make_unique(required_len); + int ret = EVP_EncodeBlock(buffer.get(), decoded, size); + return std::string(reinterpret_cast(buffer.get()), ret); +} + +std::string getSnpEvidence(const std::string report_data) { + std::unique_ptr report; + + switch (getSnpType()) { + case SnpType::SEV: + std::cout << "Getting report from /dev/sev" << std::endl; + report = sev::getReport(report_data); + break; + case SnpType::SEV_GUEST: + std::cout << "Getting report from /dev/sev-guest" << std::endl; + report = sev_guest::getReport(report_data); + break; + default: + CHECK(false) << "Unsupported or no SNP type"; + } + + return base64EncodeBytes(reinterpret_cast(report.get()), + sizeof(SnpReport)); +} + +} // namespace google::scp::azure::attestation + +#endif // ATTESTATION_EVIDENCE_H diff --git a/src/azure/attestation/src/fake_report.cc b/src/azure/attestation/src/fake_report.cc new file mode 100644 index 000000000..06a87cd27 --- /dev/null +++ b/src/azure/attestation/src/fake_report.cc @@ -0,0 +1,376 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impli +ed. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "attestation.h" + +namespace google::scp::azure::attestation { + +std::optional fetchFakeSnpAttestation() { + return AttestationReport{ + // evidence + "AgAAAAIAAAAfAAMAAAAAAAEAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAA" + "AAAAAEAAAADAAAAAAAI0gEAAAAAAAAAAAAAAAAAAAA2sD2rjodRsm2bM/ovoSlvgjojjvPdY" + "E91ikr/WytB0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsOw1b8dJW+k47Xe7" + "8B7Vf8vcCkIXtNQ9glZFAoaUfExB1O6WrLAOgU2scDBk69Hc5c7eNcMxoNTQm3hiNtd/Fflt" + "2bjmZNftzphEn6ibSCBLO9VZ+eO/KBz/eiJSxgOqu87VghAkMZHgrWtU2vPED0ZSRSyKcqsX" + "FQIr86R/IYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AC2lN7JUaqQruN7xCbmF5A00095kZm9M5Ex5ygXa4omgv///////////////////////////" + "///////////////AwAAAAAACHMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHOcn2RumKAbhKX" + "wuevuGwu3RUv/Tljqp3eiCbJhi4iwAB2XK4uMPbLcSojlKkRl8oV61G54T+BfGqorJpZGqqA" + "wAAAAAACHMENAEABDQBAAMAAAAAAAhzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh/pyLeFMLEuTT4XXY3X9RRev+7NOJ3j/1" + "2h+XcmKrN/IhHk6HnqvkKDPVRaOoeD1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYrnRk7gjA" + "hyhgTmE4zt/DhBL9Eci+ksEj4M5meaieDGOD8sgqYWdDGUkbIgS+5JrAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AA=", + // endorsements + "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZURENDQXZ1Z0F3SUJBZ0lCQ" + "URCR0Jna3Foa2lHOXcwQkFRb3dPYUFQTUEwR0NXQ0dTQUZsQXdRQ0FnVUEKb1J3d0dnWUpLb" + "1pJaHZjTkFRRUlNQTBHQ1dDR1NBRmxBd1FDQWdVQW9nTUNBVENqQXdJQkFUQjdNUlF3RWdZR" + "ApWUVFMREF0RmJtZHBibVZsY21sdVp6RUxNQWtHQTFVRUJoTUNWVk14RkRBU0JnTlZCQWNNQ" + "zFOaGJuUmhJRU5zCllYSmhNUXN3Q1FZRFZRUUlEQUpEUVRFZk1CMEdBMVVFQ2d3V1FXUjJZV" + "zVqWldRZ1RXbGpjbThnUkdWMmFXTmwKY3pFU01CQUdBMVVFQXd3SlUwVldMVTFwYkdGdU1CN" + "FhEVEl5TVRFeU9URXhNemMxTUZvWERUSTVNVEV5T1RFeApNemMxTUZvd2VqRVVNQklHQTFVR" + "UN3d0xSVzVuYVc1bFpYSnBibWN4Q3pBSkJnTlZCQVlUQWxWVE1SUXdFZ1lEClZRUUhEQXRUW" + "Vc1MFlTQkRiR0Z5WVRFTE1Ba0dBMVVFQ0F3Q1EwRXhIekFkQmdOVkJBb01Ga0ZrZG1GdVkyV" + "msKSUUxcFkzSnZJRVJsZG1salpYTXhFVEFQQmdOVkJBTU1DRk5GVmkxV1EwVkxNSFl3RUFZS" + "EtvWkl6ajBDQVFZRgpLNEVFQUNJRFlnQUUwdmxTaHJJaUw0TnFOd2ZydDhVa1NqRUFXVEp5c" + "nRUdDZQbEY1M2tUVHdaMlVES2tObWovCkM1NXVnOXdxYjBJWHhsUDR4VUdWNHdtczFyR3dma" + "FZqbkI5L1hrWFF4SVJYalYrUldLVWo3RjdMcGhDQWNPdG0KM2d0K0NSTmlQV3pobzRJQkZqQ" + "0NBUkl3RUFZSkt3WUJCQUdjZUFFQkJBTUNBUUF3RndZSkt3WUJCQUdjZUFFQwpCQW9XQ0Uxc" + "GJHRnVMVUl3TUJFR0Npc0dBUVFCbkhnQkF3RUVBd0lCQXpBUkJnb3JCZ0VFQVp4NEFRTUNCQ" + "U1DCkFRQXdFUVlLS3dZQkJBR2NlQUVEQkFRREFnRUFNQkVHQ2lzR0FRUUJuSGdCQXdVRUF3S" + "UJBREFSQmdvckJnRUUKQVp4NEFRTUdCQU1DQVFBd0VRWUtLd1lCQkFHY2VBRURCd1FEQWdFQ" + "U1CRUdDaXNHQVFRQm5IZ0JBd01FQXdJQgpDREFSQmdvckJnRUVBWng0QVFNSUJBTUNBWE13V" + "FFZSkt3WUJCQUdjZUFFRUJFQkhPY24yUnVtS0FiaEtYd3VlCnZ1R3d1M1JVdi9UbGpxcDNla" + "UNiSmhpNGl3QUIyWEs0dU1QYkxjU29qbEtrUmw4b1Y2MUc1NFQrQmZHcW9ySnAKWkdxcU1FW" + "UdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUNCUUNoSERBYUJna3Foa2lHO" + "XcwQgpBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJBNElDQVFCVTUxbjF3S" + "VdhbkpMcVdMZWtsQVN0Cmt3TVcrdGdqOEp5SE93QU9OK2tMNjQ0akpkbVZzLzVlK3k2d0FzR" + "UYwRVlEWENwOHdSSGNUemlqbjdIOHN2cEoKVlhOVURwNWpiSHNYWlNsL3ZHeEs4a3gzbytFM" + "zlOUjNWR3ZVN0J0NkUrdW8wSEVOUjJMdVF2REV4Qlg1MkJnYwpscEw1RWVMYUROdHdjYlFWU" + "UlvT3lIblh2Z290QmRrczlUZEpDaFR1bE9hem5lSUVsS29xeFhRS2dWcE54NFVZCk1UdnZpN" + "zNBZDZDYUx5dnplRXhWZHFzdnF2SFhXOXBhaGx2OS9QSUs0WFo2N201V1pWVzJ6dE1hOEpvS" + "nF6Um0KY3lFVks1NnRIUEJuWUM0bDRZenBpbjFGdGdZM25VNXFUdlMySW5na01SSkZCVHkrM" + "U5qNU45d2xoSVpQVWdmVwpBUllRc0RhS1padCtLNEJkTUxxUmFYVXE0ODV4ZWhTaWkzOUIvT" + "DBXWVAzckozQ3N6bThoMGd2SFhKd1c2cWtUCjBZZTA2Tng3K2RSM2psNWh3ZjR5R2lTRVRmZ" + "mVzWC9idURaV0xKRzNKNkNja3RDSGF2SU0zckptQ1JKYndoRWEKME4vakhtbVpRZ0dIVG5vO" + "GZlM1BNT2ZvU0NoNVFVVCtRVXkzWFkzSkxZeHQxOU01SG5VbENQRTFFUFNzRzhoUgpRNkVkM" + "XdpTFk0Q0xBcktidm1BQklzNWZLd01zdkNVOUxBbXhGbkdOekx4cXc0Q1I4TWthZlBKNElwY" + "1FpTXhNCkdySDdBaXFUQ1h0T29aTDZEOTVYS2ZUbHhKQkp2MExvYlIyM2RJNnFBWFd2Qm1xM" + "E1sRGExbU9JZUlpMFRtM3AKbCtmeFg3R1RPN2ltNU83NGxOVzZEUT09Ci0tLS0tRU5EIENFU" + "lRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdpVENDQkRpZ" + "0F3SUJBZ0lEQVFBQk1FWUdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUMKQ" + "lFDaEhEQWFCZ2txaGtpRzl3MEJBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnR" + "UJNSHN4RkRBUwpCZ05WQkFzTUMwVnVaMmx1WldWeWFXNW5NUXN3Q1FZRFZRUUdFd0pWVXpFV" + "U1CSUdBMVVFQnd3TFUyRnVkR0VnClEyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1SOHdIUVlEV" + "lFRS0RCWkJaSFpoYm1ObFpDQk5hV055YnlCRVpYWnAKWTJWek1SSXdFQVlEVlFRRERBbEJVa" + "3N0VFdsc1lXNHdIaGNOTWpBeE1ESXlNVGd5TkRJd1doY05ORFV4TURJeQpNVGd5TkRJd1dqQ" + "jdNUlF3RWdZRFZRUUxEQXRGYm1kcGJtVmxjbWx1WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhGREFTC" + "kJnTlZCQWNNQzFOaGJuUmhJRU5zWVhKaE1Rc3dDUVlEVlFRSURBSkRRVEVmTUIwR0ExVUVDZ" + "3dXUVdSMllXNWoKWldRZ1RXbGpjbThnUkdWMmFXTmxjekVTTUJBR0ExVUVBd3dKVTBWV0xVM" + "XBiR0Z1TUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBblUyZ" + "HJyTlRmYmhOUUlsbGYrVzJ5K1JPQ2JTeklkMWFLWmZ0CjJUOXpqWlFPempHY2NsMTdpMW1JS" + "1dsN05UY0IwVllYdDNKeFpTek9aanNqTE5WQUVOMk1HajlUaWVkTCtRZXcKS1pYMEptUUV1W" + "WptK1dLa3NMdHhnZExwOUU3RVpOd05EcVYxcjBxUlA1dEI4T1dreVFiSWRMZXU0YUN6N2ovU" + "wpsMUZrQnl0ZXY5c2JGR3p0N2N3bmp6aTltN25vcXNrK3VSVkJwMytJbjM1UVBkY2o4WWZsR" + "W1uSEJOdnVVREpoCkxDSk1XOEtPalA2KytQaGJzM2lDaXRKY0FORXRXNHFUTkZvS1czQ0hsY" + "mNTQ2pUTThLc05iVXgzQThlazVFVkwKalpXSDFwdDlFM1RmcFI2WHlmUUtuWTZrbDVhRUlQd" + "2RXM2VGWWFxQ0ZQcklvOXBRVDZXdURTUDRKQ1lKYlpuZQpLS0liWmp6WGtKdDNOUUczMkV1a" + "1lJbUJiOVNDa205K2ZTNUxaRmc5b2p6dWJNWDMrTmtCb1NYSTdPUHZuSE14Cmp1cDltdzVzZ" + "TZRVVY3R3FwQ0EyVE55cG9sbXVRK2NBYXhWN0pxSEU4ZGw5cFdmK1kzYXJiKzlpaUZDd0Z0N" + "GwKQWxKdzVEMENUUlRDMVk1WVdGREJDckEvdkdubVRucUc4QytqalVBUzdjampSOHE0T1Boe" + "URtSlJQbmFDL1pHNQp1UDBLMHo2R29PLzN1ZW45d3FzaEN1SGVnTFRwT2VIRUpSS3JRRnI0U" + "FZJd1ZPQjArZWJPNUZnb3lPdzQzbnlGCkQ1VUtCRHhFQjRCS28vMHVBaUtITFJ2dmdMYk9SY" + "lU4S0FSSXMxRW9xRWptRjhVdHJtUVdWMmhVand6cXd2SEYKZWk4clB4TUNBd0VBQWFPQm96Q" + "0JvREFkQmdOVkhRNEVGZ1FVTzhadUdDckQvVDFpWkVpYjQ3ZEhMTFQ4di9ndwpId1lEVlIwa" + "kJCZ3dGb0FVaGF3YTBVUDN5S3hWMU1VZFFVaXIxWGhLMUZNd0VnWURWUjBUQVFIL0JBZ3dCZ" + "0VCCi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FRUXdPZ1lEVlIwZkJETXdNVEF2b0MyZ0s0W" + "XBhSFIwY0hNNkx5OXIKWkhOcGJuUm1MbUZ0WkM1amIyMHZkbU5sYXk5Mk1TOU5hV3hoYmk5a" + "mNtd3dSZ1lKS29aSWh2Y05BUUVLTURtZwpEekFOQmdsZ2hrZ0JaUU1FQWdJRkFLRWNNQm9HQ" + "1NxR1NJYjNEUUVCQ0RBTkJnbGdoa2dCWlFNRUFnSUZBS0lECkFnRXdvd01DQVFFRGdnSUJBS" + "WdlVVFTY0FmM2xEWXFnV1UxVnRsRGJtSU44UzJkQzVrbVF6c1ovSHRBalFuTEUKUEkxamgzZ" + "0piTHhMNmdmM0s4anhjdHpPV25rWWNiZGZNT09yMjhLVDM1SWFBUjIwcmVrS1JGcHRUSGhlK" + "0RGcgozQUZ6WkxERDdjV0syOS9HcFBpdFBKREtDdkk3QTRVZzA2cms3SjB6QmUxZnovcWU0a" + "TIvRjEycnZmd0NHWWhjClJ4UHk3UUYzcThmUjZHQ0pkQjFVUTVTbHdDakZ4RDR1ZXpVUnp0S" + "WxJQWpNa3Q3REZ2S1JoKzJ6Sys1cGxWR0cKRnNqREp0TXoydWQ5eTBwdk9FNGozZEg1SVc5a" + "kd4YVNHU3RxTnJhYm5ucEYyMzZFVHIxL2E0M2I4RkZLTDVRTgptdDhWcjl4blhScHpucUNSd" + "nFqcitrVnJiNmRsZnVUbGxpWGVRVE1sQm9SV0ZKT1JMOEFjQkp4R1o0SzJtWGZ0CmwxalU1V" + "ExlaDVLWEw5Tlc3YS9xQU9JVXMyRmlPaHFydHpBaEpSZzlJajhRa1E5UGsrY0tHenc2RWwzV" + "DNrRnIKRWc2emt4bXZNdWFiWk9zZEtmUmtXZmhIMlpLY1RsRGZtSDFIMHpxMFEyYkczdXZhV" + "mRpQ3RGWTFMbFd5QjM4SgpTMmZOc1IvUHk2dDVickVKQ0ZOdnphRGt5NktlQzRpb24vY1ZnV" + "WFpN3p6UzNiR1FXektES1UzNVNxTlUyV2tQCkk4eENaMDBXdElpS0tGblhXVVF4dmxLbW1nW" + "kJJWVBlMDF6RDBOOGF0RnhtV2lTbmZKbDY5MEI5ckpwTlIvZkkKYWp4Q1czU2Vpd3M2cjFab" + "St0Q3VWYk1pTnRwUzlUaGpOWDR1dmU1dGh5ZkUyRGdveFJGdlkxQ3NvRjVNCi0tLS0tRU5EI" + "ENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUdZekNDQ" + "kJLZ0F3SUJBZ0lEQVFBQU1FWUdDU3FHU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBS" + "UMKQlFDaEhEQWFCZ2txaGtpRzl3MEJBUWd3RFFZSllJWklBV1VEQkFJQ0JRQ2lBd0lCTUtNR" + "EFnRUJNSHN4RkRBUwpCZ05WQkFzTUMwVnVaMmx1WldWeWFXNW5NUXN3Q1FZRFZRUUdFd0pWV" + "XpFVU1CSUdBMVVFQnd3TFUyRnVkR0VnClEyeGhjbUV4Q3pBSkJnTlZCQWdNQWtOQk1SOHdIU" + "VlEVlFRS0RCWkJaSFpoYm1ObFpDQk5hV055YnlCRVpYWnAKWTJWek1SSXdFQVlEVlFRRERBb" + "EJVa3N0VFdsc1lXNHdIaGNOTWpBeE1ESXlNVGN5TXpBMVdoY05ORFV4TURJeQpNVGN5TXpBM" + "VdqQjdNUlF3RWdZRFZRUUxEQXRGYm1kcGJtVmxjbWx1WnpFTE1Ba0dBMVVFQmhNQ1ZWTXhGR" + "EFTCkJnTlZCQWNNQzFOaGJuUmhJRU5zWVhKaE1Rc3dDUVlEVlFRSURBSkRRVEVmTUIwR0ExV" + "UVDZ3dXUVdSMllXNWoKWldRZ1RXbGpjbThnUkdWMmFXTmxjekVTTUJBR0ExVUVBd3dKUVZKT" + "ExVMXBiR0Z1TUlJQ0lqQU5CZ2txaGtpRwo5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBM" + "ExkNTJSSk9kZWlKbHFLMkpkc1ZtRDdGa3R1b3RXd1gxZk5nClc0MVhZOVh6MUhFaFNVbWhMe" + "jlDdTlESFJsdmdKU054YmVZWXNuSmZ2eWp4MU1mVTBWNXRrS2lVMUVlc05GdGEKMWtUQTBze" + "k5pc2RZYzlpc3FrN21YVDUrS2ZHUmJmYzRWLzl6UkljRThqbEhONjFTMWp1OFg5Mys2ZHhEV" + "XJHMgpTenhxSjRCaHF5WW1VRHJ1UFhKU1g0dlVjMDFQN2o5OE1wcU9TOTVyT1JkR0hlSTUyT" + "mF6NW0yQitPK3Zqc0MwCjYwZDM3alk5TEZldU9QNE1lcmk4cWdmaTJTNWtLcWcvYUY2YVB0d" + "UFaUVZSN3UzS0ZZWFA1OVhtSmd0Y29nMDUKZ21JMFQvT2l0TGh1elZ2cFpjTHBoMG9kaC8xS" + "VBYcXgzK01uakQ5N0E3ZlhwcUdkL3k4S3hYN2prc1RFekFPZwpiS0FlYW0zbG0rM3lLSWNUW" + "U1sc1JNWFBjak5iSXZtc0J5a0QvL3hTbml1c3VIQmtnbmxFTkVXeDFVY2JRUXJzCitnVkRrd" + "VZQaHNueklSTmdZdk00OFkrN0xHaUpZbnJtRTh4Y3JleGVrQnhydmEyVjlUSlFxbk4zUTUza" + "3Q1dmkKUWkzK2dDZm1rd0MwRjB0aXJJWmJMa1hQclB3elowTTllTnhoSXlTYjJucEpmZ25xe" + "jU1STB1MzN3aDRyMFpOUQplVEdmdzAzTUJVdHl1ekdlc0drY3crbG9xTWFxMXFSNHRqR2JQW" + "XhDdnBDcTcrT2dwQ0NvTU5pdDJ1TG85TTE4CmZIejEwbE9NVDhuV0FVdlJaRnp0ZVhDbSs3U" + "EhkWVBsbVF3VXczTHZlbkovSUxYb1FQSGZia0gwQ3lQZmhsMWoKV2hKRlphc0NBd0VBQWFOK" + "01Id3dEZ1lEVlIwUEFRSC9CQVFEQWdFR01CMEdBMVVkRGdRV0JCU0ZyQnJSUS9mSQpyRlhVe" + "FIxQlNLdlZlRXJVVXpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTURvR0ExVWRId1F6TURFd0w2Q" + "XRvQ3VHCktXaDBkSEJ6T2k4dmEyUnphVzUwWmk1aGJXUXVZMjl0TDNaalpXc3ZkakV2VFdsc" + "1lXNHZZM0pzTUVZR0NTcUcKU0liM0RRRUJDakE1b0E4d0RRWUpZSVpJQVdVREJBSUNCUUNoS" + "ERBYUJna3Foa2lHOXcwQkFRZ3dEUVlKWUlaSQpBV1VEQkFJQ0JRQ2lBd0lCTUtNREFnRUJBN" + "ElDQVFDNm0wa0RwNnp2NE9qZmd5K3psZWVoc3g2b2wwb2NnVmVsCkVUb2JweCtFdUNzcVZGU" + "lBLMWpaMXNwL2x5ZDkrMGZRMHI2Nm43a2FnUms0Q2EzOWc2NldHVEpNZUpkcVlyaXcKU1Rqa" + "kRDS1ZQU2VzV1hZUFZBeURobVA1bjJ2K0JZaXBaV2hwdnFwYWlPK0VHSzVJQlArNTc4UWVXL" + "3NTb2tySwpkSGFMQXhHMkxoWnhqOWFGNzNmcUM3T0FKWjVhUG9udzRSRTI5OUZWYXJoMVR4M" + "mVUM3dTZ2tEZ3V0Q1RCMVlxCnpUNUR1d3ZBZStjbzJDSVZJek1EYW1ZdVNGalBOMEJDZ29qb" + "DdWK2JUb3U3ZE1zcUl1L1RXL3JQQ1g5L0VVY3AKS0dLcVBRM1ArTjlyMWhqRUZZMXBsQmc5M" + "3Q1M09PbzQ5R05JK1YxenZYUExJNnhJRlZzaCttdG8yUnRnRVgvZQpwbU1LVE5ONnBzVzg4c" + "Wc3YzFoVFd0TjZNYlJ1UTB2bStPKy8ydEtCRjJoOFRIYjk0T3Z2SEhvRkRwYkNFTGxxCkhuS" + "VloeHkwWUtYR3lhVzFOamZVTHhycm14Vlc0d2NuNUU4R2RkbXZOYTZ5WW04c2NKYWdFaTEzb" + "WhHdTRKcWgKM1FVM3NmOGlVU1VyMDl4UUR3SHRPUVVWSXF4NG1hQlpQQnRTTWYrcVVEdGpYU" + "1NxOGxmV2NkOGJMcjltZHNVbgpKWkowK3R1UE1LbUJuU0g4NjBsbEtrK1ZwVlFzZ3FiekRJd" + "k9MdkQ2VzFVbXEyNWJveENZSitUdUJvYTRzK0hICkNWaUF2Z1Q5a2YvckJxMWQraXZqNnNra" + "0h4dXpjeGJrMXh2NlpHeHJ0ZUp4Vkg3S2xYN1lSZFo2ZUFSS3dMZTQKQUZaRUF3b0tDUT09C" + "i0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", + // uvm_endorsements + "0oRZE86nATglA3BhcHBsaWNhdGlvbi9qc29uGCGDWQZvMIIGazCCBFOgAwIBAgITM" + "wAAABxxpnEfWQZPEAAAAAAAHDANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJVUzEeMBwGA" + "1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgU0NEIFByb" + "2R1Y3RzIFJTQSBDQTAeFw0yMzEwMTkyMDAwMjdaFw0yNDEwMTYyMDAwMjdaMGwxCzAJBgNVB" + "AYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKE" + "xVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAMTDUNvbnRhaW5lclBsYXQwggGiMA0GC" + "SqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDDs97+QnO9QUmRY8N09HSAWzHw8fbXbwQYzBW5q" + "iIMnBFVcWC2aC0g239fcl+/ubt6p3A1xW75zaMmvibFPK/iUxKiJtek7kZdDD0PI2eoL/EmP" + "BL0OLJwSb8NzKJbva+dSXndYjidTCOSBT7f862RBNF/TmidfPl6Qte59Yim5RZ+VyDGOG2Sr" + "3qY0oiD+lzE4ZCJNtdfi8SVGXjY9VHXLKReoU1eHNtqTO6iRSk0R4VKIKfao1l4b10XM9Ufu" + "Km0O96QHwYNRDydqBivQ8Yr2HILgsKvk1lxyt6DIlUX5RsHZgpMM2CrphXQ83vRt6//BqZFk" + "z30VD1LKGJs/IcY7hS5qgYZAakulz1KWUBQuihQ2IZeIcQVuJ2MAxGX3MsW8NkFCalZTMPlN" + "/IBd0Pwb95MwT/kP4hVNjREHZBxxpOx4lXqkrAtQ3RvvtjmVxdUDGxLIgCCIx2g0eMIRS6gh" + "IwaEN2ldk3nOsBbQu6qxlyq/+H4GwW1XeuUYi8yEJECAwEAAaOCAZswggGXMA4GA1UdDwEB/" + "wQEAwIHgDAjBgNVHSUEHDAaBgsrBgEEAYI3TDsBAQYLKwYBBAGCN0w7AQIwHQYDVR0OBBYEF" + "PXTTQJXWkUWD7uFNOULaC+qbyhHMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQLExVNaWNyb3NvZ" + "nQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTQ3Mjk3Mis1MDE2MDUwHwYDVR0jBBgwFoAUVc1Nh" + "W7NSjXDjj9yAbqqmBmXS6cwXgYDVR0fBFcwVTBToFGgT4ZNaHR0cDovL3d3dy5taWNyb3NvZ" + "nQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwU0NEJTIwUHJvZHVjdHMlMjBSU0ElMjBDQ" + "S5jcmwwawYIKwYBBQUHAQEEXzBdMFsGCCsGAQUFBzAChk9odHRwOi8vd3d3Lm1pY3Jvc29md" + "C5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFNDRCUyMFByb2R1Y3RzJTIwUlNBJTIwQ" + "0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEMBQADggIBAHaZyKfQ+0uXl79Y8tgT3" + "eOzDnKhupIdqw4Gw56vT7FuQM0v+/klkLXVS/lDQ0UOIZCVOYF21hEKl5F5l/XORPRs0XMtu" + "Hi9VFUq4x/I0r7692vykPY6NdUZWCsySzaWfr6db/nvJx11w/bWsljCKvQ5xnKH+d1jCbf5S" + "oJbYLjiyGXue85X0So334BOG5+sFf7iVl3UUuM8d2cccSWXaarjjVXxw44vImFEU+1W0iQSd" + "kxojL0uFPNA3MjQNlkG2Wf4xAS6S+m6dIz380UW6Ax8c5Kivnt+tnIKkvpz9mHY+grp98Lrm" + "g5JsQLN7oSdXiIe0EGP5DudUpPpOWN32npHYnDzecR+NLapAyXmoS/EG01Fhq4fVUp+PyGr3" + "6YjnvBI297g92f6h1NtSiJel1WIAxVXYWPo8d/3YVVlM/8pDJBWCTdt+CBGGKQ3ogfSESkHs" + "VmStjM/ItOgu1iC51jQFDwhxxF80V2sqKPx7PA+Ftt1oYkHy08E8rU65djZm6dtbVsq7QZDa" + "FmpIpABs7yT3YOMuW3B++Rz1QOHVF2M3sDmb1KXyaX2S89khSZHaSVlpxWjKl4c/b1sIQiIo" + "1XDkMoQj8DndejbNpIRIUHTgS7B3PyLKbBw8DNQLKImbFlJMeXdiVD77bTAR0nmLrMY3UNAB" + "ISI0NE19NK/30eiWQbVMIIG0TCCBLmgAwIBAgITMwAAAAOVhEf/iehmCQAAAAAAAzANBgkqh" + "kiG9w0BAQwFADBfMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0a" + "W9uMTAwLgYDVQQDEydNaWNyb3NvZnQgU3VwcGx5IENoYWluIFJTQSBSb290IENBIDIwMjIwH" + "hcNMjIwMjE3MDA0NTIzWhcNNDIwMjE3MDA1NTIzWjBVMQswCQYDVQQGEwJVUzEeMBwGA1UEC" + "hMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgU0NEIFByb2R1Y" + "3RzIFJTQSBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvtf7VxvoxzvvHXy" + "p3xAdZ0h7yMQpNMn8qVdGtOR+pyhLWkFsGMQlTXDe2Yes+o7mC0IEQJMz39CJxIjG6XYIQfc" + "F2CaO/6MCzWzysbFvlTkoY/LN/g0/RlcJ/IdFlf0VWcvujpZPh9CLlEd0HS9qYFRAPRRQOvw" + "e3NT5uEd38fRbKbZ6vCJG2c/YxHByKbeooYReovPoNpVpxdaIDS64IdgGl8mX+yTPwwwLHOf" + "R+E2UWgnnQqgNYp0hCM2YZ+J5zU0QZCwZ1JMLXQ9eK0sJW3uPfj7iA/k1k57kN3dSZ4P4hkq" + "GVTAnrBzaoZsINMkGVJbgEpfSPrRLBOkr4Zmh7m8PigL8B8xIJ01Tx1KBmfiWAFGmVx++NSY" + "8oFxRW/DdKdwWLr5suCpB2ONjF7LNv4A5v4SZ+zYCwpTc8ouxPPUtZSG/fklVEFveW30jMJw" + "QAf29X8wAuJ0pwuWaP2PziQSonR4VmRP3cKz88aAbm0zmzvx+pdTCX9fH/cTuYwErjJA3d9G" + "7/3sDGE/QBqkjC+NkZI8XCdm6Ur8QIK4LaZJ/ZBT9QEkXF7xML0FBe3YLYWk5F2pc4d2wJin" + "ZIFvJJvLvkAp//guabt6wCXTjxHDz2RkiJnmiteSLO09DeQIvgEGY7nJTKy1oMwRoalGrL14" + "YD4QyNawcazBtGZQ20NAgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGCN" + "xUBBAMCAQAwHQYDVR0OBBYEFFXNTYVuzUo1w44/cgG6qpgZl0unMBEGA1UdIAQKMAgwBgYEV" + "R0gADAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdI" + "wQYMBaAFAuzaDuv2q/ucKV22SH3zEQWB9D4MGwGA1UdHwRlMGMwYaBfoF2GW2h0dHA6Ly93d" + "3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFN1cHBseSUyMENoYWluJ" + "TIwUlNBJTIwUm9vdCUyMENBJTIwMjAyMi5jcmwweQYIKwYBBQUHAQEEbTBrMGkGCCsGAQUFB" + "zAChl1odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyM" + "FN1cHBseSUyMENoYWluJTIwUlNBJTIwUm9vdCUyMENBJTIwMjAyMi5jcnQwDQYJKoZIhvcNA" + "QEMBQADggIBAG/eYdZr+kG/bRyUyOGKw8qn9DME5Ckmz3vmIdcmdU+LE3TnFzEBRo1FRF1td" + "OdqCq58vtH5luxa8hkl4wyvvAjv0ahppr+2UI79vyozKGIC4ud2zBpWgtmxifFv5KyXy7kZy" + "rvuaVDmR3hwAhpZyTfS6XLxdRnsDlsD95qdw89hBKf8l/QfFhCkPJi3BPftb0E1kFQ5qUzl4" + "jSngCKyT8fdXZBRdHlHil11BJpNm7gcJxJQfYWBX+EDRpNGS0YI5/cQhMES35jYJfGGosw9D" + "FCfORzjRmc1zpEVXUrnbnJDtcjrpeQz0DQg6KVwOjSkEkvjzKltH0+bnU1IKvrSuVy8RFWci" + "1vdrAj0I6Y2JaALcE00Lh86BHGYVK/NZEZQAAXlCPRaOQkcCaxkuT0zNZB0NppU1485jHR67" + "p78bbBpXSe9LyfpWFwB3q6jye9KW2uXi/7zTPYByX0AteoVo6JW56JXhILCWmzBjbj8WUzco" + "/sxjwbthT0WtKDADKuKREahCy0tSestD3D5XcGIdMvU9BBLFglXtW2LmdTDe4lLBSuuS2TQo" + "FBw/BoqXctCe/sDer5TVxeZ4h7zU50vcrCV74x+xCI4XpUmXI3uyLrhEVJh0C03L3pE+NTmI" + "Im+7Zk8q5MmrkQ7pVwkJdT7cW7YgiqkoCIOeygb/UVPXxhWWQWzMIIFrzCCA5egAwIBAgIQa" + "CjVTH5c2r1DOa4MwVoqNTANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQGEwJVUzEeMBwGA1UEC" + "hMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTAwLgYDVQQDEydNaWNyb3NvZnQgU3VwcGx5IENoY" + "WluIFJTQSBSb290IENBIDIwMjIwHhcNMjIwMjE3MDAxMjM2WhcNNDcwMjE3MDAyMTA5WjBfM" + "QswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTAwLgYDVQQDE" + "ydNaWNyb3NvZnQgU3VwcGx5IENoYWluIFJTQSBSb290IENBIDIwMjIwggIiMA0GCSqGSIb3D" + "QEBAQUAA4ICDwAwggIKAoICAQCeJQFmGR9kNMGdOSNiHXGLVuol0psf7ycBgr932JQzgxhIm" + "1Cee5ZkwtDDX0X/MpzoFxe9eO11mF86BggrHDebRkqQCrCvRpI+M4kq+rjnMmPzI8du0hT7J" + "lju/gaEVPrBHzeq29TsViq/Sb3M6wLtxk78rBm1EjVpFYkXTaNo6mweKZoJ8856IcYJ0Rnqj" + "zBGaTtoBCt8ii3WY13qbdY5nr0GPlvuLxFbKGunUqRoXkyk6q7OI79MNnHagUVQjsqGzv9Tw" + "7hDsyTuB3qitPrHCh17xlI1MewIH4SAklv4sdo51snn5YkEflF/9OZqZEdJ6vjspvagQ1P+2" + "sMjJNgl2hMsKrc/lN53HEx4HGr5mo/rahV3d61JhM4QQMeZSA/Vlh6AnHOhOKEDb9NNINC1Q" + "+T3LngPTve8v2XabZALW7/e6icnmWT4OXxzPdYh0u7W81MRLlXD3OrxKVfeUaF4c5ALL/XJd" + "TbrjdJtjnlduho4/98ZAajSyNHW8uuK9S7RzJMTm5yQeGVjeQTE8Z6fjDrzZAz+mB2T4o9Wp" + "WNTI7hucxZFGrb3ew/NpDL/Wv6WjeGHeNtwg6gkhWkgwm0SDeV59ipZz9ar54HmoLGILQiMC" + "7HP12w2r575A2fZQXOpq0W4cWBYGNQWLGW60QXeksVQEBGQzkfM+6+/I8CfBQIDAQABo2cwZ" + "TAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUC7NoO6/ar+5wp" + "XbZIffMRBYH0PgwEAYJKwYBBAGCNxUBBAMCAQAwEQYDVR0gBAowCDAGBgRVHSAAMA0GCSqGS" + "Ib3DQEBDAUAA4ICAQBIxzf//8FoV9eLQ2ZGOiZrL+j63mihj0fxPTSVetpVMfSV0jhfLLqPp" + "Y1RMWqJVWhsK0JkaoUkoFEDx93RcljtbB6M2JHF50kRnRl6N1ged0T7wgiYQsRN45uKDs9AR" + "U8bgHBZjJOB6A/VyCaVqfcfdwa4yu+c++hm2uU54NLSYsOn1LYYmiebJlBKcpfVs1sqpP1fL" + "37mYqMnZgz62RnMER0xqAFSCOZUDJljK+rYhNS0CBbvvkpbiFj0Bhag63pd4cdE1rsvVVYl8" + "J4M5A8S28B/r1ZdxokOcalWEuS5nKhkHrVHlZKu0HDIk318WljxBfFKuGxyGKmuH1eZJnRm9" + "R0P313w5zdbX7rwtO/kYwd+HzIYaalwWpL5eZxY1H6/cl1TRituo5lg1oWMZncWdq/ixRhb4" + "l0INtZmNxdl8C7PoeW85o0NZbRWU12fyK9OblHPiL6S6jD7LOd1P0JgxHHnl59zx5/K0bhsI" + "+pQKB0OQ8z1qRtA66aY5eUPxZIvpZbH1/o8GO4dG2ED/YbnJEEzvdjztmB88xyCA9Vgr9/0I" + "KTkgQYiWsyFM31k+OS4v4AX1PshP2Ou54+3F0Tsci41yQvQgR3pcgMJQdnfCUjmzbeyHGAlG" + "VLzPRJJ7Z2UIo5xKPjBB1Rz3TgItIWPFGyqAK9Aq7WHzrY5XHP5kBgigi9YIBKbm6PUb89nw" + "F+ay9zwqbiPujH55M/PNdYoPO2MabH+Y2lzc3hcZGlkOng1MDk6MDpzaGEyNTY6SV9faXVMM" + "jVvWEVWRmRUUF9hQkx4X2VUMVJQSGJDUV9FQ0JRZllacHQ5czo6ZWt1OjEuMy42LjEuNC4xL" + "jMxMS43Ni41OS4xLjJkZmVlZHVDb250YWluZXJQbGF0LUFNRC1VVk1rc2lnbmluZ3RpbWXBG" + "mVTyIChaXRpbWVzdGFtcFkUSTCCFEUGCSqGSIb3DQEHAqCCFDYwghQyAgEDMQ8wDQYJYIZIA" + "WUDBAIBBQAwggFsBgsqhkiG9w0BCRABBKCCAVsEggFXMIIBUwIBAQYKKwYBBAGEWQoDATAxM" + "A0GCWCGSAFlAwQCAQUABCCZ95qVu/C6ZjToQzVd4gLCIX5jnJWPDK1mDQpOI9RbswIGZSiv2" + "HUlGBMyMDIzMTExNDE5MjAzMi4yOTZaMASAAgH0AhhEJDC7K1iE55nBZ4QqG5oJwLRlSzoSa" + "c6ggdGkgc4wgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHE" + "wdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY" + "3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpFM" + "DAyLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCD" + "pkwggcgMIIFCKADAgECAhMzAAAB2ZxcBZKwg2s+AAEAAAHZMA0GCSqGSIb3DQEBCwUAMHwxC" + "zAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wH" + "AYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lL" + "VN0YW1wIFBDQSAyMDEwMB4XDTIzMDYwMTE4MzI1OFoXDTI0MDIwMTE4MzI1OFowgcsxCzAJB" + "gNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDV" + "QQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhI" + "E9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjpFMDAyLTA1RTAtRDk0NzElM" + "CMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBB" + "QADggIPADCCAgoCggIBANXpIM3WuBjbfTnIt0J1Q28cIQThnS5wPoIq8vmUDsczzVIyRbfpF" + "TvtRoEv09Jy+Kp9XMTavalFtEy0MEzATHWJqLNXYRmw0Ya7N5Hdc1g5tC8lUtoKIGS0Bl2rv" + "kE0UiKX5J92leArNVBmIMEkM3nRYIAM2utvjxnhnv8q/LNoPgZv5pl4KKgHYaDWbnd37qlRM" + "FzdY7nEdmL+usj9d2eGITr9uymOlTlq58KUgPHRAOrVBHDThp2sqFwNbIYvdJoGn+GM37gkl" + "TsrO+wpZlV1O5c+iOdpPBZwd0QZ/PGJoXfTN3xJjhhFRwwY85A5EfUg/CTDCWpCRzQcGQkJD" + "OJpdj8imAxHD9c/hS/4kEnxFkYpk3XNE9ZP13m8cZRKZfebvtEqgJ+SBImJ8iJCLoVzQ5gpL" + "qBk4Dud3i36WICuv2eKp4L9Rw065WtxULgJuTB8nZ4eRpaHXyxS3dQPxAdgtDCf3k/4ebw9k" + "mKCvVJEtyybyk4957s8Fud0j9V4omyZB2N6TZoU71UadS3MMMGjCWFeyGzBkwyQsn/iNTNCZ" + "QF+b4kAfXnXoT4bTbBLs2DMzCakdYKYBoV13sPIkioZrptxmtHtAAt2TAiFVAODNkC43GrC+" + "HghrhkjlWjKPhvvNYCGa6unCkymKPP6J55bB/pl2bKxGNH/JnpReYZrAgMBAAGjggFJMIIBR" + "TAdBgNVHQ4EFgQUHDrBKVNnqAVeXTnD+zcZrV/nXCcwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9" + "OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL" + "3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsM" + "GwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL" + "3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jc" + "nQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB" + "4AwDQYJKoZIhvcNAQELBQADggIBACo21Vgs7rVMy4hqcLtyW3SL5dFFsfCfA2jTlDezimkW1" + "3icKYH9Mk8Mnq68SvLGzS/Dlj6NDBSIqeGXZUYbAirSlYMi5pbimkxXWlhB8np20EaRGJM/V" + "4pW8BFhkxFohN71mHAkmdg/zekzEcLFoSxkLiKVjf/nl2p3hldMLP9ykblqeYNqu2daaDKzK" + "A2y1PBtYklGPzmBhGSPGL+fEoCIQXGXoZ+RyddXLwNEVCPV3cCKqx4+h4jPG7WK4AlHAOt97" + "g2coeqhOBay/t4JYmdaNZZG3tFEaum/MtCj8HFRvyLj1TBGD0blvGl3lK7Vvbbga/obUdFT6" + "okcHXOh7jUPav+JzYE+i6xX2d5grmojk8cuyECfphNCWVtX2kJs5S9k7R213CnkcfZ/Dqh8k" + "3Apw8SVqqQRzG+uGFFarA2BoRVPIhXiMxzyM9vHY2H3MDO2dv01+cMU4T7+AXxxmpNr9PrlM" + "Y0/e4yI/eCvychdDYhHAxVSguYa7ap+aEOh7Czd1y+TqzVoDqZcfD4wA0QgMoqPDeLYbom1m" + "QR6a7U5e2ySD+0ad/LBoyCrkJq5T1vp6dO0D5QT4YqeaJBbphQc+EEjQvZAbvpNEGt7k+k1U" + "eLJz/TVuNQQyl5oH4icAficPFhfHXzBskT578hsy/TXjsQUvv3Z0QsXRfCqpxTRMIIHcTCCB" + "VmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEB" + "hMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTF" + "U1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlma" + "WNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8M" + "QswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeM" + "BwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZ" + "S1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkznt" + "HIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+S" + "lr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1" + "w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU" + "8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7m" + "ka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/" + "SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc" + "6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYW" + "mEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGN" + "RiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFt" + "vUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABM" + "CMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeY" + "l2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCA" + "RYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtM" + "BMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdD" + "wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOm" + "hjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL" + "3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwS" + "gYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ" + "2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW" + "+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZ" + "mc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1Tke" + "FN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUh" + "UKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95" + "ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HL" + "cEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqR" + "RFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv" + "7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6" + "vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxB" + "moQtB1VM1izoXBm8jGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXY" + "XNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yY" + "XRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB2ZxcB" + "ZKwg2s+AAEAAAHZMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNA" + "QkQAQQwLwYJKoZIhvcNAQkEMSIEIICTLnVJTYHff0RhE3uZmq3HiBHv1TC5tEA1r+18D6H1M" + "IH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgn6AVsi06b9QwMCcRPNsl7S7QNZ3YyCmBv" + "RJxtCAHefMwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQM" + "A4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDV" + "QQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdmcXAWSsINrPgABAAAB2" + "TAiBCASe0UZ9esPNmm71kbeDGRWh76rH0q9SniHO5k8rIvMvTANBgkqhkiG9w0BAQsFAASCA" + "gDU9AD4W0lH5cGWYVuke9VDUZXu8ne+kLoEBYl0Hze83ewUPH/esUtWfpns240158Jimu9Wk" + "NnVzFzARta/b8whyosuLTQYGJtreeOfQzpModQz/Yfj94LZwn2YA1OoM8xEQQ1RY5CYL9nEK" + "T8y9SZ3k8EAnmVhhtusQYv8A+NtEAWZA0NXVRSMeXx7S+e1xBwqFvDNT4JmXrwHujTj+/zf9" + "7etxd1wFD3QcHFAHBNjrAugQ6t2daeRQof1IIzP0G78m+XGLiFR/gjgzrzk1MkmlyxdXTkDF" + "KnQu3ObED6aR8BAx2hn8Qu2+i/i56ZBNRi1hFVujL3E0+je0ggpcLwxvQzEQMdjuykydWqhg" + "JLKYzLOOA/CaA0l3jjrRKT1GstCnyT/RpEPjQZpuL+HMQt0TW87IwwfXucGPtVi8FkH8Ncx7" + "U3mpbp3n8Z7ssu8Hv2y/uNXgO7ngv3+GhNHC8zRPdAPnBqNAbgqqDSK6A9OS5Xqbr/P8XjTH" + "iE9V1h/9Vw7zUtMXlQhMuepHwXefFvUeM5lSgmyfF/k9uOUNRRWPBxfuE0xV4ChnT1KzKLd7" + "a2H+J+KjKH+Rh6VX7tv3ahRJXz5UTqNhx02ik1tXnvPLAgItvvythb9IGPx/Q7G8aBDmj4cL" + "6J+qkiSw4WAUDZHHHZcpaXw3bUifF9fIkdlmFiuewogICJ4LW1zLXNldnNucHZtLWd1ZXN0c" + "3ZuIjogIjEwMCIsCiAgIngtbXMtc2V2c25wdm0tbGF1bmNobWVhc3VyZW1lbnQiOiAiMDJjM" + "2IwZDViZjFkMjU2ZmE0ZTNiNWRlZWZjMDdiNTVmZjJmNzAyOTA4NWVkMzUwZjYwOTU5MTQwY" + "TFhNTFmMTMxMDc1M2JhNWFiMmMwM2EwNTM2YjFjMGMxOTNhZjQ3Igp9WQGAuqzoQ90fHQw50" + "3piez4xHKc7AxT8ezEbw/jV2ka6DlhBU/LaEYoTDfzukhjvAfuFY8g5O4GKzb0HtvYXOjZDC" + "8fpBQ/RAsM3xFGZnwq8tKU0NJo3qSbGp7EOY5dgLJfkA+nv8Eu5Zgdfb+Jq3RF2dRxhLezKF" + "AMpWci5ZGb04a9waBh2M8dvlRNME0q/2z11Wkuy2rtRw0EKQs725V1JcQD+Jv6cv/nD4shoC" + "z6+Q7E71zWFMRtr7uuY7DD4LGT0HIYnEmmqCO/Gq6LpPuqptGZG7iivk1GEP1JaEXd/JXx81" + "PoZdYjHG+5ho8vlGbbE8doNj6Jl5uNX+YFb4+JHtbxmGyNp9fEhM5IzuXlG8SI0ElNTdBweM" + "KL87LWeTdygcM5zsFULCHlNCNf5NNDjP0kZoO0BYulfE74Ba/71qZQEnmKhdWDim4sdVl8t7" + "UIu4AbtMpqBEjea6leuXnckZytZVDGY6C6+4DnIlfB7jEHE4f11xqAnRcxKvSpSf6Vj", + // endorsed_tcb + "0300000000000873"}; +} + +} // namespace google::scp::azure::attestation diff --git a/src/azure/attestation/src/print_report.cc b/src/azure/attestation/src/print_report.cc new file mode 100644 index 000000000..7f3a16047 --- /dev/null +++ b/src/azure/attestation/src/print_report.cc @@ -0,0 +1,36 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "absl/log/check.h" + +#include "attestation.h" + +using google::scp::azure::attestation::fetchFakeSnpAttestation; +using google::scp::azure::attestation::fetchSnpAttestation; +using google::scp::azure::attestation::hasSnp; + +int main(int argc, char* argv[]) { + std::string report_data = argc > 1 ? argv[1] : ""; + const auto report = + hasSnp() ? fetchSnpAttestation(report_data) : fetchFakeSnpAttestation(); + + CHECK(report.has_value()) << "Failed to get attestation report"; + std::cout << "report (fake=" << !hasSnp() << "):\n"; + std::cout << nlohmann::json(report.value()).dump(2) << std::endl; + return 0; +} diff --git a/src/azure/attestation/src/report.cc b/src/azure/attestation/src/report.cc new file mode 100644 index 000000000..873cf0d53 --- /dev/null +++ b/src/azure/attestation/src/report.cc @@ -0,0 +1,39 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "attestation.h" + +namespace google::scp::azure::attestation { + +std::optional fetchSnpAttestation( + const std::string report_data) { + if (!hasSnp()) { + return std::nullopt; + } + + try { + return AttestationReport{ + getSnpEvidence(report_data), + getSnpEndorsements(), + getSnpUvmEndorsements(), + getSnpEndorsedTcb(), + }; + } catch (...) { + return std::nullopt; + } +} + +} // namespace google::scp::azure::attestation diff --git a/src/azure/attestation/src/sev.h b/src/azure/attestation/src/sev.h new file mode 100644 index 000000000..c0d489114 --- /dev/null +++ b/src/azure/attestation/src/sev.h @@ -0,0 +1,93 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AZURE_ATTESTATION_SEV_H +#define AZURE_ATTESTATION_SEV_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/strings/escaping.h" + +namespace google::scp::azure::attestation::sev { + +#define SEV_GUEST_IOC_TYPE 'S' +#define SEV_SNP_GUEST_MSG_REQUEST \ + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, \ + struct google::scp::azure::attestation::sev::Request) +#define SEV_SNP_GUEST_MSG_REPORT \ + _IOWR(SEV_GUEST_IOC_TYPE, 0x1, \ + struct google::scp::azure::attestation::sev::Request) +#define SEV_SNP_GUEST_MSG_KEY \ + _IOWR(SEV_GUEST_IOC_TYPE, 0x2, \ + struct google::scp::azure::attestation::sev::Request) + +/* linux kernel 5.15.* versions of the ioctls that talk to the PSP */ + +/* From sev-snp driver include/uapi/linux/psp-sev-guest.h */ +struct Request { + uint8_t req_msg_type; + uint8_t rsp_msg_type; + uint8_t msg_version; + uint16_t request_len; + uint64_t request_uaddr; + uint16_t response_len; + uint64_t response_uaddr; + uint32_t error; /* firmware error code on failure (see psp-sev.h) */ +}; + +std::unique_ptr getReport(const std::string report_data) { + SnpRequest request = {}; + auto decodedBytes = absl::HexStringToBytes(report_data); + size_t numBytesToCopy = + std::min(decodedBytes.size(), sizeof(request.report_data)); + std::copy(decodedBytes.begin(), decodedBytes.begin() + numBytesToCopy, + request.report_data); + + SnpResponse response = {}; + + Request payload = {.req_msg_type = SNP_MSG_REPORT_REQ, + .rsp_msg_type = SNP_MSG_REPORT_RSP, + .msg_version = 1, + .request_len = sizeof(request), + .request_uaddr = (uint64_t)(void*)&request, + .response_len = sizeof(response), + .response_uaddr = (uint64_t)(void*)&response, + .error = 0}; + + auto sev_file = open("/dev/sev", O_RDWR | O_CLOEXEC); + + auto rc = ioctl(sev_file, SEV_SNP_GUEST_MSG_REPORT, &payload); + CHECK(rc >= 0) << "Failed to issue ioctl SEV_SNP_GUEST_MSG_REPORT"; + + auto report = std::make_unique(); + *report = response.report; + return report; +} + +} // namespace google::scp::azure::attestation::sev + +#endif // AZURE_ATTESTATION_SEV_H diff --git a/src/azure/attestation/src/sev_guest.h b/src/azure/attestation/src/sev_guest.h new file mode 100644 index 000000000..050862855 --- /dev/null +++ b/src/azure/attestation/src/sev_guest.h @@ -0,0 +1,84 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AZURE_ATTESTATION_SEV_GUEST_H +#define AZURE_ATTESTATION_SEV_GUEST_H + +#include +#include +#include +#include + +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/strings/escaping.h" + +namespace google::scp::azure::attestation::sev_guest { + +#define SNP_GUEST_REQ_IOC_TYPE 'S' +#define SNP_GET_REPORT \ + _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, \ + struct google::scp::azure::attestation::sev::Request) +#define SNP_GET_DERIVED_KEY \ + _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, \ + struct google::scp::azure::attestation::sev::Request) +#define SNP_GET_EXT_REPORT \ + _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, \ + struct google::scp::azure::attestation::sev::Request) + +/* linux kernel 6.* versions of the ioctls that talk to the PSP */ + +// aka/replaced by this from include/uapi/linux/sev-guest.h +// +struct Request { + uint8_t msg_version; // message version number (must be non-zero) + uint64_t req_data; // Request and response structure address + uint64_t resp_data; + uint64_t fw_err; // firmware error code on failure (see psp-sev.h) +}; + +std::unique_ptr getReport(const std::string report_data) { + SnpRequest request = {}; + auto decodedBytes = absl::HexStringToBytes(report_data); + size_t numBytesToCopy = + std::min(decodedBytes.size(), sizeof(request.report_data)); + std::copy(decodedBytes.begin(), decodedBytes.begin() + numBytesToCopy, + request.report_data); + + SnpResponse response = {}; + + Request payload = { + .msg_version = 1, + .req_data = (uint64_t)&request, + .resp_data = (uint64_t)&response, + }; + + auto sev_guest_file = open("/dev/sev-guest", O_RDWR | O_CLOEXEC); + + auto rc = ioctl(sev_guest_file, SNP_GET_REPORT, &payload); + CHECK(rc >= 0) << "Failed to issue ioctl SNP_GET_REPORT"; + + auto report = std::make_unique(); + *report = response.report; + return report; +} + +} // namespace google::scp::azure::attestation::sev_guest + +#endif // AZURE_ATTESTATION_SEV_GUEST_H diff --git a/src/azure/attestation/src/snp.cc b/src/azure/attestation/src/snp.cc new file mode 100644 index 000000000..508cf1ea2 --- /dev/null +++ b/src/azure/attestation/src/snp.cc @@ -0,0 +1,35 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "attestation.h" + +namespace google::scp::azure::attestation { + +SnpType getSnpType() { + std::ifstream sev_file("/dev/sev"); + if (sev_file.good()) { + return SnpType::SEV; + } + std::ifstream sev_guest_file("/dev/sev-guest"); + if (sev_file.good()) { + return SnpType::SEV_GUEST; + } + return SnpType::NONE; +} + +bool hasSnp() { return getSnpType() != SnpType::NONE; } + +} // namespace google::scp::azure::attestation diff --git a/src/azure/attestation/src/utils/BUILD.bazel b/src/azure/attestation/src/utils/BUILD.bazel new file mode 100644 index 000000000..4f55924c0 --- /dev/null +++ b/src/azure/attestation/src/utils/BUILD.bazel @@ -0,0 +1,34 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "attestation_utils", + srcs = [ + "host_amd_certs.cc", + "security_context.cc", + ], + hdrs = [ + "host_amd_certs.h", + "security_context.h", + ], + deps = [ + "//src/core/utils:core_utils", + "@com_google_absl//absl/log:check", + "@nlohmann_json//:lib", + ], +) diff --git a/src/azure/attestation/src/utils/host_amd_certs.cc b/src/azure/attestation/src/utils/host_amd_certs.cc new file mode 100644 index 000000000..548494a24 --- /dev/null +++ b/src/azure/attestation/src/utils/host_amd_certs.cc @@ -0,0 +1,36 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "host_amd_certs.h" + +using google::scp::azure::attestation::utils::getSecurityContextFile; +using google::scp::core::utils::Base64Decode; + +namespace google::scp::azure::attestation::utils { + +nlohmann::json getHostAmdCerts() { + // Read the local Base64 encoded AMD certs + const auto host_certs_b64 = getSecurityContextFile("/host-amd-cert-base64"); + + // Decode the contents of the file + std::string host_certs_str; + Base64Decode(host_certs_b64, host_certs_str); + + // Parse the decoded string into JSON + return nlohmann::json::parse(host_certs_str); +} + +} // namespace google::scp::azure::attestation::utils diff --git a/src/azure/attestation/src/utils/host_amd_certs.h b/src/azure/attestation/src/utils/host_amd_certs.h new file mode 100644 index 000000000..f5ac6dbb5 --- /dev/null +++ b/src/azure/attestation/src/utils/host_amd_certs.h @@ -0,0 +1,29 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "src/core/utils/base64.h" + +#include "security_context.h" + +namespace google::scp::azure::attestation::utils { + +nlohmann::json getHostAmdCerts(); + +} diff --git a/src/azure/attestation/src/utils/security_context.cc b/src/azure/attestation/src/utils/security_context.cc new file mode 100644 index 000000000..c98df21fc --- /dev/null +++ b/src/azure/attestation/src/utils/security_context.cc @@ -0,0 +1,38 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "security_context.h" + +#include "absl/log/check.h" + +namespace google::scp::azure::attestation::utils { + +std::string getSecurityContextFile(std::string file_path) { + const char* dir = std::getenv("UVM_SECURITY_CONTEXT_DIR"); + CHECK(dir) << "UVM_SECURITY_CONTEXT_DIR environment variable is not set"; + + std::string full_path = std::string(dir) + file_path; + std::ifstream file(full_path, std::ios::binary); + + CHECK(file) << "Unable to open file at full_path: " + full_path; + + std::vector file_bytes = {std::istreambuf_iterator(file), + std::istreambuf_iterator()}; + + return std::string(file_bytes.begin(), file_bytes.end()); +} + +} // namespace google::scp::azure::attestation::utils diff --git a/src/azure/attestation/src/utils/security_context.h b/src/azure/attestation/src/utils/security_context.h new file mode 100644 index 000000000..516bc2206 --- /dev/null +++ b/src/azure/attestation/src/utils/security_context.h @@ -0,0 +1,29 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include + +namespace google::scp::azure::attestation::utils { + +std::string getSecurityContextFile(std::string file_path); + +} diff --git a/src/azure/attestation/src/uvm_endorsements.cc b/src/azure/attestation/src/uvm_endorsements.cc new file mode 100644 index 000000000..e4d80391f --- /dev/null +++ b/src/azure/attestation/src/uvm_endorsements.cc @@ -0,0 +1,32 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ATTESTATION_UVM_ENDORSEMENTS_H +#define ATTESTATION_UVM_ENDORSEMENTS_H + +#include "utils/security_context.h" + +using google::scp::azure::attestation::utils::getSecurityContextFile; + +namespace google::scp::azure::attestation { + +std::string getSnpUvmEndorsements() { + return getSecurityContextFile("/reference-info-base64"); +} + +} // namespace google::scp::azure::attestation + +#endif // ATTESTATION_UVM_ENDORSEMENTS_H diff --git a/src/azure/attestation/test/BUILD.bazel b/src/azure/attestation/test/BUILD.bazel new file mode 100644 index 000000000..e014aadc1 --- /dev/null +++ b/src/azure/attestation/test/BUILD.bazel @@ -0,0 +1,26 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_test") + +package(default_visibility = ["//visibility:public"]) + +cc_test( + name = "attestation", + srcs = ["attestation_test.cc"], + deps = [ + "//src/azure:attestation", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/azure/attestation/test/attestation_test.cc b/src/azure/attestation/test/attestation_test.cc new file mode 100644 index 000000000..ba1a81c0e --- /dev/null +++ b/src/azure/attestation/test/attestation_test.cc @@ -0,0 +1,67 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/azure/attestation/src/attestation.h" + +#include +#include + +using google::scp::azure::attestation::fetchFakeSnpAttestation; +using google::scp::azure::attestation::fetchSnpAttestation; +using google::scp::azure::attestation::hasSnp; + +namespace google::scp::cc::azure::attestation::test { + +class JsonAttestationReportTest : public ::testing::Test { + protected: + void SetUp() override {} + + void TearDown() override {} +}; + +TEST_F(JsonAttestationReportTest, FetchFakeAttestation) { + EXPECT_TRUE(fetchFakeSnpAttestation().has_value()); +} + +TEST_F(JsonAttestationReportTest, FetchRealAttestation) { + if (!hasSnp()) { + return; + } + EXPECT_TRUE(fetchSnpAttestation().has_value()); +} + +TEST_F(JsonAttestationReportTest, FetchRealAttestationNormalReportData) { + if (!hasSnp()) { + return; + } + EXPECT_TRUE(fetchSnpAttestation("example_report_data").has_value()); +} + +TEST_F(JsonAttestationReportTest, FetchRealAttestationLongReportData) { + if (!hasSnp()) { + return; + } + EXPECT_TRUE(fetchSnpAttestation("a_very_long_report_data_string_which_is_so_" + "long_that_it_exceeds_report_" + "data_length") + .has_value()); +} + +TEST_F(JsonAttestationReportTest, FetchRealAttestationNonSnp) { + if (hasSnp()) { + return; + } + EXPECT_FALSE(fetchSnpAttestation().has_value()); +} +} // namespace google::scp::cc::azure::attestation::test diff --git a/src/azure/encrypt_payload/BUILD.bazel b/src/azure/encrypt_payload/BUILD.bazel new file mode 100644 index 000000000..69d7d88d2 --- /dev/null +++ b/src/azure/encrypt_payload/BUILD.bazel @@ -0,0 +1,38 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_binary") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "encrypt_payload", + srcs = ["encrypt_payload.cc"], + deps = [ + "//src/communication:ohttp_utils", + "//src/concurrent:executor", + "//src/core/utils:core_utils", + "//src/cpio/client_providers/global_cpio", + "//src/encryption/key_fetcher:fake_key_fetcher_manager", + "//src/encryption/key_fetcher:key_fetcher_manager", + "//src/encryption/key_fetcher:private_key_fetcher", + "//src/encryption/key_fetcher:public_key_fetcher", + "//src/public/cpio/interface:cpio", + "@com_github_google_quiche//quiche:oblivious_http_unstable_api", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + ], +) diff --git a/src/azure/encrypt_payload/encrypt_payload.cc b/src/azure/encrypt_payload/encrypt_payload.cc new file mode 100644 index 000000000..76c5bc98d --- /dev/null +++ b/src/azure/encrypt_payload/encrypt_payload.cc @@ -0,0 +1,303 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/log/check.h" +#include "absl/strings/escaping.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "quiche/common/quiche_random.h" +#include "quiche/oblivious_http/oblivious_http_client.h" +#include "src/communication/ohttp_utils.h" +#include "src/concurrent/event_engine_executor.h" +#include "src/core/lib/event_engine/default_event_engine.h" +#include "src/core/utils/base64.h" +#include "src/cpio/client_providers/global_cpio/global_cpio.h" +#include "src/encryption/key_fetcher/fake_key_fetcher_manager.h" +#include "src/public/cpio/interface/cpio.h" + +using google::scp::core::AsyncContext; +using google::scp::cpio::client_providers::GetSessionTokenRequest; +using google::scp::cpio::client_providers::GetSessionTokenResponse; +using google::scp::cpio::client_providers::GlobalCpio; + +/* +This tool encrypts the stdin input payload using a public key fetched from Azure +Protected Audience KMS. The output is written to stdout with the following +format: +{ + "key_id": , + "public_key_base64": "", + "ciphertext_base64": "" +} + +With --decrypt_mode, it decrypts the stdin input and output the plaintext using +the private key fetched from Azure Protected Audience KMS. The above json format +is used for the input. +*/ + +ABSL_FLAG(std::string, public_key_endpoint, + "https://127.0.0.1:8000/app/listpubkeys", + "Endpoint serving set of public keys used for encryption"); + +ABSL_FLAG(std::string, primary_coordinator_private_key_endpoint, + "https://127.0.0.1:8000/app/key?fmt=tink", + "Primary coordinator's private key vending service endpoint"); + +ABSL_FLAG(std::string, unwrap_private_key_endpoint, + "http://127.0.0.1:8000/app/unwrapKey?fmt=tink", + "Endpoint to unwrap private key"); + +ABSL_FLAG(std::string, output_path, "encrypt_payload_out", + "Path to the output of this tool"); + +ABSL_FLAG(bool, decrypt_mode, false, + "If it's true it decrypts the input with the private key fetched " + "from Azure Protected Audience KMS"); + +ABSL_FLAG(std::string, get_token_url, "get_token_url", + "http://127.0.0.1:8000/metadata/identity/oauth2/" + "token?api-version=2018-02-01"); + +namespace privacy_sandbox::azure_encrypt_payload { +namespace { +// 45 days. Value for private_key_cache_ttl_seconds in +// services/common/constants/common_service_flags.cc +static constexpr unsigned int kDefaultPrivateKeyCacheTtlSeconds = 3888000; +// 3 hours. Value for key_refresh_flow_run_frequency_seconds in +// services/common/constants/common_service_flags.cc +static constexpr unsigned int kDefaultKeyRefreshFlowRunFrequencySeconds = 10800; + +using google::scp::core::utils::Base64Decode; +using google::scp::core::utils::Base64Encode; +using ::google::scp::cpio::Cpio; +using ::google::scp::cpio::CpioOptions; +using ::google::scp::cpio::LogOption; +using PlatformToPublicKeyServiceEndpointMap = absl::flat_hash_map< + server_common::CloudPlatform, + std::vector>; + +const quiche::ObliviousHttpHeaderKeyConfig GetOhttpKeyConfig(uint8_t key_id, + uint16_t kem_id, + uint16_t kdf_id, + uint16_t aead_id) { + const auto ohttp_key_config = quiche::ObliviousHttpHeaderKeyConfig::Create( + key_id, kem_id, kdf_id, aead_id); + CHECK(ohttp_key_config.ok()) << "OHTTP key config is not OK"; + return std::move(ohttp_key_config.value()); +} + +// Copied from data-plane-shared-libraries/src/cpp/communication/ohttp_utils.cc. +// We copied it rather than making it visible to other classes to +// minimize the changes in the existing codes. +absl::StatusOr ToIntKeyId(absl::string_view key_id) { + uint32_t val; + if (!absl::SimpleAtoi(key_id, &val) || + val > std::numeric_limits::max()) { + return absl::InternalError( + absl::StrCat("Cannot parse OHTTP key ID from: ", key_id)); + } + + return val; +} + +// Based on services/common/encryption/key_fetcher_factory.cc +std::unique_ptr +CreatePublicKeyFetcher() { + std::vector endpoints = { + absl::GetFlag(FLAGS_public_key_endpoint)}; + + server_common::CloudPlatform cloud_platform = + server_common::CloudPlatform::kAzure; + + PlatformToPublicKeyServiceEndpointMap per_platform_endpoints = { + {cloud_platform, endpoints}}; + return server_common::PublicKeyFetcherFactory::Create(per_platform_endpoints); +} + +// Based on services/common/encryption/key_fetcher_factory.cc +std::unique_ptr +CreateKeyFetcherManager( + std::unique_ptr + public_key_fetcher, + const unsigned int private_key_cache_ttl_seconds = + kDefaultPrivateKeyCacheTtlSeconds, + const unsigned int key_refresh_flow_run_frequency_seconds = + kDefaultKeyRefreshFlowRunFrequencySeconds) { + google::scp::cpio::PrivateKeyVendingEndpoint primary; + + primary.private_key_vending_service_endpoint = + absl::GetFlag(FLAGS_primary_coordinator_private_key_endpoint); + absl::Duration private_key_ttl = absl::Seconds(private_key_cache_ttl_seconds); + std::unique_ptr + private_key_fetcher = server_common::PrivateKeyFetcherFactory::Create( + primary, {}, private_key_ttl); + + absl::Duration key_refresh_flow_run_freq = + absl::Seconds(key_refresh_flow_run_frequency_seconds); + + auto event_engine = std::make_unique( + grpc_event_engine::experimental::GetDefaultEventEngine()); + std::unique_ptr manager = + server_common::KeyFetcherManagerFactory::Create( + key_refresh_flow_run_freq, std::move(public_key_fetcher), + std::move(private_key_fetcher), std::move(event_engine)); + manager->Start(); + + return manager; +} + +struct EncryptPayloadResult { + std::string ciphertext_base64; + uint8_t key_id; + std::string public_key_base64; + + operator nlohmann::json() const { + return nlohmann::json{{"ciphertext_base64", ciphertext_base64}, + {"key_id", key_id}, + {"public_key_base64", public_key_base64}}; + } +}; + +void from_json(const nlohmann::json& j, EncryptPayloadResult& r) { + j.at("ciphertext_base64").get_to(r.ciphertext_base64); + j.at("key_id").get_to(r.key_id); + j.at("public_key_base64").get_to(r.public_key_base64); +} + +EncryptPayloadResult EncryptPayload( + std::unique_ptr + key_fetcher_manager, + const std::string plaintext_payload) { + server_common::CloudPlatform cloud_platform = + server_common::CloudPlatform::kAzure; + auto public_key = key_fetcher_manager->GetPublicKey(cloud_platform); + CHECK(public_key.ok()) << "Failed to fetch public key"; + + const absl::StatusOr key_id = + ToIntKeyId(public_key.value().key_id()); + CHECK(key_id.ok()) << "Failed to get key ID"; + + const auto config = + GetOhttpKeyConfig(key_id.value(), EVP_HPKE_DHKEM_X25519_HKDF_SHA256, + EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_256_GCM); + + std::string decoded_public_key; + Base64Decode(public_key.value().public_key(), decoded_public_key); + + const auto label_for_test = server_common::kBiddingAuctionOhttpResponseLabel; + const auto request = + quiche::ObliviousHttpRequest::CreateClientObliviousRequest( + plaintext_payload, decoded_public_key, config, label_for_test); + const std::string ciphertext_bytes = request->EncapsulateAndSerialize(); + std::string ciphertext_base64; + CHECK(privacy_sandbox::azure_encrypt_payload::Base64Encode(ciphertext_bytes, + ciphertext_base64) + .Successful()) + << "Failed to encode ciphertext as base64"; + + return {ciphertext_base64, key_id.value(), public_key.value().public_key()}; +} + +std::string DecryptCiphertext( + std::unique_ptr + key_fetcher_manager, + const EncryptPayloadResult& encrypt_payload_result) { + auto key_id = encrypt_payload_result.key_id; + + const auto label_for_test = server_common::kBiddingAuctionOhttpResponseLabel; + std::optional private_key = + key_fetcher_manager->GetPrivateKey(std::to_string(key_id)); + CHECK(private_key.has_value()) << "Failed to fetch private key"; + + std::string ciphertext_bytes; + CHECK(privacy_sandbox::azure_encrypt_payload::Base64Decode( + encrypt_payload_result.ciphertext_base64, ciphertext_bytes) + .Successful()) + << "Failed to encode ciphertext as base64"; + + server_common::EncapsulatedRequest encapsulatedRequest = {ciphertext_bytes, + label_for_test}; + + absl::StatusOr decrypted_result = + server_common::DecryptEncapsulatedRequest(private_key.value(), + encapsulatedRequest); + CHECK(decrypted_result.ok()) << "Decryption failed"; + return std::string(decrypted_result->GetPlaintextData()); +} +} // namespace +} // namespace privacy_sandbox::azure_encrypt_payload + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + + // Setup + const auto get_token_url = absl::GetFlag(FLAGS_get_token_url); + setenv("AZURE_BA_PARAM_GET_TOKEN_URL", get_token_url.c_str(), 0); + + const auto unwrap_private_key_endpoint = + absl::GetFlag(FLAGS_unwrap_private_key_endpoint); + setenv("AZURE_BA_PARAM_KMS_UNWRAP_URL", unwrap_private_key_endpoint.c_str(), + 0); + + // grpc_init initializes and shutdown gRPC client. + privacy_sandbox::server_common::GrpcInit grpc_init; + google::scp::cpio::CpioOptions cpio_options; + cpio_options.log_option = google::scp::cpio::LogOption::kConsoleLog; + CHECK(google::scp::cpio::Cpio::InitCpio(cpio_options).Successful()) + << "Failed to initialize CPIO library"; + + auto key_fetcher_manager = + privacy_sandbox::azure_encrypt_payload::CreateKeyFetcherManager( + privacy_sandbox::azure_encrypt_payload::CreatePublicKeyFetcher()); + + bool decrypt_mode = {absl::GetFlag(FLAGS_decrypt_mode)}; + + const std::filesystem::path output_path = absl::GetFlag(FLAGS_output_path); + if (output_path.has_parent_path()) { + create_directories(output_path.parent_path()); + } + std::ofstream fout(output_path); + + if (!decrypt_mode) { + std::string plaintext_payload; + std::cin >> plaintext_payload; + const auto encrypt_result = + privacy_sandbox::azure_encrypt_payload::EncryptPayload( + std::move(key_fetcher_manager), plaintext_payload); + fout << nlohmann::json(encrypt_result).dump(2); + + } else { + nlohmann::json input; + std::cin >> input; + auto encryption_result = input.template get< + privacy_sandbox::azure_encrypt_payload::EncryptPayloadResult>(); + const auto output_path = absl::GetFlag(FLAGS_output_path); + fout << DecryptCiphertext(std::move(key_fetcher_manager), + encryption_result); + } + + google::scp::cpio::Cpio::ShutdownCpio(cpio_options); + return 0; +} diff --git a/src/azure/fetch_auth_token/BUILD.bazel b/src/azure/fetch_auth_token/BUILD.bazel new file mode 100644 index 000000000..0f7691f1c --- /dev/null +++ b/src/azure/fetch_auth_token/BUILD.bazel @@ -0,0 +1,29 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_binary") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "fetch_auth_token", + srcs = ["fetch_auth_token.cc"], + deps = [ + "//src/cpio/client_providers/global_cpio", + "//src/public/cpio/interface:cpio", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + "@com_google_absl//absl/log:check", + ], +) diff --git a/src/azure/fetch_auth_token/fetch_auth_token.cc b/src/azure/fetch_auth_token/fetch_auth_token.cc new file mode 100644 index 000000000..4dbeba072 --- /dev/null +++ b/src/azure/fetch_auth_token/fetch_auth_token.cc @@ -0,0 +1,87 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/log/check.h" +#include "absl/synchronization/notification.h" +#include "src/cpio/client_providers/global_cpio/global_cpio.h" +#include "src/public/cpio/interface/cpio.h" + +using google::scp::core::AsyncContext; +using google::scp::cpio::client_providers::GetSessionTokenRequest; +using google::scp::cpio::client_providers::GetSessionTokenResponse; +using google::scp::cpio::client_providers::GlobalCpio; + +/* +This tool fetches JWT auth token from IDP (Managed Identity in production) and +write it to stdout. +*/ + +ABSL_FLAG(std::string, output_path, "fetch_auth_token_out", + "Path to the output of this tool"); + +ABSL_FLAG(std::string, get_token_url, "get_token_url", + "http://127.0.0.1:8000/metadata/identity/oauth2/" + "token?api-version=2018-02-01"); + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + + const auto get_token_url = absl::GetFlag(FLAGS_get_token_url); + setenv("AZURE_BA_PARAM_GET_TOKEN_URL", get_token_url.c_str(), 0); + + // Setup + google::scp::cpio::CpioOptions cpio_options; + cpio_options.log_option = google::scp::cpio::LogOption::kConsoleLog; + CHECK(google::scp::cpio::Cpio::InitCpio(cpio_options).Successful()) + << "Failed to initialize CPIO library"; + auto auth_token_provider = + &GlobalCpio::GetGlobalCpio().GetAuthTokenProvider(); + + // Fetch token + auto request = std::make_shared(); + absl::Notification finished; + AsyncContext + get_token_context(std::move(request), [&finished](auto& context) { + CHECK(context.result.Successful()) << "GetSessionTokenRequest failed"; + CHECK(context.response->session_token->size() > 0) + << "Session token needs to have length more than zero"; + + const std::filesystem::path output_path = + absl::GetFlag(FLAGS_output_path); + if (output_path.has_parent_path()) { + create_directories(output_path.parent_path()); + } + std::ofstream fout(output_path); + fout << *context.response->session_token; + + finished.Notify(); + }); + CHECK(auth_token_provider->GetSessionToken(get_token_context).Successful()) + << "Failed to run auth_token_provider"; + finished.WaitForNotification(); + + // Tear down + google::scp::cpio::Cpio::ShutdownCpio(cpio_options); + + return 0; +} diff --git a/src/cpio/client_providers/auth_token_provider/BUILD.bazel b/src/cpio/client_providers/auth_token_provider/BUILD.bazel index a3ab88bc6..5e7355a95 100644 --- a/src/cpio/client_providers/auth_token_provider/BUILD.bazel +++ b/src/cpio/client_providers/auth_token_provider/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/auth_token_provider/aws:aws_auth_token_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/auth_token_provider/azure:azure_auth_token_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/auth_token_provider/gcp:gcp_auth_token_provider", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/auth_token_provider/azure/BUILD.bazel b/src/cpio/client_providers/auth_token_provider/azure/BUILD.bazel new file mode 100644 index 000000000..6378ad47b --- /dev/null +++ b/src/cpio/client_providers/auth_token_provider/azure/BUILD.bazel @@ -0,0 +1,70 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(default_visibility = ["//src:scp_internal_pkg"]) + +cc_library( + name = "azure_auth_token_provider", + srcs = ["azure_auth_token_provider.cc"], + hdrs = ["azure_auth_token_provider.h"], + deps = [ + ":error_codes", + "//src/core/interface", + "//src/core/utils:core_utils", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/public/core/interface:execution_result", + "//src/public/cpio/interface:cpio_errors", + "//src/public/cpio/interface:type_def", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/strings", + "@nlohmann_json//:lib", + ], +) + +cc_library( + name = "error_codes", + hdrs = ["error_codes.h"], + deps = [ + "//src/core/interface", + "//src/core/utils:core_utils", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/public/core/interface:execution_result", + "//src/public/cpio/interface:cpio_errors", + "//src/public/cpio/interface:type_def", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/functional:bind_front", + "@com_google_absl//absl/strings", + "@nlohmann_json//:lib", + ], +) + +cc_test( + name = "azure_auth_token_provider_test", + size = "small", + srcs = ["azure_auth_token_provider_test.cc"], + deps = [ + ":azure_auth_token_provider", + ":error_codes", + "//src/core/curl_client/mock:mock_curl_client", + "//src/core/interface", + "//src/core/test/utils", + "//src/public/core:test_execution_result_matchers", + "//src/public/core/interface:execution_result", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.cc b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.cc new file mode 100644 index 000000000..55a1e0c61 --- /dev/null +++ b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.cc @@ -0,0 +1,228 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_auth_token_provider.h" + +#include +#include +#include +#include +#include + +#include + +#include "absl/base/nullability.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "src/core/common/uuid/uuid.h" +#include "src/core/utils/base64.h" +#include "src/public/core/interface/execution_result.h" + +#include "error_codes.h" + +using google::scp::core::AsyncContext; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::RetryExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::Uri; +using google::scp::core::common::kZeroUuid; +using google::scp::core::errors:: + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN; +using google::scp::core::utils::Base64Decode; +using google::scp::core::utils::PadBase64Encoding; +using nlohmann::json; + +namespace { +constexpr char kAzureAuthTokenProvider[] = "AzureAuthTokenProvider"; +constexpr char kMetadataHeader[] = "Metadata"; +constexpr char kMetadataHeaderValue[] = "true"; + +// Local IDP for Managed Identity. +// https://learn.microsoft.com/en-us/azure/container-instances/container-instances-managed-identity +constexpr char kDefaultGetTokenUrl[] = + "http://169.254.169.254/metadata/identity/oauth2/token"; +constexpr char kGetTokenQuery[] = + "?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F"; +constexpr char kGetTokenUrlEnvVar[] = "AZURE_BA_PARAM_GET_TOKEN_URL"; +constexpr char kJsonAccessTokenKey[] = "access_token"; +constexpr char kJsonTokenExpiryKey[] = "expires_in"; +constexpr char kJsonTokenTypeKey[] = "token_type"; + +// Returns a pair of iterators - one to the beginning, one to the end. +const auto& GetRequiredJWTComponents() { + static char const* components[3]; + using iterator_type = decltype(std::cbegin(components)); + static std::pair iterator_pair = []() { + components[0] = kJsonAccessTokenKey; + components[1] = kJsonTokenExpiryKey; + components[2] = kJsonTokenTypeKey; + return std::make_pair(std::cbegin(components), std::cend(components)); + }(); + return iterator_pair; +} +} // namespace + +namespace google::scp::cpio::client_providers { +AzureAuthTokenProvider::AzureAuthTokenProvider( + absl::Nonnull http_client) + : http_client_(http_client), get_token_url_() { + const char* value_from_env = std::getenv(kGetTokenUrlEnvVar); + if (value_from_env) { + get_token_url_ = std::string(value_from_env) + std::string(kGetTokenQuery); + } else { + get_token_url_ = + std::string(kDefaultGetTokenUrl) + std::string(kGetTokenQuery); + } +} + +ExecutionResult AzureAuthTokenProvider::GetSessionToken( + AsyncContext& + get_token_context) noexcept { + // Create request body + AsyncContext http_context; + + http_context.request = std::make_shared(); + + http_context.request->method = google::scp::core::HttpMethod::GET; + http_context.request->path = std::make_shared(get_token_url_); + http_context.request->headers = std::make_shared(); + http_context.request->headers->insert( + std::make_pair(kMetadataHeader, kMetadataHeaderValue)); + http_context.callback = + absl::bind_front(&AzureAuthTokenProvider::OnGetSessionTokenCallback, this, + get_token_context); + + auto execution_result = http_client_->PerformRequest(http_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzureAuthTokenProvider, get_token_context, + execution_result, + "Failed to perform http request to fetch access token."); + + get_token_context.result = execution_result; + get_token_context.Finish(); + return execution_result; + } + + return SuccessExecutionResult(); +} + +void AzureAuthTokenProvider::OnGetSessionTokenCallback( + AsyncContext& + get_token_context, + AsyncContext& http_client_context) noexcept { + if (!http_client_context.result.Successful()) { + SCP_ERROR_CONTEXT( + kAzureAuthTokenProvider, get_token_context, http_client_context.result, + "Failed to get access token from Instance Metadata server"); + + get_token_context.result = http_client_context.result; + get_token_context.Finish(); + return; + } + + json json_response; + try { + json_response = + json::parse(http_client_context.response->body.bytes->begin(), + http_client_context.response->body.bytes->end()); + } catch (...) { + auto result = RetryExecutionResult( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN); + SCP_ERROR_CONTEXT( + kAzureAuthTokenProvider, get_token_context, result, + "Received http response could not be parsed into a JSON."); + get_token_context.result = result; + get_token_context.Finish(); + return; + } + + if (!std::all_of(GetRequiredJWTComponents().first, + GetRequiredJWTComponents().second, + [&json_response](const char* const component) { + return json_response.contains(component); + })) { + auto result = RetryExecutionResult( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN); + SCP_ERROR_CONTEXT( + kAzureAuthTokenProvider, get_token_context, result, + "Received http response does not contain all the necessary fields."); + get_token_context.result = result; + get_token_context.Finish(); + return; + } + get_token_context.response = std::make_shared(); + + uint64_t expiry_seconds; + if (json_response[kJsonTokenExpiryKey].type() == + json::value_t::number_unsigned) { + // expires_in that follows https://www.rfc-editor.org/rfc/rfc6749. + expiry_seconds = json_response[kJsonTokenExpiryKey].get(); + } else if (json_response[kJsonTokenExpiryKey].type() == + json::value_t::string) { + // expires_in of Managed identity token is string + try { + expiry_seconds = + std::stoi(json_response[kJsonTokenExpiryKey].get()); + } catch (...) { + auto result = RetryExecutionResult( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN); + SCP_ERROR_CONTEXT(kAzureAuthTokenProvider, get_token_context, result, + "The string value for field expires_in cannot be " + "parsed as an integer."); + get_token_context.result = result; + get_token_context.Finish(); + return; + } + } else { + auto result = RetryExecutionResult( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN); + SCP_ERROR_CONTEXT(kAzureAuthTokenProvider, get_token_context, result, + "The value for field expires_in is invalid."); + get_token_context.result = result; + get_token_context.Finish(); + return; + } + get_token_context.response->token_lifetime_in_seconds = + std::chrono::seconds(expiry_seconds); + auto access_token = json_response[kJsonAccessTokenKey].get(); + get_token_context.response->session_token = + std::make_shared(std::move(access_token)); + + get_token_context.result = SuccessExecutionResult(); + get_token_context.Finish(); +} + +ExecutionResult AzureAuthTokenProvider::GetSessionTokenForTargetAudience( + AsyncContext& get_token_context) noexcept { + // Not implemented. + return FailureExecutionResult(SC_UNKNOWN); +} + +absl::Nonnull> +AuthTokenProviderFactory::Create( + absl::Nonnull http1_client) { + return std::make_unique(http1_client); +} + +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.h b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.h new file mode 100644 index 000000000..85750a716 --- /dev/null +++ b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.h @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_AZURE_AUTH_TOKEN_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_AZURE_AUTH_TOKEN_PROVIDER_H_ + +#include +#include +#include + +#include "absl/base/nullability.h" +#include "src/core/interface/async_executor_interface.h" +#include "src/core/interface/http_client_interface.h" +#include "src/cpio/client_providers/interface/auth_token_provider_interface.h" + +#include "error_codes.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc AuthTokenProviderInterface + */ +class AzureAuthTokenProvider : public AuthTokenProviderInterface { + public: + explicit AzureAuthTokenProvider( + absl::Nonnull http_client); + + core::ExecutionResult GetSessionToken( + core::AsyncContext& + get_role_credentials_context) noexcept override; + + core::ExecutionResult GetSessionTokenForTargetAudience( + core::AsyncContext& get_token_context) noexcept + override; + + private: + /** + * @brief Is called when the get session token from current instance operation + * is completed. + * + * @param get_token_context The context of the get session token + * operation. + * @param http_client_context http client operation context. + */ + void OnGetSessionTokenCallback( + core::AsyncContext& + get_token_context, + core::AsyncContext& + http_client_context) noexcept; + + /// HttpClient for issuing HTTP actions. + core::HttpClientInterface* http_client_; + std::string get_token_url_; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_AZURE_AUTH_TOKEN_PROVIDER_H_ diff --git a/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider_test.cc b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider_test.cc new file mode 100644 index 000000000..d68b23a31 --- /dev/null +++ b/src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider_test.cc @@ -0,0 +1,143 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/cpio/client_providers/auth_token_provider/azure/azure_auth_token_provider.h" + +#include +#include + +#include +#include +#include + +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/notification.h" +#include "src/core/curl_client/mock/mock_curl_client.h" +#include "src/cpio/client_providers/auth_token_provider/azure/error_codes.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::scp::core::AsyncContext; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::BytesBuffer; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::RetryExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::Uri; +using google::scp::core::errors:: + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::MockCurlClient; +using google::scp::core::test::ResultIs; +using testing::Contains; +using testing::EndsWith; +using testing::Eq; +using testing::IsNull; +using testing::Pair; +using testing::Pointee; +using testing::UnorderedElementsAre; + +namespace { +constexpr char kDefaultGetTokenUrl[] = + "http://169.254.169.254/metadata/identity/oauth2/token"; +constexpr char kGetTokenQuery[] = + "?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F"; +constexpr char kMetadataHeader[] = "Metadata"; +constexpr char kMetadataHeaderValue[] = "true"; +constexpr int kTokenTtlInSecondHeaderValue = 1000; +constexpr char kTokenPayloadValue[] = "b0Aaekm1IeizWZVKoBQQULOiiT_PDcQk"; +} // namespace + +namespace google::scp::cpio::client_providers::test { + +class AzureAuthTokenProviderTest : public testing::TestWithParam { + protected: + AzureAuthTokenProviderTest() : authorizer_provider_(&http_client_) {} + + std::string GetResponseBody() { return GetParam(); } + + AsyncContext + fetch_token_context_; + + MockCurlClient http_client_; + AzureAuthTokenProvider authorizer_provider_; +}; + +TEST_F(AzureAuthTokenProviderTest, + GetSessionTokenSuccessWithValidTokenAndExpireTime) { + EXPECT_CALL(http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = SuccessExecutionResult(); + EXPECT_EQ(http_context.request->method, HttpMethod::GET); + EXPECT_THAT(http_context.request->path, + Pointee(Eq(std::string(kDefaultGetTokenUrl) + + std::string(kGetTokenQuery)))); + EXPECT_THAT(http_context.request->headers, + Pointee(UnorderedElementsAre( + Pair(kMetadataHeader, kMetadataHeaderValue)))); + + http_context.response = std::make_shared(); + const std::string kHttpResponseMock = std::string(R"({ + "access_token":")") + kTokenPayloadValue + + R"(", + "expires_in":")" + std::to_string(kTokenTtlInSecondHeaderValue) + + R"(", + "token_type":"bearer" + })"; + http_context.response->body = BytesBuffer(kHttpResponseMock); + http_context.Finish(); + return SuccessExecutionResult(); + }); + + absl::Notification finished; + fetch_token_context_.callback = [&finished](auto& context) { + ASSERT_SUCCESS(context.result); + ASSERT_TRUE(context.response); + EXPECT_THAT(context.response->session_token, + Pointee(Eq(kTokenPayloadValue))); + EXPECT_EQ(context.response->token_lifetime_in_seconds, + std::chrono::seconds(kTokenTtlInSecondHeaderValue)); + finished.Notify(); + }; + EXPECT_THAT(authorizer_provider_.GetSessionToken(fetch_token_context_), + IsSuccessful()); + + finished.WaitForNotification(); +} + +TEST_F(AzureAuthTokenProviderTest, GetSessionTokenFailsIfHttpRequestFails) { + EXPECT_CALL(http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = FailureExecutionResult(SC_UNKNOWN); + http_context.Finish(); + return SuccessExecutionResult(); + }); + + absl::Notification finished; + fetch_token_context_.callback = [&finished](auto& context) { + EXPECT_THAT(context.result, ResultIs(FailureExecutionResult(SC_UNKNOWN))); + finished.Notify(); + }; + EXPECT_THAT(authorizer_provider_.GetSessionToken(fetch_token_context_), + IsSuccessful()); + + finished.WaitForNotification(); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/auth_token_provider/azure/error_codes.h b/src/cpio/client_providers/auth_token_provider/azure/error_codes.h new file mode 100644 index 000000000..d18642e51 --- /dev/null +++ b/src/cpio/client_providers/auth_token_provider/azure/error_codes.h @@ -0,0 +1,46 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_ERROR_CODES_H_ + +#include "src/core/interface/errors.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { + +REGISTER_COMPONENT_CODE(SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER, 0x020E) + +DEFINE_ERROR_CODE(SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_INITIALIZATION_FAILED, + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER, 0x0001, + "Cannot initialize the GCP instance authorizer provider.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN, + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER, 0x0002, + "Session token is malformed.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_INITIALIZATION_FAILED, + SC_CPIO_COMPONENT_FAILED_INITIALIZED) +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_INSTANCE_AUTHORIZER_PROVIDER_BAD_SESSION_TOKEN, + SC_CPIO_INVALID_RESOURCE) +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_AUTH_TOKEN_PROVIDER_AZURE_ERROR_CODES_H_ diff --git a/src/cpio/client_providers/blob_storage_client_provider/BUILD.bazel b/src/cpio/client_providers/blob_storage_client_provider/BUILD.bazel index 21e40d550..a563b8a0d 100644 --- a/src/cpio/client_providers/blob_storage_client_provider/BUILD.bazel +++ b/src/cpio/client_providers/blob_storage_client_provider/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/blob_storage_client_provider/aws:aws_blob_storage_client_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/blob_storage_client_provider/azure:azure_blob_storage_client_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/blob_storage_client_provider/gcp:gcp_blob_storage_client_provider", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/blob_storage_client_provider/azure/BUILD.bazel b/src/cpio/client_providers/blob_storage_client_provider/azure/BUILD.bazel new file mode 100644 index 000000000..b5a3b391d --- /dev/null +++ b/src/cpio/client_providers/blob_storage_client_provider/azure/BUILD.bazel @@ -0,0 +1,31 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//src:scp_internal_pkg"]) + +cc_library( + name = "azure_blob_storage_client_provider", + srcs = ["azure_blob_storage_client_provider.cc"], + hdrs = ["azure_blob_storage_client_provider.h"], + deps = [ + "//src/core/common/time_provider", + "//src/core/interface:async_context", + "//src/core/utils:core_utils", + "//src/cpio/client_providers/blob_storage_client_provider/common:blob_storage_provider_common", + "//src/cpio/client_providers/instance_client_provider/azure:azure_instance_client_provider", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + ], +) diff --git a/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.cc b/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.cc new file mode 100644 index 000000000..133c21908 --- /dev/null +++ b/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.cc @@ -0,0 +1,128 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_blob_storage_client_provider.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "src/core/common/global_logger/global_logger.h" +#include "src/core/interface/async_context.h" +#include "src/core/interface/async_executor_interface.h" +#include "src/core/interface/blob_storage_provider_interface.h" +#include "src/core/interface/configuration_keys.h" +#include "src/core/interface/type_def.h" +#include "src/core/utils/base64.h" +#include "src/core/utils/hashing.h" +#include "src/cpio/client_providers/blob_storage_client_provider/common/error_codes.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/interface/blob_storage_client/type_def.h" + +using google::cmrt::sdk::blob_storage_service::v1::Blob; +using google::cmrt::sdk::blob_storage_service::v1::BlobMetadata; +using google::cmrt::sdk::blob_storage_service::v1::DeleteBlobRequest; +using google::cmrt::sdk::blob_storage_service::v1::DeleteBlobResponse; +using google::cmrt::sdk::blob_storage_service::v1::GetBlobRequest; +using google::cmrt::sdk::blob_storage_service::v1::GetBlobResponse; +using google::cmrt::sdk::blob_storage_service::v1::GetBlobStreamRequest; +using google::cmrt::sdk::blob_storage_service::v1::GetBlobStreamResponse; +using google::cmrt::sdk::blob_storage_service::v1::ListBlobsMetadataRequest; +using google::cmrt::sdk::blob_storage_service::v1::ListBlobsMetadataResponse; +using google::cmrt::sdk::blob_storage_service::v1::PutBlobRequest; +using google::cmrt::sdk::blob_storage_service::v1::PutBlobResponse; +using google::cmrt::sdk::blob_storage_service::v1::PutBlobStreamRequest; +using google::cmrt::sdk::blob_storage_service::v1::PutBlobStreamResponse; +using google::protobuf::util::TimeUtil; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncPriority; +using google::scp::core::ConsumerStreamingContext; +using google::scp::core::ExecutionResult; +using google::scp::core::ExecutionResultOr; +using google::scp::core::FailureExecutionResult; +using google::scp::core::FinishContext; +using google::scp::core::ProducerStreamingContext; +using google::scp::core::RetryExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::common::kZeroUuid; +using google::scp::core::common::TimeProvider; +using google::scp::core::errors::SC_BLOB_STORAGE_PROVIDER_ERROR_GETTING_BLOB; +using google::scp::core::errors::SC_BLOB_STORAGE_PROVIDER_INVALID_ARGS; +using google::scp::core::errors::SC_BLOB_STORAGE_PROVIDER_RETRIABLE_ERROR; +using google::scp::core::errors:: + SC_BLOB_STORAGE_PROVIDER_STREAM_SESSION_CANCELLED; +using google::scp::core::errors:: + SC_BLOB_STORAGE_PROVIDER_STREAM_SESSION_EXPIRED; +using google::scp::core::utils::Base64Encode; + +namespace google::scp::cpio::client_providers { + +absl::Status AzureBlobStorageClientProvider::GetBlob( + AsyncContext& get_blob_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::Status AzureBlobStorageClientProvider::GetBlobStream( + ConsumerStreamingContext& + get_blob_stream_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::Status AzureBlobStorageClientProvider::ListBlobsMetadata( + AsyncContext& + list_blobs_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::Status AzureBlobStorageClientProvider::PutBlob( + AsyncContext& put_blob_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::Status AzureBlobStorageClientProvider::PutBlobStream( + ProducerStreamingContext& + put_blob_stream_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::Status AzureBlobStorageClientProvider::DeleteBlob( + AsyncContext& + delete_blob_context) noexcept { + // Not implemented + return absl::OkStatus(); +} + +absl::StatusOr> +BlobStorageClientProviderFactory::Create( + BlobStorageClientOptions options, + InstanceClientProviderInterface* instance_client, + core::AsyncExecutorInterface* cpu_async_executor, + core::AsyncExecutorInterface* io_async_executor) noexcept { + return std::make_unique( + std::move(options), instance_client, cpu_async_executor, + io_async_executor); +} +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.h b/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.h new file mode 100644 index 000000000..05df7e747 --- /dev/null +++ b/src/cpio/client_providers/blob_storage_client_provider/azure/azure_blob_storage_client_provider.h @@ -0,0 +1,87 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_BLOB_STORAGE_CLIENT_PROVIDER_AZURE_AZURE_BLOB_STORAGE_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_BLOB_STORAGE_CLIENT_PROVIDER_AZURE_AZURE_BLOB_STORAGE_CLIENT_PROVIDER_H_ + +#include +#include +#include +#include + +#include "src/core/interface/async_context.h" +#include "src/core/interface/async_executor_interface.h" +#include "src/core/interface/config_provider_interface.h" +#include "src/core/interface/streaming_context.h" +#include "src/cpio/client_providers/blob_storage_client_provider/common/error_codes.h" +#include "src/cpio/client_providers/interface/blob_storage_client_provider_interface.h" +#include "src/cpio/client_providers/interface/instance_client_provider_interface.h" +#include "src/public/cpio/interface/blob_storage_client/type_def.h" + +namespace google::scp::cpio::client_providers { + +/*! @copydoc BlobStorageClientProviderInterface + */ +class AzureBlobStorageClientProvider + : public BlobStorageClientProviderInterface { + public: + explicit AzureBlobStorageClientProvider( + BlobStorageClientOptions options, + InstanceClientProviderInterface* instance_client, + core::AsyncExecutorInterface* cpu_async_executor, + core::AsyncExecutorInterface* io_async_executor) {} + + absl::Status GetBlob( + core::AsyncContext& + get_blob_context) noexcept override; + + absl::Status GetBlobStream( + core::ConsumerStreamingContext< + cmrt::sdk::blob_storage_service::v1::GetBlobStreamRequest, + cmrt::sdk::blob_storage_service::v1::GetBlobStreamResponse>& + get_blob_stream_context) noexcept override; + + absl::Status ListBlobsMetadata( + core::AsyncContext< + cmrt::sdk::blob_storage_service::v1::ListBlobsMetadataRequest, + cmrt::sdk::blob_storage_service::v1::ListBlobsMetadataResponse>& + list_blobs_context) noexcept override; + + absl::Status PutBlob( + core::AsyncContext& + put_blob_context) noexcept override; + + absl::Status PutBlobStream( + core::ProducerStreamingContext< + cmrt::sdk::blob_storage_service::v1::PutBlobStreamRequest, + cmrt::sdk::blob_storage_service::v1::PutBlobStreamResponse>& + put_blob_stream_context) noexcept override; + + absl::Status DeleteBlob( + core::AsyncContext< + cmrt::sdk::blob_storage_service::v1::DeleteBlobRequest, + cmrt::sdk::blob_storage_service::v1::DeleteBlobResponse>& + delete_blob_context) noexcept override; + + private: + static constexpr std::string_view kAzureBlobStorageClientProvider = + "AzureBlobStorageClientProvider"; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_BLOB_STORAGE_CLIENT_PROVIDER_AZURE_AZURE_BLOB_STORAGE_CLIENT_PROVIDER_H_ diff --git a/src/cpio/client_providers/cloud_initializer/BUILD.bazel b/src/cpio/client_providers/cloud_initializer/BUILD.bazel index a936ef625..39fe758d7 100644 --- a/src/cpio/client_providers/cloud_initializer/BUILD.bazel +++ b/src/cpio/client_providers/cloud_initializer/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/cloud_initializer/aws:aws_initializer", ], + "//:azure_platform": [ + "//src/cpio/client_providers/cloud_initializer/azure:no_op_initializer", + ], "//:gcp_platform": [ "//src/cpio/client_providers/cloud_initializer/gcp:no_op_initializer", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/cloud_initializer/azure/BUILD.bazel b/src/cpio/client_providers/cloud_initializer/azure/BUILD.bazel new file mode 100644 index 000000000..f8286f548 --- /dev/null +++ b/src/cpio/client_providers/cloud_initializer/azure/BUILD.bazel @@ -0,0 +1,27 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +package(default_visibility = ["//src:scp_internal_pkg"]) + +cc_library( + name = "no_op_initializer", + srcs = ["no_op_initializer.cc"], + hdrs = ["no_op_initializer.h"], + deps = [ + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/public/core/interface:execution_result", + ], +) diff --git a/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.cc b/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.cc new file mode 100644 index 000000000..46103e0b8 --- /dev/null +++ b/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.cc @@ -0,0 +1,34 @@ +/* + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "no_op_initializer.h" + +#include + +#include "src/public/core/interface/execution_result.h" + +using google::scp::core::ExecutionResult; +using google::scp::core::SuccessExecutionResult; + +namespace google::scp::cpio::client_providers { +void NoOpInitializer::InitCloud() noexcept {} + +void NoOpInitializer::ShutdownCloud() noexcept {} + +std::unique_ptr CloudInitializerFactory::Create() { + return std::make_unique(); +} +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.h b/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.h new file mode 100644 index 000000000..e4b457e0c --- /dev/null +++ b/src/cpio/client_providers/cloud_initializer/azure/no_op_initializer.h @@ -0,0 +1,34 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_AZURE_NO_OP_INITIALIZER_H_ +#define CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_AZURE_NO_OP_INITIALIZER_H_ + +#include "src/cpio/client_providers/interface/cloud_initializer_interface.h" +#include "src/public/core/interface/execution_result.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc CloudInitializerInterface + */ +class NoOpInitializer : public CloudInitializerInterface { + public: + void InitCloud() noexcept override; + + void ShutdownCloud() noexcept override; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_CLOUD_INITIALIZER_AZURE_NO_OP_INITIALIZER_H_ diff --git a/src/cpio/client_providers/instance_client_provider/BUILD.bazel b/src/cpio/client_providers/instance_client_provider/BUILD.bazel index bb388e56c..b98c313a6 100644 --- a/src/cpio/client_providers/instance_client_provider/BUILD.bazel +++ b/src/cpio/client_providers/instance_client_provider/BUILD.bazel @@ -23,11 +23,14 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/instance_client_provider/aws:aws_instance_client_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/instance_client_provider/azure:azure_instance_client_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/instance_client_provider/gcp:gcp_instance_client_provider", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/instance_client_provider/azure/BUILD.bazel b/src/cpio/client_providers/instance_client_provider/azure/BUILD.bazel new file mode 100644 index 000000000..80da8c1c5 --- /dev/null +++ b/src/cpio/client_providers/instance_client_provider/azure/BUILD.bazel @@ -0,0 +1,46 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "azure_instance_client_provider", + srcs = [ + "azure_instance_client_provider.cc", + ], + hdrs = [ + "azure_instance_client_provider.h", + ], + visibility = ["//src:scp_internal_pkg"], + deps = [ + "//src/core/interface", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "@com_google_absl//absl/log:check", + ], +) + +cc_test( + name = "azure_instance_client_provider_test", + size = "small", + srcs = ["azure_instance_client_provider_test.cc"], + deps = [ + ":azure_instance_client_provider", + "//src/core/curl_client/mock:mock_curl_client", + "//src/core/interface", + "//src/cpio/client_providers/auth_token_provider/mock:auth_token_provider_mock", + "//src/public/core:test_execution_result_matchers", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.cc b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.cc new file mode 100644 index 000000000..1809807f0 --- /dev/null +++ b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.cc @@ -0,0 +1,138 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_instance_client_provider.h" + +#include "absl/log/check.h" +#include "src/core/interface/errors.h" + +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameResponse; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::InstanceDetails; +using google::cmrt::sdk::instance_service::v1:: + ListInstanceDetailsByEnvironmentRequest; +using google::cmrt::sdk::instance_service::v1:: + ListInstanceDetailsByEnvironmentResponse; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::SuccessExecutionResult; + +namespace google::scp::cpio::client_providers { + +inline constexpr char kOperatorTagName[] = "operator"; +inline constexpr char kEnvironmentTagName[] = "environment"; +inline constexpr char kServiceTagName[] = "service"; +// Dummy values +inline constexpr char kResourceNameValue[] = "azure_instance_resource_name"; +inline constexpr char kInstanceId[] = "azure_instance_id"; +inline constexpr char kOperatorTagValue[] = "azure_operator"; +inline constexpr char kEnvironmentTagValue[] = "azure_environment"; +inline constexpr char kServiceTagValue[] = "azure_service"; + +AzureInstanceClientProvider::AzureInstanceClientProvider() {} + +absl::Status AzureInstanceClientProvider::GetCurrentInstanceResourceNameSync( + std::string& resource_name) noexcept { + // Not implemented. + return absl::UnimplementedError(""); +} + +absl::Status AzureInstanceClientProvider::GetCurrentInstanceResourceName( + AsyncContext& + get_resource_name_context) noexcept { + get_resource_name_context.response = + std::make_shared(); + // We need to figure out what we should return here. + get_resource_name_context.response->set_instance_resource_name( + kResourceNameValue); + get_resource_name_context.result = SuccessExecutionResult(); + get_resource_name_context.Finish(); + return absl::OkStatus(); +} + +absl::Status AzureInstanceClientProvider::GetTagsByResourceName( + AsyncContext& + get_tags_context) noexcept { + // Not implemented. + return absl::UnimplementedError(""); +} + +absl::Status AzureInstanceClientProvider::GetInstanceDetailsByResourceNameSync( + std::string_view resource_name, + cmrt::sdk::instance_service::v1::InstanceDetails& + instance_details) noexcept { + // Not implemented. + return absl::UnimplementedError(""); +} + +absl::Status AzureInstanceClientProvider::GetInstanceDetailsByResourceName( + AsyncContext& + get_instance_details_context) noexcept { + get_instance_details_context.response = + std::make_shared(); + // We need to igure out what we should return here. + InstanceDetails instance_details; + instance_details.set_instance_id(kInstanceId); + + // We need to provide network info here. + // auto* network = instance_details.add_networks(); + // network->set_private_ipv4_address(std::move(private_ip)); + // network->set_public_ipv4_address(std::move(public_ip)); + + auto& labels_proto = *instance_details.mutable_labels(); + labels_proto[kOperatorTagName] = kOperatorTagValue; + labels_proto[kEnvironmentTagName] = kEnvironmentTagValue; + labels_proto[kServiceTagName] = kServiceTagValue; + + *(get_instance_details_context.response->mutable_instance_details()) = + instance_details; + get_instance_details_context.result = SuccessExecutionResult(); + get_instance_details_context.Finish(); + return absl::OkStatus(); +} + +absl::Status AzureInstanceClientProvider::ListInstanceDetailsByEnvironment( + AsyncContext& + get_instance_details_context) noexcept { + // Not implemented. + return absl::UnimplementedError(""); +} + +std::unique_ptr +InstanceClientProviderFactory::Create( + AuthTokenProviderInterface* auth_token_provider, + HttpClientInterface* http1_client, HttpClientInterface* http2_client, + AsyncExecutorInterface* async_executor, + AsyncExecutorInterface* io_async_executor) { + std::cout << "Returning Azure Instance Client Provider"; + return std::make_unique(); +} + +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.h b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.h new file mode 100644 index 000000000..a04e5e0e1 --- /dev/null +++ b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.h @@ -0,0 +1,71 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ + +#include +#include +#include +#include + +#include "src/core/interface/http_client_interface.h" +#include "src/cpio/client_providers/interface/instance_client_provider_interface.h" + +namespace google::scp::cpio::client_providers { +// Returns dummy values currently. +class AzureInstanceClientProvider : public InstanceClientProviderInterface { + public: + AzureInstanceClientProvider(); + + absl::Status GetCurrentInstanceResourceName( + core::AsyncContext& + context) noexcept override; + + absl::Status GetTagsByResourceName( + core::AsyncContext< + cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest, + cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse>& + context) noexcept override; + + absl::Status GetInstanceDetailsByResourceName( + core::AsyncContext& + context) noexcept override; + + absl::Status ListInstanceDetailsByEnvironment( + core::AsyncContext& + context) noexcept override; + + absl::Status GetCurrentInstanceResourceNameSync( + std::string& resource_name) noexcept override; + + absl::Status GetInstanceDetailsByResourceNameSync( + std::string_view resource_name, + cmrt::sdk::instance_service::v1::InstanceDetails& + instance_details) noexcept override; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_INSTANCE_CLIENT_PROVIDER_AZURE_AZURE_INSTANCE_CLIENT_PROVIDER_H_ diff --git a/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider_test.cc b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider_test.cc new file mode 100644 index 000000000..ce7a146aa --- /dev/null +++ b/src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider_test.cc @@ -0,0 +1,146 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "src/cpio/client_providers/instance_client_provider/azure/azure_instance_client_provider.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/blocking_counter.h" +#include "absl/synchronization/notification.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetCurrentInstanceResourceNameResponse; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1:: + GetInstanceDetailsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameRequest; +using google::cmrt::sdk::instance_service::v1::GetTagsByResourceNameResponse; +using google::cmrt::sdk::instance_service::v1::InstanceDetails; +using google::scp::core::AsyncContext; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::cpio::client_providers::AzureInstanceClientProvider; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::Pair; +using ::testing::Pointee; +using ::testing::Return; +using ::testing::SizeIs; +using ::testing::StrEq; +using ::testing::UnorderedElementsAre; + +namespace { +inline constexpr char kOperatorTagName[] = "operator"; +inline constexpr char kEnvironmentTagName[] = "environment"; +inline constexpr char kServiceTagName[] = "service"; +constexpr char kInstanceResourceName[] = "azure_instance_resource_name"; +constexpr char kInstanceId[] = "azure_instance_id"; +constexpr char kOperatorTagValue[] = "azure_operator"; +constexpr char kEnvironmentTagValue[] = "azure_environment"; +constexpr char kServiceTagValue[] = "azure_service"; +} // namespace + +namespace google::scp::cpio::client_providers::test { +class AzureInstanceClientProviderTest : public testing::Test { + protected: + AzureInstanceClientProviderTest() + : instance_provider_(std::make_unique()) {} + + ~AzureInstanceClientProviderTest() {} + + std::unique_ptr instance_provider_; +}; + +TEST_F(AzureInstanceClientProviderTest, + GetCurrentInstanceResourceNameSyncNotImplemented) { + std::string resource_name; + + EXPECT_FALSE( + instance_provider_->GetCurrentInstanceResourceNameSync(resource_name) + .ok()); +} + +TEST_F(AzureInstanceClientProviderTest, GetCurrentInstanceResourceName) { + // Currently it returns a hard coded value. + absl::Notification done; + AsyncContext + context( + std::make_shared(), + [&](AsyncContext& context) { + ASSERT_SUCCESS(context.result); + EXPECT_THAT(context.response->instance_resource_name(), + StrEq(kInstanceResourceName)); + done.Notify(); + }); + + EXPECT_TRUE(instance_provider_->GetCurrentInstanceResourceName(context).ok()); + done.WaitForNotification(); +} + +TEST_F(AzureInstanceClientProviderTest, GetInstanceDetailsSyncNotImplemented) { + InstanceDetails details; + EXPECT_FALSE( + instance_provider_ + ->GetInstanceDetailsByResourceNameSync(kInstanceResourceName, details) + .ok()); +} + +TEST_F(AzureInstanceClientProviderTest, GetInstanceDetailsSuccess) { + absl::Notification done; + AsyncContext + context( + std::make_shared(), + [&](AsyncContext& context) { + ASSERT_SUCCESS(context.result); + const auto& details = context.response->instance_details(); + EXPECT_THAT(details.instance_id(), StrEq(kInstanceId)); + // We need to update implementation to return networks. + EXPECT_THAT(details.networks(), SizeIs(0)); + EXPECT_THAT(details.labels(), + UnorderedElementsAre( + Pair(kOperatorTagName, kOperatorTagValue), + Pair(kEnvironmentTagName, kEnvironmentTagValue), + Pair(kServiceTagName, kServiceTagValue))); + done.Notify(); + }); + + EXPECT_TRUE( + instance_provider_->GetInstanceDetailsByResourceName(context).ok()); + done.WaitForNotification(); +} + +TEST_F(AzureInstanceClientProviderTest, GetTagsByResourceNameNotImplemented) { + AsyncContext + context(std::make_shared(), + [&](AsyncContext& context) {}); + + EXPECT_FALSE(instance_provider_->GetTagsByResourceName(context).ok()); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/kms_client_provider/BUILD.bazel b/src/cpio/client_providers/kms_client_provider/BUILD.bazel index ef66fb59a..3c5957bf5 100644 --- a/src/cpio/client_providers/kms_client_provider/BUILD.bazel +++ b/src/cpio/client_providers/kms_client_provider/BUILD.bazel @@ -27,6 +27,12 @@ cc_library( "//src/public/cpio/interface:aws_cpio_lib_outside_tee": [ "//src/cpio/client_providers/kms_client_provider/aws:nontee_aws_kms_client_provider", ], + "//src/public/cpio/interface:azure_cpio_lib_inside_tee": [ + "//src/cpio/client_providers/kms_client_provider/azure:azure_kms_client_provider", + ], + "//src/public/cpio/interface:azure_cpio_lib_outside_tee": [ + "//src/cpio/client_providers/kms_client_provider/azure:azure_kms_client_provider", + ], "//src/public/cpio/interface:gcp_cpio_lib_inside_tee": [ "//src/cpio/client_providers/kms_client_provider/gcp:tee_gcp_kms_client_provider", ], @@ -34,6 +40,6 @@ cc_library( "//src/cpio/client_providers/kms_client_provider/gcp:nontee_gcp_kms_client_provider", ], }, - no_match_error = "Please build for AWS or GCP, inside TEE or outside TEE", + no_match_error = "Please build for AWS, Azure or GCP, inside TEE or outside TEE", ), ) diff --git a/src/cpio/client_providers/kms_client_provider/azure/BUILD.bazel b/src/cpio/client_providers/kms_client_provider/azure/BUILD.bazel new file mode 100644 index 000000000..ada1bffd3 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/BUILD.bazel @@ -0,0 +1,77 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(default_visibility = ["//src:scp_internal_pkg"]) + +cc_library( + name = "azure_kms_client_provider", + srcs = [ + ":azure_kms_client_provider.cc", + ], + hdrs = [ + ":azure_kms_client_provider.h", + ":error_codes.h", + ], + deps = [ + ":azure_kms_client_provider_utils", + "//src/azure:attestation", + "//src/core/interface", + "//src/core/interface:async_context", + "//src/core/utils:core_utils", + "//src/cpio/client_providers/global_cpio", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/public/cpio/interface:cpio_errors", + "//src/public/cpio/interface/kms_client:type_def", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@tink_cc", + ], +) + +cc_library( + name = "azure_kms_client_provider_utils", + srcs = [ + ":azure_kms_client_provider_utils.cc", + ], + hdrs = [ + ":azure_kms_client_provider_utils.h", + ], + deps = [ + "@boringssl//:crypto", + "@boringssl//:ssl", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status:statusor", + ], +) + +cc_test( + name = "azure_kms_client_provider_test", + size = "small", + srcs = + [ + "azure_kms_client_provider_test.cc", + "azure_kms_client_provider_utils_test.cc", + ], + deps = [ + "//src/core/curl_client/mock:mock_curl_client", + "//src/core/interface", + "//src/core/test/utils", + "//src/cpio/client_providers/auth_token_provider/mock:auth_token_provider_mock", + "//src/cpio/client_providers/kms_client_provider/azure:azure_kms_client_provider", + "//src/public/core:test_execution_result_matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.cc b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.cc new file mode 100644 index 000000000..d20584aa8 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.cc @@ -0,0 +1,377 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_kms_client_provider.h" + +#include +#include +#include + +#include +#include + +#include "absl/functional/bind_front.h" +#include "absl/log/check.h" +#include "proto/hpke.pb.h" +#include "src/azure/attestation/src/attestation.h" +#include "src/core/utils/base64.h" +#include "src/cpio/client_providers/global_cpio/global_cpio.h" +#include "src/cpio/client_providers/interface/auth_token_provider_interface.h" +#include "src/cpio/client_providers/interface/kms_client_provider_interface.h" +#include "src/public/cpio/interface/kms_client/type_def.h" + +#include "error_codes.h" + +using google::cmrt::sdk::kms_service::v1::DecryptRequest; +using google::cmrt::sdk::kms_service::v1::DecryptResponse; +using google::scp::azure::attestation::fetchFakeSnpAttestation; +using google::scp::azure::attestation::fetchSnpAttestation; +using google::scp::azure::attestation::hasSnp; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncExecutorInterface; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::RetryExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::Uri; +using google::scp::core::common::kZeroUuid; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_EVP_TO_PEM_CONVERSION_ERROR; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_HASH_CREATION_ERROR; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_UNWRAPPING_DECRYPTED_KEY_ERROR; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_WRAPPING_KEY_GENERATION_ERROR; +using google::scp::core::utils::Base64Decode; +using google::scp::core::utils::Base64Encode; +using google::scp::cpio::client_providers::AzureKmsClientProviderUtils; +using google::scp::cpio::client_providers::EvpPkeyWrapper; +using std::all_of; +using std::bind; +using std::cbegin; +using std::cend; +using std::make_pair; +using std::make_shared; +using std::pair; +using std::shared_ptr; +using std::placeholders::_1; + +namespace google::scp::cpio::client_providers { + +constexpr char kAttestation[] = "attestation"; +constexpr char kAzureKmsClientProvider[] = "AzureKmsClientProvider"; + +constexpr char kDefaultKmsUnwrapPath[] = + "https://127.0.0.1:8000/app/unwrapKey?fmt=tink"; +constexpr char kAzureKmsUnwrapUrlEnvVar[] = "AZURE_BA_PARAM_KMS_UNWRAP_URL"; + +constexpr char kAuthorizationHeaderKey[] = "Authorization"; +constexpr char kBearerTokenPrefix[] = "Bearer "; + +// Define properties of API calls +constexpr char kWrappedKid[] = "wrappedKid"; +constexpr char kWrapped[] = "wrapped"; +constexpr char kWrappingKey[] = "wrappingKey"; + +absl::Status AzureKmsClientProvider::Decrypt( + core::AsyncContext& + decrypt_context) noexcept { + auto get_credentials_request = std::make_shared(); + AsyncContext + get_token_context( + std::move(get_credentials_request), + absl::bind_front( + &AzureKmsClientProvider::GetSessionCredentialsCallbackToDecrypt, + this, decrypt_context), + decrypt_context); + + if (ExecutionResult execution_result = + auth_token_provider_->GetSessionToken(get_token_context); + !execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, "Failed to get the session token."); + decrypt_context.Finish(execution_result); + + return absl::UnknownError(google::scp::core::errors::GetErrorMessage( + execution_result.status_code)); + } + + return absl::OkStatus(); +} + +void AzureKmsClientProvider::GetSessionCredentialsCallbackToDecrypt( + core::AsyncContext& decrypt_context, + core::AsyncContext& + get_token_context) noexcept { + if (!get_token_context.result.Successful()) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + get_token_context.result, + "Failed to get the access token."); + decrypt_context.result = get_token_context.result; + decrypt_context.Finish(); + return; + } + + const auto& access_token = *get_token_context.response->session_token; + + const auto& ciphertext = decrypt_context.request->ciphertext(); + if (ciphertext.empty()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to get cipher text from decryption request."); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + + // Check that there is an ID for the key to decrypt with + const auto& key_id = decrypt_context.request->key_resource_name(); + if (key_id.empty()) { + auto execution_result = + FailureExecutionResult(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to get Key ID from decryption request."); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + + AsyncContext http_context; + http_context.request = std::make_shared(); + + // For the first call, it tries to get the unwrap URL from environment + // variable. This is done here because Init() is not called by the shared code + // and it's a temporary workaround. + if (unwrap_url_.empty()) { + const char* value_from_env = std::getenv(kAzureKmsUnwrapUrlEnvVar); + if (value_from_env) { + unwrap_url_ = value_from_env; + } else { + unwrap_url_ = kDefaultKmsUnwrapPath; + } + } + http_context.request->path = std::make_shared(unwrap_url_); + http_context.request->method = HttpMethod::POST; + + std::shared_ptr public_key; + std::shared_ptr private_key; + + // Temporary store wrapping_key + std::pair, std::shared_ptr> + wrapping_key_pair; + std::string hex_hash_on_wrapping_key = ""; + if (hasSnp()) { + // Generate wrapping key + const auto wrapping_key_pair_or = + AzureKmsClientProviderUtils::GenerateWrappingKey(); + if (!wrapping_key_pair_or.ok()) { + std::string error_message = "Failed to generate wrapping key : "; + error_message += wrapping_key_pair_or.status().ToString().c_str(); + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_WRAPPING_KEY_GENERATION_ERROR); + + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, error_message); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } else { + wrapping_key_pair = wrapping_key_pair_or.value(); + } + + private_key = wrapping_key_pair.first; + public_key = wrapping_key_pair.second; + } else { + // Get test PEM public key + const auto public_pem_key = + AzureKmsClientProviderUtils::GetTestPemPublicWrapKey(); + const auto public_key_or = + AzureKmsClientProviderUtils::PemToEvpPkey(public_pem_key); + CHECK(public_key_or.ok()) << "Failed to parse public PEM key: " + << public_key_or.status().ToString().c_str(); + public_key = public_key_or.value(); + + // Get test PEM private key and convert it to EVP_PKEY* + const auto private_key_pem = + AzureKmsClientProviderUtils::GetTestPemPrivWrapKey(); + // Add the constant to avoid the key detection precommit + const auto to_test = std::string("-----") + std::string("BEGIN PRIVATE") + + std::string(" KEY-----"); + + CHECK(private_key_pem.find(to_test) == 0) + << "Failed to get private PEM key"; + const auto private_key_or = + AzureKmsClientProviderUtils::PemToEvpPkey(private_key_pem); + CHECK(private_key_or.ok()) << "Failed to parse private PEM key: " + << private_key_or.status().ToString().c_str(); + private_key = private_key_or.value(); + + wrapping_key_pair = std::make_pair(private_key, public_key); + } + + // Calculate hash on public_key + const auto hex_hash_on_wrapping_key_or = + AzureKmsClientProviderUtils::CreateHexHashOnKey(public_key); + + if (!hex_hash_on_wrapping_key_or.ok()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_HASH_CREATION_ERROR); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, "Failed to create hex hash on key: %s.", + hex_hash_on_wrapping_key_or.status().ToString().c_str()); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + hex_hash_on_wrapping_key = hex_hash_on_wrapping_key_or.value(); + + // Get Attestation Report + const auto report = hasSnp() ? fetchSnpAttestation(hex_hash_on_wrapping_key) + : fetchFakeSnpAttestation(); + CHECK(report.has_value()) << "Failed to get attestation report"; + + nlohmann::json payload; + payload[kWrapped] = ciphertext; + payload[kWrappedKid] = key_id; + payload[kAttestation] = nlohmann::json(report.value()); + const auto wrapping_key_or = + AzureKmsClientProviderUtils::EvpPkeyToPem(public_key); + if (!wrapping_key_or.ok()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_EVP_TO_PEM_CONVERSION_ERROR); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to convert public key to pem: %s.", + wrapping_key_or.status().ToString().c_str()); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + payload[kWrappingKey] = wrapping_key_or.value(); + http_context.request->body = core::BytesBuffer(nlohmann::to_string(payload)); + http_context.request->headers = std::make_shared(); + http_context.request->headers->insert( + {std::string(kAuthorizationHeaderKey), + absl::StrCat(kBearerTokenPrefix, access_token)}); + + http_context.callback = bind(&AzureKmsClientProvider::OnDecryptCallback, this, + decrypt_context, wrapping_key_pair.first, _1); + + auto execution_result = http_client_->PerformRequest(http_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, + "Failed to perform http request to decrypt wrapped key."); + + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } +} + +void AzureKmsClientProvider::OnDecryptCallback( + AsyncContext& decrypt_context, + std::shared_ptr ephemeral_private_key, + AsyncContext& http_client_context) noexcept { + if (!http_client_context.result.Successful()) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + http_client_context.result, + "Failed to decrypt wrapped key using Azure KMS"); + decrypt_context.result = http_client_context.result; + decrypt_context.Finish(); + return; + } + + std::string resp(http_client_context.response->body.bytes->begin(), + http_client_context.response->body.bytes->end()); + nlohmann::json unwrapResp; + try { + unwrapResp = nlohmann::json::parse(resp); + } catch (const nlohmann::json::parse_error& e) { + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + http_client_context.result, + "Failed to parse response from Azure KMS unwrapKey"); + decrypt_context.result = http_client_context.result; + decrypt_context.Finish(); + return; + } + std::string base64_encoded_str = unwrapResp[kWrapped].get(); + std::string decodedWrapped; + if (auto execution_result = + Base64Decode(std::string_view(base64_encoded_str), decodedWrapped); + !execution_result.Successful()) { + SCP_ERROR_CONTEXT( + kAzureKmsClientProvider, decrypt_context, http_client_context.result, + "Failed to base64 decode response from Azure KMS unwrapKey"); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + std::vector encrypted(decodedWrapped.begin(), decodedWrapped.end()); + + const auto decrypted_or = + AzureKmsClientProviderUtils::KeyUnwrap(ephemeral_private_key, encrypted); + if (!decrypted_or.ok()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_UNWRAPPING_DECRYPTED_KEY_ERROR); + SCP_ERROR_CONTEXT(kAzureKmsClientProvider, decrypt_context, + execution_result, "Failed to unwrap decrypted key: %s.", + decrypted_or.status().ToString().c_str()); + decrypt_context.result = execution_result; + decrypt_context.Finish(); + return; + } + const auto decrypted = decrypted_or.value(); + decrypt_context.response = std::make_shared(); + + decrypt_context.response->set_plaintext(decrypted); + + decrypt_context.result = SuccessExecutionResult(); + decrypt_context.Finish(); +} + +std::unique_ptr KmsClientProviderFactory::Create( + absl::Nonnull< + RoleCredentialsProviderInterface*> /*role_credentials_provider*/, + AsyncExecutorInterface* /*io_async_executor*/) noexcept { + // We uses GlobalCpio::GetGlobalCpio()->GetHttpClient() to get http_client + // object instead of adding it to KmsClientProviderFactory::Create() as a new + // parameter. This is to prevent the existing GCP and AWS implementations from + // being changed. + auto cpio_ = &GlobalCpio::GetGlobalCpio(); + auto http_client = &cpio_->GetHttpClient(); + + auto auth_token_provider = &cpio_->GetAuthTokenProvider(); + + return std::make_unique(http_client, + auth_token_provider); +} +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.h b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.h new file mode 100644 index 000000000..71e214a22 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider.h @@ -0,0 +1,87 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ + +#include +#include +#include + +#include "src/core/interface/async_context.h" +#include "src/cpio/client_providers/interface/kms_client_provider_interface.h" +#include "src/public/core/interface/execution_result.h" + +#include "azure_kms_client_provider_utils.h" + +namespace google::scp::cpio::client_providers { + +/*! @copydoc KmsClientProviderInterface + */ +class AzureKmsClientProvider : public KmsClientProviderInterface { + public: + explicit AzureKmsClientProvider( + core::HttpClientInterface* http_client, + AuthTokenProviderInterface* auth_token_provider) + : http_client_(http_client), + auth_token_provider_(auth_token_provider), + unwrap_url_() {} + + absl::Status Decrypt( + core::AsyncContext& + decrypt_context) noexcept override; + + private: + /** + * @brief Callback to pass token for decryption. + * + * @param create_kms_context the context of created KMS Client. + * @param get_token_context the context of fetched auth token + * credentials. + * @return core::ExecutionResult the creation results. + */ + void GetSessionCredentialsCallbackToDecrypt( + core::AsyncContext& + decrypt_context, + core::AsyncContext& + get_token_context) noexcept; + + /** + * @brief Is called when the decrypt operation + * is completed. + * + * @param decrypt_context The context of the decrypt operation. + * @param http_client_context http client operation context. + */ + void OnDecryptCallback( + core::AsyncContext& + decrypt_context, + std::shared_ptr ephemeral_private_key, + core::AsyncContext& + http_client_context) noexcept; + + core::HttpClientInterface* http_client_; + // Auth token provider. + AuthTokenProviderInterface* auth_token_provider_; + + std::string unwrap_url_; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_AZURE_KMS_CLIENT_PROVIDER_H_ diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_test.cc b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_test.cc new file mode 100644 index 000000000..9ed3eb4fc --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_test.cc @@ -0,0 +1,273 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "azure_kms_client_provider.h" + +#include +#include + +#include +#include + +#include + +#include "absl/synchronization/notification.h" +#include "src/core/curl_client/mock/mock_curl_client.h" +#include "src/core/interface/async_context.h" +#include "src/core/utils/base64.h" +#include "src/cpio/client_providers/auth_token_provider/mock/mock_auth_token_provider.h" +#include "src/cpio/client_providers/kms_client_provider/azure/error_codes.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::cmrt::sdk::kms_service::v1::DecryptRequest; +using google::cmrt::sdk::kms_service::v1::DecryptResponse; +using google::scp::core::AsyncContext; +using google::scp::core::BytesBuffer; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::errors:: + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND; +using google::scp::core::errors::SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::MockCurlClient; +using google::scp::core::test::ResultIs; +using google::scp::core::utils::Base64Encode; +using google::scp::cpio::client_providers::mock::MockAuthTokenProvider; +using std::atomic; +using testing::Eq; +using testing::Pointee; + +static constexpr char kServiceAccount[] = "account"; +static constexpr char kWipProvider[] = "wip"; +static constexpr char kKeyId[] = "keyId"; +static constexpr char kCiphertext[] = "ciphertext"; +static constexpr char kPlaintext[] = "plaintext"; +static constexpr char kKmsUnwrapPath[] = + "https://127.0.0.1:8000/app/unwrapKey?fmt=tink"; +static constexpr char kUnwrapKeyResponse[] = R"( +{ + "wrapped": "ku3kczLacS0R8X7WiO2mNEXI319/42gccgiU6e19UDStU/+3uJsbqu8vvQ0yZmMmrPKKU1tXRzHAbXhXjbmIHQhuyXw2V1r2+YKdY2E/NnsrxB0UPwfKwRPLkG1ziNDarX9cmVTIjvtiECrxAbHGVIKvHxFxwzSjtTZzl8YoG0JXslrdYgkFjt/JlBjOEtt5YfyDcILs09eC+Hh3uUxi8J/Wylh9LCFsYo3NJD3Aln0oPPpjHtsxzNgOQJVHLczvdjDZkDTlvSpH8n5EoWt9eAbrUTBghY3qO5bi2/ZxrvaVesPa3Yi2oQIaL2brn+YGmZ7AqIdZJmQ141JnfS9SZtIAn0ONU/tOdVm3+dhLUP+Vcc3j5xsStmPThh1lWlaaDu6Z9ZW+jSd8IjN9o+9k+EHWSjgfOuTitokX6nk+v+DAHKdfGaayBzaKbrJmP+YYnSylgyAzA2mH47B6OA1jz26hmta0aJufDgDYak1lNhgS6Mobn3C30L+bfi3cl2AaCzogeK8NSTS7cX7TwQMSUvOxEaOitRsrdtXm3bfvKXuKGS/AFl+1cDNocriTESuAcsYm9cBN0W/LiN/sc3flD8VBnpOyVfdlzZ/1/RXNiOIJJTyTq6KIGlsA08q8zxWacoKyyL/KCrkJ7LUFdnBnfd+zBEJk6pAldyAhaFtQDo8=" +})"; + +namespace google::scp::cpio::client_providers::test { + +class AzureKmsClientProviderTest : public ::testing::Test { + protected: + void SetUp() override { + client_ = std::make_unique(&http_client_, + &credentials_provider_); + } + + void TearDown() override {} + + void MockGetSessionToken() { + EXPECT_CALL(credentials_provider_, GetSessionToken) + .WillOnce([=](AsyncContext& context) { + context.result = SuccessExecutionResult(); + context.response = std::make_shared(); + context.response->session_token = + std::make_shared("test_token_contents"); + context.Finish(); + return context.result; + }); + } + + MockCurlClient http_client_; + std::unique_ptr client_; + MockAuthTokenProvider credentials_provider_; +}; + +TEST_F(AzureKmsClientProviderTest, NullKeyId) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_ciphertext(kCiphertext); + + absl::Notification condition; + + MockGetSessionToken(); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, EmptyKeyArn) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(""); + kms_decrpyt_request->set_ciphertext(kCiphertext); + + absl::Notification condition; + + MockGetSessionToken(); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, NullCiphertext) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + + absl::Notification condition; + + MockGetSessionToken(); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, EmptyCiphertext) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(""); + + absl::Notification condition; + + MockGetSessionToken(); + + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult( + SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, SuccessToDecrypt) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(kCiphertext); + kms_decrpyt_request->set_account_identity(kServiceAccount); + kms_decrpyt_request->set_gcp_wip_provider(kWipProvider); + + MockGetSessionToken(); + + EXPECT_CALL(http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = SuccessExecutionResult(); + EXPECT_EQ(http_context.request->method, HttpMethod::POST); + EXPECT_THAT(http_context.request->path, Pointee(Eq(kKmsUnwrapPath))); + std::string payload(kUnwrapKeyResponse); + + http_context.response = std::make_shared(); + http_context.response->body = BytesBuffer(kUnwrapKeyResponse); + http_context.Finish(); + return SuccessExecutionResult(); + }); + + absl::Notification condition; + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + EXPECT_EQ(context.response->plaintext(), kPlaintext); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, FailedToDecrypt) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(kCiphertext); + kms_decrpyt_request->set_account_identity(kServiceAccount); + kms_decrpyt_request->set_gcp_wip_provider(kWipProvider); + + MockGetSessionToken(); + + EXPECT_CALL(http_client_, PerformRequest).WillOnce([](auto& http_context) { + http_context.result = FailureExecutionResult(SC_UNKNOWN); + http_context.Finish(); + return SuccessExecutionResult(); + }); + + absl::Notification condition; + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult(SC_UNKNOWN))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureKmsClientProviderTest, FailedToGetAuthToken) { + auto kms_decrpyt_request = std::make_shared(); + kms_decrpyt_request->set_key_resource_name(kKeyId); + kms_decrpyt_request->set_ciphertext(kCiphertext); + kms_decrpyt_request->set_account_identity(kServiceAccount); + kms_decrpyt_request->set_gcp_wip_provider(kWipProvider); + + EXPECT_CALL(credentials_provider_, GetSessionToken) + .WillOnce([=](AsyncContext& context) { + context.result = FailureExecutionResult(SC_UNKNOWN); + context.Finish(); + return context.result; + }); + + absl::Notification condition; + AsyncContext context( + kms_decrpyt_request, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult(SC_UNKNOWN))); + condition.Notify(); + }); + + EXPECT_FALSE(client_->Decrypt(context).ok()); + condition.WaitForNotification(); +} +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.cc b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.cc new file mode 100644 index 000000000..a8549af08 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.cc @@ -0,0 +1,666 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_kms_client_provider_utils.h" + +#include +#include + +using google::scp::cpio::client_providers::AzureKmsClientProviderUtils; + +namespace { + +constexpr unsigned int kMaxOpensslErrorStringLen = 256; + +constexpr char kPemSeperator[] = "-----"; +constexpr char kPemEnd[] = "END "; +constexpr char kPemToken[] = "PRIVATE "; +constexpr char kPemKey[] = "KEY"; +constexpr char kPemBegin[] = "BEGIN "; + +constexpr char kWrappingKp[] = R"( +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDQv0UMGPJ2R2y2 +/s4qqTB0yK6BGqVIcL0tyF93uOHm4LO/rTYuGUElB4QVhyG9oSq4hItAWhnESSkY +RsRvXm83Z0rBMKLSKgQYGJ20Wa4qJVmp/jodufDg52KdAeuUY7Y9GxET4Ng87LrG +tlMoScMr46ccOxnRTao0nzQiNvFwLzeVClozHwiPwB10Zj0yy/RMwyBpWysNpBgs +Ly2YmKHFUjZ/kEcVtPLenDhKr3Gnqx75L/Jw5QXQzpEUCtlwP25H83EkoUkmUY4q +ySDAudOWE9yTiwZ4uEdTJ1F2rDT6HwQOxMhTKw3Ew5i1kRNpW4FL4hIzLz5ZCBU+ +zXZSOvfXuRod5mvwurRvBoUeu5EGk3Cq1QIG/nsgGl7uGcQMN28683+0+5t/V2/c +fES3jZOqp+jNL5r37WFwjyGk2USOH6pzLUGaFI5e+ZHOWFq5fjG/o4sI2RHGjrHZ +D/WagMI+CdoklUi4RKvTvGcLx+TjeLmoXMRdA7eHA2yN3uyoUEFNvq3k3o2bfuYw +1OqwOTKeIB4Ub7WAr9QP5ww99tQgNoKqkIPCtNfSAyqpRMMbp40+ZBo5r6yWJyLe +H1Yto3KYTN8qokwGCAqJv/57gRv8Q12/F5PVfZhelne2NkT5tGiIOA3gVioHNi1n ++J5Fyyrh8+qBk9uH6K9PajtC63NYfQIDAQABAoICAFSswX1ewTtpTZgNU+PKLXWx +0ddcz57K3HItzUvrGvdkPoWJ5Whdpic3HUT+Q5mAPqwKV9IKulj8tEa8rgHe9I4s +wA4NhH5rvK1pjs8Rcax26iAil8BnJGaWdVHq7XyL1eiDijHeCtjrzfe9DY5SHXE4 +LxksgBR+xIQD8EnQr68p+Ank4SHLfNWSwF/u+PQZ90cL/6G88YHfBk8l9ADqKPS5 +nJGyHKOZessB43OoJxo0N6Qs5tMUk39Xy1Gt9PWrRTi6bzLEmb+JZXnFjBuhRUqj +U94ljsJ5PbVlRY413Gd5HVRATmIuHK+sB83ew1kBXTlCws8wYsIKnVOUVGKWuOF0 +hmfeGrY14iqWvC1aZlurNpl5OdGrEgWb/OwcnHDNgAJ+vHrvNrrufFtJqFfJSKWq +4wjwEuetmdif2XC9gj/ZWwwASmI7FtzLB3qzCwIVr55T6QcMVQH4byORCuFuGP8X +CDz/xvfqRQyYUQdqsmbXkQ/7cx20govVPk2j1yXod7svReV3VafXSPB7LrBP6OlC +qQd7deU62JLEUpUOWR4DFlVwwnxgvr3RjyagPIdoi6MEv+hv949KfEDeshiesRHI +nJCRGpDWDKgcjLcWE3Y29c2AsOvpiE/7KzR6re9txi4kyO4f66QrUPav6yEI9Q3i +lCsMRB7ydGDXrbg/V3uhAoIBAQD5bmU4h3XL7QDp3JSEDyNlD0afJZ3RhDZ5fife +iBy5/kEqGOhhoFRZ9aObNe89Awpyv22awqfsesfIhD1cXlfWogJ8gwH+PL+jUvw0 +ikvMWf/6eBiie0XTULdrBfgQyMcX9akYfMDnf1yonOQtbU7C2BnVQjiLE69AVm97 +pMXYFi6hYEMdSrmpYwfckD2AbODqhAnl+J+9VdCpZZrtiZxaOCOStg8PN18m2lTi +20vl6tVfZpLTTepUoYuns6zgM4KPPDHvlkbYRaWzK97TBnzgekOh32cL9mTan/X5 +8QYr6z39mp1zEEllAomjPji97mFj4pasPVLpUo5phopR4TaHAoIBAQDWPpeWEArp +nSGajTqSiBGyjqUn4p+EmWGFHqKzo/Z7Wnr+LtpbGfZ5+qD6SqCzeoaYxBuGtbl2 +/KHnLrGb/oE1l19mzYPtZpP+dQBDCXcCfGtE2klLqFCvMlSQsJfL73oGsw/JfTxN +QYH3E7q2Lv7Z2+/wovMrhZAx7LiZUOGgLKCMnSrValV/rv9UWH0O95JrHyTD2a9A +A+6wJ/jFg7aC9hZySXQiyOhrP/7gEJGJhVUPxGV0wKv06EptSHOBIX9YRPkHWG8j +KKx+VNmDiLTT8WIg5nZa8U+ZOL8F/ghf/XHf8ERrcTfbSYK50PsP83sE9MH+Msix +1ElSiPa4snXbAoIBAQCoJrEcM83IxSTJg4eno2D0HyE35q8G8L+cldyg21eqV2ps +y8/VCLX003EREIIQun0PsFdebn2wIXGPjv6ix4Ml0aAlelgcoa17mFUnwlepEr9L +hiztVHdVJuQPxT1fa0s0rsrpFCkjpyu7C9GTgk4HcpGvv+3IbGPH1r1fOEycCRA0 +gGWeWKLjOzywh5i+fCgAUTUvELX3eOOrXzDbk9qQw6nPnOZ4FpcR5Tw2lyoKfI6N +uuOeibdAiItSagFQP8lzcFwlrURjRkiXiiq0TnpfBm2TsbyRRvDkpdO4RLEpaHQp +BFPCnycrblOFdkvgVtTW9okm4kyDuMEDCM00t8P/AoIBAQCgZYIFhgM1fT9QPxWv +6JEfVi4Nm1wD4PUivZnf1gxNs6LLM/akJ97g2aO1XzPKyxuDuaZGBz1P+LmZo9qy +yCqiHa79/zUbAiYgZiYJCkgAI3gHt0kSjHPDhnHLVXp/4s0/wMU7+zevOzD68tlh +VfPU1RVg2g4l8jvPNMPLfMM+sMqOG4ia+J4EFtbvpcQS9YS4EDvtKMdMrOUBGxvj +e8WjbGvHqnh5JmLjEKlXxO/AvoK9aDLw4uKaW2KFSK246oQ1aIXsWufxsZzag9nI +4QtIdboamY/YbDtEojhZWyOYAd5EYtRGgB/qW7G0PeIIwifCwR+PmSOqBx3R3dqg +0nLrAoIBAFk3YBCf4jAIWiroE8esw0QweekysEDzLBA7aYNxaypD0UA01dbG6+tH +vHVMV9LRzEd4SMMvF9KuckuWR4iGt0JjcCCR1Da7SXTJd1fWUFYNAoZqc877w+4P +RXeQc6hN1Nqwhp8V8PPwq32xBAoTa+jOk+1rdElGIKatmuLDX4St/rw7QGWp5ia6 +1YLTMZ9XyDIIIsmHkP+FsVIizFkY7OfEwVSobjAMkbNVMwzZpOCi7WY1gOL3YsXn +KoYbkERevKaeG3gqTs9xJeicglD+iJqbjoN4bvg66YqrWY6sXoF29ubryUyLbRX0 +/Kg7pJF1e2hkk3vxtCSlu9HfZ4q17vg= +)"; +} // namespace + +namespace google::scp::cpio::client_providers { + +/** + * @brief Return true if input key is a private key + */ +bool AzureKmsClientProviderUtils::isPrivate( + std::shared_ptr key) { + ERR_clear_error(); + // Determine if the key is private or public + + const int key_type = EVP_PKEY_type(EVP_PKEY_id(key->get())); + bool is_private = false; + if (key_type == EVP_PKEY_RSA) { + RSA* rsa_raw = EVP_PKEY_get1_RSA(key->get()); + RsaWrapper rsa_wrapper(rsa_raw); + if (rsa_raw != nullptr) { + is_private = (rsa_wrapper.getE() != NULL && rsa_wrapper.getN() != NULL && + rsa_wrapper.getD() != NULL); + } + } + return is_private; +} + +/** + * @brief Generate hex hash on wrapping key + */ +absl::StatusOr AzureKmsClientProviderUtils::CreateHexHashOnKey( + std::shared_ptr public_key) { + ERR_clear_error(); + CHECK(isPrivate(public_key) == 0) + << "CreateHexHashOnKey only supports public keys"; + + // Create a BIO to hold the public key in PEM format + BIOWrapper bio_wrapper; + int result = PEM_write_bio_PUBKEY(bio_wrapper.get(), public_key->get()); + if (result != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError( + std::string("PEM_write_bio_PUBKEY failed with result: ") + + std::to_string(result) + " - " + error_string); + } + + // Read the PEM key into a string + char* pem_key; + const auto pem_key_length = BIO_get_mem_data(bio_wrapper.get(), &pem_key); + if (pem_key_length == -1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError(std::string("BIO_get_mem_data failed: ") + + error_string); + } + if (pem_key == nullptr) { + return absl::InternalError("BIO_get_mem_data returned nullptr for pem_key"); + } + const std::string pem_key_str(pem_key, pem_key_length); + + // Create a SHA-2 hash of the PEM key string + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_length; + if (EVP_Digest(pem_key_str.c_str(), pem_key_str.size(), hash, &hash_length, + EVP_sha256(), NULL) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError(std::string("Creating hash failed: ") + + std::string(error_string)); + } + + // Convert the hash to a hexadecimal string + std::stringstream ss; + for (unsigned int i = 0; i < hash_length; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + // Return the hexadecimal string + return ss.str(); +} + +/** + * @brief Generate a new wrapping key + */ +absl::StatusOr< + std::pair, std::shared_ptr>> +AzureKmsClientProviderUtils::GenerateWrappingKey() { + try { + RsaWrapper rsa; + BnWrapper e; + ERR_clear_error(); + if (!BN_set_word(e.get(), RSA_F4)) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError(std::string("Failed to set RSA exponent: ") + + std::string(error_string)); + } + + if (RSA_generate_key_ex(rsa.get(), 4096, e.get(), NULL) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError(std::string("Failed to generate RSA key: ") + + std::string(error_string)); + } + + // Check RSA key components + if (rsa.getN() == nullptr || rsa.getE() == nullptr || + rsa.getD() == nullptr) { + return absl::InternalError("RSA key components are not properly set"); + } + + std::shared_ptr private_key = + std::make_shared(EVP_PKEY_new()); + if (!private_key->get()) { + return absl::InternalError("Could not retrieve private_key memory"); + } + + if (EVP_PKEY_set1_RSA(private_key->get(), rsa.get()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError( + std::string("Setting RSA key in private EVP_PKEY failed: ") + + std::string(error_string)); + } + + // Duplicating the RSA public key + RSA* rsa_pub = RSAPublicKey_dup(rsa.get()); + if (rsa_pub == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError( + std::string("Duplicating RSA public key failed: ") + + std::string(error_string)); + } + + // Creating a shared_ptr to manage the duplicated RSA public key + std::shared_ptr rsa_pub_dup = + std::make_shared(rsa_pub); + + // Creating a shared_ptr to manage the public key + std::shared_ptr public_key = + std::make_shared(EVP_PKEY_new()); + if (EVP_PKEY_set1_RSA(public_key->get(), rsa_pub_dup->get()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError(std::string("Set RSA public key failed: ") + + std::string(error_string)); + } + return std::make_pair(private_key, public_key); + } catch (const std::exception& ex) { + std::cerr << "Exception caught in GenerateWrappingKey: " << ex.what() + << std::endl; + throw; + } +} + +/** + * @brief Convert a private PEM wrapping key to EVP_PKEY using private key + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + * @return std::shared_ptr containing the EVP_PKEY. + */ +absl::StatusOr> +AzureKmsClientProviderUtils::GetPrivateEvpPkey(std::string wrapping_pem_key) { + ERR_clear_error(); + + // Create a BIO wrapper to manage memory + BIOWrapper bio_wrapper; + + // Ensure the bio is created successfully before using it + if (bio_wrapper.get() == nullptr) { + return absl::InternalError("Failed to create BIO"); + } + + // Write the PEM key data into the BIO + if (BIO_write(bio_wrapper.get(), wrapping_pem_key.c_str(), + wrapping_pem_key.size()) <= 0) { + return absl::InternalError( + "Failed to write PEM data to BIO in GetPrivateEvpPkey"); + } + + // Read the private key from the BIO + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio_wrapper.get(), NULL, NULL, NULL); + if (pkey == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError( + std::string( + "GetPrivateEvpPkey: Failed to read private key from BIO: ") + + err_buffer); + } + + // Use EvpPkeyWrapper to manage the EVP_PKEY + return std::make_shared(pkey); +} + +/** + * @brief Convert a PEM wrapping key to EVP_PKEY using public key + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + * @return std::shared_ptr containing the EVP_PKEY. + */ +absl::StatusOr> +AzureKmsClientProviderUtils::GetPublicEvpPkey(std::string wrapping_pem_key) { + ERR_clear_error(); + + // Create a BIO wrapper to manage memory + BIOWrapper bio_wrapper; + + // Ensure the bio is created successfully before using it + if (bio_wrapper.get() == nullptr) { + return absl::InternalError("Failed to create BIO"); + } + + // Write the PEM key data into the BIO + if (BIO_write(bio_wrapper.get(), wrapping_pem_key.c_str(), + wrapping_pem_key.size()) <= 0) { + return absl::InternalError( + "Failed to write PEM data to BIO in GetPublicEvpPkey"); + } + + // Read the public key from the BIO + EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio_wrapper.get(), NULL, NULL, NULL); + if (pkey == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + return absl::InternalError( + std::string("GetPublicEvpPkey: Failed to read PEM public key: ") + + err_buffer); + } + + // Return a shared pointer to manage the EVP_PKEY + return std::make_shared(pkey); +} + +/** + * @brief Convert a PEM wrapping key to pkey + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + */ +absl::StatusOr> +AzureKmsClientProviderUtils::PemToEvpPkey(std::string wrapping_pem_key) { + ERR_clear_error(); + + // check for public key or private key + const bool isPrivate = wrapping_pem_key.find(kPemToken) != std::string::npos; + std::shared_ptr key; + if (isPrivate) { + const auto key_or = GetPrivateEvpPkey(wrapping_pem_key); + if (!key_or.ok()) { + return absl::InternalError( + std::string("Failed to read private PEM key: ") + + key_or.status().ToString().c_str()); + } + key = key_or.value(); + } else { + const auto key_or = GetPublicEvpPkey(wrapping_pem_key); + if (!key_or.ok()) { + return absl::InternalError( + std::string("Failed to read private PEM key: ") + + key_or.status().ToString().c_str()); + } + key = key_or.value(); + } + + if (key->get() == NULL) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError( + std::string("Failed to read private and public PEM key: ") + + std::string(error_string)); + } + + // Wrap EVP_PKEY into std::shared_ptr + return key; +} + +/** + * @brief Convert a wrapping key to PEM + * + * @param wrapping_key RSA public key used to wrap a key. + */ +absl::StatusOr AzureKmsClientProviderUtils::EvpPkeyToPem( + std::shared_ptr key) { + ERR_clear_error(); + BIOWrapper bio_wrapper; + + BIO* bio = bio_wrapper.get(); + if (bio == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError(std::string("Failed to create BIO: ") + + error_string); + } + + const bool is_private = isPrivate(key); + int write_result; + if (is_private) { + write_result = PEM_write_bio_PrivateKey(bio, key->get(), nullptr, nullptr, + 0, nullptr, nullptr); + } else { + write_result = PEM_write_bio_PUBKEY(bio, key->get()); + } + + if (write_result != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError(std::string("Failed to write PEM: ") + + error_string); + } + + // BIO memory is managed by openssl + BUF_MEM* bio_mem; + BIO_get_mem_ptr(bio, &bio_mem); + if (bio_mem == nullptr) { + return absl::InternalError("Failed to get BIO memory pointer"); + } + + std::string pem_str(bio_mem->data, bio_mem->length); + return pem_str; +} + +/** + * @brief Wrap a key using RSA OAEP + * + * @param wrapping_key RSA public key used to wrap a key. + * @param key Key wrap. + */ +absl::StatusOr> AzureKmsClientProviderUtils::KeyWrap( + std::shared_ptr wrapping_key, const std::string& data) { + ERR_clear_error(); + + // Ensure that the wrapping key is public + if (isPrivate(wrapping_key)) { + ERR_clear_error(); + return absl::InternalError("Use public key for KeyWrap"); + } + + // Create a wrapper for the EVP_PKEY_CTX resource + EVPKeyCtxWrapper ctxWrapper(EVP_PKEY_CTX_new(wrapping_key->get(), NULL)); + if (ctxWrapper.get() == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to create EVP_PKEY_CTX: " + + std::string(error_string)); + } + + EVP_PKEY_CTX* ctx = ctxWrapper.get(); + + // Initialize the context for encryption + if (EVP_PKEY_encrypt_init(ctx) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to initialize encryption context: " + + std::string(error_string)); + } + + // Set the OAEP padding + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to set OAEP padding: " + + std::string(error_string)); + } + + // Set the OAEP parameters + const EVP_MD* md = EVP_sha256(); + if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to set OAEP digest: " + + std::string(error_string)); + } + + // Get the maximum encrypted data size + size_t encrypted_len; + if (EVP_PKEY_encrypt(ctx, nullptr, &encrypted_len, + reinterpret_cast(data.data()), + data.size()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to get maximum encrypted data size: " + + std::string(error_string)); + } + + // Allocate space for the encrypted data + std::vector encrypted(encrypted_len); + + // Encrypt the data + if (EVP_PKEY_encrypt(ctx, encrypted.data(), &encrypted_len, + reinterpret_cast(data.data()), + data.size()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Encryption failed: " + + std::string(error_string)); + } + + // Resize the encrypted data vector + encrypted.resize(encrypted_len); + return encrypted; +} + +/** + * @brief Unwrap a key using RSA OAEP + * + * @param wrapping_key RSA private key used to unwrap a key. + * @param encrypted Wrapped key to unwrap. + */ +absl::StatusOr AzureKmsClientProviderUtils::KeyUnwrap( + std::shared_ptr wrapping_key, + const std::vector& encrypted) { + ERR_clear_error(); + // Ensure that the wrapping key is private + if (!isPrivate(wrapping_key)) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Use private key for KeyUnwrap: " + + std::string(error_string)); + } + + // Create a wrapper for the EVP_PKEY_CTX resource + EVPKeyCtxWrapper ctxWrapper(EVP_PKEY_CTX_new(wrapping_key->get(), NULL)); + if (ctxWrapper.get() == nullptr) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to create EVP_PKEY_CTX: " + + std::string(error_string)); + } + + EVP_PKEY_CTX* ctx = ctxWrapper.get(); + + // Initialize the context for decryption + if (EVP_PKEY_decrypt_init(ctx) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to initialize decryption context: " + + std::string(error_string)); + } + + // Set the OAEP padding + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to set OAEP padding: " + + std::string(error_string)); + } + + // Set the OAEP parameters + const EVP_MD* md = EVP_sha256(); + if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to set OAEP digest: " + + std::string(error_string)); + } + + // Get the maximum decrypted data size + size_t decrypted_len; + if (EVP_PKEY_decrypt(ctx, nullptr, &decrypted_len, encrypted.data(), + encrypted.size()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Failed to get maximum decrypted data size: " + + std::string(error_string)); + } + + // Allocate space for the decrypted data based on the maximum size + std::vector decrypted(decrypted_len); + + // Decrypt the data + if (EVP_PKEY_decrypt(ctx, decrypted.data(), &decrypted_len, encrypted.data(), + encrypted.size()) != 1) { + char err_buffer[kMaxOpensslErrorStringLen]; + char* error_string = nullptr; + error_string = + ERR_error_string_n(ERR_get_error(), error_string, sizeof(err_buffer)); + ERR_clear_error(); + return absl::InternalError("Decryption failed: " + + std::string(error_string)); + } + + // Resize the decrypted data vector and convert to string + decrypted.resize(decrypted_len); + return std::string(decrypted.begin(), decrypted.end()); +} + +std::string AzureKmsClientProviderUtils::GetTestPemPublicWrapKey() { + return R"( +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0L9FDBjydkdstv7OKqkw +dMiugRqlSHC9Lchfd7jh5uCzv602LhlBJQeEFYchvaEquISLQFoZxEkpGEbEb15v +N2dKwTCi0ioEGBidtFmuKiVZqf46Hbnw4OdinQHrlGO2PRsRE+DYPOy6xrZTKEnD +K+OnHDsZ0U2qNJ80IjbxcC83lQpaMx8Ij8AddGY9Msv0TMMgaVsrDaQYLC8tmJih +xVI2f5BHFbTy3pw4Sq9xp6se+S/ycOUF0M6RFArZcD9uR/NxJKFJJlGOKskgwLnT +lhPck4sGeLhHUydRdqw0+h8EDsTIUysNxMOYtZETaVuBS+ISMy8+WQgVPs12Ujr3 +17kaHeZr8Lq0bwaFHruRBpNwqtUCBv57IBpe7hnEDDdvOvN/tPubf1dv3HxEt42T +qqfozS+a9+1hcI8hpNlEjh+qcy1BmhSOXvmRzlhauX4xv6OLCNkRxo6x2Q/1moDC +PgnaJJVIuESr07xnC8fk43i5qFzEXQO3hwNsjd7sqFBBTb6t5N6Nm37mMNTqsDky +niAeFG+1gK/UD+cMPfbUIDaCqpCDwrTX0gMqqUTDG6eNPmQaOa+slici3h9WLaNy +mEzfKqJMBggKib/+e4Eb/ENdvxeT1X2YXpZ3tjZE+bRoiDgN4FYqBzYtZ/ieRcsq +4fPqgZPbh+ivT2o7QutzWH0CAwEAAQ== +-----END PUBLIC KEY----- + +)"; +} + +std::string AzureKmsClientProviderUtils::GetTestPemPrivWrapKey() { + std::string result = std::string(kPemSeperator) + kPemBegin + kPemToken + + kPemKey + kPemSeperator + "\n" + kWrappingKp + + kPemSeperator + kPemEnd + kPemToken + kPemKey + + kPemSeperator + "\n"; + return result; +} + +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.h b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.h new file mode 100644 index 000000000..912707951 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils.h @@ -0,0 +1,223 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROV_AZURE_KMS_CLIENT_PROVIDER_UTILS_H_ +#define CPIO_CLIENT_PROV_AZURE_KMS_CLIENT_PROVIDER_UTILS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "absl/log/check.h" +#include "absl/status/statusor.h" + +namespace google::scp::cpio::client_providers { + +/* + * What is the wrapping key? + * The wrapping key is an RSA public key used by the /unwrapkey KMS endpoint. + * When making a request to this endpoint, the caller must provide this RSA + * public key. The /unwrapkey endpoint uses the provided public key to encrypt + * the plaintext HPKE private key which is returned in the respone. This ensures + * that the HPKE private key remains encrypted while traveling over proxies + * between KMS and B&A services. The caller can later use its corresponding + * private key to decrypt and obtain the actual plaintext HPKE private key. + * + * Security: + * To prevent man-in-the-middle attacks, the KMS /unwrapkey endpoint validates + * that the presented wrapping key is owned by the calling service. This + * validation is done by checking the report data in the attestation report. + * + * Why do we use a test wrapping key in non-production environments? + * In production environments on real SNP hardware, the wrapping key is + * generated dynamically. A hash of the key is included in the attestation + * report (report data). In test environments, we cannot generate real-time + * attestation reports. Instead, we use a fake attestation report that hardcodes + * the hash of a test wrapping key. + */ + +// Define RAII memory allocation/deallocation classes +class RsaWrapper { + public: + RsaWrapper() : rsa_(RSA_new()) {} + explicit RsaWrapper(RSA* rsa_raw) : rsa_(rsa_raw) {} + + ~RsaWrapper() { RSA_free(rsa_); } + + RSA* get() { return rsa_; } + + const BIGNUM* getE() const { return rsa_->e; } + const BIGNUM* getN() const { return rsa_->n; } + const BIGNUM* getD() const { return rsa_->d; } + + private: + RSA* rsa_; +}; + +class BnWrapper { + public: + BnWrapper() : bn_(BN_new()) {} + explicit BnWrapper(BIGNUM* bn) : bn_(bn) {} + ~BnWrapper() { BN_free(bn_); } + + BIGNUM* get() { return bn_; } + + private: + BIGNUM* bn_; +}; + +class EvpPkeyWrapper { + public: + // Constructor accepting an EVP_PKEY* + explicit EvpPkeyWrapper(EVP_PKEY* pkey) : pkey_(pkey) {} + + // Default constructor + EvpPkeyWrapper() : pkey_(nullptr) {} + + // Destructor + ~EvpPkeyWrapper() { + if (pkey_) { + EVP_PKEY_free(pkey_); + } + } + + // Getter for the EVP_PKEY* pointer + EVP_PKEY* get() const { return pkey_; } + + private: + EVP_PKEY* pkey_; +}; + +class BIOWrapper { + public: + BIOWrapper() : bio_(BIO_new(BIO_s_mem())) { + CHECK(bio_) << "Failed to create BIO"; + } + + ~BIOWrapper() { BIO_free(bio_); } + + BIO* get() { return bio_; } + + private: + BIO* bio_; +}; + +class EVPKeyCtxWrapper { + public: + explicit EVPKeyCtxWrapper(EVP_PKEY_CTX* ctx) : ctx_(ctx) {} + + ~EVPKeyCtxWrapper() { + if (ctx_) { + EVP_PKEY_CTX_free(ctx_); + ctx_ = nullptr; + } + } + + EVP_PKEY_CTX* get() const { return ctx_; } + + private: + EVP_PKEY_CTX* ctx_; +}; + +class AzureKmsClientProviderUtils { + public: + /** + * @brief Generate a new wrapping key + */ + static absl::StatusOr, + std::shared_ptr>> + GenerateWrappingKey(); + + /** + * @brief Convert a wrapping key in PEM + * + * @param wrapping_key RSA public key used to wrap a key. + */ + static absl::StatusOr EvpPkeyToPem( + std::shared_ptr wrapping_key); + + /** + * @brief Generate hex hash on wrapping key + */ + static absl::StatusOr CreateHexHashOnKey( + std::shared_ptr public_key); + + /** + * @brief Wrap a key using RSA OAEP + * + * @param wrapping_key RSA public key used to wrap a key. + * @param key Key in PEM format to wrap. + */ + static absl::StatusOr> KeyWrap( + std::shared_ptr wrapping_key, const std::string& key); + + /** + * @brief Unwrap a key using RSA OAEP + * + * @param wrapping_key RSA private key used to unwrap a key. + * @param encrypted Wrapped key to unwrap. + */ + static absl::StatusOr KeyUnwrap( + std::shared_ptr wrapping_key, + const std::vector& encrypted); + + // Declare the isPrivate function as private + static bool isPrivate(std::shared_ptr key); + + /** + * @brief Convert a PEM wrapping key to pkey + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + */ + static absl::StatusOr> PemToEvpPkey( + std::string wrapping_pem_key); + + /** + * @brief Return public wrapping key for test + */ + static std::string GetTestPemPublicWrapKey(); + + /** + * @brief Return private wrapping key for test + */ + static std::string GetTestPemPrivWrapKey(); + + private: + /** + * @brief Convert a public PEM wrapping key to pkey + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + */ + static absl::StatusOr> GetPublicEvpPkey( + std::string wrapping_pem_key); + + /** + * @brief Convert a private PEM wrapping key to pkey + * + * @param wrapping_pem_key RSA PEM key used to wrap a key. + */ + static absl::StatusOr> GetPrivateEvpPkey( + std::string wrapping_pem_key); +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROV_AZURE_KMS_CLIENT_PROVIDER_UTILS_H_ diff --git a/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils_test.cc b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils_test.cc new file mode 100644 index 000000000..cc62fa47d --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/azure_kms_client_provider_utils_test.cc @@ -0,0 +1,108 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_kms_client_provider_utils.h" + +#include + +#include +#include + +#include "src/core/interface/http_types.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::scp::core::ExecutionResult; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; + +namespace { +constexpr char kKeyId[] = "123"; +constexpr char kPrivateKeyBaseUri[] = "http://localhost.test:8000"; +} // namespace + +namespace google::scp::cpio::client_providers::test { + +TEST(AzureKmsClientProviderUtilsTest, GenerateWrappingKey) { + std::cout << "Starting test GenerateWrappingKey..." << std::endl; + + auto wrapping_key = + AzureKmsClientProviderUtils::GenerateWrappingKey().value(); + + ASSERT_NE(wrapping_key.first, nullptr) << "Private key is null"; + ASSERT_TRUE(AzureKmsClientProviderUtils::isPrivate(wrapping_key.first)); + ASSERT_NE(wrapping_key.second, nullptr) << "Public key is null"; + ASSERT_FALSE(AzureKmsClientProviderUtils::isPrivate(wrapping_key.second)); + std::cout << "GenerateWrappingKey generated keys" << std::endl; + + std::string pem = + AzureKmsClientProviderUtils::EvpPkeyToPem(wrapping_key.first).value(); + std::cout << "GenerateWrappingKey PEM: " << pem << std::endl; + + // Add the constant to avoid the key detection precommit + auto to_test = std::string("-----") + std::string("BEGIN PRIVATE") + + std::string(" KEY-----"); + ASSERT_EQ(pem.find(to_test), 0) << "Private key PEM header not found"; + std::cout << "Private key found" << std::endl; + + pem = AzureKmsClientProviderUtils::EvpPkeyToPem(wrapping_key.second).value(); + ASSERT_EQ(pem.find("-----BEGIN PUBLIC KEY-----"), 0) + << "Public key PEM header not found"; + std::cout << "Public key found" << std::endl; + + std::cout << "Test GenerateWrappingKey completed successfully." << std::endl; +} + +TEST(AzureKmsClientProviderUtilsTest, WrapUnwrap) { + // Generate wrapping key + auto wrapping_key_pair = + AzureKmsClientProviderUtils::GenerateWrappingKey().value(); + auto public_key = wrapping_key_pair.second; + auto private_key = wrapping_key_pair.first; + std::cout << "key pair generated" << std::endl; + // Original message to encrypt + const std::string payload = "payload"; + + // Encrypt the payload + const auto cipher = + AzureKmsClientProviderUtils::KeyWrap(public_key, payload).value(); + ASSERT_FALSE(cipher.empty()); + + // Decrypt the encrypted message + std::string decrypted = + AzureKmsClientProviderUtils::KeyUnwrap(private_key, cipher).value(); + + // Assert that decrypted message matches original payload + ASSERT_EQ(decrypted, payload); + std::cout << decrypted << " matches " << payload << std::endl; +} + +TEST(AzureKmsClientProviderUtilsTest, GenerateWrappingKeyHash) { + auto public_pem_key = AzureKmsClientProviderUtils::GetTestPemPublicWrapKey(); + std::cout << "Test GenerateWrappingKeyHash PEM key: " << public_pem_key + << std::endl; + auto public_key = + AzureKmsClientProviderUtils::PemToEvpPkey(public_pem_key).value(); + + auto hex_hash = + AzureKmsClientProviderUtils::CreateHexHashOnKey(public_key).value(); + std::cout << "##################HASH: " << hex_hash << std::endl; + ASSERT_EQ(hex_hash.size(), 64); + ASSERT_EQ(hex_hash, + "36b03dab8e8751b26d9b33fa2fa1296f823a238ef3dd604f758a4aff5b2b41d0"); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/kms_client_provider/azure/error_codes.h b/src/cpio/client_providers/kms_client_provider/azure/error_codes.h new file mode 100644 index 000000000..7ab5a1004 --- /dev/null +++ b/src/cpio/client_providers/kms_client_provider/azure/error_codes.h @@ -0,0 +1,93 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ + +#include "src/core/interface/errors.h" +#include "src/public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { +REGISTER_COMPONENT_CODE(SC_AZURE_KMS_CLIENT_PROVIDER, 0x022C) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0001, + "Cannot find cipher text", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0002, + "Cannot find decryption Key ID", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0003, + "Unwrapped key is malformed.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_WRAPPING_KEY_GENERATION_ERROR, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0004, + "Error during generation of wrapping key.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0005, + "Credential provider is not found.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_HASH_CREATION_ERROR, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0006, + "Error generating key hash.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_EVP_TO_PEM_CONVERSION_ERROR, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0007, + "Error converting EVP key to PEM.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +DEFINE_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_UNWRAPPING_DECRYPTED_KEY_ERROR, + SC_AZURE_KMS_CLIENT_PROVIDER, 0x0008, + "Error unwrapping decrypted key.", + HttpStatusCode::INTERNAL_SERVER_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_CIPHER_TEXT_NOT_FOUND, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_ID_NOT_FOUND, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_BAD_UNWRAPPED_KEY, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_KMS_CLIENT_PROVIDER_WRAPPING_KEY_GENERATION_ERROR, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_KMS_CLIENT_PROVIDER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_KMS_CLIENT_PROVIDER_KEY_HASH_CREATION_ERROR, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_KMS_CLIENT_PROVIDER_EVP_TO_PEM_CONVERSION_ERROR, + SC_CPIO_INTERNAL_ERROR) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_KMS_CLIENT_PROVIDER_UNWRAPPING_DECRYPTED_KEY_ERROR, + SC_CPIO_INTERNAL_ERROR) +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_KMS_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ diff --git a/src/cpio/client_providers/parameter_client_provider/BUILD.bazel b/src/cpio/client_providers/parameter_client_provider/BUILD.bazel index 4815c72ab..096f757c3 100644 --- a/src/cpio/client_providers/parameter_client_provider/BUILD.bazel +++ b/src/cpio/client_providers/parameter_client_provider/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,10 +24,13 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/parameter_client_provider/aws:aws_parameter_client_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/parameter_client_provider/azure:azure_parameter_client_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/parameter_client_provider/gcp:gcp_parameter_client_provider", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/parameter_client_provider/azure/BUILD.bazel b/src/cpio/client_providers/parameter_client_provider/azure/BUILD.bazel new file mode 100644 index 000000000..c63027c92 --- /dev/null +++ b/src/cpio/client_providers/parameter_client_provider/azure/BUILD.bazel @@ -0,0 +1,53 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +package(default_visibility = ["//src:scp_internal_pkg"]) + +cc_library( + name = "azure_parameter_client_provider", + srcs = [ + ":azure_parameter_client_provider.cc", + ":azure_parameter_client_provider.h", + ":error_codes.h", + ], + deps = [ + "//src/cpio/client_providers/instance_client_provider/azure:azure_instance_client_provider", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/cpio/client_providers/interface:type_def", + "//src/public/cpio/interface:cpio_errors", + "//src/public/cpio/proto/parameter_service/v1:parameter_service_cc_proto", + "@com_github_googleapis_google_cloud_cpp//:secretmanager", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/log:check", + ], +) + +cc_test( + name = "azure_parameter_client_provider_test", + size = "small", + srcs = ["azure_parameter_client_provider_test.cc"], + deps = [ + "//src/core/async_executor/mock:core_async_executor_mock", + "//src/core/interface", + "//src/core/interface:async_context", + "//src/core/test/utils", + "//src/cpio/client_providers/parameter_client_provider/azure:azure_parameter_client_provider", + "//src/public/core:test_execution_result_matchers", + "@com_github_googleapis_google_cloud_cpp//:secretmanager_mocks", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.cc b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.cc new file mode 100644 index 000000000..6a43a88fb --- /dev/null +++ b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.cc @@ -0,0 +1,138 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_parameter_client_provider.h" + +#include +#include +#include +#include + +#include "absl/base/nullability.h" +#include "absl/log/check.h" +#include "absl/strings/str_format.h" +#include "google/cloud/secretmanager/secret_manager_client.h" +#include "google/cloud/secretmanager/secret_manager_connection.h" +#include "src/core/common/uuid/uuid.h" +#include "src/core/interface/async_context.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +#include "error_codes.h" + +using google::cloud::StatusCode; +using google::cloud::StatusOr; +using google::cloud::secretmanager::MakeSecretManagerServiceConnection; +using google::cloud::secretmanager::SecretManagerServiceClient; +using google::cloud::secretmanager::v1::AccessSecretVersionRequest; +using google::cloud::secretmanager::v1::AccessSecretVersionResponse; +using google::cmrt::sdk::parameter_service::v1::GetParameterRequest; +using google::cmrt::sdk::parameter_service::v1::GetParameterResponse; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncPriority; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::common::kZeroUuid; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND; +using std::bind; +using std::placeholders::_1; + +static constexpr char kAzureParameterClientProvider[] = + "AzureParameterClientProvider"; + +namespace google::scp::cpio::client_providers { + +std::shared_ptr +AzureParameterClientProvider::GetSecretManagerClient() noexcept { + return std::make_shared( + MakeSecretManagerServiceConnection()); +} + +absl::Status AzureParameterClientProvider::GetParameter( + AsyncContext& + get_parameter_context) noexcept { + get_parameter_context.response = std::make_shared(); + const auto& parameter_name = get_parameter_context.request->parameter_name(); + // The `parameter_name` follows the format of -, and + // the prefix consists of the values from `instance_client_provider`. Our + // instance client always returns the same dummy values for the current + // implementation. So we can just ignore the prefix for now. + const std::string prefix = "azure_operator-azure_environment-"; + + if (parameter_name.empty()) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, "Failed due to an empty parameter."); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return absl::InvalidArgumentError( + google::scp::core::errors::GetErrorMessage( + execution_result.status_code)); + } + + if (parameter_name.size() <= prefix.size() || + parameter_name.substr(0, prefix.size()) != prefix) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, + "Request does not have expected prefix."); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return absl::InternalError(google::scp::core::errors::GetErrorMessage( + execution_result.status_code)); + } + + // Example value: "BUYER_FRONTEND_PORT" + const auto& flag = parameter_name.substr( + prefix.size(), parameter_name.size() - prefix.size()); + + // Get flag values from environment variables. + // We need to consider adding prefix for environment variables to avoid + // collision. + const char* value_from_env = std::getenv(flag.c_str()); + if (value_from_env) { + get_parameter_context.response->set_parameter_value(value_from_env); + get_parameter_context.result = SuccessExecutionResult(); + get_parameter_context.Finish(); + return absl::OkStatus(); + } else { + auto execution_result = FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND); + SCP_ERROR_CONTEXT(kAzureParameterClientProvider, get_parameter_context, + execution_result, + "Failed to get the parameter value for %s.", + get_parameter_context.request->parameter_name().c_str()); + get_parameter_context.result = execution_result; + get_parameter_context.Finish(); + return absl::OkStatus(); + } +} + +absl::StatusOr> +ParameterClientProviderFactory::Create( + ParameterClientOptions options, + absl::Nonnull instance_client_provider, + absl::Nonnull cpu_async_executor, + absl::Nonnull io_async_executor) { + return std::make_unique(); +} +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.h b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.h new file mode 100644 index 000000000..cb4854e7e --- /dev/null +++ b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.h @@ -0,0 +1,58 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ + +#include +#include +#include + +#include "google/cloud/secretmanager/secret_manager_client.h" +#include "src/core/interface/async_context.h" +#include "src/core/interface/async_executor_interface.h" +#include "src/cpio/client_providers/interface/instance_client_provider_interface.h" +#include "src/cpio/client_providers/interface/parameter_client_provider_interface.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +#include "error_codes.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc ParameterClientProviderInterface + */ +class AzureParameterClientProvider : public ParameterClientProviderInterface { + public: + AzureParameterClientProvider() {} + + absl::Status GetParameter( + core::AsyncContext< + cmrt::sdk::parameter_service::v1::GetParameterRequest, + cmrt::sdk::parameter_service::v1::GetParameterResponse>& + get_parameter_context) noexcept override; + + protected: + /** + * @brief Get the default Secret Manager Service Client object. + * + * @return std::shared_ptr + */ + virtual std::shared_ptr + GetSecretManagerClient() noexcept; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_AZURE_PARAMETER_CLIENT_PROVIDER_H_ diff --git a/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider_test.cc b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider_test.cc new file mode 100644 index 000000000..fc3eab71d --- /dev/null +++ b/src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider_test.cc @@ -0,0 +1,132 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/cpio/client_providers/parameter_client_provider/azure/azure_parameter_client_provider.h" + +#include + +#include + +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/notification.h" +#include "src/core/async_executor/mock/mock_async_executor.h" +#include "src/core/interface/async_context.h" +#include "src/cpio/client_providers/parameter_client_provider/azure/error_codes.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/core/test_execution_result_matchers.h" +#include "src/public/cpio/proto/parameter_service/v1/parameter_service.pb.h" + +using google::cmrt::sdk::parameter_service::v1::GetParameterRequest; +using google::cmrt::sdk::parameter_service::v1::GetParameterResponse; +using google::scp::core::AsyncContext; +using google::scp::core::AsyncOperation; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME; +using google::scp::core::errors:: + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::cpio::client_providers::AzureParameterClientProvider; +using testing::Eq; +using testing::ExplainMatchResult; + +namespace { +constexpr char kTestParameterName[] = "TEST_PARAM"; +constexpr char kTestValue[] = "test-param-value"; +constexpr char kParameterRequestPrefix[] = "azure_operator-azure_environment-"; +} // namespace + +namespace google::scp::cpio::test { +class AzureParameterClientProviderTest : public ::testing::Test { + protected: + void SetUp() override { + client_ = std::make_unique(); + + SetParameter(kTestParameterName, kTestValue); + } + + void TearDown() override {} + + void SetParameter(const std::string& parameter_name, + const std::string& parameter_value) { + EXPECT_EQ(setenv(parameter_name.c_str(), parameter_value.c_str(), 1), 0); + } + + std::unique_ptr client_; +}; + +TEST_F(AzureParameterClientProviderTest, SucceedToFetchParameter) { + absl::Notification condition; + auto request = std::make_shared(); + const std::string parameter_with_prefix = + std::string(kParameterRequestPrefix) + std::string(kTestParameterName); + request->set_parameter_name(parameter_with_prefix); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + EXPECT_EQ(context.response->parameter_value(), kTestValue); + condition.Notify(); + }); + + EXPECT_TRUE(client_->GetParameter(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureParameterClientProviderTest, FailedToFetchParameterErrorNotFound) { + absl::Notification condition; + auto request = std::make_shared(); + const std::string parameter_with_prefix = + std::string(kParameterRequestPrefix) + std::string("DO_NOT_EXIST"); + request->set_parameter_name(parameter_with_prefix); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) { + EXPECT_THAT( + context.result, + ResultIs(FailureExecutionResult( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND))); + condition.Notify(); + }); + + EXPECT_TRUE(client_->GetParameter(context).ok()); + condition.WaitForNotification(); +} + +TEST_F(AzureParameterClientProviderTest, FailedWithInvalidParameterName) { + absl::Notification condition; + auto request = std::make_shared(); + request->set_parameter_name(kTestParameterName /*No correct prefix*/); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) {}); + + EXPECT_FALSE(client_->GetParameter(context).ok()); +} + +TEST_F(AzureParameterClientProviderTest, FailedToFetchParameterEmptyInput) { + absl::Notification condition; + auto request = std::make_shared(); + request->set_parameter_name(""); + AsyncContext context( + std::move(request), + [&](AsyncContext& context) {}); + + EXPECT_FALSE(client_->GetParameter(context).ok()); +} +} // namespace google::scp::cpio::test diff --git a/src/cpio/client_providers/parameter_client_provider/azure/error_codes.h b/src/cpio/client_providers/parameter_client_provider/azure/error_codes.h new file mode 100644 index 000000000..fd6a3c29f --- /dev/null +++ b/src/cpio/client_providers/parameter_client_provider/azure/error_codes.h @@ -0,0 +1,41 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ + +#include "src/core/interface/errors.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { +/// Registers component code as 0x0208 for AZURE parameter client. +REGISTER_COMPONENT_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0208) + +DEFINE_ERROR_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND, + SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0001, + "Parameter is not found", HttpStatusCode::BAD_REQUEST) +DEFINE_ERROR_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME, + SC_AZURE_PARAMETER_CLIENT_PROVIDER, 0x0003, + "Parameter name is invalid", HttpStatusCode::BAD_REQUEST) + +MAP_TO_PUBLIC_ERROR_CODE(SC_AZURE_PARAMETER_CLIENT_PROVIDER_PARAMETER_NOT_FOUND, + SC_CPIO_INTERNAL_ERROR) +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_PARAMETER_CLIENT_PROVIDER_INVALID_PARAMETER_NAME, + SC_CPIO_INVALID_REQUEST) + +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_PARAMETER_CLIENT_PROVIDER_AZURE_ERROR_CODES_H_ diff --git a/src/cpio/client_providers/private_key_fetcher_provider/BUILD.bazel b/src/cpio/client_providers/private_key_fetcher_provider/BUILD.bazel index a07688f30..02f0e6901 100644 --- a/src/cpio/client_providers/private_key_fetcher_provider/BUILD.bazel +++ b/src/cpio/client_providers/private_key_fetcher_provider/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,6 +24,9 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/private_key_fetcher_provider/aws:aws_private_key_fetcher_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/private_key_fetcher_provider/azure:azure_private_key_fetcher_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/private_key_fetcher_provider/gcp:gcp_private_key_fetcher_provider", ], diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/BUILD.bazel b/src/cpio/client_providers/private_key_fetcher_provider/azure/BUILD.bazel new file mode 100644 index 000000000..b52e8df1d --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/BUILD.bazel @@ -0,0 +1,71 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +cc_library( + name = "azure_private_key_fetcher_provider", + srcs = [ + ":azure_private_key_fetcher_provider.cc", + ":azure_private_key_fetcher_provider.h", + ":azure_private_key_fetcher_provider_utils.cc", + ":azure_private_key_fetcher_provider_utils.h", + ":error_codes.h", + ], + visibility = ["//src:scp_internal_pkg"], + deps = [ + "//src/azure:attestation", + "//src/core/http2_client", + "//src/cpio/client_providers/auth_token_provider/azure:azure_auth_token_provider", + "//src/cpio/client_providers/interface:cpio_client_providers_interface", + "//src/cpio/client_providers/interface:type_def", + "//src/cpio/client_providers/private_key_fetcher_provider", + "//src/public/cpio/interface/private_key_client:type_def", + "@com_google_absl//absl/log:check", + ], +) + +cc_test( + name = "azure_private_key_fetcher_provider_test", + size = "small", + srcs = ["azure_private_key_fetcher_provider_test.cc"], + deps = [ + "//src/core/async_executor/mock:core_async_executor_mock", + "//src/core/http2_client/mock:http2_client_mock", + "//src/core/interface", + "//src/core/test/utils", + "//src/cpio/client_providers/auth_token_provider/mock:auth_token_provider_mock", + "//src/cpio/client_providers/private_key_fetcher_provider/azure:azure_private_key_fetcher_provider", + "//src/public/core:test_execution_result_matchers", + "@com_google_absl//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "azure_private_key_fetcher_provider_utils_test", + size = "small", + srcs = ["azure_private_key_fetcher_provider_utils_test.cc"], + deps = [ + "//src/core/async_executor/mock:core_async_executor_mock", + "//src/core/http2_client/mock:http2_client_mock", + "//src/core/interface", + "//src/core/test/utils", + "//src/cpio/client_providers/private_key_fetcher_provider/azure:azure_private_key_fetcher_provider", + "//src/cpio/client_providers/private_key_fetcher_provider/mock:private_key_fetcher_provider_mock", + "//src/public/core:test_execution_result_matchers", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.cc b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.cc new file mode 100644 index 000000000..61bb9a49a --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.cc @@ -0,0 +1,267 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_private_key_fetcher_provider.h" + +#include +#include + +#include + +#include "absl/functional/bind_front.h" +#include "absl/strings/str_cat.h" +#include "src/azure/attestation/src/attestation.h" +#include "src/core/interface/http_client_interface.h" +#include "src/cpio/client_providers/interface/auth_token_provider_interface.h" +#include "src/cpio/client_providers/interface/role_credentials_provider_interface.h" +#include "src/cpio/client_providers/private_key_fetcher_provider/private_key_fetcher_provider_utils.h" + +#include "azure_private_key_fetcher_provider_utils.h" +#include "error_codes.h" + +using google::scp::core::AsyncContext; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpClientInterface; +using google::scp::core::HttpHeaders; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::common::kZeroUuid; +using google::scp::core::errors:: + SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND; +using std::bind; +using std::placeholders::_1; + +namespace { +constexpr char kAzurePrivateKeyFetcherProvider[] = + "AzurePrivateKeyFetcherProvider"; +constexpr char kAuthorizationHeaderKey[] = "Authorization"; +constexpr char kBearerTokenPrefix[] = "Bearer "; + +// Define properties of API calls +constexpr char kWrappedKid[] = "wrappedKid"; +constexpr char kWrapped[] = "wrapped"; + +} // namespace + +namespace google::scp::cpio::client_providers { + +ExecutionResult AzurePrivateKeyFetcherProvider::Init() noexcept { + RETURN_IF_FAILURE(PrivateKeyFetcherProvider::Init()); + + if (!auth_token_provider_) { + auto execution_result = FailureExecutionResult( + SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND); + SCP_ERROR(kAzurePrivateKeyFetcherProvider, kZeroUuid, execution_result, + "Failed to get credentials provider."); + return execution_result; + } + + return SuccessExecutionResult(); +} + +ExecutionResult AzurePrivateKeyFetcherProvider::SignHttpRequest( + AsyncContext& + sign_request_context) noexcept { + auto request = std::make_shared(); + AsyncContext + get_token_context( + std::move(request), + absl::bind_front( + &AzurePrivateKeyFetcherProvider::OnGetSessionTokenCallback, this, + sign_request_context), + sign_request_context); + + return auth_token_provider_->GetSessionToken(get_token_context); +} + +void AzurePrivateKeyFetcherProvider::OnGetSessionTokenCallback( + AsyncContext& + sign_request_context, + AsyncContext& + get_token_context) noexcept { + if (!get_token_context.result.Successful()) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, sign_request_context, + get_token_context.result, + "Failed to get the access token."); + sign_request_context.result = get_token_context.result; + sign_request_context.Finish(); + return; + } + + const auto& access_token = *get_token_context.response->session_token; + auto http_request = std::make_shared(); + AzurePrivateKeyFetchingClientUtils::CreateHttpRequest( + *sign_request_context.request, *http_request); + http_request->headers = std::make_shared(); + http_request->headers->insert( + {std::string(kAuthorizationHeaderKey), + absl::StrCat(kBearerTokenPrefix, access_token)}); + sign_request_context.response = std::move(http_request); + sign_request_context.result = SuccessExecutionResult(); + sign_request_context.Finish(); +} + +ExecutionResult AzurePrivateKeyFetcherProvider::FetchPrivateKey( + AsyncContext& + private_key_fetching_context) noexcept { + AsyncContext + sign_http_request_context( + private_key_fetching_context.request, + bind(&AzurePrivateKeyFetcherProvider::SignHttpRequestCallback, this, + private_key_fetching_context, _1), + private_key_fetching_context); + + return SignHttpRequest(sign_http_request_context); +} + +void AzurePrivateKeyFetcherProvider::SignHttpRequestCallback( + AsyncContext& + private_key_fetching_context, + AsyncContext& + sign_http_request_context) noexcept { + auto execution_result = sign_http_request_context.result; + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, + private_key_fetching_context, execution_result, + "Failed to sign http request."); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + return; + } + + AsyncContext http_client_context( + std::move(sign_http_request_context.response), + bind(&AzurePrivateKeyFetcherProvider::PrivateKeyFetchingCallback, this, + private_key_fetching_context, _1), + private_key_fetching_context); + execution_result = http_client_->PerformRequest(http_client_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + execution_result, + "Failed to perform sign http request to reach endpoint %s.", + private_key_fetching_context.request->key_vending_endpoint + ->private_key_vending_service_endpoint.c_str()); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + } +} + +void AzurePrivateKeyFetcherProvider::PrivateKeyFetchingCallback( + AsyncContext& + private_key_fetching_context, + AsyncContext& http_client_context) noexcept { + private_key_fetching_context.result = http_client_context.result; + if (!http_client_context.result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + private_key_fetching_context.result, "Failed to fetch private key."); + private_key_fetching_context.Finish(); + return; + } + + if (static_cast(http_client_context.response->code) == 202) { + // `OperationDispatcher` will limit number of retry and control the amount + // of wait before sending next request based on + // `http_client_context.retry_count` value. Incrementing it here might not + // be the expected usage of the field. In that case we can either: + // - Modify `HttpClient` implementation under `http2_client/` so that it + // retries for 202 like it already does for some other status codes + // (set `RetryExecutionResult()` to http_context.result). + // - Implement a retry mechanism in this class without depending on + // `OperationDispatcher`. + http_client_context.retry_count++; + auto execution_result = http_client_->PerformRequest(http_client_context); + if (!execution_result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + execution_result, + "Failed to perform sign http request to reach endpoint %s.", + private_key_fetching_context.request->key_vending_endpoint + ->private_key_vending_service_endpoint.c_str()); + private_key_fetching_context.result = execution_result; + private_key_fetching_context.Finish(); + } + return; + } + std::string resp(http_client_context.response->body.bytes->begin(), + http_client_context.response->body.bytes->end()); + + nlohmann::json private_key_resp; + try { + private_key_resp = nlohmann::json::parse(resp); + } catch (const nlohmann::json::parse_error& e) { + std::string error_message = + "Received http response could not be parsed into a JSON: "; + error_message += e.what(); + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, + private_key_fetching_context, http_client_context.result, + error_message); + private_key_fetching_context.result = http_client_context.result; + private_key_fetching_context.Finish(); + return; + } + if (!private_key_resp.contains(kWrappedKid)) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, + private_key_fetching_context, http_client_context.result, + "/key did not provide the wrappedKid property"); + private_key_fetching_context.result = http_client_context.result; + private_key_fetching_context.Finish(); + return; + } + + if (!private_key_resp.contains(kWrapped)) { + SCP_ERROR_CONTEXT(kAzurePrivateKeyFetcherProvider, + private_key_fetching_context, http_client_context.result, + "/key did not provide the wrapped property"); + private_key_fetching_context.result = http_client_context.result; + private_key_fetching_context.Finish(); + return; + } + + std::string wrapped = private_key_resp[kWrapped]; + core::BytesBuffer buffer(wrapped); + PrivateKeyFetchingResponse response; + auto result = + PrivateKeyFetchingClientUtils::ParsePrivateKey(buffer, response); + if (!result.Successful()) { + SCP_ERROR_CONTEXT( + kAzurePrivateKeyFetcherProvider, private_key_fetching_context, + private_key_fetching_context.result, "Failed to parse private key."); + private_key_fetching_context.result = result; + private_key_fetching_context.Finish(); + return; + } + + private_key_fetching_context.response = + std::make_shared(response); + private_key_fetching_context.Finish(); +} + +#ifndef TEST_CPIO +std::unique_ptr +PrivateKeyFetcherProviderFactory::Create( + HttpClientInterface* http_client, + RoleCredentialsProviderInterface* role_credentials_provider, + AuthTokenProviderInterface* auth_token_provider) { + return std::make_unique(http_client, + auth_token_provider); +} +#endif +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.h b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.h new file mode 100644 index 000000000..b8b9bab96 --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider.h @@ -0,0 +1,103 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ + +#include +#include + +#include "src/core/interface/async_context.h" +#include "src/cpio/client_providers/interface/auth_token_provider_interface.h" +#include "src/cpio/client_providers/private_key_fetcher_provider/private_key_fetcher_provider.h" +#include "src/public/core/interface/execution_result.h" + +#include "error_codes.h" + +namespace google::scp::cpio::client_providers { +/*! @copydoc PrivateKeyFetcherProviderInterface + */ +class AzurePrivateKeyFetcherProvider : public PrivateKeyFetcherProvider { + public: + /** + * @brief Constructs a new GCP Private Key Fetching Client Provider object. + * + * @param http_client http client to issue http requests. + * @param auth_token_provider auth token provider. + * service. + */ + AzurePrivateKeyFetcherProvider( + core::HttpClientInterface* http_client, + AuthTokenProviderInterface* auth_token_provider) + : PrivateKeyFetcherProvider(http_client), + auth_token_provider_(auth_token_provider) {} + + core::ExecutionResult Init() noexcept override; + + core::ExecutionResult FetchPrivateKey( + core::AsyncContext& + private_key_fetching_context) noexcept override; + + core::ExecutionResult SignHttpRequest( + core::AsyncContext& + sign_http_request_context) noexcept override; + + protected: + /** + * @brief Triggered to fetch private key when http request is signed. + * + * @param private_key_fetching_context context to fetch private key. + * @param sign_http_request_context context to sign http request. + * @return core::ExecutionResult execution result. + */ + void SignHttpRequestCallback( + core::AsyncContext& + private_key_fetching_context, + core::AsyncContext& + sign_http_request_context) noexcept; + + /** + * @brief Triggered to parse private key when private key payload is fetched. + * + * @param private_key_fetching_context context to fetch private key. + * @param http_client_context context to perform http request. + * @return core::ExecutionResult execution result. + */ + void PrivateKeyFetchingCallback( + core::AsyncContext& + private_key_fetching_context, + core::AsyncContext& + http_client_context) noexcept; + + private: + /** + * @brief Is called after auth_token_provider GetSessionToken() for session + * token is completed + * + * @param sign_http_request_context the context for sign http request. + * @param get_session_token the context of get session token. + */ + void OnGetSessionTokenCallback( + core::AsyncContext& + sign_http_request_context, + core::AsyncContext& + get_session_token) noexcept; + + // Auth token provider. + AuthTokenProviderInterface* auth_token_provider_; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_AZURE_PRIVATE_KEY_FETCHER_PROVIDER_H_ diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_test.cc b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_test.cc new file mode 100644 index 000000000..a84476505 --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_test.cc @@ -0,0 +1,266 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "azure_private_key_fetcher_provider.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/synchronization/notification.h" +#include "src/core/http2_client/mock/mock_http_client.h" +#include "src/core/interface/async_context.h" +#include "src/cpio/client_providers/auth_token_provider/mock/mock_auth_token_provider.h" +#include "src/cpio/client_providers/private_key_fetcher_provider/error_codes.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::scp::core::AsyncContext; +using google::scp::core::Byte; +using google::scp::core::BytesBuffer; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::HttpResponse; +using google::scp::core::SuccessExecutionResult; +using google::scp::core::errors:: + SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND; +using google::scp::core::errors:: + SC_PRIVATE_KEY_FETCHER_PROVIDER_HTTP_CLIENT_NOT_FOUND; +using google::scp::core::errors:: + SC_PRIVATE_KEY_FETCHER_PROVIDER_KEY_DATA_NOT_FOUND; +using google::scp::core::http2_client::mock::MockHttpClient; +using google::scp::core::test::IsSuccessful; +using google::scp::core::test::ResultIs; +using google::scp::cpio::client_providers::AzurePrivateKeyFetcherProvider; +using google::scp::cpio::client_providers::mock::MockAuthTokenProvider; +using std::atomic; +using testing::Pair; +using testing::Pointee; +using testing::Return; +using testing::SetArgPointee; +using ::testing::StrEq; +using testing::UnorderedElementsAre; + +namespace { +constexpr char kAccountIdentity[] = "accountIdentity"; +constexpr char kRegion[] = "us-east-1"; +constexpr char kKeyId[] = "123"; +constexpr char kPrivateKeyBaseUri[] = "http://localhost.test:8000"; +constexpr char kSessionTokenMock[] = "session-token-test"; +constexpr char kAuthorizationHeaderKey[] = "Authorization"; +constexpr char kBearerTokenPrefix[] = "Bearer "; +constexpr char kKeyResponse[] = R"({ + "wrappedKid": "NC0GVa6iXjyP90TocNFlpkzlw-1SAKq0zT6ytWuzcOQ_1", + "wrapped": "{\"keys\":[{\"name\":\"encryptionKeys/123456\",\"encryptionKeyType\":\"SINGLE_PARTY_HYBRID_KEY\",\"publicKeysetHandle\":\"TBD\",\"publicKeyMaterial\":\"testtest\",\"creationTime\":\"1714724806912\",\"expirationTime\":\"1746260806912\",\"keyData\":[{\"publicKeySignature\":\"\",\"keyEncryptionKeyUri\":\"azu-kms://NC0GVa6iXjyP90TocNFlpkzlw-1SAKq0zT6ytWuzcOQ_1\",\"keyMaterial\":\"{\\\"encryptedKeyset\\\":\\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\\"}\"}]}]}" +})"; +constexpr char kKeyResponseWithoutKeyData[] = R"({ + "wrappedKid": "NC0GVa6iXjyP90TocNFlpkzlw-1SAKq0zT6ytWuzcOQ_1", + "wrapped": "{\"keys\":[{\"name\":\"encryptionKeys/123456\",\"encryptionKeyType\":\"SINGLE_PARTY_HYBRID_KEY\",\"publicKeysetHandle\":\"TBD\",\"publicKeyMaterial\":\"testtest\",\"creationTime\":\"1714724806912\",\"expirationTime\":\"1746260806912\",\"xx\":[{\"publicKeySignature\":\"\",\"keyEncryptionKeyUri\":\"azu-kms://NC0GVa6iXjyP90TocNFlpkzlw-1SAKq0zT6ytWuzcOQ_1\",\"keyMaterial\":\"{\\\"encryptedKeyset\\\":\\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\\"}\"}]}]}" +})"; + +// constexpr char kKeyResponse[] = "{\"wrapped\": \"1\",\"wrappedKid\": +// \"12345\"}"; + +} // namespace + +namespace google::scp::cpio::client_providers::test { + +class AzurePrivateKeyFetcherProviderTest : public ::testing::Test { + protected: + AzurePrivateKeyFetcherProviderTest() + : http_client_(std::make_shared()), + credentials_provider_(std::make_shared()), + azure_private_key_fetcher_provider_( + std::make_unique( + http_client_.get(), credentials_provider_.get())) { + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Init()); + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Run()); + + request_ = std::make_shared(); + request_->key_id = std::make_shared(kKeyId); + auto endpoint = std::make_shared(); + endpoint->private_key_vending_service_endpoint = kPrivateKeyBaseUri; + endpoint->service_region = kRegion; + endpoint->account_identity = kAccountIdentity; + request_->key_vending_endpoint = std::move(endpoint); + } + + ~AzurePrivateKeyFetcherProviderTest() { + if (azure_private_key_fetcher_provider_) { + EXPECT_SUCCESS(azure_private_key_fetcher_provider_->Stop()); + } + } + + void MockRequest(const std::string& uri) { + http_client_->request_mock = HttpRequest(); + http_client_->request_mock.path = std::make_shared(uri); + } + + void MockResponse(const std::string& str) { + http_client_->response_mock = HttpResponse(); + http_client_->response_mock.body = BytesBuffer(str); + } + + void MockGetSessionToken() { + EXPECT_CALL(*credentials_provider_, GetSessionToken) + .WillOnce([=](AsyncContext& context) { + context.result = SuccessExecutionResult(); + context.response = std::make_shared(); + context.response->session_token = + std::make_shared("test_token_contents"); + context.Finish(); + return context.result; + }); + } + + std::shared_ptr http_client_; + std::shared_ptr credentials_provider_; + std::unique_ptr + azure_private_key_fetcher_provider_; + std::shared_ptr request_; +}; + +TEST_F(AzurePrivateKeyFetcherProviderTest, MissingHttpClient) { + azure_private_key_fetcher_provider_ = + std::make_unique( + nullptr, credentials_provider_.get()); + + EXPECT_THAT(azure_private_key_fetcher_provider_->Init(), + ResultIs(FailureExecutionResult( + SC_PRIVATE_KEY_FETCHER_PROVIDER_HTTP_CLIENT_NOT_FOUND))); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, MissingCredentialsProvider) { + azure_private_key_fetcher_provider_ = + std::make_unique(http_client_.get(), + nullptr); + + EXPECT_THAT( + azure_private_key_fetcher_provider_->Init(), + ResultIs(FailureExecutionResult( + SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND))); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, SignHttpRequest) { + MockGetSessionToken(); + absl::Notification condition; + AsyncContext context( + request_, + [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + const auto& signed_request_ = *context.response; + EXPECT_EQ(signed_request_.method, HttpMethod::POST); + condition.Notify(); + return SuccessExecutionResult(); + }); + + EXPECT_THAT(azure_private_key_fetcher_provider_->SignHttpRequest(context), + IsSuccessful()); + condition.WaitForNotification(); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, FailedToGetCredentials) { + EXPECT_CALL(*credentials_provider_, GetSessionToken) + .WillOnce([=](AsyncContext& context) { + context.result = FailureExecutionResult(SC_UNKNOWN); + context.Finish(); + return context.result; + }); + + absl::Notification condition; + AsyncContext context( + request_, + [&](AsyncContext& context) { + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult(SC_UNKNOWN))); + condition.Notify(); + }); + + EXPECT_THAT(azure_private_key_fetcher_provider_->SignHttpRequest(context), + ResultIs(FailureExecutionResult(SC_UNKNOWN))); + condition.WaitForNotification(); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, FetchPrivateKey) { + MockGetSessionToken(); + MockRequest(std::string(kPrivateKeyBaseUri)); + MockResponse(kKeyResponse); + + absl::Notification condition; + + AsyncContext context( + request_, [&](AsyncContext& context) { + EXPECT_SUCCESS(context.result); + EXPECT_EQ(context.response->encryption_keys.size(), 1); + const auto& encryption_key = *context.response->encryption_keys.begin(); + EXPECT_THAT(*encryption_key->resource_name, + StrEq("encryptionKeys/123456")); + + condition.Notify(); + return SuccessExecutionResult(); + }); + auto res = azure_private_key_fetcher_provider_->FetchPrivateKey(context); + EXPECT_THAT(res, IsSuccessful()); + condition.WaitForNotification(); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, FailedToFetchPrivateKey) { + MockGetSessionToken(); + ExecutionResult result = FailureExecutionResult(SC_UNKNOWN); + http_client_->http_get_result_mock = result; + + absl::Notification condition; + AsyncContext context( + std::move(request_), + [&](AsyncContext& + context) { + condition.Notify(); + EXPECT_THAT(context.result, ResultIs(result)); + }); + EXPECT_THAT(azure_private_key_fetcher_provider_->FetchPrivateKey(context), + IsSuccessful()); + condition.WaitForNotification(); +} + +TEST_F(AzurePrivateKeyFetcherProviderTest, PrivateKeyNotFound) { + MockGetSessionToken(); + MockRequest(std::string(kPrivateKeyBaseUri)); + MockResponse(kKeyResponseWithoutKeyData); + absl::Notification condition; + AsyncContext context( + std::move(request_), + [&](AsyncContext& + context) { + condition.Notify(); + EXPECT_THAT(context.result, + ResultIs(FailureExecutionResult( + SC_PRIVATE_KEY_FETCHER_PROVIDER_KEY_DATA_NOT_FOUND))); + }); + EXPECT_THAT(azure_private_key_fetcher_provider_->FetchPrivateKey(context), + IsSuccessful()); + condition.WaitForNotification(); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.cc b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.cc new file mode 100644 index 000000000..9a0dadcef --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.cc @@ -0,0 +1,57 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_private_key_fetcher_provider_utils.h" + +#include +#include + +#include "absl/log/check.h" +#include "src/azure/attestation/src/attestation.h" + +using google::scp::azure::attestation::fetchFakeSnpAttestation; +using google::scp::azure::attestation::fetchSnpAttestation; +using google::scp::azure::attestation::hasSnp; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; +using google::scp::core::Uri; +using google::scp::cpio::client_providers::AzurePrivateKeyFetchingClientUtils; + +namespace { +constexpr char kAttestation[] = "attestation"; +} + +namespace google::scp::cpio::client_providers { +void AzurePrivateKeyFetchingClientUtils::CreateHttpRequest( + const PrivateKeyFetchingRequest& request, HttpRequest& http_request) { + const auto& base_uri = + request.key_vending_endpoint->private_key_vending_service_endpoint; + http_request.method = HttpMethod::POST; + + http_request.path = std::make_shared(base_uri); + + // Get Attestation Report + const auto report = + hasSnp() ? fetchSnpAttestation("") : fetchFakeSnpAttestation(); + CHECK(report.has_value()) << "Failed to get attestation report"; + + nlohmann::json json_obj; + json_obj[kAttestation] = report.value(); + + http_request.body = core::BytesBuffer(json_obj.dump()); +} + +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.h b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.h new file mode 100644 index 000000000..8f2e1cb1a --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils.h @@ -0,0 +1,49 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROV_PK_FETCHER_PROVIDER_AZURE_PK_FETCHER_PROV_UTILS_H_ +#define CPIO_CLIENT_PROV_PK_FETCHER_PROVIDER_AZURE_PK_FETCHER_PROV_UTILS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "src/cpio/client_providers/private_key_fetcher_provider/private_key_fetcher_provider.h" + +namespace google::scp::cpio::client_providers { + +class AzurePrivateKeyFetchingClientUtils { + public: + /** + * @brief Create a Http Request object to query private key vending endpoint. + * + * @param private_key_fetching_request request to query private key. + * @param http_request returned http request. + */ + static void CreateHttpRequest( + const PrivateKeyFetchingRequest& private_key_fetching_request, + core::HttpRequest& http_request); +}; + +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROV_PK_FETCHER_PROVIDER_AZURE_PK_FETCHER_PROV_UTILS_H_ diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils_test.cc b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils_test.cc new file mode 100644 index 000000000..fcc21beee --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/azure_private_key_fetcher_provider_utils_test.cc @@ -0,0 +1,52 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_private_key_fetcher_provider_utils.h" + +#include + +#include +#include + +#include "src/core/interface/http_types.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/core/test_execution_result_matchers.h" + +using google::scp::core::ExecutionResult; +using google::scp::core::HttpMethod; +using google::scp::core::HttpRequest; + +namespace { +constexpr char kKeyId[] = "123"; +constexpr char kPrivateKeyBaseUri[] = "http://localhost.test:8000"; +} // namespace + +namespace google::scp::cpio::client_providers::test { + +TEST(AzurePrivateKeyFetchingClientUtilsTest, CreateHttpRequest) { + PrivateKeyFetchingRequest request; + request.key_vending_endpoint = std::make_shared(); + request.key_vending_endpoint->private_key_vending_service_endpoint = + kPrivateKeyBaseUri; + request.max_age_seconds = 1000000; + HttpRequest http_request; + AzurePrivateKeyFetchingClientUtils::CreateHttpRequest(request, http_request); + + EXPECT_EQ(http_request.method, HttpMethod::POST); + EXPECT_EQ(*http_request.path, std::string(kPrivateKeyBaseUri)); +} + +} // namespace google::scp::cpio::client_providers::test diff --git a/src/cpio/client_providers/private_key_fetcher_provider/azure/error_codes.h b/src/cpio/client_providers/private_key_fetcher_provider/azure/error_codes.h new file mode 100644 index 000000000..0ea18d032 --- /dev/null +++ b/src/cpio/client_providers/private_key_fetcher_provider/azure/error_codes.h @@ -0,0 +1,35 @@ +// Portions Copyright (c) Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_ERROR_CODES_H_ +#define CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_ERROR_CODES_H_ + +#include "src/core/interface/errors.h" +#include "src/public/core/interface/execution_result.h" +#include "src/public/cpio/interface/error_codes.h" + +namespace google::scp::core::errors { + +REGISTER_COMPONENT_CODE(SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER, 0x022B) + +DEFINE_ERROR_CODE(SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_AZURE_PRIVATE_KEY_FETCHER_PROVIDER, 0x0001, + "No credentials provider found", HttpStatusCode::NOT_FOUND) + +MAP_TO_PUBLIC_ERROR_CODE( + SC_AZURE_PRIVATE_KEY_FETCHER_CREDENTIALS_PROVIDER_NOT_FOUND, + SC_CPIO_COMPONENT_FAILED_INITIALIZED) +} // namespace google::scp::core::errors + +#endif // CPIO_CLIENT_PROVIDERS_PRIVATE_KEY_FETCHER_PROVIDER_AZURE_ERROR_CODES_H_ diff --git a/src/cpio/client_providers/role_credentials_provider/BUILD.bazel b/src/cpio/client_providers/role_credentials_provider/BUILD.bazel index e95a7c9e6..2151f7d3c 100644 --- a/src/cpio/client_providers/role_credentials_provider/BUILD.bazel +++ b/src/cpio/client_providers/role_credentials_provider/BUILD.bazel @@ -23,10 +23,13 @@ cc_library( "//:aws_platform": [ "//src/cpio/client_providers/role_credentials_provider/aws:aws_role_credentials_provider", ], + "//:azure_platform": [ + "//src/cpio/client_providers/role_credentials_provider/azure:azure_role_credentials_provider", + ], "//:gcp_platform": [ "//src/cpio/client_providers/role_credentials_provider/gcp:gcp_role_credentials_provider", ], }, - no_match_error = "Please build for AWS or GCP", + no_match_error = "Please build for AWS, Azure or GCP", ), ) diff --git a/src/cpio/client_providers/role_credentials_provider/azure/BUILD.bazel b/src/cpio/client_providers/role_credentials_provider/azure/BUILD.bazel new file mode 100644 index 000000000..fc81bb44d --- /dev/null +++ b/src/cpio/client_providers/role_credentials_provider/azure/BUILD.bazel @@ -0,0 +1,30 @@ +# Portions Copyright (c) Microsoft Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "azure_role_credentials_provider", + srcs = [ + ":azure_role_credentials_provider.cc", + ":azure_role_credentials_provider.h", + ], + visibility = ["//src:scp_internal_pkg"], + deps = [ + "//src/core/interface", + "//src/cpio/client_providers/instance_client_provider/azure:azure_instance_client_provider", + "//src/public/core/interface:execution_result", + "//src/public/cpio/interface:type_def", + ], +) diff --git a/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.cc b/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.cc new file mode 100644 index 000000000..fced707fc --- /dev/null +++ b/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.cc @@ -0,0 +1,45 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "azure_role_credentials_provider.h" + +#include + +#include "src/cpio/client_providers/interface/role_credentials_provider_interface.h" + +using google::scp::core::AsyncContext; +using google::scp::core::ExecutionResult; +using google::scp::core::FailureExecutionResult; +using google::scp::core::SuccessExecutionResult; + +namespace google::scp::cpio::client_providers { +absl::Status AzureRoleCredentialsProvider::GetRoleCredentials( + core::AsyncContext& + get_credentials_context) noexcept { + return absl::UnknownError(""); +} + +absl::StatusOr> +RoleCredentialsProviderFactory::Create( + RoleCredentialsProviderOptions /*options*/, + absl::Nonnull< + InstanceClientProviderInterface*> /*instance_client_provider*/, + absl::Nonnull /*cpu_async_executor*/, + absl::Nonnull< + core::AsyncExecutorInterface*> /*io_async_executor*/) noexcept { + return std::make_unique(); +} +} // namespace google::scp::cpio::client_providers diff --git a/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.h b/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.h new file mode 100644 index 000000000..821fc2a28 --- /dev/null +++ b/src/cpio/client_providers/role_credentials_provider/azure/azure_role_credentials_provider.h @@ -0,0 +1,32 @@ +/* + * Portions Copyright (c) Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CPIO_CLIENT_PROVIDERS_ROLE_CREDENTIALS_PROVIDER_AZURE_AZURE_ROLE_CREDENTIALS_PROVIDER_H_ +#define CPIO_CLIENT_PROVIDERS_ROLE_CREDENTIALS_PROVIDER_AZURE_AZURE_ROLE_CREDENTIALS_PROVIDER_H_ + +#include "src/cpio/client_providers/interface/role_credentials_provider_interface.h" + +namespace google::scp::cpio::client_providers { + +class AzureRoleCredentialsProvider : public RoleCredentialsProviderInterface { + public: + absl::Status GetRoleCredentials( + core::AsyncContext& + get_credentials_context) noexcept override; +}; +} // namespace google::scp::cpio::client_providers + +#endif // CPIO_CLIENT_PROVIDERS_ROLE_CREDENTIALS_PROVIDER_AZURE_AZURE_ROLE_CREDENTIALS_PROVIDER_H_ diff --git a/src/public/core/interface/cloud_platform.h b/src/public/core/interface/cloud_platform.h index 846e146a4..fb643b103 100644 --- a/src/public/core/interface/cloud_platform.h +++ b/src/public/core/interface/cloud_platform.h @@ -1,5 +1,6 @@ /* * Copyright 2023 Google LLC + * Copyright (C) Microsoft Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ enum class CloudPlatform { kLocal, kGcp, kAws, + kAzure, }; } // namespace privacy_sandbox::server_common diff --git a/src/public/cpio/interface/BUILD.bazel b/src/public/cpio/interface/BUILD.bazel index 9c0541be6..b3e25e344 100644 --- a/src/public/cpio/interface/BUILD.bazel +++ b/src/public/cpio/interface/BUILD.bazel @@ -1,4 +1,5 @@ # Copyright 2022 Google LLC +# Copyright (C) Microsoft Corporation. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -31,6 +32,14 @@ config_setting( }, ) +config_setting( + name = "azure_cpio_lib_inside_tee", + flag_values = { + "//:platform": "azure", + ":run_inside_tee": "True", + }, +) + config_setting( name = "gcp_cpio_lib_inside_tee", flag_values = { @@ -47,6 +56,14 @@ config_setting( }, ) +config_setting( + name = "azure_cpio_lib_outside_tee", + flag_values = { + "//:platform": "azure", + ":run_inside_tee": "False", + }, +) + config_setting( name = "gcp_cpio_lib_outside_tee", flag_values = { diff --git a/src/telemetry/BUILD.bazel b/src/telemetry/BUILD.bazel index 8125be6cf..06633eff2 100644 --- a/src/telemetry/BUILD.bazel +++ b/src/telemetry/BUILD.bazel @@ -189,6 +189,7 @@ alias( name = "init", actual = select({ "//:aws_instance": ":init_aws", + "//:azure_instance": ":init_aws", "//:gcp_instance": ":init_gcp", ":local_otel_ostream": ":init_local_ostream", ":local_otel_otlp": ":init_local_otlp",