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