blob: d2e7d97a3cc9a688d0c606135460fa4c361e6fe3 [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <cstdint>
#include "pw_bytes/span.h"
#include "pw_crypto/sha256_backend.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_stream/stream.h"
namespace pw::crypto::sha256 {
/// The size of a SHA256 digest in bytes.
constexpr uint32_t kDigestSizeBytes = 32;
/// A state machine of a hashing session.
enum class Sha256State {
/// Initialized and accepting input (via `Update()`).
kReady = 1,
/// Finalized by `Final()`. Any additional requests to `Update()` or `Final()`
/// will trigger a transition to `kError`.
kFinalized = 2,
/// In an unrecoverable error state.
kError = 3,
};
namespace backend {
// Primitive operations to be implemented by backends.
Status DoInit(NativeSha256Context& ctx);
Status DoUpdate(NativeSha256Context& ctx, ConstByteSpan data);
Status DoFinal(NativeSha256Context& ctx, ByteSpan out_digest);
} // namespace backend
/// Computes the SHA256 digest of potentially long, non-contiguous input
/// messages.
///
/// Usage:
///
/// @code{.cpp}
/// if (!Sha256().Update(message).Update(more_message).Final(out_digest).ok()) {
/// // Error handling.
/// }
/// @endcode
class Sha256 {
public:
Sha256() {
if (!backend::DoInit(native_ctx_).ok()) {
PW_LOG_DEBUG("backend::DoInit() failed");
state_ = Sha256State::kError;
return;
}
state_ = Sha256State::kReady;
}
/// Feeds `data` to the running hasher. The feeding can involve zero
/// or more `Update()` calls and the order matters.
Sha256& Update(ConstByteSpan data) {
if (state_ != Sha256State::kReady) {
PW_LOG_DEBUG("The backend is not ready/initialized");
return *this;
}
if (!backend::DoUpdate(native_ctx_, data).ok()) {
PW_LOG_DEBUG("backend::DoUpdate() failed");
state_ = Sha256State::kError;
return *this;
}
return *this;
}
/// Finishes the hashing session and outputs the final digest in the
/// first `kDigestSizeBytes` of `out_digest`. `out_digest` must be at least
/// `kDigestSizeBytes` long.
///
/// `Final()` locks down the `Sha256` instance from any additional use.
///
/// Any error, including those occurring inside the constructor or `Update()`
/// will be reflected in the return value of `Final()`.
Status Final(ByteSpan out_digest) {
if (out_digest.size() < kDigestSizeBytes) {
PW_LOG_DEBUG("Digest output buffer is too small");
state_ = Sha256State::kError;
return Status::InvalidArgument();
}
if (state_ != Sha256State::kReady) {
PW_LOG_DEBUG("The backend is not ready/initialized");
return Status::FailedPrecondition();
}
auto status = backend::DoFinal(native_ctx_, out_digest);
if (!status.ok()) {
PW_LOG_DEBUG("backend::DoFinal() failed");
state_ = Sha256State::kError;
return status;
}
state_ = Sha256State::kFinalized;
return OkStatus();
}
private:
// Common hasher state. Tracked by the front-end.
Sha256State state_;
// Backend-specific context.
backend::NativeSha256Context native_ctx_;
};
/// Calculates the SHA256 digest of `message` and stores the result
/// in `out_digest`. `out_digest` must be at least `kDigestSizeBytes` long.
///
/// One-shot digest example:
///
/// @code{.cpp}
/// #include "pw_crypto/sha256.h"
///
/// std::byte digest[32];
/// if (!pw::crypto::sha256::Hash(message, digest).ok()) {
/// // Handle errors.
/// }
///
/// // The content can also come from a pw::stream::Reader.
/// if (!pw::crypto::sha256::Hash(reader, digest).ok()) {
/// // Handle errors.
/// }
/// @endcode
///
/// Long, potentially non-contiguous message example:
///
/// @code{.cpp}
/// #include "pw_crypto/sha256.h"
///
/// std::byte digest[32];
///
/// if (!pw::crypto::sha256::Sha256()
/// .Update(chunk1).Update(chunk2).Update(chunk...)
/// .Final().ok()) {
/// // Handle errors.
/// }
/// @endcode
inline Status Hash(ConstByteSpan message, ByteSpan out_digest) {
return Sha256().Update(message).Final(out_digest);
}
inline Status Hash(stream::Reader& reader, ByteSpan out_digest) {
if (out_digest.size() < kDigestSizeBytes) {
return Status::InvalidArgument();
}
Sha256 sha256;
while (true) {
Result<ByteSpan> res = reader.Read(out_digest);
if (res.status().IsOutOfRange()) {
break;
}
PW_TRY(res.status());
sha256.Update(res.value());
}
return sha256.Final(out_digest);
}
} // namespace pw::crypto::sha256