Skip to content

Commit

Permalink
Add intermediate digest computation to Hash_SHA256_Stream (#8496)
Browse files Browse the repository at this point in the history
* Add intermediate digest computation to Hash_SHA256_Stream

- Add GetDigest() to Hash_SHA256_Stream and associated tests
- Make Hash_SHA256_Stream work with safe spans (partial fix
  to #4189)
- Fix all usages of Hash_SHA256_Stream
- Drive-by simplification of hex conversion in Rotating ID

Testing done:

- Cert tests (test_suite.sh) for both mbedTLS and OpenSSL
- gn check on host
- cross-validation check of rotating ID with the following
  Python snippet:

```
  >>> to_hash = b"TEST_SN" + b"\x00\x00"
  >>> print(to_octet_string(b"\x00\x00" + CHIP_Crypto_Hash(to_hash)[-16:]).replace(":","").upper())
  00007C5F6E176CD40F68685D100A1CF8A98B
```

This was verified against cert tests output:
```
[1626748850.369690][132218:132218] CHIP:DL: Device Configuration:
[1626748850.369695][132218:132218] CHIP:DL:   Serial Number: TEST_SN
[1626748850.369699][132218:132218] CHIP:DL:   Vendor Id: 9050 (0x235A)
[1626748850.369702][132218:132218] CHIP:DL:   Product Id: 65279 (0xFEFF)
[1626748850.369706][132218:132218] CHIP:DL:   Product Revision: 1
[1626748850.369709][132218:132218] CHIP:DL:   Setup Pin Code: 20202021
[1626748850.369713][132218:132218] CHIP:DL:   Setup Discriminator: 3840 (0xF00)
[1626748850.369716][132218:132218] CHIP:DL:   Manufacturing Date: (not set)
[1626748850.369720][132218:132218] CHIP:DL:   Device Type: 65535 (0xFFFF)
[1626748850.369732][132218:132218] CHIP:SVR: SetupQRCode: [MT:YNJV7VSC00KA0648G00]
.............................
[1626748850.370936][132218:132218] CHIP:DL: rotatingDeviceId: 00007C5F6E176CD40F68685D100A1CF8A98B
```

Issue #4189
Fixes #8495

* Restyled by clang-format

* Fix for scripts/build test failure

* Fix build on ESP32, apply review comments

* Apply review comment from @emargolis

Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Switi Mhaiske <switi.mhaiske@espressif.com>
  • Loading branch information
3 people authored and pull[bot] committed Sep 11, 2021
1 parent b9fd312 commit 1551897
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 60 deletions.
7 changes: 3 additions & 4 deletions src/credentials/GenerateChipX509Cert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ namespace chip {
namespace Credentials {

using namespace chip::ASN1;
using namespace chip::Crypto;
using namespace chip::Protocols;

namespace {
Expand All @@ -58,8 +59,6 @@ enum IsCACert
kNotCACert,
};

constexpr uint8_t kSHA1_Hash_Langth = 20;

CHIP_ERROR EncodeSubjectPublicKeyInfo(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Expand Down Expand Up @@ -95,7 +94,7 @@ CHIP_ERROR EncodeAuthorityKeyIdentifierExtension(const Crypto::P256PublicKey & p
{
ASN1_START_SEQUENCE
{
uint8_t keyid[kSHA1_Hash_Langth];
uint8_t keyid[kSHA1_Hash_Length];
ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid));

ReturnErrorOnFailure(
Expand Down Expand Up @@ -123,7 +122,7 @@ CHIP_ERROR EncodeSubjectKeyIdentifierExtension(const Crypto::P256PublicKey & pub

ASN1_START_OCTET_STRING_ENCAPSULATED
{
uint8_t keyid[kSHA1_Hash_Langth];
uint8_t keyid[kSHA1_Hash_Length];
ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid));

ReturnErrorOnFailure(writer.PutOctetString(keyid, static_cast<uint8_t>(sizeof(keyid))));
Expand Down
5 changes: 3 additions & 2 deletions src/crypto/CHIPCryptoPAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,13 +472,14 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::InitImpl()

CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::Hash(const uint8_t * in, size_t in_len)
{
ReturnErrorOnFailure(sha256_hash_ctx.AddData(in, in_len));
ReturnErrorOnFailure(sha256_hash_ctx.AddData(ByteSpan{ in, in_len }));
return CHIP_NO_ERROR;
}

CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::HashFinalize(uint8_t * out)
{
ReturnErrorOnFailure(sha256_hash_ctx.Finish(out));
MutableByteSpan out_span(out, kSHA256_Hash_Length);
ReturnErrorOnFailure(sha256_hash_ctx.Finish(out_span));
return CHIP_NO_ERROR;
}

Expand Down
62 changes: 59 additions & 3 deletions src/crypto/CHIPCryptoPAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ constexpr size_t kP256_FE_Length = 32;
constexpr size_t kP256_ECDSA_Signature_Length_Raw = (2 * kP256_FE_Length);
constexpr size_t kP256_Point_Length = (2 * kP256_FE_Length + 1);
constexpr size_t kSHA256_Hash_Length = 32;
constexpr size_t kSHA1_Hash_Length = 20;

constexpr size_t CHIP_CRYPTO_GROUP_SIZE_BYTES = kP256_FE_Length;
constexpr size_t CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES = kP256_Point_Length;
Expand All @@ -66,12 +67,24 @@ constexpr size_t kP256_PublicKey_Length = CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES;
* the implementation files.
*/
constexpr size_t kMAX_Spake2p_Context_Size = 1024;
constexpr size_t kMAX_Hash_SHA256_Context_Size = 296;
constexpr size_t kMAX_P256Keypair_Context_Size = 512;

constexpr size_t kEmitDerIntegerWithoutTagOverhead = 1; // 1 sign stuffer
constexpr size_t kEmitDerIntegerOverhead = 3; // Tag + Length byte + 1 sign stuffer

/*
* Worst case is OpenSSL, so let's use its worst case and let static assert tell us if
* we are wrong, since `typedef SHA_LONG unsigned int` is default.
* SHA_LONG h[8];
* SHA_LONG Nl, Nh;
* SHA_LONG data[SHA_LBLOCK]; // SHA_LBLOCK is 16 for SHA256
* unsigned int num, md_len;
*
* We also have to account for possibly some custom extensions on some targets,
* especially for mbedTLS, so an extra sizeof(uint64_t) is added to account.
*/
constexpr size_t kMAX_Hash_SHA256_Context_Size = ((sizeof(unsigned int) * (8 + 2 + 16 + 2)) + sizeof(uint64_t));

/*
* Overhead to encode a raw ECDSA signature in X9.62 format in ASN.1 DER
*
Expand Down Expand Up @@ -509,9 +522,52 @@ class Hash_SHA256_stream
Hash_SHA256_stream();
~Hash_SHA256_stream();

/**
* @brief Re-initialize digest computation to an empty context.
*
* @return CHIP_ERROR_INTERNAL on failure to initialize the context,
* CHIP_NO_ERROR otherwise.
*/
CHIP_ERROR Begin();
CHIP_ERROR AddData(const uint8_t * data, size_t data_length);
CHIP_ERROR Finish(uint8_t * out_buffer);

/**
* @brief Add some data to the digest computation, updating internal state.
*
* @param[in] data The span of bytes to include in the digest update process.
*
* @return CHIP_ERROR_INTERNAL on failure to ingest the data, CHIP_NO_ERROR otherwise.
*/
CHIP_ERROR AddData(const ByteSpan data);

/**
* @brief Get the intermediate padded digest for the current state of the stream.
*
* More data can be added before finish is called.
*
* @param[inout] out_buffer Output buffer to receive the digest. `out_buffer` must
* be at least `kSHA256_Hash_Length` bytes long. The `out_buffer` size
* will be set to `kSHA256_Hash_Length` on success.
*
* @return CHIP_ERROR_INTERNAL on failure to compute the digest, CHIP_ERROR_BUFFER_TOO_SMALL
* if out_buffer is too small, CHIP_NO_ERROR otherwise.
*/
CHIP_ERROR GetDigest(MutableByteSpan & out_buffer);

/**
* @brief Finalize the stream digest computation, getting the final digest.
*
* @param[inout] out_buffer Output buffer to receive the digest. `out_buffer` must
* be at least `kSHA256_Hash_Length` bytes long. The `out_buffer` size
* will be set to `kSHA256_Hash_Length` on success.
*
* @return CHIP_ERROR_INTERNAL on failure to compute the digest, CHIP_ERROR_BUFFER_TOO_SMALL
* if out_buffer is too small, CHIP_NO_ERROR otherwise.
*/
CHIP_ERROR Finish(MutableByteSpan & out_buffer);

/**
* @brief Clear-out internal digest data to avoid lingering the state.
*/
void Clear();

private:
Expand Down
38 changes: 31 additions & 7 deletions src/crypto/CHIPCryptoPALOpenSSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,13 @@ CHIP_ERROR Hash_SHA1(const uint8_t * data, const size_t data_length, uint8_t * o

Hash_SHA256_stream::Hash_SHA256_stream() {}

Hash_SHA256_stream::~Hash_SHA256_stream() {}
Hash_SHA256_stream::~Hash_SHA256_stream()
{
Clear();
}

static_assert(kMAX_Hash_SHA256_Context_Size >= sizeof(SHA256_CTX),
"kMAX_Hash_SHA256_Context_Size is too small for the size of underlying SHA256_CTX");

static inline SHA256_CTX * to_inner_hash_sha256_context(HashSHA256OpaqueContext * context)
{
Expand All @@ -321,29 +327,47 @@ CHIP_ERROR Hash_SHA256_stream::Begin()
return CHIP_NO_ERROR;
}

CHIP_ERROR Hash_SHA256_stream::AddData(const uint8_t * data, const size_t data_length)
CHIP_ERROR Hash_SHA256_stream::AddData(const ByteSpan data)
{
SHA256_CTX * const context = to_inner_hash_sha256_context(&mContext);

const int result = SHA256_Update(context, Uint8::to_const_uchar(data), data_length);
const int result = SHA256_Update(context, Uint8::to_const_uchar(data.data()), data.size());
VerifyOrReturnError(result == 1, CHIP_ERROR_INTERNAL);

return CHIP_NO_ERROR;
}

CHIP_ERROR Hash_SHA256_stream::Finish(uint8_t * out_buffer)
CHIP_ERROR Hash_SHA256_stream::GetDigest(MutableByteSpan & out_buffer)
{
SHA256_CTX * const context = to_inner_hash_sha256_context(&mContext);
SHA256_CTX * context = to_inner_hash_sha256_context(&mContext);

// Back-up context as we are about to finalize the hash to extract digest.
SHA256_CTX previous_ctx = *context;

// Pad + compute digest, then finalize context. It is restored next line to continue.
CHIP_ERROR result = Finish(out_buffer);

const int result = SHA256_Final(Uint8::to_uchar(out_buffer), context);
// Restore context prior to finalization.
*context = previous_ctx;

return result;
}

CHIP_ERROR Hash_SHA256_stream::Finish(MutableByteSpan & out_buffer)
{
VerifyOrReturnError(out_buffer.size() >= kSHA256_Hash_Length, CHIP_ERROR_BUFFER_TOO_SMALL);

SHA256_CTX * const context = to_inner_hash_sha256_context(&mContext);
const int result = SHA256_Final(Uint8::to_uchar(out_buffer.data()), context);
VerifyOrReturnError(result == 1, CHIP_ERROR_INTERNAL);
out_buffer = out_buffer.SubSpan(0, kSHA256_Hash_Length);

return CHIP_NO_ERROR;
}

void Hash_SHA256_stream::Clear()
{
memset(this, 0, sizeof(*this));
OPENSSL_cleanse(this, sizeof(*this));
}

CHIP_ERROR HKDF_sha::HKDF_SHA256(const uint8_t * secret, const size_t secret_length, const uint8_t * salt, const size_t salt_length,
Expand Down
36 changes: 30 additions & 6 deletions src/crypto/CHIPCryptoPALmbedTLS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,13 @@ CHIP_ERROR Hash_SHA1(const uint8_t * data, const size_t data_length, uint8_t * o

Hash_SHA256_stream::Hash_SHA256_stream(void) {}

Hash_SHA256_stream::~Hash_SHA256_stream(void) {}
Hash_SHA256_stream::~Hash_SHA256_stream(void)
{
Clear();
}

static_assert(kMAX_Hash_SHA256_Context_Size >= sizeof(mbedtls_sha256_context),
"kMAX_Hash_SHA256_Context_Size is too small for the size of underlying mbedtls_sha256_context");

static inline mbedtls_sha256_context * to_inner_hash_sha256_context(HashSHA256OpaqueContext * context)
{
Expand All @@ -221,29 +227,47 @@ CHIP_ERROR Hash_SHA256_stream::Begin(void)
return CHIP_NO_ERROR;
}

CHIP_ERROR Hash_SHA256_stream::AddData(const uint8_t * data, const size_t data_length)
CHIP_ERROR Hash_SHA256_stream::AddData(const ByteSpan data)
{
mbedtls_sha256_context * const context = to_inner_hash_sha256_context(&mContext);

const int result = mbedtls_sha256_update_ret(context, Uint8::to_const_uchar(data), data_length);
const int result = mbedtls_sha256_update_ret(context, Uint8::to_const_uchar(data.data()), data.size());
VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL);

return CHIP_NO_ERROR;
}

CHIP_ERROR Hash_SHA256_stream::Finish(uint8_t * out_buffer)
CHIP_ERROR Hash_SHA256_stream::GetDigest(MutableByteSpan & out_buffer)
{
mbedtls_sha256_context * context = to_inner_hash_sha256_context(&mContext);

// Back-up context as we are about to finalize the hash to extract digest.
mbedtls_sha256_context previous_ctx = *context;

// Pad + compute digest, then finalize context. It is restored next line to continue.
CHIP_ERROR result = Finish(out_buffer);

// Restore context prior to finalization.
*context = previous_ctx;

return result;
}

CHIP_ERROR Hash_SHA256_stream::Finish(MutableByteSpan & out_buffer)
{
VerifyOrReturnError(out_buffer.size() >= kSHA256_Hash_Length, CHIP_ERROR_BUFFER_TOO_SMALL);
mbedtls_sha256_context * const context = to_inner_hash_sha256_context(&mContext);

const int result = mbedtls_sha256_finish_ret(context, Uint8::to_uchar(out_buffer));
const int result = mbedtls_sha256_finish_ret(context, Uint8::to_uchar(out_buffer.data()));
VerifyOrReturnError(result == 0, CHIP_ERROR_INTERNAL);
out_buffer = out_buffer.SubSpan(0, kSHA256_Hash_Length);

return CHIP_NO_ERROR;
}

void Hash_SHA256_stream::Clear(void)
{
memset(this, 0, sizeof(*this));
mbedtls_platform_zeroize(this, sizeof(*this));
}

CHIP_ERROR HKDF_sha::HKDF_SHA256(const uint8_t * secret, const size_t secret_length, const uint8_t * salt, const size_t salt_length,
Expand Down
93 changes: 89 additions & 4 deletions src/crypto/tests/CHIPCryptoPALTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,23 +766,108 @@ static void TestHash_SHA256_Stream(nlTestSuite * inSuite, void * inContext)
{
size_t rand_data_length = static_cast<unsigned int>(rand()) % (data_length + 1);

error = sha256.AddData(data, rand_data_length);
error = sha256.AddData(ByteSpan{ data, rand_data_length });
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);

data += rand_data_length;
data_length -= rand_data_length;
}

error = sha256.AddData(data, data_length);
error = sha256.AddData(ByteSpan{ data, data_length });
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);

error = sha256.Finish(out_buffer);
MutableByteSpan out_span(out_buffer);
error = sha256.Finish(out_span);
NL_TEST_ASSERT(inSuite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, out_span.size() == kSHA256_Hash_Length);

bool success = memcmp(v.hash, out_buffer, sizeof(out_buffer)) == 0;
bool success = memcmp(v.hash, out_span.data(), out_span.size()) == 0;
NL_TEST_ASSERT(inSuite, success);
}

NL_TEST_ASSERT(inSuite, numOfTestsExecuted == ArraySize(hash_sha256_test_vectors));

// Test partial digests
uint8_t source_buf[2 * kSHA256_Hash_Length];

// Use a basic counter for all data
for (size_t idx = 0; idx < sizeof(source_buf); idx++)
{
source_buf[idx] = static_cast<uint8_t>(idx & 0xFFu);
}

// Use split blocks of every length including digest length, to cover
// all padding cases.
for (size_t block1_size = 1; block1_size <= kSHA256_Hash_Length; block1_size++)
{
for (size_t block2_size = 1; block2_size <= kSHA256_Hash_Length; block2_size++)
{
uint8_t partial_digest1[kSHA256_Hash_Length];
uint8_t partial_digest2[kSHA256_Hash_Length];
uint8_t partial_digest_ref[kSHA256_Hash_Length];
uint8_t total_digest[kSHA256_Hash_Length];
uint8_t total_digest_ref[kSHA256_Hash_Length];
MutableByteSpan partial_digest_span1(partial_digest1);
MutableByteSpan partial_digest_span2(partial_digest2);
MutableByteSpan total_digest_span(total_digest);

Hash_SHA256_stream sha256;
NL_TEST_ASSERT(inSuite, sha256.Begin() == CHIP_NO_ERROR);

// Compute partial digest after first block
NL_TEST_ASSERT(inSuite, sha256.AddData(ByteSpan{ &source_buf[0], block1_size }) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, sha256.GetDigest(partial_digest_span1) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, partial_digest_span1.size() == kSHA256_Hash_Length);

// Validate partial digest matches expectations
Hash_SHA256(&source_buf[0], block1_size, &partial_digest_ref[0]);
NL_TEST_ASSERT(inSuite, 0 == memcmp(partial_digest_span1.data(), partial_digest_ref, partial_digest_span1.size()));

// Compute partial digest and total digest after second block
NL_TEST_ASSERT(inSuite, sha256.AddData(ByteSpan{ &source_buf[block1_size], block2_size }) == CHIP_NO_ERROR);

NL_TEST_ASSERT(inSuite, sha256.GetDigest(partial_digest_span2) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, partial_digest_span2.size() == kSHA256_Hash_Length);

NL_TEST_ASSERT(inSuite, sha256.Finish(total_digest_span) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, total_digest_span.size() == kSHA256_Hash_Length);

// Validate second partial digest matches final digest
Hash_SHA256(&source_buf[0], block1_size + block2_size, &total_digest_ref[0]);
NL_TEST_ASSERT(inSuite, 0 == memcmp(partial_digest_span2.data(), total_digest_ref, partial_digest_span2.size()));
NL_TEST_ASSERT(inSuite, 0 == memcmp(total_digest_span.data(), total_digest_ref, total_digest_span.size()));
}
}

// Validate error cases
{
uint8_t source_buf2[5] = { 1, 2, 3, 4, 5 };
uint8_t digest_buf_too_small[kSHA256_Hash_Length - 1];
uint8_t digest_buf_ok[kSHA256_Hash_Length];
uint8_t digest_buf_ref[kSHA256_Hash_Length];
MutableByteSpan digest_span_too_small(digest_buf_too_small);
MutableByteSpan digest_span_ok(digest_buf_ok);

Hash_SHA256(&source_buf2[0], sizeof(source_buf2), &digest_buf_ref[0]);

Hash_SHA256_stream sha256;
NL_TEST_ASSERT(inSuite, sha256.Begin() == CHIP_NO_ERROR);

NL_TEST_ASSERT(inSuite, sha256.AddData(ByteSpan{ source_buf2 }) == CHIP_NO_ERROR);

// Check that error behavior works on buffer too small
NL_TEST_ASSERT(inSuite, sha256.GetDigest(digest_span_too_small) == CHIP_ERROR_BUFFER_TOO_SMALL);
NL_TEST_ASSERT(inSuite, sha256.Finish(digest_span_too_small) == CHIP_ERROR_BUFFER_TOO_SMALL);

// Check that both GetDigest/Finish can still work after error.
NL_TEST_ASSERT(inSuite, sha256.GetDigest(digest_span_ok) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 0 == memcmp(digest_span_ok.data(), digest_buf_ref, digest_span_ok.size()));

memset(digest_buf_ok, 0, sizeof(digest_buf_ok));

NL_TEST_ASSERT(inSuite, sha256.Finish(digest_span_ok) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 0 == memcmp(digest_span_ok.data(), digest_buf_ref, digest_span_ok.size()));
}
}

static void TestHMAC_SHA256(nlTestSuite * inSuite, void * inContext)
Expand Down
Loading

0 comments on commit 1551897

Please sign in to comment.