xref: /aosp_15_r20/external/tink/cc/subtle/prf/hkdf_streaming_prf.cc (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 #include "tink/subtle/prf/hkdf_streaming_prf.h"
18 
19 #include <algorithm>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 
24 #include "absl/memory/memory.h"
25 #include "absl/status/status.h"
26 #include "absl/strings/str_cat.h"
27 #include "openssl/evp.h"
28 #include "openssl/hmac.h"
29 #include "tink/internal/md_util.h"
30 #include "tink/internal/ssl_unique_ptr.h"
31 #include "tink/subtle/subtle_util.h"
32 #include "tink/util/secret_data.h"
33 #include "tink/util/status.h"
34 #include "tink/util/statusor.h"
35 
36 namespace crypto {
37 namespace tink {
38 namespace subtle {
39 
40 namespace {
41 
42 class HkdfInputStream : public InputStream {
43  public:
HkdfInputStream(const EVP_MD * digest,const util::SecretData & secret,absl::string_view salt,absl::string_view input)44   HkdfInputStream(const EVP_MD *digest, const util::SecretData &secret,
45                   absl::string_view salt, absl::string_view input)
46       : input_(input) {
47     stream_status_ = Init(digest, secret, salt);
48   }
49 
Next(const void ** data)50   crypto::tink::util::StatusOr<int> Next(const void **data) override {
51     if (!stream_status_.ok()) {
52       return stream_status_;
53     }
54     if (position_in_ti_ < ti_.size()) {
55       return returnDataFromPosition(data);
56     }
57     if (i_ == 255) {
58       stream_status_ =
59           crypto::tink::util::Status(absl::StatusCode::kOutOfRange, "EOF");
60       return stream_status_;
61     }
62     stream_status_ = UpdateTi();
63     if (!stream_status_.ok()) {
64       return stream_status_;
65     }
66     return returnDataFromPosition(data);
67   }
68 
BackUp(int count)69   void BackUp(int count) override {
70     position_in_ti_ -= std::min(std::max(0, count), position_in_ti_);
71   }
72 
Position() const73   int64_t Position() const override {
74     if (i_ == 0) return 0;
75     return (i_ - 1) * ti_.size() + position_in_ti_;
76   }
77 
78  private:
Init(const EVP_MD * digest,const util::SecretData & secret,absl::string_view salt)79   util::Status Init(const EVP_MD *digest, const util::SecretData &secret,
80                     absl::string_view salt) {
81     // PRK as by RFC 5869, Section 2.2
82     util::SecretData prk(EVP_MAX_MD_SIZE);
83 
84     if (!digest) {
85       return util::Status(absl::StatusCode::kInvalidArgument, "Invalid digest");
86     }
87     const size_t digest_size = EVP_MD_size(digest);
88     if (digest_size == 0) {
89       return util::Status(absl::StatusCode::kInvalidArgument,
90                           "Invalid digest size (0)");
91     }
92     ti_.resize(digest_size);
93 
94     // BoringSSL's `HKDF_extract` function is implemented as an HMAC [1]. We
95     // replace calls to `HKDF_extract` with a direct call to `HMAC` to make this
96     // compatible to OpenSSL, which doesn't expose `HKDF*` functions.
97     //
98     // [1] https://github.com/google/boringssl/blob/master/crypto/hkdf/hkdf.c#L42
99     unsigned prk_len;
100     if (HMAC(digest, reinterpret_cast<const uint8_t *>(salt.data()),
101              salt.size(), secret.data(), secret.size(), prk.data(),
102              &prk_len) == nullptr ||
103         prk_len != digest_size) {
104       return util::Status(absl::StatusCode::kInternal, "HKDF-Extract failed");
105     }
106     prk.resize(prk_len);
107     if (!hmac_ctx_) {
108       return util::Status(absl::StatusCode::kInternal, "HMAC_CTX_new failed");
109     }
110     if (!HMAC_Init_ex(hmac_ctx_.get(), prk.data(), prk.size(), digest,
111                       nullptr)) {
112       return util::Status(absl::StatusCode::kInternal, "HMAC_Init_ex failed");
113     }
114     return UpdateTi();
115   }
116 
returnDataFromPosition(const void ** data)117   int returnDataFromPosition(const void **data) {
118     // There's still data in ti to return.
119     *data = ti_.data() + position_in_ti_;
120     int result = ti_.size() - position_in_ti_;
121     position_in_ti_ = ti_.size();
122     return result;
123   }
124 
125   // Sets T(i+i) = HMAC-Hash(PRK, T(i) | info | i + 1) as in RFC 5869,
126   // Section 2.3
127   // Unfortunately, boringSSL does not provide a function which updates T(i)
128   // for a single round; hence we implement this ourselves.
UpdateTi()129   util::Status UpdateTi() {
130     if (!HMAC_Init_ex(hmac_ctx_.get(), nullptr, 0, nullptr, nullptr)) {
131       return util::Status(absl::StatusCode::kInternal, "HMAC_Init_ex failed");
132     }
133     if (i_ != 0 && !HMAC_Update(hmac_ctx_.get(), ti_.data(), ti_.size())) {
134       return util::Status(absl::StatusCode::kInternal,
135                           "HMAC_Update failed on ti_");
136     }
137     if (!HMAC_Update(hmac_ctx_.get(),
138                      reinterpret_cast<const uint8_t *>(&input_[0]),
139                      input_.size())) {
140       return util::Status(absl::StatusCode::kInternal,
141                           "HMAC_Update failed on input_");
142     }
143     uint8_t i_as_uint8 = i_ + 1;
144     if (!HMAC_Update(hmac_ctx_.get(), &i_as_uint8, 1)) {
145       return util::Status(absl::StatusCode::kInternal,
146                           "HMAC_Update failed on i_");
147     }
148     if (!HMAC_Final(hmac_ctx_.get(), ti_.data(), nullptr)) {
149       return util::Status(absl::StatusCode::kInternal, "HMAC_Final failed");
150     }
151     i_++;
152     position_in_ti_ = 0;
153     return util::OkStatus();
154   }
155 
156   // OUT_OF_RANGE_ERROR in case we returned all the data. Other errors indicate
157   // problems and are permanent.
158   util::Status stream_status_ = util::OkStatus();
159 
160   internal::SslUniquePtr<HMAC_CTX> hmac_ctx_{HMAC_CTX_new()};
161 
162   // Current value T(i).
163   util::SecretData ti_;
164   // By RFC 5869: 0 <= i_ <= 255*HashLen
165   int i_ = 0;
166 
167   std::string input_;
168 
169   // The current position of ti which we returned.
170   int position_in_ti_ = 0;
171 };
172 
173 }  // namespace
174 
ComputePrf(absl::string_view input) const175 std::unique_ptr<InputStream> HkdfStreamingPrf::ComputePrf(
176     absl::string_view input) const {
177   return absl::make_unique<HkdfInputStream>(hash_, secret_, salt_, input);
178 }
179 
180 // static
181 crypto::tink::util::StatusOr<std::unique_ptr<StreamingPrf>>
New(HashType hash,util::SecretData secret,absl::string_view salt)182 HkdfStreamingPrf::New(HashType hash, util::SecretData secret,
183                       absl::string_view salt) {
184   auto status = internal::CheckFipsCompatibility<HkdfStreamingPrf>();
185   if (!status.ok()) return status;
186 
187   if (hash != SHA256 && hash != SHA512 && hash != SHA1) {
188     return util::Status(
189         absl::StatusCode::kInvalidArgument,
190         absl::StrCat("Hash ", hash, " not acceptable for HkdfStreamingPrf"));
191   }
192 
193   if (secret.size() < 10) {
194     return util::Status(absl::StatusCode::kInvalidArgument,
195                         "Too short secret for HkdfStreamingPrf");
196   }
197   util::StatusOr<const EVP_MD *> evp_md = internal::EvpHashFromHashType(hash);
198   if (!evp_md.ok()) {
199     return util::Status(absl::StatusCode::kUnimplemented, "Unsupported hash");
200   }
201 
202   return {
203       absl::WrapUnique(new HkdfStreamingPrf(*evp_md, std::move(secret), salt))};
204 }
205 
206 }  // namespace subtle
207 }  // namespace tink
208 }  // namespace crypto
209