1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #pragma once
16
17 #include <cstdint>
18
19 #include "pw_bytes/span.h"
20 #include "pw_crypto/sha256_backend.h"
21 #include "pw_log/log.h"
22 #include "pw_status/status.h"
23 #include "pw_status/try.h"
24 #include "pw_stream/stream.h"
25
26 namespace pw::crypto::sha256 {
27
28 // Size in bytes of a SHA256 digest.
29 constexpr uint32_t kDigestSizeBytes = 32;
30
31 // State machine of a hashing session.
32 enum class Sha256State {
33 // Initialized and accepting input (via Update()).
34 kReady = 1,
35
36 // Finalized by Final(). Any additional requests, Update() or Final(), will
37 // trigger a transition to kError.
38 kFinalized = 2,
39
40 // In an unrecoverable error state.
41 kError = 3,
42 };
43
44 namespace backend {
45
46 // Primitive operations to be implemented by backends.
47 Status DoInit(NativeSha256Context& ctx);
48 Status DoUpdate(NativeSha256Context& ctx, ConstByteSpan data);
49 Status DoFinal(NativeSha256Context& ctx, ByteSpan out_digest);
50
51 } // namespace backend
52
53 // Sha256 computes the SHA256 digest of potentially long, non-contiguous input
54 // messages.
55 //
56 // Usage:
57 //
58 // if (!Sha256().Update(message).Update(more_message).Final(out_digest).ok()) {
59 // // Error handling.
60 // }
61 class Sha256 {
62 public:
Sha256()63 Sha256() {
64 if (!backend::DoInit(native_ctx_).ok()) {
65 PW_LOG_DEBUG("backend::DoInit() failed");
66 state_ = Sha256State::kError;
67 return;
68 }
69
70 state_ = Sha256State::kReady;
71 }
72
73 // Update feeds `data` to the running hasher. The feeding can involve zero
74 // or more `Update()` calls and the order matters.
Update(ConstByteSpan data)75 Sha256& Update(ConstByteSpan data) {
76 if (state_ != Sha256State::kReady) {
77 PW_LOG_DEBUG("The backend is not ready/initialized");
78 return *this;
79 }
80
81 if (!backend::DoUpdate(native_ctx_, data).ok()) {
82 PW_LOG_DEBUG("backend::DoUpdate() failed");
83 state_ = Sha256State::kError;
84 return *this;
85 }
86
87 return *this;
88 }
89
90 // Final wraps up the hashing session and outputs the final digest in the
91 // first `kDigestSizeBytes` of `out_digest`. `out_digest` must be at least
92 // `kDigestSizeBytes` long.
93 //
94 // Final locks down the Sha256 instance from any additional use.
95 //
96 // Any error, including those occurr inside `Init()` or `Update()` will be
97 // reflected in the return value of Final();
Final(ByteSpan out_digest)98 Status Final(ByteSpan out_digest) {
99 if (out_digest.size() < kDigestSizeBytes) {
100 PW_LOG_DEBUG("Digest output buffer is too small");
101 state_ = Sha256State::kError;
102 return Status::InvalidArgument();
103 }
104
105 if (state_ != Sha256State::kReady) {
106 PW_LOG_DEBUG("The backend is not ready/initialized");
107 return Status::FailedPrecondition();
108 }
109
110 auto status = backend::DoFinal(native_ctx_, out_digest);
111 if (!status.ok()) {
112 PW_LOG_DEBUG("backend::DoFinal() failed");
113 state_ = Sha256State::kError;
114 return status;
115 }
116
117 state_ = Sha256State::kFinalized;
118 return OkStatus();
119 }
120
121 private:
122 // Common hasher state. Tracked by the front-end.
123 Sha256State state_;
124 // Backend-specific context.
125 backend::NativeSha256Context native_ctx_;
126 };
127
128 // Hash calculates the SHA256 digest of `message` and stores the result
129 // in `out_digest`. `out_digest` must be at least `kDigestSizeBytes` long.
Hash(ConstByteSpan message,ByteSpan out_digest)130 inline Status Hash(ConstByteSpan message, ByteSpan out_digest) {
131 return Sha256().Update(message).Final(out_digest);
132 }
133
Hash(stream::Reader & reader,ByteSpan out_digest)134 inline Status Hash(stream::Reader& reader, ByteSpan out_digest) {
135 if (out_digest.size() < kDigestSizeBytes) {
136 return Status::InvalidArgument();
137 }
138
139 Sha256 sha256;
140 while (true) {
141 Result<ByteSpan> res = reader.Read(out_digest);
142 if (res.status().IsOutOfRange()) {
143 break;
144 }
145
146 PW_TRY(res.status());
147 sha256.Update(res.value());
148 }
149
150 return sha256.Final(out_digest);
151 }
152
153 } // namespace pw::crypto::sha256
154