blob: 04e5dcbe0bc5384f115cebeabfe11ff0578cf4c9 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/lib/digest/digest.h"
#include <stdlib.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <utility>
#include <fbl/span.h>
#include <fbl/string.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/testing/predicates/status.h"
// These unit tests are for the Digest object in ulib/digest.
namespace digest {
namespace {
using ::testing::ElementsAreArray;
// echo -n | sha256sum
const char* kZeroDigest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
// echo -n | sha256sum | cut -c1-64 | tr -d '\n' | xxd -p -r | sha256sum
const char* kDoubleZeroDigest = "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456";
TEST(DigestTest, Strings) {
Digest actual;
size_t len = strlen(kZeroDigest);
// Incorrect length
EXPECT_STATUS(actual.Parse(kZeroDigest, len - 1), ZX_ERR_INVALID_ARGS);
EXPECT_STATUS(actual.Parse(kZeroDigest, len + 1), ZX_ERR_INVALID_ARGS);
// Not hex
char bad[kSha256HexLength + 1];
snprintf(bad, sizeof(bad), "%s", kZeroDigest);
bad[0] = 'g';
EXPECT_STATUS(actual.Parse(bad), ZX_ERR_INVALID_ARGS);
auto ostream_to_string = [](const Digest& d) {
std::stringstream ss;
ss << d;
return ss.str();
};
// Explicit length
EXPECT_OK(actual.Parse(kZeroDigest, len));
EXPECT_EQ(kZeroDigest, actual.ToString());
EXPECT_STREQ(kZeroDigest, ostream_to_string(actual).c_str());
// Implicit length
EXPECT_OK(actual.Parse(kDoubleZeroDigest));
EXPECT_EQ(kDoubleZeroDigest, actual.ToString());
EXPECT_STREQ(kDoubleZeroDigest, ostream_to_string(actual).c_str());
// fbl::String
EXPECT_OK(actual.Parse(fbl::String(kZeroDigest)));
EXPECT_EQ(kZeroDigest, actual.ToString());
EXPECT_STREQ(kZeroDigest, ostream_to_string(actual).c_str());
}
TEST(DigestTest, Zero) {
Digest actual, expected;
ASSERT_OK(expected.Parse(kZeroDigest));
actual.Hash(nullptr, 0);
EXPECT_THAT(fbl::Span(actual.get(), kSha256Length),
ElementsAreArray(expected.get(), kSha256Length));
}
TEST(DigestTest, Self) {
Digest actual, expected;
ASSERT_OK(expected.Parse(kDoubleZeroDigest));
ASSERT_OK(actual.Parse(kZeroDigest));
uint8_t buf[kSha256Length];
actual.CopyTo(buf, sizeof(buf));
actual.Hash(buf, kSha256Length);
EXPECT_THAT(fbl::Span(actual.get(), kSha256Length),
ElementsAreArray(expected.get(), kSha256Length));
}
TEST(DigestTest, Split) {
Digest actual, expected;
actual.Init();
size_t n = strlen(kZeroDigest);
expected.Hash(kZeroDigest, n);
for (size_t i = 1; i < n; ++i) {
actual.Init();
actual.Update(kZeroDigest, i);
actual.Update(kZeroDigest + i, n - i);
actual.Final();
EXPECT_THAT(fbl::Span(actual.get(), kSha256Length),
ElementsAreArray(expected.get(), kSha256Length));
}
}
TEST(DigestTest, Equality) {
Digest actual, expected;
ASSERT_OK(expected.Parse(kZeroDigest));
ASSERT_OK(actual.Parse(kZeroDigest));
EXPECT_FALSE(actual.Equals(nullptr, actual.len())) << "Does not equal NULL";
EXPECT_FALSE(actual.Equals(actual.get(), actual.len() - 1)) << "Does not equal length-1";
EXPECT_TRUE(actual.Equals(actual.get(), actual.len())) << "Equals self";
EXPECT_TRUE(actual.Equals(expected.get(), expected.len())) << "Equals expected";
EXPECT_TRUE(actual == actual) << "Equals self";
EXPECT_TRUE(actual == expected) << "Equals expected";
EXPECT_FALSE(actual != actual) << "Doesn't not equal self";
EXPECT_FALSE(actual != expected) << "Doesn't not equal expected";
}
TEST(DigestTest, Less) {
Digest null_digest;
EXPECT_FALSE(null_digest < null_digest);
uint8_t one[kSha256Length];
memset(one, 0, kSha256Length);
one[kSha256Length - 1] = 1;
Digest digest_one(one);
uint8_t two[kSha256Length];
memset(two, 0, kSha256Length);
two[kSha256Length - 1] = 2;
Digest digest_two(two);
EXPECT_TRUE(digest_one < digest_two);
EXPECT_FALSE(digest_two < digest_one);
}
TEST(DigestTest, CopyTo) {
Digest actual;
uint8_t buf[kSha256Length * 2];
memset(buf, 1, sizeof(buf));
ASSERT_OK(actual.Parse(kZeroDigest));
// CopyTo uses ZX_DEBUG_ASSERT and won't crash in release builds. This test should
// only run when ZX_DEBUG_ASSERT is implemented.
if constexpr (ZX_DEBUG_ASSERT_IMPLEMENTED) {
ASSERT_DEATH({ actual.CopyTo(buf, kSha256Length - 1); }, "") << "Disallow truncation";
}
for (size_t len = 0; len < sizeof(buf); ++len) {
actual.CopyTruncatedTo(buf, len);
// First bytes match digest.
EXPECT_EQ(memcmp(buf, actual.get(), std::min(len, kSha256Length)), 0);
// Pad with zeros up to |len|.
for (size_t i = kSha256Length; i < len; ++i) {
EXPECT_EQ(buf[i], 0);
}
// Remaining bytes are untouched.
for (size_t i = len; i < sizeof(buf); ++i) {
EXPECT_EQ(buf[i], 1);
}
}
}
TEST(DigestTest, Copy) {
Digest uninitialized_digest;
Digest digest1;
digest1.Init();
digest1.Update("data", 4); // Hash this string.
digest1.Final();
EXPECT_NE(uninitialized_digest, digest1);
// Test copy constructor
Digest digest2(digest1);
EXPECT_EQ(digest2, digest1);
EXPECT_NE(uninitialized_digest, digest2);
// Test assignment to empty.
digest2 = uninitialized_digest;
EXPECT_EQ(uninitialized_digest, digest2);
// Test assignment to nonempty.
digest2 = digest1;
EXPECT_EQ(digest2, digest1);
EXPECT_NE(uninitialized_digest, digest2);
}
TEST(DigestTest, Move) {
const Digest uninitialized_digest;
Digest digest1;
{
// Verify that digest1 is not valid, and that it's current digest value
// is all zeros. Verify that when move digest1 into digest2, that
// both retain this property (not valid, digest full of zeros)
EXPECT_EQ(digest1, uninitialized_digest);
Digest digest2(std::move(digest1));
EXPECT_THAT(fbl::Span(digest1.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
EXPECT_THAT(fbl::Span(digest2.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
}
// Start a hash operation in digest1, verify that this does not update the
// initial hash value.
digest1.Init();
EXPECT_THAT(fbl::Span(digest1.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
// Hash some nothing into the hash. Again verify the digest is still
// valid, but that the internal result is still full of nothing.
digest1.Update(nullptr, 0);
EXPECT_THAT(fbl::Span(digest1.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
// Move the hash into digest2. Verify that the context goes with the move
// operation.
Digest digest2(std::move(digest1));
EXPECT_THAT(fbl::Span(digest1.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
// Finish the hash operation started in digest1 which was moved into
// digest2. Verify that digest2 is no longer valid, but that the result is
// what we had expected.
Digest zero_digest;
ASSERT_OK(zero_digest.Parse(kZeroDigest));
digest2.Final();
EXPECT_THAT(fbl::Span(digest2.get(), kSha256Length),
ElementsAreArray(zero_digest.get(), kSha256Length));
// Move the result of the hash into a new digest3. Verify that neither is
// valid, but that the result was properly moved.
Digest digest3(std::move(digest2));
EXPECT_THAT(fbl::Span(digest2.get(), kSha256Length),
ElementsAreArray(uninitialized_digest.get(), kSha256Length));
EXPECT_THAT(fbl::Span(digest3.get(), kSha256Length),
ElementsAreArray(zero_digest.get(), kSha256Length));
}
} // namespace
} // namespace digest