From 26c8e90e45fbcbec3b85ea28b21676ae7929dac1 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Thu, 21 Apr 2022 15:24:08 -0700 Subject: [PATCH 01/18] Add ClientCertificateCredential --- sdk/identity/azure-identity/CHANGELOG.md | 1 + sdk/identity/azure-identity/CMakeLists.txt | 5 + .../azure-identity/inc/azure/identity.hpp | 1 + .../client_certificate_credential.hpp | 96 ++++++ .../azure-identity/samples/CMakeLists.txt | 5 + .../samples/client_certificate_credential.cpp | 42 +++ .../src/client_certificate_credential.cpp | 302 ++++++++++++++++++ .../src/environment_credential.cpp | 16 +- .../azure-identity/test/ut/CMakeLists.txt | 1 + .../ut/client_certificate_credential_test.cpp | 95 ++++++ .../azure-identity/vcpkg/Config.cmake.in | 2 + sdk/identity/azure-identity/vcpkg/vcpkg.json | 4 + 12 files changed, 563 insertions(+), 7 deletions(-) create mode 100644 sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp create mode 100644 sdk/identity/azure-identity/samples/client_certificate_credential.cpp create mode 100644 sdk/identity/azure-identity/src/client_certificate_credential.cpp create mode 100644 sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 092df68ff5..a5c5a92e25 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.3.0-beta.2 (Unreleased) ### Features Added +- Added `ClientCertificateCredential`. ### Breaking Changes diff --git a/sdk/identity/azure-identity/CMakeLists.txt b/sdk/identity/azure-identity/CMakeLists.txt index 4a9d097b21..0230f51ceb 100644 --- a/sdk/identity/azure-identity/CMakeLists.txt +++ b/sdk/identity/azure-identity/CMakeLists.txt @@ -47,6 +47,7 @@ endif() set( AZURE_IDENTITY_HEADER inc/azure/identity/chained_token_credential.hpp + inc/azure/identity//client_certificate_credential.hpp inc/azure/identity/client_secret_credential.hpp inc/azure/identity/dll_import_export.hpp inc/azure/identity/environment_credential.hpp @@ -60,6 +61,7 @@ set( src/private/package_version.hpp src/private/token_credential_impl.hpp src/chained_token_credential.cpp + src/client_certificate_credential.cpp src/client_secret_credential.cpp src/environment_credential.cpp src/managed_identity_credential.cpp @@ -85,6 +87,9 @@ target_include_directories( target_link_libraries(azure-identity PUBLIC Azure::azure-core) +find_package(OpenSSL REQUIRED) +target_link_libraries(azure-identity PRIVATE OpenSSL::Crypto) + get_az_version("${CMAKE_CURRENT_SOURCE_DIR}/src/private/package_version.hpp") generate_documentation(azure-identity ${AZ_LIBRARY_VERSION}) diff --git a/sdk/identity/azure-identity/inc/azure/identity.hpp b/sdk/identity/azure-identity/inc/azure/identity.hpp index 284d1934d7..0ef5230656 100644 --- a/sdk/identity/azure-identity/inc/azure/identity.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity.hpp @@ -9,6 +9,7 @@ #pragma once #include "azure/identity/chained_token_credential.hpp" +#include "azure/identity/client_certificate_credential.hpp" #include "azure/identity/client_secret_credential.hpp" #include "azure/identity/dll_import_export.hpp" #include "azure/identity/environment_credential.hpp" diff --git a/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp new file mode 100644 index 0000000000..d4d951bc98 --- /dev/null +++ b/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Client Secret Credential and options. + */ + +#pragma once + +#include "azure/identity/dll_import_export.hpp" + +#include +#include +#include + +#include +#include + +namespace Azure { namespace Identity { + namespace _detail { + class TokenCredentialImpl; + } // namespace _detail + + /** + * @brief Options for client certificate authentication. + * + */ + struct ClientCertificateCredentialOptions final : public Core::Credentials::TokenCredentialOptions + { + }; + + /** + * @brief Client Certificate Credential authenticates with the Azure services using a Tenant ID, + * Client ID and a client certificate. + * + */ + class ClientCertificateCredential final : public Core::Credentials::TokenCredential { + private: + std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl; + Core::Url m_requestUrl; + std::string m_requestBody; + std::string m_tokenHeaderEncoded; + std::string m_tokenPayloadStaticPart; + void* m_pkey; + + public: + /** + * @brief Constructs a Client Secret Credential. + * + * @param tenantId Tenant ID. + * @param clientId Client ID. + * @param clientCertificatePath Client certificate path. + * @param options Options for token retrieval. + */ + explicit ClientCertificateCredential( + std::string const& tenantId, + std::string const& clientId, + std::string const& clientCertificatePath, + Core::Credentials::TokenCredentialOptions const& options + = Core::Credentials::TokenCredentialOptions()); + + /** + * @brief Constructs a Client Secret Credential. + * + * @param tenantId Tenant ID. + * @param clientId Client ID. + * @param clientCertificatePath Client certificate path. + * @param options Options for token retrieval. + */ + explicit ClientCertificateCredential( + std::string const& tenantId, + std::string const& clientId, + std::string const& clientCertificatePath, + ClientCertificateCredentialOptions const& options); + + /** + * @brief Destructs `%ClientCertificateCredential`. + * + */ + ~ClientCertificateCredential() override; + + /** + * @brief Gets an authentication token. + * + * @param tokenRequestContext A context to get the token in. + * @param context A context to control the request lifetime. + * + * @throw Azure::Core::Credentials::AuthenticationException Authentication error occurred. + */ + Core::Credentials::AccessToken GetToken( + Core::Credentials::TokenRequestContext const& tokenRequestContext, + Core::Context const& context) const override; + }; + +}} // namespace Azure::Identity diff --git a/sdk/identity/azure-identity/samples/CMakeLists.txt b/sdk/identity/azure-identity/samples/CMakeLists.txt index 14e989f303..c22e87d3c2 100644 --- a/sdk/identity/azure-identity/samples/CMakeLists.txt +++ b/sdk/identity/azure-identity/samples/CMakeLists.txt @@ -12,6 +12,11 @@ target_link_libraries(chained_token_credential_sample PRIVATE azure-identity) target_include_directories(chained_token_credential_sample PRIVATE .) create_per_service_target_build_for_sample(identity chained_token_credential_sample) +add_executable(client_certificate_credential_sample client_certificate_credential.cpp) +target_link_libraries(client_certificate_credential_sample PRIVATE azure-identity) +target_include_directories(client_certificate_credential_sample PRIVATE .) +create_per_service_target_build_for_sample(identity client_certificate_credential_sample) + add_executable(client_secret_credential_sample client_secret_credential.cpp) target_link_libraries(client_secret_credential_sample PRIVATE azure-identity) target_include_directories(client_secret_credential_sample PRIVATE .) diff --git a/sdk/identity/azure-identity/samples/client_certificate_credential.cpp b/sdk/identity/azure-identity/samples/client_certificate_credential.cpp new file mode 100644 index 0000000000..f5a79c389b --- /dev/null +++ b/sdk/identity/azure-identity/samples/client_certificate_credential.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include + +#include + +#include + +// These functions should be getting the real Tenant ID, Client ID, and the Client Certificate to +// authenticate. +std::string GetTenantId() { return std::string(); } +std::string GetClientId() { return std::string(); } +std::string GetClientCertificatePath() { return std::string(); } + +int main() +{ + try + { + // Step 1: Initialize Client Certificate Credential. + auto clientCertificateCredential + = std::make_shared( + GetTenantId(), GetClientId(), GetClientCertificatePath()); + + // Step 2: Pass the credential to an Azure Service Client. + Azure::Service::Client azureServiceClient("serviceUrl", clientCertificateCredential); + + // Step 3: Start using the Azure Service Client. + azureServiceClient.DoSomething(Azure::Core::Context::ApplicationContext); + + std::cout << "Success!" << std::endl; + } + catch (const Azure::Core::Credentials::AuthenticationException& exception) + { + // Step 4: Handle authentication errors, if needed + // (invalid credential parameters, insufficient permissions). + std::cout << "Authentication error: " << exception.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp new file mode 100644 index 0000000000..5ac5cd0f1c --- /dev/null +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -0,0 +1,302 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/identity/client_certificate_credential.hpp" + +#include "private/token_credential_impl.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Azure::Identity; + +ClientCertificateCredential::ClientCertificateCredential( + std::string const& tenantId, + std::string const& clientId, + std::string const& clientCertificatePath, + Azure::Core::Credentials::TokenCredentialOptions const& options) + : m_tokenCredentialImpl(new _detail::TokenCredentialImpl(options)), m_pkey(nullptr) +{ + BIO* bio = nullptr; + X509* x509 = nullptr; + try + { + { + using Azure::Core::Credentials::AuthenticationException; + + // Open certificate file, then get private key and X509: + if ((bio = BIO_new_file(clientCertificatePath.c_str(), "r")) != nullptr) + { + if ((m_pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr)) == nullptr) + { + throw AuthenticationException("Failed to read certificate private key."); + } + + if ((x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) == nullptr) + { + static_cast(BIO_seek(bio, 0)); + if ((x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) == nullptr) + { + throw AuthenticationException("Failed to read certificate private key."); + } + } + + BIO_free(bio); + bio = nullptr; + + // Get certificate thumbprint: + { + using Azure::Core::_internal::Base64Url; + + std::string thumbprintHexStr; + std::string thumbprintBase64Str; + { + std::vector mdVec(EVP_MAX_MD_SIZE); + { + unsigned int mdLen = 0; + const auto digestResult = X509_digest(x509, EVP_sha1(), mdVec.data(), &mdLen); + + X509_free(x509); + x509 = nullptr; + + if (!digestResult) + { + throw AuthenticationException("Failed to get certificate thumbprint."); + } + + // Drop unused buffer space: + const auto mdLenSz = static_cast(mdLen); + if (mdVec.size() > mdLenSz) + { + mdVec.resize(mdLenSz); + } + + // Get thumbprint as hex string: + { + std::ostringstream thumbprintStream; + for (const auto md : mdVec) + { + thumbprintStream << std::uppercase << std::hex << std::setfill('0') + << std::setw(2) << static_cast(md); + } + thumbprintHexStr = thumbprintStream.str(); + } + } + + // Get thumbprint as Base64: + { + static_assert( + sizeof(decltype(mdVec)::value_type) == sizeof(uint8_t), + "mdVec value should be representable as uint8_t"); + thumbprintBase64Str = Base64Url::Base64UrlEncode( + reinterpret_cast&>(mdVec)); + } + } + + // Form a JWT token: + const auto tokenHeader = std::string("{\"x5t\":\"") + thumbprintBase64Str + + "\",\"kid\":\"" + thumbprintHexStr + "\",\"alg\":\"RS256\",\"typ\":\"JWT\"}"; + + static_assert( + sizeof(std::string::value_type) == sizeof(uint8_t), + "std::string::value_type value should be representable as uint8_t"); + + const auto tokenHeaderVec + = std::vector(tokenHeader.begin(), tokenHeader.end()); + + m_tokenHeaderEncoded = Base64Url::Base64UrlEncode( + reinterpret_cast&>(tokenHeaderVec)); + } + } + else + { + throw AuthenticationException("Failed to open certificate file."); + } + } + + using Azure::Core::Url; + { + + m_requestUrl = Url("https://login.microsoftonline.com/"); + m_requestUrl.AppendPath(tenantId); + m_requestUrl.AppendPath("oauth2/v2.0/token"); + } + + m_tokenPayloadStaticPart = std::string("{\"aud\":\"") + m_requestUrl.GetAbsoluteUrl() + + "\",\"iss\":\"" + clientId + "\",\"sub\":\"" + clientId + "\",\"jti\":\""; + + { + std::ostringstream body; + body << "grant_type=client_credentials" + "&client_assertion_type=" + "urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" + "&client_id=" + << Url::Encode(clientId); + + m_requestBody = body.str(); + } + } + catch (...) + { + if (bio != nullptr) + { + BIO_free(bio); + } + + if (x509 != nullptr) + { + X509_free(x509); + } + + if (m_pkey != nullptr) + { + EVP_PKEY_free(static_cast(m_pkey)); + } + + throw; + } +} + +ClientCertificateCredential::ClientCertificateCredential( + std::string const& tenantId, + std::string const& clientId, + std::string const& clientCertificatePath, + ClientCertificateCredentialOptions const& options) + : ClientCertificateCredential( + tenantId, + clientId, + clientCertificatePath, + static_cast(options)) +{ +} + +ClientCertificateCredential::~ClientCertificateCredential() +{ + EVP_PKEY_free(static_cast(m_pkey)); +} + +Azure::Core::Credentials::AccessToken ClientCertificateCredential::GetToken( + Azure::Core::Credentials::TokenRequestContext const& tokenRequestContext, + Azure::Core::Context const& context) const +{ + return m_tokenCredentialImpl->GetToken(context, [&]() { + using _detail::TokenCredentialImpl; + using Azure::Core::Http::HttpMethod; + + std::ostringstream body; + body << m_requestBody; + { + auto const& scopes = tokenRequestContext.Scopes; + if (!scopes.empty()) + { + body << "&scope=" << TokenCredentialImpl::FormatScopes(scopes, false); + } + } + + std::string assertion = m_tokenHeaderEncoded; + { + using Azure::Core::_internal::Base64Url; + // Form the asserion to sign. + { + std::string payloadStr; + // Add GUID, current time, and expiration time to the payload + { + using Azure::Core::Uuid; + using Azure::Core::_internal::PosixTimeConverter; + + std::ostringstream payloadStream; + + const Azure::DateTime now = std::chrono::system_clock::now(); + const Azure::DateTime exp = now + std::chrono::minutes(10); + + payloadStream << m_tokenPayloadStaticPart << Uuid::CreateUuid().ToString() + << "\",\"nbf\":" << PosixTimeConverter::DateTimeToPosixTime(now) + << ",\"exp\":" << PosixTimeConverter::DateTimeToPosixTime(exp) << "}"; + + payloadStr = payloadStream.str(); + } + + static_assert( + sizeof(std::string::value_type) == sizeof(uint8_t), + "std::string::value_type value should be representable as uint8_t"); + + // Concatenate JWT token header + "." + encoded payload + const auto payloadVec + = std::vector(payloadStr.begin(), payloadStr.end()); + + assertion += std::string(".") + + Base64Url::Base64UrlEncode(reinterpret_cast&>(payloadVec)); + } + + // Get assertion signature. + std::string signature; + if (auto mdCtx = EVP_MD_CTX_new()) + { + try + { + EVP_PKEY_CTX* signCtx = nullptr; + if ((EVP_DigestSignInit( + mdCtx, &signCtx, EVP_sha256(), nullptr, static_cast(m_pkey)) + == 1) + && (EVP_PKEY_CTX_set_rsa_padding(signCtx, RSA_PKCS1_PADDING) == 1)) + { + size_t sigLen = 0; + if (EVP_DigestSign(mdCtx, nullptr, &sigLen, nullptr, 0) == 1) + { + const auto bufToSign = reinterpret_cast(assertion.data()); + const auto bufToSignLen = static_cast(assertion.size()); + + std::vector sigVec(sigLen); + if (EVP_DigestSign(mdCtx, sigVec.data(), &sigLen, bufToSign, bufToSignLen) == 1) + { + static_assert( + sizeof(unsigned char) == sizeof(uint8_t), + "unsigned char should be representable as uint8_t"); + + signature = Base64Url::Base64UrlEncode( + reinterpret_cast&>(sigVec)); + } + } + } + + if (signature.empty()) + { + throw Azure::Core::Credentials::AuthenticationException( + "Failed to sign token request."); + } + + EVP_MD_CTX_free(mdCtx); + } + catch (...) + { + EVP_MD_CTX_free(mdCtx); + throw; + } + } + + // Add signature to the end of assertion + assertion += std::string(".") + signature; + } + + body << "&client_assertion=" << Azure::Core::Url::Encode(assertion); + + auto request = std::make_unique( + HttpMethod::Post, m_requestUrl, body.str()); + + return request; + }); +} diff --git a/sdk/identity/azure-identity/src/environment_credential.cpp b/sdk/identity/azure-identity/src/environment_credential.cpp index f7f3660de0..5c775874b4 100644 --- a/sdk/identity/azure-identity/src/environment_credential.cpp +++ b/sdk/identity/azure-identity/src/environment_credential.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #include "azure/identity/environment_credential.hpp" +#include "azure/identity/client_certificate_credential.hpp" #include "azure/identity/client_secret_credential.hpp" #include @@ -21,8 +22,8 @@ EnvironmentCredential::EnvironmentCredential( // auto username = Environment::GetVariable("AZURE_USERNAME"); // auto password = Environment::GetVariable("AZURE_PASSWORD"); - // - // auto clientCertificatePath = Environment::GetVariable("AZURE_CLIENT_CERTIFICATE_PATH"); + + auto clientCertificatePath = Environment::GetVariable("AZURE_CLIENT_CERTIFICATE_PATH"); if (!tenantId.empty() && !clientId.empty()) { @@ -44,16 +45,17 @@ EnvironmentCredential::EnvironmentCredential( new ClientSecretCredential(tenantId, clientId, clientSecret, options)); } } - // TODO: These credential types are not implemented. Uncomment when implemented. + // TODO: UsernamePasswordCredential is not implemented. Uncomment when implemented. // else if (!username.empty() && !password.empty()) // { // m_credentialImpl.reset( // new UsernamePasswordCredential(tenantId, clientId, username, password, options)); // } - // else if (!clientCertificatePath.empty()) - // { - // m_credentialImpl.reset(new ClientCertificateCredential(tenantId, clientId, options)); - // } + else if (!clientCertificatePath.empty()) + { + m_credentialImpl.reset( + new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, options)); + } } } diff --git a/sdk/identity/azure-identity/test/ut/CMakeLists.txt b/sdk/identity/azure-identity/test/ut/CMakeLists.txt index 512825359f..784e32018b 100644 --- a/sdk/identity/azure-identity/test/ut/CMakeLists.txt +++ b/sdk/identity/azure-identity/test/ut/CMakeLists.txt @@ -17,6 +17,7 @@ add_compile_definitions(AZURE_TEST_RECORDING_DIR="${CMAKE_CURRENT_LIST_DIR}") add_executable ( azure-identity-test chained_token_credential_test.cpp + client_certificate_credential_test.cpp client_secret_credential_test.cpp credential_test_helper.cpp credential_test_helper.hpp diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp new file mode 100644 index 0000000000..2b4b68f9d2 --- /dev/null +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/identity/client_certificate_credential.hpp" + +#include "credential_test_helper.hpp" + +#include + +using Azure::Core::Http::HttpMethod; +using Azure::Identity::ClientCertificateCredential; +using Azure::Identity::ClientCertificateCredentialOptions; +using Azure::Identity::Test::_detail::CredentialTestHelper; + +TEST(ClientCertificateCredential, Regular) +{ + auto const actual = CredentialTestHelper::SimulateTokenRequest( + [](auto transport) { + ClientCertificateCredentialOptions options; + options.Transport.Transport = transport; + + return std::make_unique( + "01234567-89ab-cdef-fedc-ba8976543210", + "fedcba98-7654-3210-0123-456789abcdef", + "CLIENTSECRET", + options); + }, + {{{"https://azure.com/.default"}}, {{}}}, + std::vector{ + "{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}", + "{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}"}); + + EXPECT_EQ(actual.Requests.size(), 2U); + EXPECT_EQ(actual.Responses.size(), 2U); + + auto const& request0 = actual.Requests.at(0); + auto const& request1 = actual.Requests.at(1); + + auto const& response0 = actual.Responses.at(0); + auto const& response1 = actual.Responses.at(1); + + EXPECT_EQ(request0.HttpMethod, HttpMethod::Post); + EXPECT_EQ(request1.HttpMethod, HttpMethod::Post); + + EXPECT_EQ( + request0.AbsoluteUrl, + "https://login.microsoftonline.com/01234567-89ab-cdef-fedc-ba8976543210/oauth2/v2.0/token"); + + EXPECT_EQ( + request1.AbsoluteUrl, + "https://login.microsoftonline.com/01234567-89ab-cdef-fedc-ba8976543210/oauth2/v2.0/token"); + + { + constexpr char expectedBodyStart0[] + = "grant_type=client_credentials" + "&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" + "&client_id=fedcba98-7654-3210-0123-456789abcdef" + "&scope=https%3A%2F%2Fazure.com%2F.default" // cspell:disable-line + "&client_assertion="; + + constexpr char expectedBodyStart1[] + = "grant_type=client_credentials" + "&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" + "&client_id=fedcba98-7654-3210-0123-456789abcdef" + "&client_assertion="; + + EXPECT_GT(request0.Body.size(), (sizeof(expectedBodyStart0) - 1)); + EXPECT_GT(request1.Body.size(), (sizeof(expectedBodyStart1) - 1)); + + EXPECT_EQ(request0.Body.substr(0, (sizeof(expectedBodyStart0) - 1)), expectedBodyStart0); + EXPECT_EQ(request1.Body.substr(0, (sizeof(expectedBodyStart1) - 1)), expectedBodyStart1); + + EXPECT_NE(request0.Headers.find("Content-Length"), request0.Headers.end()); + EXPECT_GT(std::stoi(request0.Headers.at("Content-Length")), (sizeof(expectedBodyStart0) - 1)); + + EXPECT_NE(request1.Headers.find("Content-Length"), request1.Headers.end()); + EXPECT_GT(std::stoi(request1.Headers.at("Content-Length")), (sizeof(expectedBodyStart1) - 1)); + } + + EXPECT_NE(request0.Headers.find("Content-Type"), request0.Headers.end()); + EXPECT_EQ(request0.Headers.at("Content-Type"), "application/x-www-form-urlencoded"); + + EXPECT_NE(request1.Headers.find("Content-Type"), request1.Headers.end()); + EXPECT_EQ(request1.Headers.at("Content-Type"), "application/x-www-form-urlencoded"); + + EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1"); + EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2"); + + using namespace std::chrono_literals; + EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s); + EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s); + + EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); + EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); +} diff --git a/sdk/identity/azure-identity/vcpkg/Config.cmake.in b/sdk/identity/azure-identity/vcpkg/Config.cmake.in index 588fe3e91e..7da70019f4 100644 --- a/sdk/identity/azure-identity/vcpkg/Config.cmake.in +++ b/sdk/identity/azure-identity/vcpkg/Config.cmake.in @@ -6,6 +6,8 @@ include(CMakeFindDependencyMacro) find_dependency(azure-core-cpp "1.4.0") +find_dependency(OpenSSL) + include("${CMAKE_CURRENT_LIST_DIR}/azure-identity-cppTargets.cmake") check_required_components("azure-identity-cpp") diff --git a/sdk/identity/azure-identity/vcpkg/vcpkg.json b/sdk/identity/azure-identity/vcpkg/vcpkg.json index 27f3485dee..e5fbd7dc51 100644 --- a/sdk/identity/azure-identity/vcpkg/vcpkg.json +++ b/sdk/identity/azure-identity/vcpkg/vcpkg.json @@ -16,6 +16,10 @@ "default-features": false, "version>=": "1.4.0" }, + { + "name": "openssl", + "version>=": "1.1.1n" + }, { "name": "vcpkg-cmake", "host": true From 053b19ebc35b49a9a237869bcdf86a36c6873d83 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 09:45:18 -0700 Subject: [PATCH 02/18] Update unit test --- .../ut/client_certificate_credential_test.cpp | 198 +++++++++++++++++- 1 file changed, 196 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index 2b4b68f9d2..e257cb94ce 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -5,6 +5,11 @@ #include "credential_test_helper.hpp" +#include + +#include +#include + #include using Azure::Core::Http::HttpMethod; @@ -12,8 +17,21 @@ using Azure::Identity::ClientCertificateCredential; using Azure::Identity::ClientCertificateCredentialOptions; using Azure::Identity::Test::_detail::CredentialTestHelper; +namespace { +struct TempCertFile final +{ + static const char* const Path; + ~TempCertFile(); + TempCertFile(); +}; + +std::vector SplitString(const std::string& s, char separator); +} // namespace + TEST(ClientCertificateCredential, Regular) { + TempCertFile tempCertFile; + auto const actual = CredentialTestHelper::SimulateTokenRequest( [](auto transport) { ClientCertificateCredentialOptions options; @@ -22,7 +40,7 @@ TEST(ClientCertificateCredential, Regular) return std::make_unique( "01234567-89ab-cdef-fedc-ba8976543210", "fedcba98-7654-3210-0123-456789abcdef", - "CLIENTSECRET", + TempCertFile::Path, options); }, {{{"https://azure.com/.default"}}, {{}}}, @@ -66,7 +84,7 @@ TEST(ClientCertificateCredential, Regular) EXPECT_GT(request0.Body.size(), (sizeof(expectedBodyStart0) - 1)); EXPECT_GT(request1.Body.size(), (sizeof(expectedBodyStart1) - 1)); - + EXPECT_EQ(request0.Body.substr(0, (sizeof(expectedBodyStart0) - 1)), expectedBodyStart0); EXPECT_EQ(request1.Body.substr(0, (sizeof(expectedBodyStart1) - 1)), expectedBodyStart1); @@ -75,6 +93,68 @@ TEST(ClientCertificateCredential, Regular) EXPECT_NE(request1.Headers.find("Content-Length"), request1.Headers.end()); EXPECT_GT(std::stoi(request1.Headers.at("Content-Length")), (sizeof(expectedBodyStart1) - 1)); + + { + using Azure::Core::_internal::Base64Url; + + const auto assertion0 = request0.Body.substr((sizeof(expectedBodyStart0) - 1)); + const auto assertion1 = request1.Body.substr((sizeof(expectedBodyStart1) - 1)); + + const auto assertion0Parts = SplitString(assertion0, '.'); + const auto assertion1Parts = SplitString(assertion1, '.'); + + EXPECT_EQ(assertion0Parts.size(), 3); + EXPECT_EQ(assertion1Parts.size(), 3); + + const auto header0Vec = Base64Url::Base64UrlDecode(assertion0Parts[0]); + const auto header1Vec = Base64Url::Base64UrlDecode(assertion1Parts[0]); + + const auto payload0Vec = Base64Url::Base64UrlDecode(assertion0Parts[1]); + const auto payload1Vec = Base64Url::Base64UrlDecode(assertion1Parts[1]); + + const auto signature0 = assertion0Parts[2]; + const auto signature1 = assertion1Parts[2]; + + static_assert( + sizeof(uint8_t) == sizeof(std::string::value_type), + "Base64Url decoded vector element type" + " should be representable as std::string characters."); + + const auto header0 = std::string( + reinterpret_cast&>(header0Vec).begin(), + reinterpret_cast&>(header0Vec).end()); + + const auto header1 = std::string( + reinterpret_cast&>(header1Vec).begin(), + reinterpret_cast&>(header1Vec).end()); + + const auto payload0 = std::string( + reinterpret_cast&>(payload0Vec).begin(), + reinterpret_cast&>(payload0Vec).end()); + + const auto payload1 = std::string( + reinterpret_cast&>(payload1Vec).begin(), + reinterpret_cast&>(payload1Vec).end()); + + constexpr auto ExpectedHeader + = "{\"x5t\":\"V0pIIQwSzNn6vfSTPv-1f7Vt_Pw\",\"kid\":" + "\"574A48210C12CCD9FABDF4933EFFB57FB56DFCFC\",\"alg\":\"RS256\",\"typ\":\"JWT\"}"; + + EXPECT_EQ(header0, ExpectedHeader); + EXPECT_EQ(header1, ExpectedHeader); + + constexpr char ExpectedPayloadStart[] + = "{\"aud\":\"https://login.microsoftonline.com/01234567-89ab-cdef-fedc-ba8976543210/" + "oauth2/v2.0/token\"," + "\"iss\":\"fedcba98-7654-3210-0123-456789abcdef\"," + "\"sub\":\"fedcba98-7654-3210-0123-456789abcdef\",\"jti\":\""; + + EXPECT_EQ(payload0.substr(0, (sizeof(ExpectedPayloadStart) - 1)), ExpectedPayloadStart); + EXPECT_EQ(payload1.substr(0, (sizeof(ExpectedPayloadStart) - 1)), ExpectedPayloadStart); + + EXPECT_EQ(Base64Url::Base64UrlDecode(signature0).size(), 256); + EXPECT_EQ(Base64Url::Base64UrlDecode(signature1).size(), 256); + } } EXPECT_NE(request0.Headers.find("Content-Type"), request0.Headers.end()); @@ -93,3 +173,117 @@ TEST(ClientCertificateCredential, Regular) EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 7200s); EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 7200s); } + +namespace { +const char* const TempCertFile::Path = "azure-identity-test.pem"; + +TempCertFile::~TempCertFile() { std::remove(Path); } + +TempCertFile::TempCertFile() +{ + std::ofstream cert(Path, std::ios_base::out | std::ios_base::trunc); + + cert << // cSpell:disable + "Bag Attributes\n" + " Microsoft Local Key set: \n" + " localKeyID: 01 00 00 00 \n" + " friendlyName: te-66f5c973-4fc8-4cd3-8acc-64964d79b693\n" + " Microsoft CSP Name: Microsoft Software Key Storage Provider\n" + "Key Attributes\n" + " X509v3 Key Usage: 90 \n" + "-----BEGIN PRIVATE KEY-----\n"; + // cSpell:enable + + cert << // cSpell:disable + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPdm4pukO7ugEx\n" + "8wXrmo4VIEoicp7w3QsEJGA2bMx9nHMvwugG54t14QpfqBQYQWLeL1HmpcDeivVD\n" + "+15ZXeGLCPVZBHhoY8ZWGibfhAAzqQ0P9Ca1kydjvB4uJcEnF/RYtQv6n6OwmdO1\n" + "wJ22JNcRlMtZqmnb/Q0In2fjXEbdl85/GZlYzMQRdyfI0yriSRBcYV2kg0zeXCxf\n" + "mCvB3rb6I1KpoUFHlkeHtkeDwm0VHUEt4Hz8ghcB00tI5eS2fH2rPkINQKc6+0QU\n" + "C2KICQC+GzJsYDbwQOao5Vhk80H5LRuM9Ndzv+fU3lLnktYCgXgL9AX4L/R9Z4Pz\n" + "tuao/qbRAgMBAAECggEBAMQZIrooiTuZ7uVC3Ja96Y1IjyqOg3QSzAXnSFZJcuVM\n" + "i4hayC02khkjVUXjvtLKg2SW/+hvRqZUXM8cfCsm1Tkxh4/T7OhnXyMl5xahU/uA\n" + "0IsC8c/xv2rDdxeRskh8mQd8Yk1MtlIIpRgIcEqp+exxY+FmdldtkvNSkcVUBNwQ\n" + "nXi+oWPhE2guo2g1BPk2gbF0+3FvSrQ8QwGHg+uQJwrQpJ+SB9TyuQFauGR5/wSq\n" + "H93cFH5YC/+v5I7qW6ZQe0f7rEKQDybGVzkBlKJyGCVYmPn7Xa/wJriws+FZIfHz\n" + "f3m0kJigxJd/HwTrnKSg+H8oBgng7lZLdBYWHMGJhA0CgYEA48moW7szegvfLuUF\n" + "a0sHfyKuNyvOv7Wud4sa0lwdKPHS+atwL6TNUWCAGkomYADEe3qiYgMXDX9U3hlW\n"; + // cSpell:enable + + cert << // cSpell:disable + "6zktYFj03tnRg4iBjp8nchLBVLf3Wd5TPRw1VKu4ZW43y8BRhYWV+3Z4s1nyMEDA\n" + "NFbKRmL7LDB05oWHdJMjFK/L6YcCgYEA6ShV4v2RQiXzkW6GHSBZDIVHCeWwvIld\n" + "OlEfG7wzZW4e8wNDhfSMtXyJrzfbEyXBtVKoESdP6Nnm9W7ftcynW965S94THuy7\n" + "+ofvHo6JAm8g/0uX70wZ26LU8qhkJMTWmsONBNKLwUzkFT7VGsdaBliam1RLvjeT\n" + "URdQgnftIucCgYEA4FYamT0k1W4bv/OOAr1CBNQDABME64ni6Zj2MXbGwSxou7s8\n" + "IbANBbgkcb/VS3d2CqYchqrEaWaeDp6mG8OUDO+POmsLDJ/D+NKF5rLR9L25vahY\n" + "EjdVzq3QTRTfnqspnnaR37Yt6XUMMLmUkfdn/yo8dKjEeMPJQ+YlBpqcGMECgYBZ\n" + "rmIaxV2yC9b8AX8khOS7pCgG7opkepGZdMp6aJF8WjcdUgwO4lmdFSIAe4OQgd1Y\n" + "WUq8Dlr2PZpQnSz/SJC3DZxISksggf5sBw06u6iHfyc6C2GNccAgcylljM+4NN42\n" + "+TCswi9vUpwIb/qYKkW+WyZcyLe5mrbXYhhdlrNn0QKBgDe8aRG+MOSUTTXjAVss\n" + "bDY0Us943FN91qBmagNqDyozKAAqDoKvdRxM0IlIDnOptj4AfbpJ1JThNOJDYBpU\n" + "+Azo8UoedANgndtZ2n11RSjmlQ6TE/WGlsirHExqr6y/l71znoQm1y3E2cArbsmy\n" + "hp0P5v42PKxmAx4pR0EjNKsd\n"; + // cSpell:enable + + cert << // cSpell:disable + "-----END PRIVATE KEY-----\n" + "Bag Attributes\n" + " localKeyID: 01 00 00 00 \n" + " 1.3.6.1.4.1.311.17.3.71: 61 00 6E 00 74 00 6B 00 2D 00 6C 00 61 00 70 00 " + "74 00 6F 00 70 00 00 00 \n" + "subject=CN = azure-identity-test\n" + "\n" + "issuer=CN = azure-identity-test\n" + "\n" + "-----BEGIN CERTIFICATE-----\n"; + // cSpell:enable + + cert << // cSpell:disable + "MIIDODCCAiCgAwIBAgIQNqa9U3MBxqBF7ksWk+XRkzANBgkqhkiG9w0BAQsFADAe\n" + "MRwwGgYDVQQDDBNhenVyZS1pZGVudGl0eS10ZXN0MCAXDTIyMDQyMjE1MDYwNloY\n" + "DzIyMjIwMTAxMDcwMDAwWjAeMRwwGgYDVQQDDBNhenVyZS1pZGVudGl0eS10ZXN0\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz3ZuKbpDu7oBMfMF65qO\n" + "FSBKInKe8N0LBCRgNmzMfZxzL8LoBueLdeEKX6gUGEFi3i9R5qXA3or1Q/teWV3h\n" + "iwj1WQR4aGPGVhom34QAM6kND/QmtZMnY7weLiXBJxf0WLUL+p+jsJnTtcCdtiTX\n" + "EZTLWapp2/0NCJ9n41xG3ZfOfxmZWMzEEXcnyNMq4kkQXGFdpINM3lwsX5grwd62\n" + "+iNSqaFBR5ZHh7ZHg8JtFR1BLeB8/IIXAdNLSOXktnx9qz5CDUCnOvtEFAtiiAkA\n" + "vhsybGA28EDmqOVYZPNB+S0bjPTXc7/n1N5S55LWAoF4C/QF+C/0fWeD87bmqP6m\n"; + // cSpell:enable + + cert << // cSpell:disable + "0QIDAQABo3AwbjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIG\n" + "CCsGAQUFBwMBMB4GA1UdEQQXMBWCE2F6dXJlLWlkZW50aXR5LXRlc3QwHQYDVR0O\n" + "BBYEFCoJ5tInmafyNuR0tGxZOz522jlWMA0GCSqGSIb3DQEBCwUAA4IBAQBzLXpw\n" + "Xmrg1sQTmzMnS24mREKxj9B3YILmgsdBMrHkH07QUROee7IbQ8gfBKeln0dEcfYi\n" + "Jyh42jn+fmg9AR17RP80wPthD2eKOt4WYNkNM3H8U4JEo+0ML0jZyswynpR48h/E\n" + "m96sm/NUeKUViD5iVTb1uHL4j8mQAN1IbXcunXvrrek1CzFVn5Rpah0Tn+6cYVKd\n" + "Jg531i53udzusgZtV1NPZ82tzYkPQG1vxB//D9vd0LzmcfCvT50MKhz0r/c5yJYk\n" + "i9q94DBuzMhe+O9j+Ob2pVQt5akVFJVtIVSfBZzRBAd66u9JeADlT4sxwS4QAUHi\n" + "RrCsEpJsnJXkx/6O\n" + "-----END CERTIFICATE-----\n"; + // cSpell:enable +} + +std::vector SplitString(const std::string& s, char separator) +{ + std::vector result; + + const auto len = s.size(); + size_t start = 0; + while (start < len) + { + auto end = s.find(separator, start); + if (end == std::string::npos) + { + end = len; + } + + result.push_back(s.substr(start, end - start)); + + start = end + 1; + } + + return result; +} +} // namespace From 6db1902db7a76b2550393fa0cfaccd6f330fea55 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 09:47:19 -0700 Subject: [PATCH 03/18] cspell --- .../ut/client_certificate_credential_test.cpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index e257cb94ce..b3f4f4c0f0 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -69,18 +69,18 @@ TEST(ClientCertificateCredential, Regular) "https://login.microsoftonline.com/01234567-89ab-cdef-fedc-ba8976543210/oauth2/v2.0/token"); { - constexpr char expectedBodyStart0[] + constexpr char expectedBodyStart0[] // cspell:disable = "grant_type=client_credentials" "&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" "&client_id=fedcba98-7654-3210-0123-456789abcdef" - "&scope=https%3A%2F%2Fazure.com%2F.default" // cspell:disable-line - "&client_assertion="; + "&scope=https%3A%2F%2Fazure.com%2F.default" + "&client_assertion="; // cspell:enable - constexpr char expectedBodyStart1[] + constexpr char expectedBodyStart1[] // cspell:disable = "grant_type=client_credentials" "&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" "&client_id=fedcba98-7654-3210-0123-456789abcdef" - "&client_assertion="; + "&client_assertion="; // cspell:enable EXPECT_GT(request0.Body.size(), (sizeof(expectedBodyStart0) - 1)); EXPECT_GT(request1.Body.size(), (sizeof(expectedBodyStart1) - 1)); @@ -183,7 +183,7 @@ TempCertFile::TempCertFile() { std::ofstream cert(Path, std::ios_base::out | std::ios_base::trunc); - cert << // cSpell:disable + cert << // cspell:disable "Bag Attributes\n" " Microsoft Local Key set: \n" " localKeyID: 01 00 00 00 \n" @@ -192,9 +192,9 @@ TempCertFile::TempCertFile() "Key Attributes\n" " X509v3 Key Usage: 90 \n" "-----BEGIN PRIVATE KEY-----\n"; - // cSpell:enable + // cspell:enable - cert << // cSpell:disable + cert << // cspell:disable "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPdm4pukO7ugEx\n" "8wXrmo4VIEoicp7w3QsEJGA2bMx9nHMvwugG54t14QpfqBQYQWLeL1HmpcDeivVD\n" "+15ZXeGLCPVZBHhoY8ZWGibfhAAzqQ0P9Ca1kydjvB4uJcEnF/RYtQv6n6OwmdO1\n" @@ -208,9 +208,9 @@ TempCertFile::TempCertFile() "H93cFH5YC/+v5I7qW6ZQe0f7rEKQDybGVzkBlKJyGCVYmPn7Xa/wJriws+FZIfHz\n" "f3m0kJigxJd/HwTrnKSg+H8oBgng7lZLdBYWHMGJhA0CgYEA48moW7szegvfLuUF\n" "a0sHfyKuNyvOv7Wud4sa0lwdKPHS+atwL6TNUWCAGkomYADEe3qiYgMXDX9U3hlW\n"; - // cSpell:enable + // cspell:enable - cert << // cSpell:disable + cert << // cspell:disable "6zktYFj03tnRg4iBjp8nchLBVLf3Wd5TPRw1VKu4ZW43y8BRhYWV+3Z4s1nyMEDA\n" "NFbKRmL7LDB05oWHdJMjFK/L6YcCgYEA6ShV4v2RQiXzkW6GHSBZDIVHCeWwvIld\n" "OlEfG7wzZW4e8wNDhfSMtXyJrzfbEyXBtVKoESdP6Nnm9W7ftcynW965S94THuy7\n" @@ -224,9 +224,9 @@ TempCertFile::TempCertFile() "bDY0Us943FN91qBmagNqDyozKAAqDoKvdRxM0IlIDnOptj4AfbpJ1JThNOJDYBpU\n" "+Azo8UoedANgndtZ2n11RSjmlQ6TE/WGlsirHExqr6y/l71znoQm1y3E2cArbsmy\n" "hp0P5v42PKxmAx4pR0EjNKsd\n"; - // cSpell:enable + // cspell:enable - cert << // cSpell:disable + cert << // cspell:disable "-----END PRIVATE KEY-----\n" "Bag Attributes\n" " localKeyID: 01 00 00 00 \n" @@ -237,9 +237,9 @@ TempCertFile::TempCertFile() "issuer=CN = azure-identity-test\n" "\n" "-----BEGIN CERTIFICATE-----\n"; - // cSpell:enable + // cspell:enable - cert << // cSpell:disable + cert << // cspell:disable "MIIDODCCAiCgAwIBAgIQNqa9U3MBxqBF7ksWk+XRkzANBgkqhkiG9w0BAQsFADAe\n" "MRwwGgYDVQQDDBNhenVyZS1pZGVudGl0eS10ZXN0MCAXDTIyMDQyMjE1MDYwNloY\n" "DzIyMjIwMTAxMDcwMDAwWjAeMRwwGgYDVQQDDBNhenVyZS1pZGVudGl0eS10ZXN0\n" @@ -249,9 +249,9 @@ TempCertFile::TempCertFile() "EZTLWapp2/0NCJ9n41xG3ZfOfxmZWMzEEXcnyNMq4kkQXGFdpINM3lwsX5grwd62\n" "+iNSqaFBR5ZHh7ZHg8JtFR1BLeB8/IIXAdNLSOXktnx9qz5CDUCnOvtEFAtiiAkA\n" "vhsybGA28EDmqOVYZPNB+S0bjPTXc7/n1N5S55LWAoF4C/QF+C/0fWeD87bmqP6m\n"; - // cSpell:enable + // cspell:enable - cert << // cSpell:disable + cert << // cspell:disable "0QIDAQABo3AwbjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIG\n" "CCsGAQUFBwMBMB4GA1UdEQQXMBWCE2F6dXJlLWlkZW50aXR5LXRlc3QwHQYDVR0O\n" "BBYEFCoJ5tInmafyNuR0tGxZOz522jlWMA0GCSqGSIb3DQEBCwUAA4IBAQBzLXpw\n" @@ -262,7 +262,7 @@ TempCertFile::TempCertFile() "i9q94DBuzMhe+O9j+Ob2pVQt5akVFJVtIVSfBZzRBAd66u9JeADlT4sxwS4QAUHi\n" "RrCsEpJsnJXkx/6O\n" "-----END CERTIFICATE-----\n"; - // cSpell:enable + // cspell:enable } std::vector SplitString(const std::string& s, char separator) From 3912b2d1174e52893f9db2a0b898d88508a53bdc Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 11:38:45 -0700 Subject: [PATCH 04/18] Update Readme --- sdk/identity/azure-identity/README.md | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/README.md b/sdk/identity/azure-identity/README.md index 30087d5338..bef2f23609 100644 --- a/sdk/identity/azure-identity/README.md +++ b/sdk/identity/azure-identity/README.md @@ -45,11 +45,16 @@ The Azure Identity library focuses on OAuth authentication with Azure Active dir authenticates a service principal using a secret Service principal authentication + + ClientCertificateCredential + authenticates a service principal using a certificate + Service principal authentication + ## Environment Variables -`EnvironmentCredential` can be configured with environment variables. +`EnvironmentCredential` can be configured with environment variables. Each type of authentication requires values for specific variables:s #### Service principal with secret @@ -75,6 +80,32 @@ The Azure Identity library focuses on OAuth authentication with Azure Active dir
+#### Service principal with certificate + + + + + + + + + + + + + + + + + + + + + +
variable namevalue
AZURE_CLIENT_IDid of an Azure Active Directory application
AZURE_TENANT_IDid of the application's Azure Active Directory tenant
AZURE_CLIENT_CERTIFICATE_PATHpath to a PEM-encoded certificate file including private key (without password protection)
+ +Configuration is attempted in the above order. For example, if values for a client secret and certificate are both present, the client secret will be used. + ## Managed Identity Support The [Managed identity authentication](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) is supported via the `ManagedIdentityCredential` for the following Azure Services: * [Azure Virtual Machines](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token) From cb78f22dd3e0b7479b1fe27f0d2b7efa1ca34628 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 11:43:25 -0700 Subject: [PATCH 05/18] Cosmetic fixes --- sdk/identity/azure-identity/CMakeLists.txt | 2 +- .../inc/azure/identity/client_certificate_credential.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/CMakeLists.txt b/sdk/identity/azure-identity/CMakeLists.txt index 0230f51ceb..f4760cfb01 100644 --- a/sdk/identity/azure-identity/CMakeLists.txt +++ b/sdk/identity/azure-identity/CMakeLists.txt @@ -47,7 +47,7 @@ endif() set( AZURE_IDENTITY_HEADER inc/azure/identity/chained_token_credential.hpp - inc/azure/identity//client_certificate_credential.hpp + inc/azure/identity/client_certificate_credential.hpp inc/azure/identity/client_secret_credential.hpp inc/azure/identity/dll_import_export.hpp inc/azure/identity/environment_credential.hpp diff --git a/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp index d4d951bc98..620eedbea9 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/client_certificate_credential.hpp @@ -3,7 +3,7 @@ /** * @file - * @brief Client Secret Credential and options. + * @brief Client Certificate Credential and options. */ #pragma once From 70d83a2393574e546ec2d220f247cc552205281e Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 11:46:03 -0700 Subject: [PATCH 06/18] Changelog to mention env cred update --- sdk/identity/azure-identity/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index a5c5a92e25..03ee061648 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -3,7 +3,7 @@ ## 1.3.0-beta.2 (Unreleased) ### Features Added -- Added `ClientCertificateCredential`. +- Added `ClientCertificateCredential`, `EnvironmentCredential` is updated to support client certificate authentication. ### Breaking Changes From 4be70760f24bbbb13f4c00fef1e7d0dc69390604 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 12:54:28 -0700 Subject: [PATCH 07/18] Fix warning --- .../test/ut/client_certificate_credential_test.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index b3f4f4c0f0..a94082ee96 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -89,10 +89,14 @@ TEST(ClientCertificateCredential, Regular) EXPECT_EQ(request1.Body.substr(0, (sizeof(expectedBodyStart1) - 1)), expectedBodyStart1); EXPECT_NE(request0.Headers.find("Content-Length"), request0.Headers.end()); - EXPECT_GT(std::stoi(request0.Headers.at("Content-Length")), (sizeof(expectedBodyStart0) - 1)); + EXPECT_GT( + std::stoi(request0.Headers.at("Content-Length")), + static_cast(sizeof(expectedBodyStart0) - 1)); EXPECT_NE(request1.Headers.find("Content-Length"), request1.Headers.end()); - EXPECT_GT(std::stoi(request1.Headers.at("Content-Length")), (sizeof(expectedBodyStart1) - 1)); + EXPECT_GT( + std::stoi(request1.Headers.at("Content-Length")), + static_cast(sizeof(expectedBodyStart1) - 1)); { using Azure::Core::_internal::Base64Url; From 5aa8a67ce297482388641c0956cef748d74f5001 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 13:29:57 -0700 Subject: [PATCH 08/18] cspell --- .../azure-identity/src/client_certificate_credential.cpp | 4 ++-- .../test/ut/client_certificate_credential_test.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index 5ac5cd0f1c..da4f19e76a 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -143,7 +143,7 @@ ClientCertificateCredential::ClientCertificateCredential( std::ostringstream body; body << "grant_type=client_credentials" "&client_assertion_type=" - "urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" + "urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line "&client_id=" << Url::Encode(clientId); @@ -210,7 +210,7 @@ Azure::Core::Credentials::AccessToken ClientCertificateCredential::GetToken( std::string assertion = m_tokenHeaderEncoded; { using Azure::Core::_internal::Base64Url; - // Form the asserion to sign. + // Form the assertion to sign. { std::string payloadStr; // Add GUID, current time, and expiration time to the payload diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index a94082ee96..e806fb9cd2 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -107,8 +107,8 @@ TEST(ClientCertificateCredential, Regular) const auto assertion0Parts = SplitString(assertion0, '.'); const auto assertion1Parts = SplitString(assertion1, '.'); - EXPECT_EQ(assertion0Parts.size(), 3); - EXPECT_EQ(assertion1Parts.size(), 3); + EXPECT_EQ(assertion0Parts.size(), 3U); + EXPECT_EQ(assertion1Parts.size(), 3U); const auto header0Vec = Base64Url::Base64UrlDecode(assertion0Parts[0]); const auto header1Vec = Base64Url::Base64UrlDecode(assertion1Parts[0]); From cfdc6beb08c3867dfbddc440e14b943c97e3bd38 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 14:25:06 -0700 Subject: [PATCH 09/18] Tell CI to install openssl --- eng/pipelines/templates/stages/platform-matrix.json | 1 + .../test/ut/client_certificate_credential_test.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/templates/stages/platform-matrix.json b/eng/pipelines/templates/stages/platform-matrix.json index 5dbf5885f7..0c5ca33db0 100644 --- a/eng/pipelines/templates/stages/platform-matrix.json +++ b/eng/pipelines/templates/stages/platform-matrix.json @@ -68,6 +68,7 @@ { "StaticConfigs": { "Windows2019": { + "VcpkgInstall": "openssl", "OSVmImage": "MMS2019", "Pool": "azsdk-pool-mms-win-2019-general", "CMAKE_GENERATOR": "Visual Studio 16 2019", diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index e806fb9cd2..9e0ffb88d7 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -156,8 +156,8 @@ TEST(ClientCertificateCredential, Regular) EXPECT_EQ(payload0.substr(0, (sizeof(ExpectedPayloadStart) - 1)), ExpectedPayloadStart); EXPECT_EQ(payload1.substr(0, (sizeof(ExpectedPayloadStart) - 1)), ExpectedPayloadStart); - EXPECT_EQ(Base64Url::Base64UrlDecode(signature0).size(), 256); - EXPECT_EQ(Base64Url::Base64UrlDecode(signature1).size(), 256); + EXPECT_EQ(Base64Url::Base64UrlDecode(signature0).size(), 256U); + EXPECT_EQ(Base64Url::Base64UrlDecode(signature1).size(), 256U); } } From 15bd7a465b174ad09da7d5846e4b06a2115f773b Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 14:30:20 -0700 Subject: [PATCH 10/18] openssl for all Windows --- eng/pipelines/templates/stages/platform-matrix.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/eng/pipelines/templates/stages/platform-matrix.json b/eng/pipelines/templates/stages/platform-matrix.json index 0c5ca33db0..1ca5881196 100644 --- a/eng/pipelines/templates/stages/platform-matrix.json +++ b/eng/pipelines/templates/stages/platform-matrix.json @@ -78,13 +78,11 @@ }, "TargetPlatform": { "UWP_debug": { - "VcpkgInstall": "openssl", - "CMAKE_SYSTEM_NAME": "WindowsStore", + "CMAKE_SYSTEM_NAME": "WindowsStore", "CMAKE_SYSTEM_VERSION": "10.0", "BuildArgs": "--parallel 8 --config Debug" }, "UWP_release": { - "VcpkgInstall": "openssl", "CMAKE_SYSTEM_NAME": "WindowsStore", "CMAKE_SYSTEM_VERSION": "10.0", "BuildArgs": "--parallel 8 --config Release" From f5261b4104666020b863f4919467e83769834637 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 14:50:33 -0700 Subject: [PATCH 11/18] update dependency manifest --- sdk/identity/azure-identity/vcpkg.json | 3 ++- sdk/identity/azure-identity/vcpkg/vcpkg.json | 3 +-- sdk/identity/ci.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/identity/azure-identity/vcpkg.json b/sdk/identity/azure-identity/vcpkg.json index 22c9f4be71..222462e840 100644 --- a/sdk/identity/azure-identity/vcpkg.json +++ b/sdk/identity/azure-identity/vcpkg.json @@ -2,6 +2,7 @@ "name": "azure-identity-cpp", "version-string": "1.0.0", "dependencies": [ - "azure-core-cpp" + "azure-core-cpp", + "openssl" ] } diff --git a/sdk/identity/azure-identity/vcpkg/vcpkg.json b/sdk/identity/azure-identity/vcpkg/vcpkg.json index e5fbd7dc51..d53c9d7fd9 100644 --- a/sdk/identity/azure-identity/vcpkg/vcpkg.json +++ b/sdk/identity/azure-identity/vcpkg/vcpkg.json @@ -17,8 +17,7 @@ "version>=": "1.4.0" }, { - "name": "openssl", - "version>=": "1.1.1n" + "name": "openssl" }, { "name": "vcpkg-cmake", diff --git a/sdk/identity/ci.yml b/sdk/identity/ci.yml index 63862a4a6f..f3d26dd382 100644 --- a/sdk/identity/ci.yml +++ b/sdk/identity/ci.yml @@ -28,8 +28,8 @@ stages: ServiceDirectory: identity CtestRegex: azure-identity. LiveTestCtestRegex: azure-identity. - LineCoverageTarget: 99 - BranchCoverageTarget: 62 + LineCoverageTarget: 95 + BranchCoverageTarget: 57 Artifacts: - Name: azure-identity Path: azure-identity From 9c8c26e934c0f86a475d18cf2ada4d69bffb7b12 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 14:52:46 -0700 Subject: [PATCH 12/18] Re-phrase changelog --- sdk/identity/azure-identity/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 03ee061648..e7395e3e52 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -3,7 +3,7 @@ ## 1.3.0-beta.2 (Unreleased) ### Features Added -- Added `ClientCertificateCredential`, `EnvironmentCredential` is updated to support client certificate authentication. +- Added `ClientCertificateCredential`, and updated `EnvironmentCredential` to support client certificate authentication. ### Breaking Changes From 322dabfc67f1041a6d60a35c0ffb9de9aa1850a6 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 15:13:09 -0700 Subject: [PATCH 13/18] Clang warnings --- .../src/client_certificate_credential.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index da4f19e76a..7b5890f658 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -103,7 +103,7 @@ ClientCertificateCredential::ClientCertificateCredential( sizeof(decltype(mdVec)::value_type) == sizeof(uint8_t), "mdVec value should be representable as uint8_t"); thumbprintBase64Str = Base64Url::Base64UrlEncode( - reinterpret_cast&>(mdVec)); + *static_cast*>(static_cast(&mdVec))); } } @@ -119,7 +119,7 @@ ClientCertificateCredential::ClientCertificateCredential( = std::vector(tokenHeader.begin(), tokenHeader.end()); m_tokenHeaderEncoded = Base64Url::Base64UrlEncode( - reinterpret_cast&>(tokenHeaderVec)); + *static_cast*>(static_cast(&tokenHeaderVec))); } } else @@ -141,11 +141,12 @@ ClientCertificateCredential::ClientCertificateCredential( { std::ostringstream body; - body << "grant_type=client_credentials" - "&client_assertion_type=" - "urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line - "&client_id=" - << Url::Encode(clientId); + body + << "grant_type=client_credentials" + "&client_assertion_type=" + "urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line + "&client_id=" + << Url::Encode(clientId); m_requestBody = body.str(); } From fbd1ed2c190d006365e93ba338b650a6c316b111 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 15:57:37 -0700 Subject: [PATCH 14/18] Clang warning --- .../src/client_certificate_credential.cpp | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index 7b5890f658..4926816c17 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -24,6 +24,20 @@ using namespace Azure::Identity; +namespace { +template std::vector ToUInt8Vector(T const& in) +{ + const auto size = in.size(); + std::vector outVec(size); + for (auto i = 0; i < size; ++i) + { + outVec[i] = static_cast(in[i]); + } + + return outVec; +} +} // namespace + ClientCertificateCredential::ClientCertificateCredential( std::string const& tenantId, std::string const& clientId, @@ -98,28 +112,17 @@ ClientCertificateCredential::ClientCertificateCredential( } // Get thumbprint as Base64: - { - static_assert( - sizeof(decltype(mdVec)::value_type) == sizeof(uint8_t), - "mdVec value should be representable as uint8_t"); - thumbprintBase64Str = Base64Url::Base64UrlEncode( - *static_cast*>(static_cast(&mdVec))); - } + thumbprintBase64Str = Base64Url::Base64UrlEncode(ToUInt8Vector(mdVec)); } // Form a JWT token: const auto tokenHeader = std::string("{\"x5t\":\"") + thumbprintBase64Str + "\",\"kid\":\"" + thumbprintHexStr + "\",\"alg\":\"RS256\",\"typ\":\"JWT\"}"; - static_assert( - sizeof(std::string::value_type) == sizeof(uint8_t), - "std::string::value_type value should be representable as uint8_t"); - const auto tokenHeaderVec = std::vector(tokenHeader.begin(), tokenHeader.end()); - m_tokenHeaderEncoded = Base64Url::Base64UrlEncode( - *static_cast*>(static_cast(&tokenHeaderVec))); + m_tokenHeaderEncoded = Base64Url::Base64UrlEncode(ToUInt8Vector(tokenHeaderVec)); } } else @@ -231,16 +234,11 @@ Azure::Core::Credentials::AccessToken ClientCertificateCredential::GetToken( payloadStr = payloadStream.str(); } - static_assert( - sizeof(std::string::value_type) == sizeof(uint8_t), - "std::string::value_type value should be representable as uint8_t"); - // Concatenate JWT token header + "." + encoded payload const auto payloadVec = std::vector(payloadStr.begin(), payloadStr.end()); - assertion += std::string(".") - + Base64Url::Base64UrlEncode(reinterpret_cast&>(payloadVec)); + assertion += std::string(".") + Base64Url::Base64UrlEncode(ToUInt8Vector(payloadVec)); } // Get assertion signature. @@ -264,12 +262,7 @@ Azure::Core::Credentials::AccessToken ClientCertificateCredential::GetToken( std::vector sigVec(sigLen); if (EVP_DigestSign(mdCtx, sigVec.data(), &sigLen, bufToSign, bufToSignLen) == 1) { - static_assert( - sizeof(unsigned char) == sizeof(uint8_t), - "unsigned char should be representable as uint8_t"); - - signature = Base64Url::Base64UrlEncode( - reinterpret_cast&>(sigVec)); + signature = Base64Url::Base64UrlEncode(ToUInt8Vector(sigVec)); } } } From 4357d40a047184e75078bd71ed4b58b24b8136c1 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 16:03:56 -0700 Subject: [PATCH 15/18] Clang warning - 2 --- .../azure-identity/src/client_certificate_credential.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index 4926816c17..84d8714904 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -27,9 +27,9 @@ using namespace Azure::Identity; namespace { template std::vector ToUInt8Vector(T const& in) { - const auto size = in.size(); + const size_t size = in.size(); std::vector outVec(size); - for (auto i = 0; i < size; ++i) + for (size_t i = 0; i < size; ++i) { outVec[i] = static_cast(in[i]); } From 8148bbbc1836235e68a393a80c9a2f944f3283ca Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Fri, 22 Apr 2022 16:31:28 -0700 Subject: [PATCH 16/18] Ubuntu18 warning --- .../ut/client_certificate_credential_test.cpp | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp index 9e0ffb88d7..6ed07c0112 100644 --- a/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/client_certificate_credential_test.cpp @@ -26,6 +26,8 @@ struct TempCertFile final }; std::vector SplitString(const std::string& s, char separator); + +std::string ToString(std::vector const& vec); } // namespace TEST(ClientCertificateCredential, Regular) @@ -119,26 +121,11 @@ TEST(ClientCertificateCredential, Regular) const auto signature0 = assertion0Parts[2]; const auto signature1 = assertion1Parts[2]; - static_assert( - sizeof(uint8_t) == sizeof(std::string::value_type), - "Base64Url decoded vector element type" - " should be representable as std::string characters."); - - const auto header0 = std::string( - reinterpret_cast&>(header0Vec).begin(), - reinterpret_cast&>(header0Vec).end()); - - const auto header1 = std::string( - reinterpret_cast&>(header1Vec).begin(), - reinterpret_cast&>(header1Vec).end()); + const auto header0 = ToString(header0Vec); + const auto header1 = ToString(header1Vec); - const auto payload0 = std::string( - reinterpret_cast&>(payload0Vec).begin(), - reinterpret_cast&>(payload0Vec).end()); - - const auto payload1 = std::string( - reinterpret_cast&>(payload1Vec).begin(), - reinterpret_cast&>(payload1Vec).end()); + const auto payload0 = ToString(payload0Vec); + const auto payload1 = ToString(payload1Vec); constexpr auto ExpectedHeader = "{\"x5t\":\"V0pIIQwSzNn6vfSTPv-1f7Vt_Pw\",\"kid\":" @@ -290,4 +277,16 @@ std::vector SplitString(const std::string& s, char separator) return result; } + +std::string ToString(std::vector const& vec) +{ + const size_t size = vec.size(); + std::string str(size, '\0'); + for (size_t i = 0; i < size; ++i) + { + str[i] = static_cast(vec[i]); + } + + return str; +} } // namespace From 08511eb79f6e85c0a8d7eac45a1f980866d42668 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk <41349689+antkmsft@users.noreply.github.com> Date: Tue, 26 Apr 2022 14:51:31 -0700 Subject: [PATCH 17/18] Update sdk/identity/azure-identity/CHANGELOG.md Co-authored-by: Victor Vazquez --- sdk/identity/azure-identity/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index e7395e3e52..2dbf4d1b9d 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -3,6 +3,7 @@ ## 1.3.0-beta.2 (Unreleased) ### Features Added + - Added `ClientCertificateCredential`, and updated `EnvironmentCredential` to support client certificate authentication. ### Breaking Changes From 0a2ace2d58d97fb302240819bc65cb39d646e1ee Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 26 Apr 2022 16:36:26 -0700 Subject: [PATCH 18/18] PR feedback --- sdk/identity/azure-identity/README.md | 2 +- .../src/client_certificate_credential.cpp | 113 +++++++++--------- .../src/client_secret_credential.cpp | 3 +- 3 files changed, 59 insertions(+), 59 deletions(-) diff --git a/sdk/identity/azure-identity/README.md b/sdk/identity/azure-identity/README.md index bef2f23609..02c391d782 100644 --- a/sdk/identity/azure-identity/README.md +++ b/sdk/identity/azure-identity/README.md @@ -54,7 +54,7 @@ The Azure Identity library focuses on OAuth authentication with Azure Active dir ## Environment Variables -`EnvironmentCredential` can be configured with environment variables. Each type of authentication requires values for specific variables:s +`EnvironmentCredential` can be configured with environment variables. Each type of authentication requires values for specific variables: #### Service principal with secret diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp index 84d8714904..9ecbbd2d36 100644 --- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp +++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp @@ -43,7 +43,8 @@ ClientCertificateCredential::ClientCertificateCredential( std::string const& clientId, std::string const& clientCertificatePath, Azure::Core::Credentials::TokenCredentialOptions const& options) - : m_tokenCredentialImpl(new _detail::TokenCredentialImpl(options)), m_pkey(nullptr) + : m_tokenCredentialImpl(std::make_unique<_detail::TokenCredentialImpl>(options)), + m_pkey(nullptr) { BIO* bio = nullptr; X509* x509 = nullptr; @@ -53,81 +54,79 @@ ClientCertificateCredential::ClientCertificateCredential( using Azure::Core::Credentials::AuthenticationException; // Open certificate file, then get private key and X509: - if ((bio = BIO_new_file(clientCertificatePath.c_str(), "r")) != nullptr) + if ((bio = BIO_new_file(clientCertificatePath.c_str(), "r")) == nullptr) { - if ((m_pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr)) == nullptr) - { - throw AuthenticationException("Failed to read certificate private key."); - } + throw AuthenticationException("Failed to open certificate file."); + } + if ((m_pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr)) == nullptr) + { + throw AuthenticationException("Failed to read certificate private key."); + } + + if ((x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) == nullptr) + { + static_cast(BIO_seek(bio, 0)); if ((x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) == nullptr) { - static_cast(BIO_seek(bio, 0)); - if ((x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) == nullptr) - { - throw AuthenticationException("Failed to read certificate private key."); - } + throw AuthenticationException("Failed to read certificate private key."); } + } - BIO_free(bio); - bio = nullptr; + static_cast(BIO_free(bio)); + bio = nullptr; - // Get certificate thumbprint: - { - using Azure::Core::_internal::Base64Url; + // Get certificate thumbprint: + { + using Azure::Core::_internal::Base64Url; - std::string thumbprintHexStr; - std::string thumbprintBase64Str; + std::string thumbprintHexStr; + std::string thumbprintBase64Str; + { + std::vector mdVec(EVP_MAX_MD_SIZE); { - std::vector mdVec(EVP_MAX_MD_SIZE); - { - unsigned int mdLen = 0; - const auto digestResult = X509_digest(x509, EVP_sha1(), mdVec.data(), &mdLen); + unsigned int mdLen = 0; + const auto digestResult = X509_digest(x509, EVP_sha1(), mdVec.data(), &mdLen); - X509_free(x509); - x509 = nullptr; + X509_free(x509); + x509 = nullptr; - if (!digestResult) - { - throw AuthenticationException("Failed to get certificate thumbprint."); - } + if (!digestResult) + { + throw AuthenticationException("Failed to get certificate thumbprint."); + } - // Drop unused buffer space: - const auto mdLenSz = static_cast(mdLen); - if (mdVec.size() > mdLenSz) - { - mdVec.resize(mdLenSz); - } + // Drop unused buffer space: + const auto mdLenSz = static_cast(mdLen); + if (mdVec.size() > mdLenSz) + { + mdVec.resize(mdLenSz); + } - // Get thumbprint as hex string: + // Get thumbprint as hex string: + { + std::ostringstream thumbprintStream; + for (const auto md : mdVec) { - std::ostringstream thumbprintStream; - for (const auto md : mdVec) - { - thumbprintStream << std::uppercase << std::hex << std::setfill('0') - << std::setw(2) << static_cast(md); - } - thumbprintHexStr = thumbprintStream.str(); + thumbprintStream << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(md); } + thumbprintHexStr = thumbprintStream.str(); } - - // Get thumbprint as Base64: - thumbprintBase64Str = Base64Url::Base64UrlEncode(ToUInt8Vector(mdVec)); } - // Form a JWT token: - const auto tokenHeader = std::string("{\"x5t\":\"") + thumbprintBase64Str - + "\",\"kid\":\"" + thumbprintHexStr + "\",\"alg\":\"RS256\",\"typ\":\"JWT\"}"; + // Get thumbprint as Base64: + thumbprintBase64Str = Base64Url::Base64UrlEncode(ToUInt8Vector(mdVec)); + } - const auto tokenHeaderVec - = std::vector(tokenHeader.begin(), tokenHeader.end()); + // Form a JWT token: + const auto tokenHeader = std::string("{\"x5t\":\"") + thumbprintBase64Str + "\",\"kid\":\"" + + thumbprintHexStr + "\",\"alg\":\"RS256\",\"typ\":\"JWT\"}"; - m_tokenHeaderEncoded = Base64Url::Base64UrlEncode(ToUInt8Vector(tokenHeaderVec)); - } - } - else - { - throw AuthenticationException("Failed to open certificate file."); + const auto tokenHeaderVec + = std::vector(tokenHeader.begin(), tokenHeader.end()); + + m_tokenHeaderEncoded = Base64Url::Base64UrlEncode(ToUInt8Vector(tokenHeaderVec)); } } @@ -158,7 +157,7 @@ ClientCertificateCredential::ClientCertificateCredential( { if (bio != nullptr) { - BIO_free(bio); + static_cast(BIO_free(bio)); } if (x509 != nullptr) diff --git a/sdk/identity/azure-identity/src/client_secret_credential.cpp b/sdk/identity/azure-identity/src/client_secret_credential.cpp index 2b895f7456..5b04416c59 100644 --- a/sdk/identity/azure-identity/src/client_secret_credential.cpp +++ b/sdk/identity/azure-identity/src/client_secret_credential.cpp @@ -18,7 +18,8 @@ ClientSecretCredential::ClientSecretCredential( std::string const& clientSecret, std::string const& authorityHost, Azure::Core::Credentials::TokenCredentialOptions const& options) - : m_tokenCredentialImpl(new _detail::TokenCredentialImpl(options)), m_isAdfs(tenantId == "adfs") + : m_tokenCredentialImpl(std::make_unique<_detail::TokenCredentialImpl>(options)), + m_isAdfs(tenantId == "adfs") { using Azure::Core::Url; m_requestUrl = Url(authorityHost);