• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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