Skip to content

Commit 93601e7

Browse files
T-y-c-o-o-nNikita Siniachenko
andauthored
[k2] implemented aes-128-gcm, aes-256-gcm + supported "no padding" in f$openssl_encrypt and f$openssl_decrypt builtins (#1517)
* supported NoPadding for f$openssl_encrypt and f$openssl_decrypt * encrypt and decrypt functions expect tl::Maybe<tl::string> instead of tl::String from response stream * implemented aes-gcm ciphers * ai review --------- Co-authored-by: Nikita Siniachenko <n.sinyachenko@vk.team>
1 parent 6699f00 commit 93601e7

7 files changed

Lines changed: 310 additions & 83 deletions

File tree

runtime-light/stdlib/crypto/crypto-functions.cpp

Lines changed: 114 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ namespace {
166166

167167
constexpr std::string_view AES_128_CBC = "aes-128-cbc";
168168
constexpr std::string_view AES_256_CBC = "aes-256-cbc";
169+
constexpr std::string_view AES_128_GCM = "aes-128-gcm";
170+
constexpr std::string_view AES_256_GCM = "aes-256-gcm";
169171

170172
std::optional<tl::CipherAlgorithm> parse_cipher_algorithm(const string& method) noexcept {
171173
using namespace std::string_view_literals;
@@ -174,28 +176,45 @@ std::optional<tl::CipherAlgorithm> parse_cipher_algorithm(const string& method)
174176
const auto ichar_equals = [](char a, char b) { return std::tolower(a) == std::tolower(b); };
175177

176178
if (std::ranges::equal(method_sv, AES_128_CBC, ichar_equals)) {
177-
return tl::CipherAlgorithm::AES128;
179+
return tl::CipherAlgorithm::AES128_CBC;
178180
} else if (std::ranges::equal(method_sv, AES_256_CBC, ichar_equals)) {
179-
return tl::CipherAlgorithm::AES256;
181+
return tl::CipherAlgorithm::AES256_CBC;
182+
} else if (std::ranges::equal(method_sv, AES_128_GCM, ichar_equals)) {
183+
return tl::CipherAlgorithm::AES128_GCM;
184+
} else if (std::ranges::equal(method_sv, AES_256_GCM, ichar_equals)) {
185+
return tl::CipherAlgorithm::AES256_GCM;
180186
}
181-
return {};
187+
return std::nullopt;
182188
}
183189

190+
bool is_gcm_algorithm(tl::CipherAlgorithm algorithm) {
191+
return algorithm == tl::AES128_GCM || algorithm == tl::AES256_GCM;
192+
}
193+
194+
bool is_aead_algorithm(tl::CipherAlgorithm algorithm) {
195+
return is_gcm_algorithm(algorithm);
196+
}
197+
198+
constexpr size_t GCM_NONCE_LEN = 12;
184199
constexpr size_t AES_BLOCK_LEN = 16;
185200
constexpr size_t AES_128_KEY_LEN = 16;
186201
constexpr size_t AES_256_KEY_LEN = 32;
187202

188203
int64_t algorithm_iv_len([[maybe_unused]] tl::CipherAlgorithm algorithm) noexcept {
189-
/* since only aes-128/256-cbc supported for now */
204+
if (is_gcm_algorithm(algorithm)) {
205+
return GCM_NONCE_LEN;
206+
}
190207
return AES_BLOCK_LEN;
191208
}
192209

193210
int64_t algorithm_key_len(tl::CipherAlgorithm algorithm) noexcept {
194211
switch (algorithm) {
195-
case tl::CipherAlgorithm::AES128: {
212+
case tl::CipherAlgorithm::AES128_GCM:
213+
case tl::CipherAlgorithm::AES128_CBC: {
196214
return AES_128_KEY_LEN;
197215
}
198-
case tl::CipherAlgorithm::AES256: {
216+
case tl::CipherAlgorithm::AES256_GCM:
217+
case tl::CipherAlgorithm::AES256_CBC: {
199218
return AES_256_KEY_LEN;
200219
}
201220
default: {
@@ -212,12 +231,19 @@ Optional<std::pair<string, string>> algorithm_pad_key_iv(tl::CipherAlgorithm alg
212231
const size_t iv_required_len = algorithm_iv_len(algorithm);
213232
const size_t key_required_len = algorithm_key_len(algorithm);
214233
auto iv = source_iv;
215-
if (iv.size() < iv_required_len) {
216-
kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len);
217-
iv.append(static_cast<string::size_type>(iv_required_len - iv.size()), '\0');
218-
} else if (iv.size() > iv_required_len) {
219-
kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len);
220-
iv.shrink(static_cast<string::size_type>(iv_required_len));
234+
if (is_aead_algorithm(algorithm)) {
235+
if (source_iv.empty()) {
236+
kphp::log::warning("Setting of IV length for AEAD mode failed");
237+
return false;
238+
}
239+
} else {
240+
if (iv.size() < iv_required_len) {
241+
kphp::log::warning("IV passed is only {} bytes long, cipher expects an IV of precisely {} bytes, padding with \\0", iv.size(), iv_required_len);
242+
iv.append(static_cast<string::size_type>(iv_required_len - iv.size()), '\0');
243+
} else if (iv.size() > iv_required_len) {
244+
kphp::log::warning("IV passed is {} bytes long which is longer than the {} expected by selected cipher, truncating", iv.size(), iv_required_len);
245+
iv.shrink(static_cast<string::size_type>(iv_required_len));
246+
}
221247
}
222248

223249
auto key = source_key;
@@ -234,25 +260,22 @@ Optional<std::pair<string, string>> algorithm_pad_key_iv(tl::CipherAlgorithm alg
234260
key.shrink(static_cast<string::size_type>(key_required_len));
235261
}
236262

237-
if (options & static_cast<int64_t>(cipher_opts::OPENSSL_ZERO_PADDING)) {
238-
kphp::log::warning("OPENSSL_ZERO_PADDING option is not supported for now\n");
239-
return false;
240-
}
241263
return std::make_pair(key, iv);
242264
}
243265

244266
} // namespace
245267

246268
array<string> f$openssl_get_cipher_methods([[maybe_unused]] bool aliases) noexcept {
247269
array<string> return_value{
248-
{std::make_pair(0, string{AES_128_CBC.data(), AES_128_CBC.size()}), std::make_pair(1, string{AES_256_CBC.data(), AES_256_CBC.size()})}};
270+
{std::make_pair(0, string{AES_128_CBC.data(), AES_128_CBC.size()}), std::make_pair(1, string{AES_256_CBC.data(), AES_256_CBC.size()}),
271+
std::make_pair(2, string{AES_128_GCM.data(), AES_128_GCM.size()}), std::make_pair(3, string{AES_256_GCM.data(), AES_256_GCM.size()})}};
249272
return return_value;
250273
}
251274

252275
Optional<int64_t> f$openssl_cipher_iv_length(const string& method) noexcept {
253276
auto algorithm{parse_cipher_algorithm(method)};
254277
if (!algorithm) {
255-
kphp::log::warning("Unknown cipher algorithm");
278+
kphp::log::warning("Unknown cipher algorithm {}", method.c_str());
256279
return false;
257280
}
258281
return algorithm_iv_len(*algorithm);
@@ -263,32 +286,43 @@ kphp::coro::task<Optional<string>> f$openssl_encrypt(string data, string method,
263286
[[maybe_unused]] int64_t tag_length) noexcept {
264287
auto algorithm{parse_cipher_algorithm(method)};
265288
if (!algorithm) {
266-
kphp::log::warning("Unknown cipher algorithm");
289+
kphp::log::warning("Unknown cipher algorithm {}", method.c_str());
267290
co_return false;
268291
}
269292

270-
if (tag.has_value()) {
271-
kphp::log::warning("The authenticated tag cannot be provided for cipher that doesn not support AEAD");
293+
bool aead{is_aead_algorithm(*algorithm)};
294+
if (aead && (!tag.has_value() || tag_length == 0)) {
295+
kphp::log::warning("A tag must be provided when using AEAD mode");
296+
co_return false;
297+
}
298+
if (tag.has_value() && !aead) {
299+
kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD");
272300
}
273-
if (!aad.empty()) {
274-
kphp::log::warning("The additional authenticated data cannot be provided for cipher that doesn not support AEAD");
301+
if (!aad.empty() && !aead) {
302+
kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD");
275303
}
276304
if (source_iv.empty()) {
277305
kphp::log::warning("Using an empty Initialization Vector (iv) is potentially insecure and not recommended");
278306
}
279307

280308
auto key_iv{algorithm_pad_key_iv(*algorithm, source_key, source_iv, options)};
281-
if (key_iv.is_null()) {
309+
if (!key_iv.has_value()) {
282310
co_return false;
283311
}
284312

285-
tl::CbcEncrypt cbc_encrypt{.algorithm = *algorithm,
286-
.padding = tl::BlockPadding::PKCS7,
287-
.passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}},
288-
.iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}},
289-
.data = {.value = {data.c_str(), data.size()}}};
290-
tl::storer tls{cbc_encrypt.footprint()};
291-
cbc_encrypt.store(tls);
313+
tl::BlockPadding padding{tl::BlockPadding::PKCS7};
314+
if (options & static_cast<int64_t>(cipher_opts::OPENSSL_ZERO_PADDING)) {
315+
padding = tl::BlockPadding::NO_PADDING;
316+
}
317+
tl::Encrypt encrypt{.algorithm = *algorithm,
318+
.padding = padding,
319+
.passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}},
320+
.iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}},
321+
.tag_size = {.value = tag_length},
322+
.aad = {.value = {aad.c_str(), aad.size()}},
323+
.data = {.value = {data.c_str(), data.size()}}};
324+
tl::storer tls{encrypt.footprint()};
325+
encrypt.store(tls);
292326

293327
auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)};
294328
if (!expected_stream) [[unlikely]] {
@@ -302,9 +336,18 @@ kphp::coro::task<Optional<string>> f$openssl_encrypt(string data, string method,
302336
}
303337

304338
tl::fetcher tlf{response_bytes};
305-
tl::String response{};
339+
tl::Maybe<tl::tuple<tl::string, 2>> response{};
306340
kphp::log::assertion(response.fetch(tlf));
307-
string result{response.inner.value.data(), static_cast<string::size_type>(response.inner.value.size())};
341+
if (!response.opt_value) {
342+
co_return false;
343+
}
344+
345+
string result{(*response.opt_value).value[0].value.data(), static_cast<string::size_type>((*response.opt_value).value[0].value.size())};
346+
347+
if (tag.has_value()) {
348+
string received_tag{(*response.opt_value).value[1].value.data(), static_cast<string::size_type>((*response.opt_value).value[1].value.size())};
349+
tag.value().get() = std::move(received_tag);
350+
}
308351
co_return (options & static_cast<int64_t>(cipher_opts::OPENSSL_RAW_DATA)) ? std::move(result) : f$base64_encode(result);
309352
}
310353

@@ -321,29 +364,36 @@ kphp::coro::task<Optional<string>> f$openssl_decrypt(string data, string method,
321364

322365
auto algorithm{parse_cipher_algorithm(method)};
323366
if (!algorithm.has_value()) {
324-
kphp::log::warning("Unknown cipher algorithm");
367+
kphp::log::warning("Unknown cipher algorithm {}", method.c_str());
325368
co_return false;
326369
}
327370

328-
if (!tag.empty()) {
329-
kphp::log::warning("The authenticated tag cannot be provided for cipher that doesn not support AEAD");
371+
bool aead{is_aead_algorithm(*algorithm)};
372+
if (!tag.empty() && !aead) {
373+
kphp::log::warning("The authenticated tag cannot be provided for cipher that does not support AEAD");
330374
}
331-
if (!aad.empty()) {
332-
kphp::log::warning("The additional authenticated data cannot be provided for cipher that doesn not support AEAD");
375+
if (!aad.empty() && !aead) {
376+
kphp::log::warning("The additional authenticated data cannot be provided for cipher that does not support AEAD");
333377
}
334378

335379
auto key_iv{algorithm_pad_key_iv(*algorithm, source_key, source_iv, options)};
336-
if (key_iv.is_null()) {
380+
if (!key_iv.has_value()) {
337381
co_return false;
338382
}
339383

340-
tl::CbcDecrypt cbc_decrypt{.algorithm = *algorithm,
341-
.padding = tl::BlockPadding::PKCS7,
342-
.passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}},
343-
.iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}},
344-
.data = {.value = {data.c_str(), data.size()}}};
345-
tl::storer tls{cbc_decrypt.footprint()};
346-
cbc_decrypt.store(tls);
384+
tl::BlockPadding padding{tl::BlockPadding::PKCS7};
385+
if (options & static_cast<int64_t>(cipher_opts::OPENSSL_ZERO_PADDING)) {
386+
padding = tl::BlockPadding::NO_PADDING;
387+
}
388+
tl::Decrypt decrypt{.algorithm = *algorithm,
389+
.padding = padding,
390+
.passphrase = {.value = {key_iv.val().first.c_str(), key_iv.val().first.size()}},
391+
.iv = {.value = {key_iv.val().second.c_str(), key_iv.val().second.size()}},
392+
.tag = {.value = {tag.c_str(), tag.size()}},
393+
.aad = {.value = {aad.c_str(), aad.size()}},
394+
.data = {.value = {data.c_str(), data.size()}}};
395+
tl::storer tls{decrypt.footprint()};
396+
decrypt.store(tls);
347397

348398
auto expected_stream{kphp::component::stream::open(CRYPTO_COMPONENT_NAME, k2::stream_kind::component)};
349399
if (!expected_stream) [[unlikely]] {
@@ -357,9 +407,13 @@ kphp::coro::task<Optional<string>> f$openssl_decrypt(string data, string method,
357407
}
358408

359409
tl::fetcher tlf{response_bytes};
360-
tl::String response{};
410+
tl::Maybe<tl::string> response{};
361411
kphp::log::assertion(response.fetch(tlf));
362-
co_return string{response.inner.value.data(), static_cast<string::size_type>(response.inner.value.size())};
412+
if (!response.opt_value) {
413+
co_return false;
414+
}
415+
416+
co_return string{(*response.opt_value).value.data(), static_cast<string::size_type>((*response.opt_value).value.size())};
363417
}
364418

365419
kphp::coro::task<Optional<string>> f$openssl_pkey_get_public(string key) noexcept {
@@ -437,9 +491,13 @@ kphp::coro::task<bool> f$openssl_public_encrypt(string data, string& encrypted_d
437491
}
438492

439493
tl::fetcher tlf{response_bytes};
440-
tl::String response{};
494+
tl::Maybe<tl::string> response{};
441495
kphp::log::assertion(response.fetch(tlf));
442-
encrypted_data = {response.inner.value.data(), static_cast<string::size_type>(response.inner.value.size())};
496+
if (!response.opt_value) {
497+
co_return false;
498+
}
499+
500+
encrypted_data = {(*response.opt_value).value.data(), static_cast<string::size_type>((*response.opt_value).value.size())};
443501
co_return true;
444502
}
445503

@@ -473,9 +531,13 @@ kphp::coro::task<bool> f$openssl_private_decrypt(string data, string& decrypted_
473531
}
474532

475533
tl::fetcher tlf{response_bytes};
476-
tl::String response{};
534+
tl::Maybe<tl::string> response{};
477535
kphp::log::assertion(response.fetch(tlf));
478-
decrypted_data = {response.inner.value.data(), static_cast<string::size_type>(response.inner.value.size())};
536+
if (!response.opt_value) {
537+
co_return false;
538+
}
539+
540+
decrypted_data = {(*response.opt_value).value.data(), static_cast<string::size_type>((*response.opt_value).value.size())};
479541
co_return true;
480542
}
481543

runtime-light/stdlib/crypto/crypto_schema.tl

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,13 @@ hashAlgorithmMD5#257ddf13 = HashAlgorithm;
3333
hashAlgorithmMD4#317fe3d1 = HashAlgorithm;
3434
hashAlgorithmMD2#5aca6998 = HashAlgorithm;
3535

36-
cipherAlgorithmAes128#e627c460 = CipherAlgorithm;
37-
cipherAlgorithmAes256#4c98c1f9 = CipherAlgorithm;
36+
cipherAlgorithmAes128gcm#ee7e4261 = CipherAlgorithm;
37+
cipherAlgorithmAes256gcm#c2f9eb95 = CipherAlgorithm;
38+
cipherAlgorithmAes128cbc#e627c460 = CipherAlgorithm;
39+
cipherAlgorithmAes256cbc#4c98c1f9 = CipherAlgorithm;
3840

3941
blockPaddingPkcs7#699ec5de = BlockPadding;
42+
noPadding#ffc3d2f3 = BlockPadding;
4043

4144
---functions---
4245

@@ -62,21 +65,25 @@ digestVerify#5760bd0e
6265
signature : string
6366
= Bool;
6467

65-
cbcDecrypt#7f2ee1e4
66-
algorithm : CipherAlgorithm
67-
padding : BlockPadding
68-
passphrase : string
69-
iv : string
70-
data : string
71-
= String;
72-
73-
cbcEncrypt#6d4ee36a
74-
algorithm : CipherAlgorithm
75-
padding : BlockPadding
76-
passphrase : string
77-
iv : string
78-
data : string
79-
= String;
68+
decrypt#7f2ee1e4
69+
algorithm : CipherAlgorithm
70+
padding : BlockPadding
71+
key : string
72+
iv : string
73+
tag : string
74+
aad : string
75+
data : string
76+
= Maybe string;
77+
78+
encrypt#6d4ee36a
79+
algorithm : CipherAlgorithm
80+
padding : BlockPadding
81+
key : string
82+
iv : string
83+
tag_size : long
84+
aad : string
85+
data : string
86+
= Maybe tuple string 2; // encrypted data + tag (only for gcm algorithm)
8087

8188
getPublicKey#4b1e7d3d
8289
key : string
@@ -90,12 +97,12 @@ getPrivateKey#34eadfdb
9097
publicEncrypt#7612f4ad
9198
key : string
9299
data : string
93-
= String;
100+
= Maybe string;
94101

95102
privateDecrypt#c6e91d4d
96103
key : string
97104
data : string
98-
= String;
105+
= Maybe string;
99106

100107
hash#50732a27
101108
algorithm : HashAlgorithm

0 commit comments

Comments
 (0)