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 /// The size of a SHA256 digest in bytes.
29 constexpr uint32_t kDigestSizeBytes = 32;
30
31 /// A 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 to `Update()` or `Final()`
37 /// will 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 /// Computes the SHA256 digest of potentially long, non-contiguous input
54 /// messages.
55 ///
56 /// Usage:
57 ///
58 /// @code{.cpp}
59 /// if (!Sha256().Update(message).Update(more_message).Final(out_digest).ok()) {
60 /// // Error handling.
61 /// }
62 /// @endcode
63 class Sha256 {
64 public:
Sha256()65 Sha256() {
66 if (!backend::DoInit(native_ctx_).ok()) {
67 PW_LOG_DEBUG("backend::DoInit() failed");
68 state_ = Sha256State::kError;
69 return;
70 }
71
72 state_ = Sha256State::kReady;
73 }
74
75 /// Feeds `data` to the running hasher. The feeding can involve zero
76 /// or more `Update()` calls and the order matters.
Update(ConstByteSpan data)77 Sha256& Update(ConstByteSpan data) {
78 if (state_ != Sha256State::kReady) {
79 PW_LOG_DEBUG("The backend is not ready/initialized");
80 return *this;
81 }
82
83 if (!backend::DoUpdate(native_ctx_, data).ok()) {
84 PW_LOG_DEBUG("backend::DoUpdate() failed");
85 state_ = Sha256State::kError;
86 return *this;
87 }
88
89 return *this;
90 }
91
92 /// Finishes the hashing session and outputs the final digest in the
93 /// first `kDigestSizeBytes` of `out_digest`. `out_digest` must be at least
94 /// `kDigestSizeBytes` long.
95 ///
96 /// `Final()` locks down the `Sha256` instance from any additional use.
97 ///
98 /// Any error, including those occurring inside the constructor or `Update()`
99 /// will be reflected in the return value of `Final()`.
Final(ByteSpan out_digest)100 Status Final(ByteSpan out_digest) {
101 if (out_digest.size() < kDigestSizeBytes) {
102 PW_LOG_DEBUG("Digest output buffer is too small");
103 state_ = Sha256State::kError;
104 return Status::InvalidArgument();
105 }
106
107 if (state_ != Sha256State::kReady) {
108 PW_LOG_DEBUG("The backend is not ready/initialized");
109 return Status::FailedPrecondition();
110 }
111
112 auto status = backend::DoFinal(native_ctx_, out_digest);
113 if (!status.ok()) {
114 PW_LOG_DEBUG("backend::DoFinal() failed");
115 state_ = Sha256State::kError;
116 return status;
117 }
118
119 state_ = Sha256State::kFinalized;
120 return OkStatus();
121 }
122
123 private:
124 // Common hasher state. Tracked by the front-end.
125 Sha256State state_;
126 // Backend-specific context.
127 backend::NativeSha256Context native_ctx_;
128 };
129
130 /// Calculates the SHA256 digest of `message` and stores the result
131 /// in `out_digest`. `out_digest` must be at least `kDigestSizeBytes` long.
132 ///
133 /// One-shot digest example:
134 ///
135 /// @code{.cpp}
136 /// #include "pw_crypto/sha256.h"
137 ///
138 /// std::byte digest[32];
139 /// if (!pw::crypto::sha256::Hash(message, digest).ok()) {
140 /// // Handle errors.
141 /// }
142 ///
143 /// // The content can also come from a pw::stream::Reader.
144 /// if (!pw::crypto::sha256::Hash(reader, digest).ok()) {
145 /// // Handle errors.
146 /// }
147 /// @endcode
148 ///
149 /// Long, potentially non-contiguous message example:
150 ///
151 /// @code{.cpp}
152 /// #include "pw_crypto/sha256.h"
153 ///
154 /// std::byte digest[32];
155 ///
156 /// if (!pw::crypto::sha256::Sha256()
157 /// .Update(chunk1).Update(chunk2).Update(chunk...)
158 /// .Final().ok()) {
159 /// // Handle errors.
160 /// }
161 /// @endcode
Hash(ConstByteSpan message,ByteSpan out_digest)162 inline Status Hash(ConstByteSpan message, ByteSpan out_digest) {
163 return Sha256().Update(message).Final(out_digest);
164 }
165
Hash(stream::Reader & reader,ByteSpan out_digest)166 inline Status Hash(stream::Reader& reader, ByteSpan out_digest) {
167 if (out_digest.size() < kDigestSizeBytes) {
168 return Status::InvalidArgument();
169 }
170
171 Sha256 sha256;
172 while (true) {
173 Result<ByteSpan> res = reader.Read(out_digest);
174 if (res.status().IsOutOfRange()) {
175 break;
176 }
177
178 PW_TRY(res.status());
179 sha256.Update(res.value());
180 }
181
182 return sha256.Final(out_digest);
183 }
184
185 } // namespace pw::crypto::sha256
186