xref: /aosp_15_r20/external/pigweed/pw_crypto/public/pw_crypto/sha256.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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