diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 04042fa0c..606f7b80c 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -5,7 +5,7 @@ ### New Features - Added support for HTTP conditional requests `MatchConditions` and `RequestConditions`. -- Added MD5 hashing APIs to the `Azure::Core` namespace available from `azure/core/md5.hpp`. +- Added the `Hash` base class and MD5 hashing APIs to the `Azure::Core::Cryptography` namespace available from `azure/core/cryptography/hash.hpp`. ### Breaking Changes diff --git a/sdk/core/azure-core/CMakeLists.txt b/sdk/core/azure-core/CMakeLists.txt index 17a4c743a..0b393491b 100644 --- a/sdk/core/azure-core/CMakeLists.txt +++ b/sdk/core/azure-core/CMakeLists.txt @@ -45,6 +45,7 @@ set( AZURE_CORE_HEADER ${CURL_TRANSPORT_ADAPTER_INC} ${WIN_TRANSPORT_ADAPTER_INC} + inc/azure/core/cryptography/hash.hpp inc/azure/core/http/body_stream.hpp inc/azure/core/http/http.hpp inc/azure/core/http/pipeline.hpp @@ -64,7 +65,6 @@ set( inc/azure/core/etag.hpp inc/azure/core/exception.hpp inc/azure/core/match_conditions.hpp - inc/azure/core/md5.hpp inc/azure/core/nullable.hpp inc/azure/core/operation.hpp inc/azure/core/operation_status.hpp @@ -80,6 +80,7 @@ set( AZURE_CORE_SOURCE ${CURL_TRANSPORT_ADAPTER_SRC} ${WIN_TRANSPORT_ADAPTER_SRC} + src/cryptography/md5.cpp src/http/bearer_token_authentication_policy.cpp src/http/body_stream.cpp src/http/http.cpp @@ -95,7 +96,6 @@ set( src/base64.cpp src/context.cpp src/datetime.cpp - src/md5.cpp src/operation_status.cpp src/strings.cpp src/version.cpp diff --git a/sdk/core/azure-core/inc/azure/core.hpp b/sdk/core/azure-core/inc/azure/core.hpp index 8c4c085d7..ce2155755 100644 --- a/sdk/core/azure-core/inc/azure/core.hpp +++ b/sdk/core/azure-core/inc/azure/core.hpp @@ -18,13 +18,15 @@ #include "azure/core/dll_import_export.hpp" #include "azure/core/etag.hpp" #include "azure/core/match_conditions.hpp" -#include "azure/core/md5.hpp" #include "azure/core/nullable.hpp" #include "azure/core/request_conditions.hpp" #include "azure/core/response.hpp" #include "azure/core/uuid.hpp" #include "azure/core/version.hpp" +// azure/core/cryptography +#include "azure/core/cryptography/hash.hpp" + // azure/core/http #include "azure/core/http/body_stream.hpp" #include "azure/core/http/http.hpp" diff --git a/sdk/core/azure-core/inc/azure/core/cryptography/hash.hpp b/sdk/core/azure-core/inc/azure/core/cryptography/hash.hpp new file mode 100644 index 000000000..040bb5a39 --- /dev/null +++ b/sdk/core/azure-core/inc/azure/core/cryptography/hash.hpp @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Utility functions to help compute the hash value for the input binary data, using + * algorithms such as MD5. + */ + +#pragma once + +#include +#include +#include +#include + +namespace Azure { namespace Core { namespace Cryptography { + + /** + * @brief Represents the base class for hash algorithms which map binary data of an arbitrary + * length to small binary data of a fixed length. + */ + class Hash { + private: + /** + * @brief Used to append partial binary input data to compute the hash in a streaming fashion. + * @remark Once all the data has been added, call #Final() to get the computed hash value. + * @param data The pointer to the current block of binary data that is used for hash + * calculation. + * @param length The size of the data provided. + */ + virtual void OnAppend(const uint8_t* data, std::size_t length) = 0; + + /** + * @brief Computes the hash value of the specified binary input data, including any previously + * appended. + * @param data The pointer to binary data to compute the hash value for. + * @param length The size of the data provided. + * @return The computed hash value corresponding to the input provided including any previously + * appended. + */ + virtual std::vector OnFinal(const uint8_t* data, std::size_t length) = 0; + + public: + /** + * @brief Used to append partial binary input data to compute the hash in a streaming fashion. + * @remark Once all the data has been added, call #Final() to get the computed hash value. + * @remark Do not call this function after a call to #Final(). + * @param data The pointer to the current block of binary data that is used for hash + * calculation. + * @param length The size of the data provided. + */ + void Append(const uint8_t* data, std::size_t length) + { + if (!data && length != 0) + { + throw std::invalid_argument( + "Length cannot be " + std::to_string(length) + " if the data pointer is null."); + } + if (m_isdone) + { + throw std::runtime_error("Cannot call Append after calling Final()."); + }; + OnAppend(data, length); + } + + /** + * @brief Computes the hash value of the specified binary input data, including any previously + * appended. + * @remark Do not call this function multiple times. + * @param data The pointer to the last block of binary data to compute the hash value for. + * @param length The size of the data provided. + * @return The computed hash value corresponding to the input provided, including any previously + * appended. + */ + std::vector Final(const uint8_t* data, std::size_t length) + { + if (!data && length != 0) + { + throw std::invalid_argument( + "Length cannot be " + std::to_string(length) + " if the data pointer is null."); + } + if (m_isdone) + { + throw std::runtime_error("Cannot call Final() multiple times."); + }; + m_isdone = true; + return OnFinal(data, length); + } + + /** + * @brief Computes the hash value of all the binary input data appended to the instance so far. + * @remark Use #Append() to add more partial data before calling this function. + * @remark Do not call this function multiple times. + * @return The computed hash value corresponding to the input provided. + */ + std::vector Final() { return Final(nullptr, 0); }; + + /** + * @brief Cleanup any state when destroying the instance of @Hash. + */ + virtual ~Hash(){}; + + private: + bool m_isdone = false; + }; + + /** + * @brief Represents the class for the MD5 hash function which maps binary data of an arbitrary + * length to small binary data of a fixed length. + */ + class Md5Hash : public Hash { + + public: + /** + * @brief Construct a default instance of @Md5Hash. + */ + explicit Md5Hash(); + + /** + * @brief Cleanup any state when destroying the instance of @Md5Hash. + */ + ~Md5Hash(); + + private: + void* m_md5Context; + + /** + * @brief Computes the hash value of the specified binary input data, including any previously + * appended. + * @param data The pointer to binary data to compute the hash value for. + * @param length The size of the data provided. + * @return The computed MD5 hash value corresponding to the input provided including any + * previously appended. + */ + std::vector OnFinal(const uint8_t* data, std::size_t length) override; + + /** + * @brief Used to append partial binary input data to compute the MD5 hash in a streaming + * fashion. + * @remark Once all the data has been added, call #Final() to get the computed hash value. + * @param data The pointer to the current block of binary data that is used for hash + * calculation. + * @param length The size of the data provided. + */ + void OnAppend(const uint8_t* data, std::size_t length) override; + }; + +}}} // namespace Azure::Core::Cryptography diff --git a/sdk/core/azure-core/inc/azure/core/md5.hpp b/sdk/core/azure-core/inc/azure/core/md5.hpp deleted file mode 100644 index 2ac47d0e8..000000000 --- a/sdk/core/azure-core/inc/azure/core/md5.hpp +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: MIT - -/** - * @file - * @brief Utility functions to help compute the MD5 hash value for the input binary data. - */ - -#pragma once - -#include -#include - -namespace Azure { namespace Core { - - /** - * @brief Represents the class for the MD5 hash function which maps binary data of an arbitrary - * length to small binary data of a fixed length. - */ - class Md5 { - public: - /** - * @brief Construct a default instance of @Md5. - */ - explicit Md5(); - - /** - * @brief Cleanup any state when destroying the instance of @Md5. - */ - ~Md5(); - - /** - * @brief Used to append partial binary input data to compute the hash in a streaming fashion. - * @remark Once all the data has been added, call #Digest() to get the computed hash value. - * @param data The pointer to the current block of binary data that is used for hash - * calculation. - * @param length The size of the data provided. - */ - void Update(const uint8_t* data, std::size_t length); - - /** - * @brief Computes the hash value of all the binary input data appended to the instance so far. - * @remark Use #Update() to add more partial data before calling this function. - * @return The computed MD5 hash value corresponding to the input provided. - */ - std::vector Digest() const; - - /** - * @brief Computes the hash value of the specified binary input data. - * @param data The pointer to binary data to compute the hash value for. - * @param length The size of the data provided. - * @return The computed MD5 hash value corresponding to the input provided. - */ - static std::vector Hash(const uint8_t* data, std::size_t length) - { - Md5 instance; - instance.Update(data, length); - return instance.Digest(); - } - - /** - * @brief Computes the hash value of the specified binary input data. - * @param data The input vector to compute the hash value for. - * @return The computed MD5 hash value corresponding to the input provided. - */ - static std::vector Hash(const std::vector& data) - { - return Hash(data.data(), data.size()); - } - - private: - void* m_md5Context; - }; - -}} // namespace Azure::Core diff --git a/sdk/core/azure-core/src/md5.cpp b/sdk/core/azure-core/src/cryptography/md5.cpp similarity index 87% rename from sdk/core/azure-core/src/md5.cpp rename to sdk/core/azure-core/src/cryptography/md5.cpp index d0c34fe8d..4ca2f73a9 100644 --- a/sdk/core/azure-core/src/md5.cpp +++ b/sdk/core/azure-core/src/cryptography/md5.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT -#include "azure/core/md5.hpp" +#include "azure/core/cryptography/hash.hpp" #include "azure/core/platform.hpp" #if defined(AZ_PLATFORM_WINDOWS) @@ -16,7 +16,7 @@ #include #include -namespace Azure { namespace Core { +namespace Azure { namespace Core { namespace Cryptography { #if defined(AZ_PLATFORM_WINDOWS) @@ -74,7 +74,7 @@ namespace Azure { namespace Core { }; } // namespace Details - Md5::Md5() + Md5Hash::Md5Hash() { static Details::AlgorithmProviderInstance AlgorithmProvider{}; @@ -97,14 +97,14 @@ namespace Azure { namespace Core { } } - Md5::~Md5() + Md5Hash::~Md5Hash() { Details::Md5HashContext* md5Context = static_cast(m_md5Context); BCryptDestroyHash(md5Context->hashHandle); delete md5Context; } - void Md5::Update(const uint8_t* data, std::size_t length) + void Md5Hash::OnAppend(const uint8_t* data, std::size_t length) { Details::Md5HashContext* md5Context = static_cast(m_md5Context); @@ -119,8 +119,9 @@ namespace Azure { namespace Core { } } - std::vector Md5::Digest() const + std::vector Md5Hash::OnFinal(const uint8_t* data, std::size_t length) { + OnAppend(data, length); Details::Md5HashContext* md5Context = static_cast(m_md5Context); std::vector hash; hash.resize(md5Context->hashLength); @@ -138,27 +139,28 @@ namespace Azure { namespace Core { #elif defined(AZ_PLATFORM_POSIX) - Md5::Md5() + Md5Hash::Md5Hash() { MD5_CTX* md5Context = new MD5_CTX; m_md5Context = md5Context; MD5_Init(md5Context); } - Md5::~Md5() + Md5Hash::~Md5Hash() { MD5_CTX* md5Context = static_cast(m_md5Context); delete md5Context; } - void Md5::Update(const uint8_t* data, std::size_t length) + void Md5Hash::OnAppend(const uint8_t* data, std::size_t length) { MD5_CTX* md5Context = static_cast(m_md5Context); MD5_Update(md5Context, data, length); } - std::vector Md5::Digest() const + std::vector Md5Hash::OnFinal(const uint8_t* data, std::size_t length) { + OnAppend(data, length); MD5_CTX* md5Context = static_cast(m_md5Context); unsigned char hash[MD5_DIGEST_LENGTH]; MD5_Final(hash, md5Context); @@ -166,4 +168,4 @@ namespace Azure { namespace Core { } #endif -}} // namespace Azure::Core +}}} // namespace Azure::Core::Cryptography diff --git a/sdk/core/azure-core/test/ut/md5.cpp b/sdk/core/azure-core/test/ut/md5.cpp index 96ffaae2d..2ef17a64b 100644 --- a/sdk/core/azure-core/test/ut/md5.cpp +++ b/sdk/core/azure-core/test/ut/md5.cpp @@ -3,19 +3,19 @@ #include #include -#include +#include #include #include #include #include -using namespace Azure::Core; +using namespace Azure::Core::Cryptography; -static std::vector Hash(const std::string& data) +static std::vector ComputeHash(const std::string& data) { const uint8_t* ptr = reinterpret_cast(data.data()); - std::vector v(ptr, ptr + data.length()); - return Md5::Hash(v); + Md5Hash instance; + return instance.Final(ptr, data.length()); } static thread_local std::mt19937_64 random_generator(std::random_device{}()); @@ -62,13 +62,17 @@ uint64_t RandomInt(uint64_t minNumber, uint64_t maxNumber) return distribution(random_generator); } -TEST(Md5, Basic) +TEST(Md5Hash, Basic) { - EXPECT_EQ(Base64Encode(Hash("")), "1B2M2Y8AsgTpgAmY7PhCfg=="); - EXPECT_EQ(Base64Encode(Hash("Hello Azure!")), "Pz8543xut4RVSbb2g52Mww=="); + Md5Hash md5empty; + EXPECT_EQ(Azure::Core::Base64Encode(md5empty.Final()), "1B2M2Y8AsgTpgAmY7PhCfg=="); + EXPECT_EQ(Azure::Core::Base64Encode(ComputeHash("")), "1B2M2Y8AsgTpgAmY7PhCfg=="); + EXPECT_EQ(Azure::Core::Base64Encode(ComputeHash("Hello Azure!")), "Pz8543xut4RVSbb2g52Mww=="); auto data = RandomBuffer(static_cast(16777216)); - Md5 md5Instance; + + Md5Hash md5Single; + Md5Hash md5Streaming; // There are two ways to get the hash value, a "single-shot" static API called `Hash()` and one // where you can stream partial data blocks with multiple calls to `Update()` and then once you @@ -83,9 +87,32 @@ TEST(Md5, Basic) { std::size_t s = static_cast(RandomInt(0, 4194304)); s = std::min(s, data.size() - length); - md5Instance.Update(&data[length], s); - md5Instance.Update(&data[length], 0); + md5Streaming.Append(&data[length], s); + md5Streaming.Append(&data[length], 0); length += s; } - EXPECT_EQ(md5Instance.Digest(), Md5::Hash(data)); + EXPECT_EQ(md5Streaming.Final(), md5Single.Final(data.data(), data.size())); +} + +TEST(Md5Hash, ExpectThrow) +{ + std::string data = ""; + const uint8_t* ptr = reinterpret_cast(data.data()); + Md5Hash instance; + + EXPECT_THROW(instance.Final(nullptr, 1), std::invalid_argument); + EXPECT_THROW(instance.Append(nullptr, 1), std::invalid_argument); + + EXPECT_EQ( + Azure::Core::Base64Encode(instance.Final(ptr, data.length())), "1B2M2Y8AsgTpgAmY7PhCfg=="); + EXPECT_THROW(instance.Final(), std::runtime_error); + EXPECT_THROW(instance.Final(ptr, data.length()), std::runtime_error); + EXPECT_THROW(instance.Append(ptr, data.length()), std::runtime_error); +} + +TEST(Md5Hash, CtorDtor) +{ + { + Md5Hash instance; + } } diff --git a/sdk/core/azure-core/test/ut/simplified_header.cpp b/sdk/core/azure-core/test/ut/simplified_header.cpp index 246671556..38b11cd42 100644 --- a/sdk/core/azure-core/test/ut/simplified_header.cpp +++ b/sdk/core/azure-core/test/ut/simplified_header.cpp @@ -23,7 +23,7 @@ TEST(SimplifiedHeader, core) EXPECT_NO_THROW(Azure::Core::DateTime(2020, 11, 03, 15, 30, 44)); EXPECT_NO_THROW(Azure::Core::ETag e); EXPECT_NO_THROW(Azure::Core::Base64Decode("foo")); - EXPECT_NO_THROW(Azure::Core::Md5 m); + EXPECT_NO_THROW(Azure::Core::Cryptography::Md5Hash m); EXPECT_NO_THROW(Azure::Core::Http::RawResponse r( 1, 1, Azure::Core::Http::HttpStatusCode::Accepted, "phrase")); EXPECT_NO_THROW(Azure::Core::MatchConditions mc);