Repository: Azure/azure-sdk-for-cpp (cpp)

Issue:
KeyVaultChallengeBasedAuthenticationPolicy is not thread-safe
`m_tokenRequestContext` needs to be protected by `std::shared_timed_mutex`, with unique-locking for writing and shared-locking for reading (see core/logger.cpp for example).

https://github.com/Azure/azure-sdk-for-cpp/blob/fd687c32fa18bd8f1daacadd8c8aff870eb08e26/sdk/keyvault/azure-security-keyvault-shared/inc/azure/keyvault/shared/keyvault_challenge_based_authentication_policy.hpp#L25

Pull request:
Add thread safety for `KeyVaultChallengeBasedAuthenticationPolicy`
Closes #4754.

Live test CI leg does also pass.

--
UWP CI is currently blocked until https://github.com/microsoft/vcpkg/issues/35279 is fixed / https://github.com/microsoft/vcpkg/pull/35116 is merged (#5181 would unblock)

Patch:
diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index 9f399c9e7..17f0a8d71 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -52,6 +52,7 @@
     "ABFS",
     "ABNF",
     "adamdebreceni",
+    "adfs",
     "Adls",
     "ahojnnes",
     "ahsonkhan",
@@ -61,6 +62,7 @@
     "AMQP",
     "AMQPS",
     "AMQPVALUE",
+    "avro",
     "antkmsft",
     "azcore",
     "azsdk",
@@ -128,6 +130,7 @@
     "LPBYTE",
     "LPSTR",
     "LPWSTR",
+    "mbps",
     "MHSM",
     "mmdc",
     "moxygen",
@@ -189,6 +192,7 @@
     "uaenorth",
     "uamqp",
     "uksouth",
+    "Uncomitted",
     "undeleted",
     "unscoped",
     "unskipped",
@@ -200,6 +204,7 @@
     "Verdana",
     "Viet",
     "Viktor",
+    "versionid",
     "vusg",
     "Wdocumentation",
     "Werror",
@@ -316,12 +321,24 @@
         "pfile"
       ]
     },
+    {
+      "filename": "**/sdk/storage/faq.md",
+      "words": [
+        "dont"
+      ]
+    },
     {
       "filename": "**/sdk/storage/azure-storage-queues/src/rest_client.cpp",
       "words": [
         "numofmessages"
       ]
     },
+    {
+      "filename": "**/sdk/storage/azure-storage-queues/swagger/README.md",
+      "words": [
+        "messageid"
+      ]
+    },
     {
       "filename": "**/sdk/storage/azure-storage-files-shares/**/*",
       "words": [
diff --git a/CMakePresets.json b/CMakePresets.json
index 6b18df3c2..fdca2fd92 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -521,6 +521,7 @@
       "inherits": [
         "x64-static",
         "release-build",
+        "enable-tests",
         "enable-perf"
       ]
     },
diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md
index be3b2d465..c5b9b56ff 100644
--- a/sdk/core/azure-core/CHANGELOG.md
+++ b/sdk/core/azure-core/CHANGELOG.md
@@ -13,6 +13,8 @@
 
 ### Other Changes
 
+- [[#4756]] (https://github.com/Azure/azure-sdk-for-cpp/issues/4756) `BearerTokenAuthenticationPolicy` now uses shared mutex lock for read operations.
+
 ## 1.11.0-beta.2 (2023-11-02)
 
 ### Features Added
diff --git a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp
index c0dbe7d9e..f9e4ec1ab 100644
--- a/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp
+++ b/sdk/core/azure-core/inc/azure/core/http/policies/policy.hpp
@@ -25,6 +25,7 @@
 #include <memory>
 #include <mutex>
 #include <set>
+#include <shared_mutex>
 #include <string>
 #include <utility>
 #include <vector>
@@ -549,7 +550,7 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
       Credentials::TokenRequestContext m_tokenRequestContext;
 
       mutable Credentials::AccessToken m_accessToken;
-      mutable std::mutex m_accessTokenMutex;
+      mutable std::shared_timed_mutex m_accessTokenMutex;
       mutable Credentials::TokenRequestContext m_accessTokenContext;
 
     public:
@@ -569,6 +570,7 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
 
       std::unique_ptr<HttpPolicy> Clone() const override
       {
+        // Can't use std::make_shared here because copy constructor is not public.
         return std::unique_ptr<HttpPolicy>(new BearerTokenAuthenticationPolicy(*this));
       }
 
@@ -581,6 +583,9 @@ namespace Azure { namespace Core { namespace Http { namespace Policies {
       BearerTokenAuthenticationPolicy(BearerTokenAuthenticationPolicy const& other)
           : BearerTokenAuthenticationPolicy(other.m_credential, other.m_tokenRequestContext)
       {
+        std::shared_lock<std::shared_timed_mutex> readLock(other.m_accessTokenMutex);
+        m_accessToken = other.m_accessToken;
+        m_accessTokenContext = other.m_accessTokenContext;
       }
 
       void operator=(BearerTokenAuthenticationPolicy const&) = delete;
diff --git a/sdk/core/azure-core/src/http/bearer_token_authentication_policy.cpp b/sdk/core/azure-core/src/http/bearer_token_authentication_policy.cpp
index 66cd6b00e..bfce41f86 100644
--- a/sdk/core/azure-core/src/http/bearer_token_authentication_policy.cpp
+++ b/sdk/core/azure-core/src/http/bearer_token_authentication_policy.cpp
@@ -61,21 +61,49 @@ bool BearerTokenAuthenticationPolicy::AuthorizeRequestOnChallenge(
   return false;
 }
 
+namespace {
+bool TokenNeedsRefresh(
+    Azure::Core::Credentials::AccessToken const& cachedToken,
+    Azure::Core::Credentials::TokenRequestContext const& cachedTokenRequestContext,
+    Azure::DateTime const& currentTime,
+    Azure::Core::Credentials::TokenRequestContext const& newTokenRequestContext)
+{
+  return newTokenRequestContext.TenantId != cachedTokenRequestContext.TenantId
+      || newTokenRequestContext.Scopes != cachedTokenRequestContext.Scopes
+      || currentTime > (cachedToken.ExpiresOn - newTokenRequestContext.MinimumExpiration);
+}
+
+void ApplyBearerToken(
+    Azure::Core::Http::Request& request,
+    Azure::Core::Credentials::AccessToken const& token)
+{
+  request.SetHeader("authorization", "Bearer " + token.Token);
+}
+} // namespace
+
 void BearerTokenAuthenticationPolicy::AuthenticateAndAuthorizeRequest(
     Request& request,
     Credentials::TokenRequestContext const& tokenRequestContext,
     Context const& context) const
 {
-  std::lock_guard<std::mutex> lock(m_accessTokenMutex);
+  DateTime const currentTime = std::chrono::system_clock::now();
+
+  {
+    std::shared_lock<std::shared_timed_mutex> readLock(m_accessTokenMutex);
+    if (!TokenNeedsRefresh(m_accessToken, m_accessTokenContext, currentTime, tokenRequestContext))
+    {
+      ApplyBearerToken(request, m_accessToken);
+      return;
+    }
+  }
 
-  if (tokenRequestContext.TenantId != m_accessTokenContext.TenantId
-      || tokenRequestContext.Scopes != m_accessTokenContext.Scopes
-      || std::chrono::system_clock::now()
-          > (m_accessToken.ExpiresOn - tokenRequestContext.MinimumExpiration))
+  std::unique_lock<std::shared_timed_mutex> writeLock(m_accessTokenMutex);
+  // Check if token needs refresh for the second time in case another thread has just updated it.
+  if (TokenNeedsRefresh(m_accessToken, m_accessTokenContext, currentTime, tokenRequestContext))
   {
     m_accessToken = m_credential->GetToken(tokenRequestContext, context);
     m_accessTokenContext = tokenRequestContext;
   }
 
-  request.SetHeader("authorization", "Bearer " + m_accessToken.Token);
+  ApplyBearerToken(request, m_accessToken);
 }
diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md
index a024f17a0..3e9081429 100644
--- a/sdk/identity/azure-identity/CHANGELOG.md
+++ b/sdk/identity/azure-identity/CHANGELOG.md
@@ -10,6 +10,8 @@
 
 ### Other Changes
 
+- [[#5141]](https://github.com/Azure/azure-sdk-for-cpp/issues/5141) Added error response details to the Identity exceptions thrown when the authority host returns error response.
+
 ## 1.6.0 (2023-11-10)
 
 ### Features Added
diff --git a/sdk/identity/azure-identity/src/client_certificate_credential.cpp b/sdk/identity/azure-identity/src/client_certificate_credential.cpp
index 1599bd456..b257130fd 100644
--- a/sdk/identity/azure-identity/src/client_certificate_credential.cpp
+++ b/sdk/identity/azure-identity/src/client_certificate_credential.cpp
@@ -9,6 +9,7 @@
 #include <azure/core/base64.hpp>
 #include <azure/core/datetime.hpp>
 #include <azure/core/internal/cryptography/sha_hash.hpp>
+#include <azure/core/internal/strings.hpp>
 #include <azure/core/io/body_stream.hpp>
 #include <azure/core/platform.hpp>
 #include <azure/core/uuid.hpp>
@@ -399,16 +400,33 @@ ClientCertificateCredential::ClientCertificateCredential(
     CertificateThumbprint mdVec;
     try
     {
+      if (clientCertificatePath.empty())
+      {
+        throw AuthenticationException("Certificate file path is empty.");
+      }
+
+      using Azure::Core::_internal::StringExtensions;
+      std::string const PemExtension = ".pem";
+      auto const certFileExtensionStart = clientCertificatePath.find_last_of('.');
+      auto const certFileExtension = certFileExtensionStart != std::string::npos
+          ? clientCertificatePath.substr(certFileExtensionStart)
+          : std::string{};
+
+      if (!StringExtensions::LocaleInvariantCaseInsensitiveEqual(certFileExtension, PemExtension))
+      {
+        throw AuthenticationException(
+            "Certificate format"
+            + (certFileExtension.empty() ? " " : " ('" + certFileExtension + "') ")
+            + "is not supported. Please convert your certificate to '" + PemExtension + "'.");
+      }
+
       std::tie(mdVec, m_pkey) = ReadPemCertificate(clientCertificatePath);
     }
-    catch (AuthenticationException&)
-    {
-      throw;
-    }
-    catch (std::exception& e)
+    catch (std::exception const& e)
     {
       // WIL does not throw AuthenticationException.
-      throw AuthenticationException(e.what());
+      throw AuthenticationException(
+          std::string("Identity: ClientCertificateCredential: ") + e.what());
     }
 
     // Get thumbprint as hex string:
diff --git a/sdk/identity/azure-identity/src/token_credential_impl.cpp b/sdk/identity/azure-identity/src/token_credential_impl.cpp
index aeb8b1397..474e4e5c0 100644
--- a/sdk/identity/azure-identity/src/token_credential_impl.cpp
+++ b/sdk/identity/azure-identity/src/token_credential_impl.cpp
@@ -72,16 +72,13 @@ std::string TokenCredentialImpl::FormatScopes(
 
     if (scopesIter != scopesEnd)
     {
-      auto const scope = *scopesIter;
-      scopesStr += OptionalUrlEncode(scope, urlEncode);
-    }
-
-    for (++scopesIter; scopesIter != scopesEnd; ++scopesIter)
-    {
-      auto const Separator = std::string(" "); // Element separator never gets URL-encoded
+      scopesStr += OptionalUrlEncode(*scopesIter, urlEncode);
 
-      auto const scope = *scopesIter;
-      scopesStr += Separator + OptionalUrlEncode(scope, urlEncode);
+      constexpr auto Separator = " "; // Element separator never gets URL-encoded
+      for (++scopesIter; scopesIter != scopesEnd; ++scopesIter)
+      {
+        scopesStr += Separator + OptionalUrlEncode(*scopesIter, urlEncode);
+      }
     }
   }
 
@@ -117,11 +114,16 @@ AccessToken TokenCredentialImpl::GetToken(
         request = shouldRetry(statusCode, *response);
         if (request == nullptr)
         {
+          auto const bodyStream = response->ExtractBodyStream();
+          auto const bodyVec = bodyStream ? bodyStream->ReadToEnd(context) : response->GetBody();
+          auto const responseBody
+              = std::string(reinterpret_cast<char const*>(bodyVec.data()), bodyVec.size());
+
           throw std::runtime_error(
               std::string("error response: ")
               + std::to_string(
                   static_cast<std::underlying_type<decltype(statusCode)>::type>(statusCode))
-              + " " + response->GetReasonPhrase());
+              + " " + response->GetReasonPhrase() + "\n\n" + responseBody);
         }
 
         response.reset();
diff --git a/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md
index 661744019..ca1c47d8d 100644
--- a/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md
+++ b/sdk/keyvault/azure-security-keyvault-administration/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ### Bugs Fixed
 
+- [[#4754]](https://github.com/Azure/azure-sdk-for-cpp/issues/4754) Thread safety for authentication policy.
+
 ### Other Changes
 
 ## 4.0.0-beta.3 (2023-04-11)
diff --git a/sdk/keyvault/azure-security-keyvault-certificates/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-certificates/CHANGELOG.md
index 30185487c..b22b36353 100644
--- a/sdk/keyvault/azure-security-keyvault-certificates/CHANGELOG.md
+++ b/sdk/keyvault/azure-security-keyvault-certificates/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ### Bugs Fixed
 
+- [[#4754]](https://github.com/Azure/azure-sdk-for-cpp/issues/4754) Thread safety for authentication policy.
+
 ### Other Changes
 
 ## 4.2.0 (2023-05-09)
diff --git a/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md
index 7be35f06a..93864d5b4 100644
--- a/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md
+++ b/sdk/keyvault/azure-security-keyvault-keys/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ### Bugs Fixed
 
+- [[#4754]](https://github.com/Azure/azure-sdk-for-cpp/issues/4754) Thread safety for authentication policy.
+
 ### Other Changes
 
 - Fixed GCC 13 compilation error. (A community contribution, courtesy of _[adamdebreceni](https://github.com/adamdebreceni)_)
diff --git a/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md b/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md
index cc3d31c6c..8e31fa46f 100644
--- a/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md
+++ b/sdk/keyvault/azure-security-keyvault-secrets/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ### Bugs Fixed
 
+- [[#4754]](https://github.com/Azure/azure-sdk-for-cpp/issues/4754) Thread safety for authentication policy.
+
 ### Other Changes
 
 ## 4.2.0 (2023-05-09)
diff --git a/sdk/keyvault/azure-security-keyvault-shared/inc/azure/keyvault/shared/keyvault_challenge_based_auth.hpp b/sdk/keyvault/azure-security-keyvault-shared/inc/azure/keyvault/shared/keyvault_challenge_based_auth.hpp
index cd3e41f65..d438ed216 100644
--- a/sdk/keyvault/azure-security-keyvault-shared/inc/azure/keyvault/shared/keyvault_challenge_based_auth.hpp
+++ b/sdk/keyvault/azure-security-keyvault-shared/inc/azure/keyvault/shared/keyvault_challenge_based_auth.hpp
@@ -12,6 +12,8 @@
 #include <azure/core/http/policies/policy.hpp>
 #include <azure/core/internal/credentials/authorization_challenge_parser.hpp>
 
+#include <mutex>
+#include <shared_mutex>
 #include <stdexcept>
 
 namespace Azure { namespace Security { namespace KeyVault { namespace _internal {
@@ -23,6 +25,16 @@ namespace Azure { namespace Security { namespace KeyVault { namespace _internal
       : public Core::Http::Policies::_internal::BearerTokenAuthenticationPolicy {
   private:
     mutable Core::Credentials::TokenRequestContext m_tokenRequestContext;
+    mutable std::shared_timed_mutex m_tokenRequestContextMutex;
+
+  protected:
+    KeyVaultChallengeBasedAuthenticationPolicy(
+        KeyVaultChallengeBasedAuthenticationPolicy const& other)
+        : BearerTokenAuthenticationPolicy(other)
+    {
+      std::shared_lock<std::shared_timed_mutex> readLock(other.m_tokenRequestContextMutex);
+      m_tokenRequestContext = other.m_tokenRequestContext;
+    }
 
   public:
     explicit KeyVaultChallengeBasedAuthenticationPolicy(
@@ -35,7 +47,9 @@ namespace Azure { namespace Security { namespace KeyVault { namespace _internal
 
     std::unique_ptr<HttpPolicy> Clone() const override
     {
-      return std::make_unique<KeyVaultChallengeBasedAuthenticationPolicy>(*this);
+      // Can't use std::make_shared here because copy constructor is not public.
+      return std::unique_ptr<KeyVaultChallengeBasedAuthenticationPolicy>(
+          new KeyVaultChallengeBasedAuthenticationPolicy(*this));
     }
 
   private:
@@ -44,7 +58,11 @@ namespace Azure { namespace Security { namespace KeyVault { namespace _internal
         Core::Http::Policies::NextHttpPolicy& nextPolicy,
         Core::Context const& context) const override
     {
-      AuthenticateAndAuthorizeRequest(request, m_tokenRequestContext, context);
+      {
+        std::shared_lock<std::shared_timed_mutex> readLock(m_tokenRequestContextMutex);
+        AuthenticateAndAuthorizeRequest(request, m_tokenRequestContext, context);
+      }
+
       return nextPolicy.Send(request, context);
     }
 
@@ -62,8 +80,11 @@ namespace Azure { namespace Security { namespace KeyVault { namespace _internal
       ValidateChallengeResponse(scope, request.GetUrl().GetHost());
 
       auto const tenantId = GetTenantId(GetAuthorization(challenge));
-      m_tokenRequestContext.TenantId = tenantId;
-      m_tokenRequestContext.Scopes = {scope};
+      {
+        std::unique_lock<std::shared_timed_mutex> writeLock(m_tokenRequestContextMutex);
+        m_tokenRequestContext.TenantId = tenantId;
+        m_tokenRequestContext.Scopes = {scope};
+      }
 
       AuthenticateAndAuthorizeRequest(request, m_tokenRequestContext, context);
       return true;
diff --git a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp
index 95fd8c31c..efd0122c8 100644
--- a/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp
+++ b/sdk/storage/azure-storage-blobs/inc/azure/storage/blobs/blob_options.hpp
@@ -1144,7 +1144,7 @@ namespace Azure { namespace Storage { namespace Blobs {
      * @brief Creates CSV text configuration.
      *
      * @param recordSeparator Record separator.
-     * @param columnSeparator Column sepeartor.
+     * @param columnSeparator Column separator.
      * @param quotationCharacter Field quote.
      * @param escapeCharacter Escape character.
      * @param hasHeaders If CSV file has headers.
@@ -1191,7 +1191,7 @@ namespace Azure { namespace Storage { namespace Blobs {
      * @brief Creates CSV text configuration.
      *
      * @param recordSeparator Record separator.
-     * @param columnSeparator Column sepeartor.
+     * @param columnSeparator Column separator.
      * @param quotationCharacter Field quote.
      * @param escapeCharacter Escape character.
      * @param hasHeaders If CSV file has headers.
diff --git a/sdk/storage/azure-storage-blobs/src/blob_client.cpp b/sdk/storage/azure-storage-blobs/src/blob_client.cpp
index f172d0d8e..0272b5031 100644
--- a/sdk/storage/azure-storage-blobs/src/blob_client.cpp
+++ b/sdk/storage/azure-storage-blobs/src/blob_client.cpp
@@ -204,10 +204,15 @@ namespace Azure { namespace Storage { namespace Blobs {
     {
       // In case network failure during reading the body
       const Azure::ETag eTag = downloadResponse.Value.Details.ETag;
-
-      auto retryFunction
-          = [this, options, eTag](int64_t retryOffset, const Azure::Core::Context& context)
-          -> std::unique_ptr<Azure::Core::IO::BodyStream> {
+      const std::string client_request_id
+          = downloadResponse.RawResponse->GetHeaders().find(_internal::HttpHeaderClientRequestId)
+              == downloadResponse.RawResponse->GetHeaders().end()
+          ? std::string()
+          : downloadResponse.RawResponse->GetHeaders().at(_internal::HttpHeaderClientRequestId);
+      auto retryFunction =
+          [this, options, eTag, client_request_id](
+              int64_t retryOffset,
+              const Azure::Core::Context& context) -> std::unique_ptr<Azure::Core::IO::BodyStream> {
         DownloadBlobOptions newOptions = options;
         newOptions.Range = Core::Http::HttpRange();
         newOptions.Range.Value().Offset
@@ -217,7 +222,11 @@ namespace Azure { namespace Storage { namespace Blobs {
           newOptions.Range.Value().Length = options.Range.Value().Length.Value() - retryOffset;
         }
         newOptions.AccessConditions.IfMatch = eTag;
-        return std::move(Download(newOptions, context).Value.BodyStream);
+        return std::move(
+            Download(
+                newOptions,
+                context.WithValue(_internal::ReliableStreamClientRequestIdKey, client_request_id))
+                .Value.BodyStream);
       };
 
       _internal::ReliableStreamOptions reliableStreamOptions;
diff --git a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp
index f87bf76ab..6973d31fe 100644
--- a/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp
+++ b/sdk/storage/azure-storage-blobs/src/block_blob_client.cpp
@@ -633,7 +633,7 @@ namespace Azure { namespace Storage { namespace Blobs {
     auto response = _detail::BlobClient::Query(
         *m_pipeline, m_blobUrl, protocolLayerOptions, _internal::WithReplicaStatus(context));
 
-    const auto stautsCode = response.RawResponse->GetStatusCode();
+    const auto statusCode = response.RawResponse->GetStatusCode();
     const auto reasonPhrase = response.RawResponse->GetReasonPhrase();
     const auto requestId
         = response.RawResponse->GetHeaders().count(_internal::HttpHeaderRequestId) != 0
@@ -646,11 +646,11 @@ namespace Azure { namespace Storage { namespace Blobs {
         : std::string();
 
     auto defaultErrorHandler
-        = [stautsCode, reasonPhrase, requestId, clientRequestId](BlobQueryError e) {
+        = [statusCode, reasonPhrase, requestId, clientRequestId](BlobQueryError e) {
             if (e.IsFatal)
             {
               StorageException exception("Fatal " + e.Name + " at " + std::to_string(e.Position));
-              exception.StatusCode = stautsCode;
+              exception.StatusCode = statusCode;
               exception.ReasonPhrase = reasonPhrase;
               exception.RequestId = requestId;
               exception.ClientRequestId = clientRequestId;
diff --git a/sdk/storage/azure-storage-blobs/swagger/README.md b/sdk/storage/azure-storage-blobs/swagger/README.md
index fd5e33a4b..f142823e5 100644
--- a/sdk/storage/azure-storage-blobs/swagger/README.md
+++ b/sdk/storage/azure-storage-blobs/swagger/README.md
@@ -1786,7 +1786,7 @@ directive:
       $.ContainerProperties.properties["DefaultEncryptionScope"].description = "The default encryption scope for the container.";
       $.ContainerProperties.properties["DenyEncryptionScopeOverride"].description = "Indicates whether the container's default encryption scope can be overriden.";
       $.ContainerProperties.properties["DeletedTime"].description = "Data and time at which this container was deleted. Only valid when this container was deleted.";
-      $.ContainerProperties.properties["RemainingRetentionDays"].description = "Remaining days before this container will be permanantely deleted. Only valid when this container was deleted.";
+      $.ContainerProperties.properties["RemainingRetentionDays"].description = "Remaining days before this container will be permanently deleted. Only valid when this container was deleted.";
       $.FilterBlobItem.properties["Name"].description = "Blob name.";
       $.FilterBlobItem.properties["ContainerName"].description = "Blob container name.";
       $.BlobItemInternal.properties["Name"].description = "Blob name.";
@@ -1810,7 +1810,7 @@ directive:
       $.BlobPropertiesInternal.properties["IncrementalCopy"].description = "Included if the blob is incremental copy blob.";
       $.BlobPropertiesInternal.properties["DestinationSnapshot"].description = "Included if the blob is incremental copy blob or incremental copy snapshot, if x-ms-copy-status is success. Snapshot time of the last successful incremental copy snapshot for this blob.";
       $.BlobPropertiesInternal.properties["DeletedTime"].description = "Data and time at which this blob was deleted. Only valid when this blob was deleted.";
-      $.BlobPropertiesInternal.properties["RemainingRetentionDays"].description = "Remaining days before this blob will be permanantely deleted. Only valid when this blob was deleted.";
+      $.BlobPropertiesInternal.properties["RemainingRetentionDays"].description = "Remaining days before this blob will be permanently deleted. Only valid when this blob was deleted.";
       $.BlobPropertiesInternal.properties["AccessTierInferred"].description = "True if the access tier is not explicitly set on the blob.";
       $.BlobPropertiesInternal.properties["CustomerProvidedKeySha256"].description = "SHA-256 hash of the encryption key.";
       $.BlobPropertiesInternal.properties["AccessTierChangeTime"].description = "The time the tier was changed on the object. This is only returned if the tier on the block blob was ever set.";
diff --git a/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/constants.hpp b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/constants.hpp
index 1b7db941e..df0486cf7 100644
--- a/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/constants.hpp
+++ b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/constants.hpp
@@ -10,9 +10,11 @@ namespace Azure { namespace Storage { namespace _internal {
   constexpr static const char* QueueServicePackageName = "storage-queues";
   constexpr static const char* HttpQuerySnapshot = "snapshot";
   constexpr static const char* HttpQueryVersionId = "versionid";
+  constexpr static const char* HttpQueryTimeout = "timeout";
   constexpr static const char* StorageScope = "https://storage.azure.com/.default";
   constexpr static const char* StorageDefaultAudience = "https://storage.azure.com";
   constexpr static const char* HttpHeaderDate = "date";
+  constexpr static const char* HttpHeaderXMsDate = "x-ms-date";
   constexpr static const char* HttpHeaderXMsVersion = "x-ms-version";
   constexpr static const char* HttpHeaderRequestId = "x-ms-request-id";
   constexpr static const char* HttpHeaderClientRequestId = "x-ms-client-request-id";
diff --git a/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/reliable_stream.hpp b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/reliable_stream.hpp
index 22c23768a..373f8e600 100644
--- a/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/reliable_stream.hpp
+++ b/sdk/storage/azure-storage-common/inc/azure/storage/common/internal/reliable_stream.hpp
@@ -1,6 +1,10 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+#pragma once
+
+#include "azure/storage/common/dll_import_export.hpp"
+
 #include <azure/core/context.hpp>
 #include <azure/core/io/body_stream.hpp>
 
@@ -10,6 +14,9 @@
 
 namespace Azure { namespace Storage { namespace _internal {
 
+  AZ_STORAGE_COMMON_DLLEXPORT extern const Azure::Core::Context::Key
+      ReliableStreamClientRequestIdKey;
+
   // Options used by reliable stream
   struct ReliableStreamOptions final
   {
diff --git a/sdk/storage/azure-storage-common/src/reliable_stream.cpp b/sdk/storage/azure-storage-common/src/reliable_stream.cpp
index 2a424c9a7..c3f9f8a30 100644
--- a/sdk/storage/azure-storage-common/src/reliable_stream.cpp
+++ b/sdk/storage/azure-storage-common/src/reliable_stream.cpp
@@ -20,6 +20,7 @@ namespace Azure { namespace Storage {
   } // namespace _detail
 
   namespace _internal {
+    Azure::Core::Context::Key const ReliableStreamClientRequestIdKey;
 
     size_t ReliableStream::OnRead(uint8_t* buffer, size_t count, Context const& context)
     {
diff --git a/sdk/storage/azure-storage-common/src/storage_per_retry_policy.cpp b/sdk/storage/azure-storage-common/src/storage_per_retry_policy.cpp
index ac3b74216..e9433752c 100644
--- a/sdk/storage/azure-storage-common/src/storage_per_retry_policy.cpp
+++ b/sdk/storage/azure-storage-common/src/storage_per_retry_policy.cpp
@@ -3,6 +3,9 @@
 
 #include "azure/storage/common/internal/storage_per_retry_policy.hpp"
 
+#include "azure/storage/common/internal/constants.hpp"
+#include "azure/storage/common/internal/reliable_stream.hpp"
+
 #include <azure/core/datetime.hpp>
 #include <azure/core/platform.hpp>
 
@@ -16,9 +19,6 @@ namespace Azure { namespace Storage { namespace _internal {
       Core::Http::Policies::NextHttpPolicy nextPolicy,
       Core::Context const& context) const
   {
-    const char* HttpHeaderDate = "Date";
-    const char* HttpHeaderXMsDate = "x-ms-date";
-
     const auto& headers = request.GetHeaders();
     if (headers.find(HttpHeaderDate) == headers.end())
     {
@@ -29,11 +29,10 @@ namespace Azure { namespace Storage { namespace _internal {
               .ToString(Azure::DateTime::DateFormat::Rfc1123));
     }
 
-    const char* HttpHeaderTimeout = "timeout";
     auto cancelTimepoint = context.GetDeadline();
     if (cancelTimepoint == Azure::DateTime::max())
     {
-      request.GetUrl().RemoveQueryParameter(HttpHeaderTimeout);
+      request.GetUrl().RemoveQueryParameter(HttpQueryTimeout);
     }
     else
     {
@@ -43,8 +42,18 @@ namespace Azure { namespace Storage { namespace _internal {
                 .count()
           : -1;
       request.GetUrl().AppendQueryParameter(
-          HttpHeaderTimeout, std::to_string(std::max(numSeconds, int64_t(1))));
+          HttpQueryTimeout, std::to_string(std::max(numSeconds, int64_t(1))));
+    }
+
+    std::string client_request_id;
+    if (context.TryGetValue(ReliableStreamClientRequestIdKey, client_request_id))
+    {
+      if (!client_request_id.empty())
+      {
+        request.SetHeader(HttpHeaderClientRequestId, client_request_id);
+      }
     }
+
     return nextPolicy.Send(request, context);
   }
 
diff --git a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp
index 12ade37bc..3da9a69f4 100644
--- a/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp
+++ b/sdk/storage/azure-storage-files-shares/src/share_file_client.cpp
@@ -305,10 +305,15 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
     {
       // In case network failure during reading the body
       auto eTag = downloadResponse.Value.Details.ETag;
-
-      auto retryFunction
-          = [this, options, eTag](int64_t retryOffset, const Azure::Core::Context& context)
-          -> std::unique_ptr<Azure::Core::IO::BodyStream> {
+      const std::string client_request_id
+          = downloadResponse.RawResponse->GetHeaders().find(_internal::HttpHeaderClientRequestId)
+              == downloadResponse.RawResponse->GetHeaders().end()
+          ? std::string()
+          : downloadResponse.RawResponse->GetHeaders().at(_internal::HttpHeaderClientRequestId);
+      auto retryFunction =
+          [this, options, eTag, client_request_id](
+              int64_t retryOffset,
+              const Azure::Core::Context& context) -> std::unique_ptr<Azure::Core::IO::BodyStream> {
         DownloadFileOptions newOptions = options;
         newOptions.Range = Core::Http::HttpRange();
         newOptions.Range.Value().Offset
@@ -318,7 +323,9 @@ namespace Azure { namespace Storage { namespace Files { namespace Shares {
           newOptions.Range.Value().Length = options.Range.Value().Length.Value() - retryOffset;
         }
 
-        auto newResponse = Download(newOptions, context);
+        auto newResponse = Download(
+            newOptions,
+            context.WithValue(_internal::ReliableStreamClientRequestIdKey, client_request_id));
         if (eTag != newResponse.Value.Details.ETag)
         {
           throw Azure::Core::RequestFailedException("File was modified in the middle of download.");
diff --git a/sdk/storage/azure-storage-files-shares/swagger/README.md b/sdk/storage/azure-storage-files-shares/swagger/README.md
index baac9d0de..e2a235fc4 100644
--- a/sdk/storage/azure-storage-files-shares/swagger/README.md
+++ b/sdk/storage/azure-storage-files-shares/swagger/README.md
@@ -614,7 +614,7 @@ directive:
       $["x-ms-marker"]["x-nullable"] = true;
 ```
 
-### CreateDirecotry
+### CreateDirectory
 
 ```yaml
 directive:


Test patch:
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 6103da818..511b73393 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
@@ -148,6 +148,77 @@ TEST_P(GetCredentialName, )
   EXPECT_EQ(cred.GetCredentialName(), "ClientCertificateCredential");
 }
 
+TEST(ClientCertificateCredential, UnsupportedExtension)
+{
+  try
+  {
+    ClientCertificateCredential const cred(
+        "01234567-89ab-cdef-fedc-ba8976543210", "fedcba98-7654-3210-0123-456789abcdef", "file.pfx");
+
+    EXPECT_TRUE(
+        !"ClientCertificateCredential with unsupported extension (.pfx) is supposed to throw.");
+  }
+  catch (Azure::Core::Credentials::AuthenticationException const& ex)
+  {
+    EXPECT_EQ(
+        ex.what(),
+        std::string("Identity: ClientCertificateCredential: "
+                    "Certificate format ('.pfx') is not supported. "
+                    "Please convert your certificate to '.pem'."));
+  }
+
+  try
+  {
+    ClientCertificateCredential const cred(
+        "01234567-89ab-cdef-fedc-ba8976543210",
+        "fedcba98-7654-3210-0123-456789abcdef",
+        "file.cert");
+
+    EXPECT_TRUE(
+        !"ClientCertificateCredential with unsupported extension (.cert) is supposed to throw.");
+  }
+  catch (Azure::Core::Credentials::AuthenticationException const& ex)
+  {
+    EXPECT_EQ(
+        ex.what(),
+        std::string("Identity: ClientCertificateCredential: "
+                    "Certificate format ('.cert') is not supported. "
+                    "Please convert your certificate to '.pem'."));
+  }
+
+  try
+  {
+    ClientCertificateCredential const cred(
+        "01234567-89ab-cdef-fedc-ba8976543210",
+        "fedcba98-7654-3210-0123-456789abcdef",
+        "noextension");
+
+    EXPECT_TRUE(!"ClientCertificateCredential without an extension is supposed to throw.");
+  }
+  catch (Azure::Core::Credentials::AuthenticationException const& ex)
+  {
+    EXPECT_EQ(
+        ex.what(),
+        std::string("Identity: ClientCertificateCredential: "
+                    "Certificate format is not supported. "
+                    "Please convert your certificate to '.pem'."));
+  }
+
+  try
+  {
+    ClientCertificateCredential const cred(
+        "01234567-89ab-cdef-fedc-ba8976543210", "fedcba98-7654-3210-0123-456789abcdef", "");
+
+    EXPECT_TRUE(!"ClientCertificateCredential with an empty path is supposed to throw.");
+  }
+  catch (Azure::Core::Credentials::AuthenticationException const& ex)
+  {
+    EXPECT_EQ(
+        ex.what(),
+        std::string("Identity: ClientCertificateCredential: Certificate file path is empty."));
+  }
+}
+
 TEST(ClientCertificateCredential, GetOptionsFromEnvironment)
 {
   {
diff --git a/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp b/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp
index ab07a4c0a..5ff80f5a7 100644
--- a/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp
+++ b/sdk/identity/azure-identity/test/ut/token_credential_impl_test.cpp
@@ -710,6 +710,49 @@ TEST(TokenCredentialImpl, MaxValues)
       std::exception);
 }
 
+TEST(TokenCredentialImpl, AuthErrorResponse)
+{
+  std::string const errorJson
+      = "{\"error\":\"invalid_request\","
+        "\"error_description\":\"AADSTS90014: "
+        "The required field 'scope' is missing from the credential. "
+        "Ensure that you have all the necessary parameters for the login request. "
+        "Trace ID: 01234567-89ab-cdef-0123-456789abcdef "
+        "Correlation ID: fedcba98-7654-3210-0123-456789abcdef "
+        "Timestamp: 2023-11-30 00:51:37Z\","
+        "\"error_codes\":[90014],"
+        "\"timestamp\":\"2023-11-30 00:51:37Z\","
+        "\"trace_id\":\"01234567-89ab-cdef-0123-456789abcdef\","
+        "\"correlation_id\":\"fedcba98-7654-3210-0123-456789abcdef\","
+        "\"error_uri\":\"https://login.microsoftonline.com/error?code=90014\"}";
+
+  try
+  {
+    using Azure::Core::Http::HttpStatusCode;
+    std::vector<std::string> const testScopes;
+    CredentialTestHelper::TokenRequestSimulationServerResponse testResponse;
+    testResponse.StatusCode = HttpStatusCode::BadRequest;
+    testResponse.Body = errorJson;
+
+    static_cast<void>(CredentialTestHelper::SimulateTokenRequest(
+        [](auto transport) {
+          TokenCredentialOptions options;
+          options.Transport.Transport = transport;
+
+          return std::make_unique<TokenCredentialImplTester>(
+              HttpMethod::Delete, Url("https://outlook.com/"), options);
+        },
+        {testScopes},
+        {testResponse}));
+
+    EXPECT_TRUE(!"TokenCredentialImpl should throw given the response above.");
+  }
+  catch (AuthenticationException const& ex)
+  {
+    EXPECT_EQ(ex.what(), "GetToken(): error response: 400 Test\n\n" + errorJson);
+  }
+}
+
 TEST(TokenCredentialImpl, Diagnosability)
 {
   using Azure::Core::Diagnostics::Logger;
diff --git a/sdk/storage/azure-storage-blobs/test/perf/inc/azure/storage/blobs/test/upload_blob_test.hpp b/sdk/storage/azure-storage-blobs/test/perf/inc/azure/storage/blobs/test/upload_blob_test.hpp
index 789cea639..2503265be 100644
--- a/sdk/storage/azure-storage-blobs/test/perf/inc/azure/storage/blobs/test/upload_blob_test.hpp
+++ b/sdk/storage/azure-storage-blobs/test/perf/inc/azure/storage/blobs/test/upload_blob_test.hpp
@@ -27,7 +27,7 @@ namespace Azure { namespace Storage { namespace Blobs { namespace Test {
    */
   class UploadBlob : public Azure::Storage::Blobs::Test::BlobsTest {
   private:
-    // C++ can upload and download from contiguos memory or file only
+    // C++ can upload and download from continuous memory or file only
     std::vector<uint8_t> m_uploadBuffer;
 
   public:
diff --git a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp
index 1af88bc7f..b69b61e10 100644
--- a/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp
+++ b/sdk/storage/azure-storage-files-datalake/test/ut/datalake_path_client_test.cpp
@@ -318,6 +318,7 @@ namespace Azure { namespace Storage { namespace Test {
 
     // Set Acls
     auto rootDirClient = m_fileSystemClient->GetDirectoryClient("");
+    /* cspell:disable-next-line */
     rootDirClient.SetPermissions("rwxrwxrwx");
     auto aclResult = rootDirClient.GetAccessControlList();
     auto acls = aclResult.Value.Acls;