diff --git a/src/libutil-tests/hash.cc b/src/libutil-tests/hash.cc index f9d425d92..15e639180 100644 --- a/src/libutil-tests/hash.cc +++ b/src/libutil-tests/hash.cc @@ -1,13 +1,17 @@ #include #include +#include #include "nix/util/hash.hh" +#include "nix/util/tests/characterization.hh" namespace nix { -class BLAKE3HashTest : public virtual ::testing::Test +class HashTest : public CharacterizationTest { + std::filesystem::path unitTestData = getUnitTestData() / "hash"; + public: /** @@ -16,8 +20,14 @@ public: */ ExperimentalFeatureSettings mockXpSettings; -private: + std::filesystem::path goldenMaster(std::string_view testStem) const override + { + return unitTestData / testStem; + } +}; +class BLAKE3HashTest : public HashTest +{ void SetUp() override { mockXpSettings.set("experimental-features", "blake3-hashes"); @@ -137,6 +147,46 @@ TEST(hashString, testKnownSHA512Hashes2) "c7d329eeb6dd26545e96e55b874be909"); } +/* ---------------------------------------------------------------------------- + * parsing hashes + * --------------------------------------------------------------------------*/ + +TEST(hashParseExplicitFormatUnprefixed, testKnownSHA256Hashes1_correct) +{ + // values taken from: https://tools.ietf.org/html/rfc4634 + auto s = "abc"; + + auto hash = hashString(HashAlgorithm::SHA256, s); + ASSERT_EQ( + hash, + Hash::parseExplicitFormatUnprefixed( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + HashAlgorithm::SHA256, + HashFormat::Base16)); +} + +TEST(hashParseExplicitFormatUnprefixed, testKnownSHA256Hashes1_wrongAlgo) +{ + // values taken from: https://tools.ietf.org/html/rfc4634 + ASSERT_THROW( + Hash::parseExplicitFormatUnprefixed( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + HashAlgorithm::SHA1, + HashFormat::Base16), + BadHash); +} + +TEST(hashParseExplicitFormatUnprefixed, testKnownSHA256Hashes1_wrongBase) +{ + // values taken from: https://tools.ietf.org/html/rfc4634 + ASSERT_THROW( + Hash::parseExplicitFormatUnprefixed( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + HashAlgorithm::SHA256, + HashFormat::Nix32), + BadHash); +} + /* ---------------------------------------------------------------------------- * parseHashFormat, parseHashFormatOpt, printHashFormat * --------------------------------------------------------------------------*/ diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 220181ed6..6715b8112 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -99,22 +99,37 @@ struct DecodeNamePair } // namespace +static DecodeNamePair baseExplicit(HashFormat format) +{ + switch (format) { + case HashFormat::Base16: + return {base16::decode, "base16"}; + case HashFormat::Nix32: + return {BaseNix32::decode, "nix32"}; + case HashFormat::Base64: + return {base64::decode, "Base64"}; + case HashFormat::SRI: + assert(false); + } +} + /** * Given the expected size of the message once decoded it, figure out * which encoding we are using by looking at the size of the encoded * message. */ -static DecodeNamePair baseFromSize(std::string_view rest, HashAlgorithm algo) +static HashFormat baseFromSize(std::string_view rest, HashAlgorithm algo) { auto hashSize = regularHashSize(algo); + if (rest.size() == base16::encodedLength(hashSize)) - return {base16::decode, "base16"}; + return HashFormat::Base16; if (rest.size() == BaseNix32::encodedLength(hashSize)) - return {BaseNix32::decode, "nix32"}; + return HashFormat::Nix32; if (rest.size() == base64::encodedLength(hashSize)) - return {base64::decode, "Base64"}; + return HashFormat::Base64; throw BadHash("hash '%s' has wrong length for hash algorithm '%s'", rest, printHashAlgo(algo)); } @@ -190,7 +205,7 @@ static Hash parseAnyHelper(std::string_view rest, auto resolveAlgo) } else { /* Otherwise, decide via the length of the hash (for the given algorithm) what base encoding it is. */ - return baseFromSize(rest, algo); + return baseExplicit(baseFromSize(rest, algo)); } }(); @@ -225,7 +240,12 @@ Hash Hash::parseAny(std::string_view original, std::optional optA Hash Hash::parseNonSRIUnprefixed(std::string_view s, HashAlgorithm algo) { - return parseLowLevel(s, algo, baseFromSize(s, algo)); + return parseExplicitFormatUnprefixed(s, algo, baseFromSize(s, algo)); +} + +Hash Hash::parseExplicitFormatUnprefixed(std::string_view s, HashAlgorithm algo, HashFormat format) +{ + return parseLowLevel(s, algo, baseExplicit(format)); } Hash Hash::random(HashAlgorithm algo) diff --git a/src/libutil/include/nix/util/hash.hh b/src/libutil/include/nix/util/hash.hh index f4d137bd0..571b6acca 100644 --- a/src/libutil/include/nix/util/hash.hh +++ b/src/libutil/include/nix/util/hash.hh @@ -90,6 +90,15 @@ struct Hash */ static Hash parseNonSRIUnprefixed(std::string_view s, HashAlgorithm algo); + /** + * Like `parseNonSRIUnprefixed`, but the hash format has been + * explicitly given. + * + * @param explicitFormat cannot be SRI, but must be one of the + * "bases". + */ + static Hash parseExplicitFormatUnprefixed(std::string_view s, HashAlgorithm algo, HashFormat explicitFormat); + static Hash parseSRI(std::string_view original); public: