qemu

Форк
0
/
akcipher-gcrypt.c.inc 
595 строк · 18.7 Кб
1
/*
2
 * QEMU Crypto akcipher algorithms
3
 *
4
 * Copyright (c) 2022 Bytedance
5
 * Author: lei he <helei.sig11@bytedance.com>
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 *
20
 */
21

22
#include <gcrypt.h>
23

24
#include "qemu/osdep.h"
25
#include "qemu/host-utils.h"
26
#include "crypto/akcipher.h"
27
#include "crypto/random.h"
28
#include "qapi/error.h"
29
#include "sysemu/cryptodev.h"
30
#include "rsakey.h"
31

32
typedef struct QCryptoGcryptRSA {
33
    QCryptoAkCipher akcipher;
34
    gcry_sexp_t key;
35
    QCryptoRSAPaddingAlgorithm padding_alg;
36
    QCryptoHashAlgorithm hash_alg;
37
} QCryptoGcryptRSA;
38

39
static void qcrypto_gcrypt_rsa_free(QCryptoAkCipher *akcipher)
40
{
41
    QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
42
    if (!rsa) {
43
        return;
44
    }
45

46
    gcry_sexp_release(rsa->key);
47
    g_free(rsa);
48
}
49

50
static QCryptoGcryptRSA *qcrypto_gcrypt_rsa_new(
51
    const QCryptoAkCipherOptionsRSA *opt,
52
    QCryptoAkCipherKeyType type,
53
    const uint8_t *key,  size_t keylen,
54
    Error **errp);
55

56
QCryptoAkCipher *qcrypto_akcipher_new(const QCryptoAkCipherOptions *opts,
57
                                      QCryptoAkCipherKeyType type,
58
                                      const uint8_t *key, size_t keylen,
59
                                      Error **errp)
60
{
61
    switch (opts->alg) {
62
    case QCRYPTO_AKCIPHER_ALG_RSA:
63
        return (QCryptoAkCipher *)qcrypto_gcrypt_rsa_new(
64
            &opts->u.rsa, type, key, keylen, errp);
65

66
    default:
67
        error_setg(errp, "Unsupported algorithm: %u", opts->alg);
68
        return NULL;
69
    }
70

71
    return NULL;
72
}
73

74
static void qcrypto_gcrypt_set_rsa_size(QCryptoAkCipher *akcipher, gcry_mpi_t n)
75
{
76
    size_t key_size = (gcry_mpi_get_nbits(n) + 7) / 8;
77
    akcipher->max_plaintext_len = key_size;
78
    akcipher->max_ciphertext_len = key_size;
79
    akcipher->max_dgst_len = key_size;
80
    akcipher->max_signature_len = key_size;
81
}
82

83
static int qcrypto_gcrypt_parse_rsa_private_key(
84
    QCryptoGcryptRSA *rsa,
85
    const uint8_t *key, size_t keylen, Error **errp)
86
{
87
    g_autoptr(QCryptoAkCipherRSAKey) rsa_key = qcrypto_akcipher_rsakey_parse(
88
        QCRYPTO_AKCIPHER_KEY_TYPE_PRIVATE, key, keylen, errp);
89
    gcry_mpi_t n = NULL, e = NULL, d = NULL, p = NULL, q = NULL, u = NULL;
90
    bool compute_mul_inv = false;
91
    int ret = -1;
92
    gcry_error_t err;
93

94
    if (!rsa_key) {
95
        return ret;
96
    }
97

98
    err = gcry_mpi_scan(&n, GCRYMPI_FMT_STD,
99
                        rsa_key->n.data, rsa_key->n.len, NULL);
100
    if (gcry_err_code(err) != 0) {
101
        error_setg(errp, "Failed to parse RSA parameter n: %s/%s",
102
                   gcry_strsource(err), gcry_strerror(err));
103
        goto cleanup;
104
    }
105

106
    err = gcry_mpi_scan(&e, GCRYMPI_FMT_STD,
107
                        rsa_key->e.data, rsa_key->e.len, NULL);
108
    if (gcry_err_code(err) != 0) {
109
        error_setg(errp, "Failed to parse RSA parameter e: %s/%s",
110
                   gcry_strsource(err), gcry_strerror(err));
111
        goto cleanup;
112
    }
113

114
    err = gcry_mpi_scan(&d, GCRYMPI_FMT_STD,
115
                        rsa_key->d.data, rsa_key->d.len, NULL);
116
    if (gcry_err_code(err) != 0) {
117
        error_setg(errp, "Failed to parse RSA parameter d: %s/%s",
118
                   gcry_strsource(err), gcry_strerror(err));
119
        goto cleanup;
120
    }
121

122
    err = gcry_mpi_scan(&p, GCRYMPI_FMT_STD,
123
                        rsa_key->p.data, rsa_key->p.len, NULL);
124
    if (gcry_err_code(err) != 0) {
125
        error_setg(errp, "Failed to parse RSA parameter p: %s/%s",
126
                   gcry_strsource(err), gcry_strerror(err));
127
        goto cleanup;
128
    }
129

130
    err = gcry_mpi_scan(&q, GCRYMPI_FMT_STD,
131
                        rsa_key->q.data, rsa_key->q.len, NULL);
132
    if (gcry_err_code(err) != 0) {
133
        error_setg(errp, "Failed to parse RSA parameter q: %s/%s",
134
                   gcry_strsource(err), gcry_strerror(err));
135
        goto cleanup;
136
    }
137

138
    if (gcry_mpi_cmp_ui(p, 0) > 0 && gcry_mpi_cmp_ui(q, 0) > 0) {
139
        compute_mul_inv = true;
140

141
        u = gcry_mpi_new(0);
142
        if (gcry_mpi_cmp(p, q) > 0) {
143
            gcry_mpi_swap(p, q);
144
        }
145
        gcry_mpi_invm(u, p, q);
146
    }
147

148
    if (compute_mul_inv) {
149
        err = gcry_sexp_build(&rsa->key, NULL,
150
            "(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
151
            n, e, d, p, q, u);
152
    } else {
153
        err = gcry_sexp_build(&rsa->key, NULL,
154
            "(private-key (rsa (n %m) (e %m) (d %m)))", n, e, d);
155
    }
156
    if (gcry_err_code(err) != 0) {
157
        error_setg(errp, "Failed to build RSA private key: %s/%s",
158
                   gcry_strsource(err), gcry_strerror(err));
159
        goto cleanup;
160
    }
161
    qcrypto_gcrypt_set_rsa_size((QCryptoAkCipher *)rsa,  n);
162
    ret = 0;
163

164
cleanup:
165
    gcry_mpi_release(n);
166
    gcry_mpi_release(e);
167
    gcry_mpi_release(d);
168
    gcry_mpi_release(p);
169
    gcry_mpi_release(q);
170
    gcry_mpi_release(u);
171
    return ret;
172
}
173

174
static int qcrypto_gcrypt_parse_rsa_public_key(QCryptoGcryptRSA *rsa,
175
                                               const uint8_t *key,
176
                                               size_t keylen,
177
                                               Error **errp)
178
{
179

180
    g_autoptr(QCryptoAkCipherRSAKey) rsa_key = qcrypto_akcipher_rsakey_parse(
181
        QCRYPTO_AKCIPHER_KEY_TYPE_PUBLIC, key, keylen, errp);
182
    gcry_mpi_t n = NULL, e = NULL;
183
    int ret = -1;
184
    gcry_error_t err;
185

186
    if (!rsa_key) {
187
        return ret;
188
    }
189

190
    err = gcry_mpi_scan(&n, GCRYMPI_FMT_STD,
191
                        rsa_key->n.data, rsa_key->n.len, NULL);
192
    if (gcry_err_code(err) != 0) {
193
        error_setg(errp, "Failed to parse RSA parameter n: %s/%s",
194
                   gcry_strsource(err), gcry_strerror(err));
195
        goto cleanup;
196
    }
197

198
    err = gcry_mpi_scan(&e, GCRYMPI_FMT_STD,
199
                        rsa_key->e.data, rsa_key->e.len, NULL);
200
    if (gcry_err_code(err) != 0) {
201
        error_setg(errp, "Failed to parse RSA parameter e: %s/%s",
202
                   gcry_strsource(err), gcry_strerror(err));
203
        goto cleanup;
204
    }
205

206
    err = gcry_sexp_build(&rsa->key, NULL,
207
                          "(public-key (rsa (n %m) (e %m)))", n, e);
208
    if (gcry_err_code(err) != 0) {
209
        error_setg(errp, "Failed to build RSA public key: %s/%s",
210
                   gcry_strsource(err), gcry_strerror(err));
211
        goto cleanup;
212
    }
213
    qcrypto_gcrypt_set_rsa_size((QCryptoAkCipher *)rsa, n);
214
    ret = 0;
215

216
cleanup:
217
    gcry_mpi_release(n);
218
    gcry_mpi_release(e);
219
    return ret;
220
}
221

222
static int qcrypto_gcrypt_rsa_encrypt(QCryptoAkCipher *akcipher,
223
                                      const void *in, size_t in_len,
224
                                      void *out, size_t out_len,
225
                                      Error **errp)
226
{
227
    QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
228
    int ret = -1;
229
    gcry_sexp_t data_sexp = NULL, cipher_sexp = NULL;
230
    gcry_sexp_t cipher_sexp_item = NULL;
231
    gcry_mpi_t cipher_mpi = NULL;
232
    const char *result;
233
    gcry_error_t err;
234
    size_t actual_len;
235

236
    if (in_len > akcipher->max_plaintext_len) {
237
        error_setg(errp, "Plaintext length is greater than key size: %d",
238
                   akcipher->max_plaintext_len);
239
        return ret;
240
    }
241

242
    err = gcry_sexp_build(&data_sexp, NULL,
243
                          "(data (flags %s) (value %b))",
244
                          QCryptoRSAPaddingAlgorithm_str(rsa->padding_alg),
245
                          in_len, in);
246
    if (gcry_err_code(err) != 0) {
247
        error_setg(errp, "Failed to build plaintext: %s/%s",
248
                   gcry_strsource(err), gcry_strerror(err));
249
        goto cleanup;
250
    }
251

252
    err = gcry_pk_encrypt(&cipher_sexp, data_sexp, rsa->key);
253
    if (gcry_err_code(err) != 0) {
254
        error_setg(errp, "Failed to encrypt: %s/%s",
255
                   gcry_strsource(err), gcry_strerror(err));
256
        goto cleanup;
257
    }
258

259
    /* S-expression of cipher: (enc-val (rsa (a a-mpi))) */
260
    cipher_sexp_item = gcry_sexp_find_token(cipher_sexp, "a", 0);
261
    if (!cipher_sexp_item || gcry_sexp_length(cipher_sexp_item) != 2) {
262
        error_setg(errp, "Invalid ciphertext result");
263
        goto cleanup;
264
    }
265

266
    if (rsa->padding_alg == QCRYPTO_RSA_PADDING_ALG_RAW) {
267
        cipher_mpi = gcry_sexp_nth_mpi(cipher_sexp_item, 1, GCRYMPI_FMT_USG);
268
        if (!cipher_mpi) {
269
            error_setg(errp, "Invalid ciphertext result");
270
            goto cleanup;
271
        }
272
        err = gcry_mpi_print(GCRYMPI_FMT_USG, out, out_len,
273
                             &actual_len, cipher_mpi);
274
        if (gcry_err_code(err) != 0) {
275
            error_setg(errp, "Failed to print MPI: %s/%s",
276
                       gcry_strsource(err), gcry_strerror(err));
277
            goto cleanup;
278
        }
279

280
        if (actual_len > out_len) {
281
            error_setg(errp, "Ciphertext buffer length is too small");
282
            goto cleanup;
283
        }
284

285
        /* We always padding leading-zeros for RSA-RAW */
286
        if (actual_len < out_len) {
287
            memmove((uint8_t *)out + (out_len - actual_len), out, actual_len);
288
            memset(out, 0, out_len - actual_len);
289
        }
290
        ret = out_len;
291

292
    } else {
293
        result = gcry_sexp_nth_data(cipher_sexp_item, 1, &actual_len);
294
        if (!result) {
295
            error_setg(errp, "Invalid ciphertext result");
296
            goto cleanup;
297
        }
298
        if (actual_len > out_len) {
299
            error_setg(errp, "Ciphertext buffer length is too small");
300
            goto cleanup;
301
        }
302
        memcpy(out, result, actual_len);
303
        ret = actual_len;
304
    }
305

306
cleanup:
307
    gcry_sexp_release(data_sexp);
308
    gcry_sexp_release(cipher_sexp);
309
    gcry_sexp_release(cipher_sexp_item);
310
    gcry_mpi_release(cipher_mpi);
311
    return ret;
312
}
313

314
static int qcrypto_gcrypt_rsa_decrypt(QCryptoAkCipher *akcipher,
315
                                      const void *in, size_t in_len,
316
                                      void *out, size_t out_len,
317
                                      Error **errp)
318
{
319
    QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
320
    int ret = -1;
321
    gcry_sexp_t data_sexp = NULL, cipher_sexp = NULL;
322
    gcry_mpi_t data_mpi = NULL;
323
    gcry_error_t err;
324
    size_t actual_len;
325
    const char *result;
326

327
    if (in_len > akcipher->max_ciphertext_len) {
328
        error_setg(errp, "Ciphertext length is greater than key size: %d",
329
                   akcipher->max_ciphertext_len);
330
        return ret;
331
    }
332

333
    err = gcry_sexp_build(&cipher_sexp, NULL,
334
                          "(enc-val (flags %s) (rsa (a %b) ))",
335
                          QCryptoRSAPaddingAlgorithm_str(rsa->padding_alg),
336
                          in_len, in);
337
    if (gcry_err_code(err) != 0) {
338
        error_setg(errp, "Failed to build ciphertext: %s/%s",
339
                   gcry_strsource(err), gcry_strerror(err));
340
        goto cleanup;
341
    }
342

343
    err = gcry_pk_decrypt(&data_sexp, cipher_sexp, rsa->key);
344
    if (gcry_err_code(err) != 0) {
345
        error_setg(errp, "Failed to decrypt: %s/%s",
346
                   gcry_strsource(err), gcry_strerror(err));
347
        goto cleanup;
348
    }
349

350
    /* S-expression of plaintext: (value plaintext) */
351
    if (rsa->padding_alg == QCRYPTO_RSA_PADDING_ALG_RAW) {
352
        data_mpi = gcry_sexp_nth_mpi(data_sexp, 1, GCRYMPI_FMT_USG);
353
        if (!data_mpi) {
354
            error_setg(errp, "Invalid plaintext result");
355
            goto cleanup;
356
        }
357
        err = gcry_mpi_print(GCRYMPI_FMT_USG, out, out_len,
358
                             &actual_len, data_mpi);
359
        if (gcry_err_code(err) != 0) {
360
            error_setg(errp, "Failed to print MPI: %s/%s",
361
                       gcry_strsource(err), gcry_strerror(err));
362
            goto cleanup;
363
        }
364
        if (actual_len > out_len) {
365
            error_setg(errp, "Plaintext buffer length is too small");
366
            goto cleanup;
367
        }
368
        /* We always padding leading-zeros for RSA-RAW */
369
        if (actual_len < out_len) {
370
            memmove((uint8_t *)out + (out_len - actual_len), out, actual_len);
371
            memset(out, 0, out_len - actual_len);
372
        }
373
        ret = out_len;
374
    } else {
375
        result = gcry_sexp_nth_data(data_sexp, 1, &actual_len);
376
        if (!result) {
377
            error_setg(errp, "Invalid plaintext result");
378
            goto cleanup;
379
        }
380
        if (actual_len > out_len) {
381
            error_setg(errp, "Plaintext buffer length is too small");
382
            goto cleanup;
383
        }
384
        memcpy(out, result, actual_len);
385
        ret = actual_len;
386
    }
387

388
cleanup:
389
    gcry_sexp_release(cipher_sexp);
390
    gcry_sexp_release(data_sexp);
391
    gcry_mpi_release(data_mpi);
392
    return ret;
393
}
394

395
static int qcrypto_gcrypt_rsa_sign(QCryptoAkCipher *akcipher,
396
                                   const void *in, size_t in_len,
397
                                   void *out, size_t out_len, Error **errp)
398
{
399
    QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
400
    int ret = -1;
401
    gcry_sexp_t dgst_sexp = NULL, sig_sexp = NULL;
402
    gcry_sexp_t sig_sexp_item = NULL;
403
    const char *result;
404
    gcry_error_t err;
405
    size_t actual_len;
406

407
    if (in_len > akcipher->max_dgst_len) {
408
        error_setg(errp, "Data length is greater than key size: %d",
409
                   akcipher->max_dgst_len);
410
        return ret;
411
    }
412

413
    if (rsa->padding_alg != QCRYPTO_RSA_PADDING_ALG_PKCS1) {
414
        error_setg(errp, "Invalid padding %u", rsa->padding_alg);
415
        return ret;
416
    }
417

418
    err = gcry_sexp_build(&dgst_sexp, NULL,
419
                          "(data (flags pkcs1) (hash %s %b))",
420
                          QCryptoHashAlgorithm_str(rsa->hash_alg),
421
                          in_len, in);
422
    if (gcry_err_code(err) != 0) {
423
        error_setg(errp, "Failed to build dgst: %s/%s",
424
                   gcry_strsource(err), gcry_strerror(err));
425
        goto cleanup;
426
    }
427

428
    err = gcry_pk_sign(&sig_sexp, dgst_sexp, rsa->key);
429
    if (gcry_err_code(err) != 0) {
430
        error_setg(errp, "Failed to make signature: %s/%s",
431
                   gcry_strsource(err), gcry_strerror(err));
432
        goto cleanup;
433
    }
434

435
    /* S-expression of signature: (sig-val (rsa (s s-mpi))) */
436
    sig_sexp_item = gcry_sexp_find_token(sig_sexp, "s", 0);
437
    if (!sig_sexp_item || gcry_sexp_length(sig_sexp_item) != 2) {
438
        error_setg(errp, "Invalid signature result");
439
        goto cleanup;
440
    }
441

442
    result = gcry_sexp_nth_data(sig_sexp_item, 1, &actual_len);
443
    if (!result) {
444
        error_setg(errp, "Invalid signature result");
445
        goto cleanup;
446
    }
447

448
    if (actual_len > out_len) {
449
        error_setg(errp, "Signature buffer length is too small");
450
        goto cleanup;
451
    }
452
    memcpy(out, result, actual_len);
453
    ret = actual_len;
454

455
cleanup:
456
    gcry_sexp_release(dgst_sexp);
457
    gcry_sexp_release(sig_sexp);
458
    gcry_sexp_release(sig_sexp_item);
459

460
    return ret;
461
}
462

463
static int qcrypto_gcrypt_rsa_verify(QCryptoAkCipher *akcipher,
464
                                     const void *in, size_t in_len,
465
                                     const void *in2, size_t in2_len,
466
                                     Error **errp)
467
{
468
    QCryptoGcryptRSA *rsa = (QCryptoGcryptRSA *)akcipher;
469
    int ret = -1;
470
    gcry_sexp_t sig_sexp = NULL, dgst_sexp = NULL;
471
    gcry_error_t err;
472

473
    if (in_len > akcipher->max_signature_len) {
474
        error_setg(errp, "Signature length is greater than key size: %d",
475
                   akcipher->max_signature_len);
476
        return ret;
477
    }
478

479
    if (in2_len > akcipher->max_dgst_len) {
480
        error_setg(errp, "Data length is greater than key size: %d",
481
                   akcipher->max_dgst_len);
482
        return ret;
483
    }
484

485
    if (rsa->padding_alg != QCRYPTO_RSA_PADDING_ALG_PKCS1) {
486
        error_setg(errp, "Invalid padding %u", rsa->padding_alg);
487
        return ret;
488
    }
489

490
    err = gcry_sexp_build(&sig_sexp, NULL,
491
                          "(sig-val (rsa (s %b)))", in_len, in);
492
    if (gcry_err_code(err) != 0) {
493
        error_setg(errp, "Failed to build signature: %s/%s",
494
                   gcry_strsource(err), gcry_strerror(err));
495
        goto cleanup;
496
    }
497

498
    err = gcry_sexp_build(&dgst_sexp, NULL,
499
                          "(data (flags pkcs1) (hash %s %b))",
500
                          QCryptoHashAlgorithm_str(rsa->hash_alg),
501
                          in2_len, in2);
502
    if (gcry_err_code(err) != 0) {
503
        error_setg(errp, "Failed to build dgst: %s/%s",
504
                   gcry_strsource(err), gcry_strerror(err));
505
        goto cleanup;
506
    }
507

508
    err = gcry_pk_verify(sig_sexp, dgst_sexp, rsa->key);
509
    if (gcry_err_code(err) != 0) {
510
        error_setg(errp, "Failed to verify signature: %s/%s",
511
                   gcry_strsource(err), gcry_strerror(err));
512
        goto cleanup;
513
    }
514
    ret = 0;
515

516
cleanup:
517
    gcry_sexp_release(dgst_sexp);
518
    gcry_sexp_release(sig_sexp);
519

520
    return ret;
521
}
522

523
QCryptoAkCipherDriver gcrypt_rsa = {
524
    .encrypt = qcrypto_gcrypt_rsa_encrypt,
525
    .decrypt = qcrypto_gcrypt_rsa_decrypt,
526
    .sign = qcrypto_gcrypt_rsa_sign,
527
    .verify = qcrypto_gcrypt_rsa_verify,
528
    .free = qcrypto_gcrypt_rsa_free,
529
};
530

531
static QCryptoGcryptRSA *qcrypto_gcrypt_rsa_new(
532
    const QCryptoAkCipherOptionsRSA *opt,
533
    QCryptoAkCipherKeyType type,
534
    const uint8_t *key, size_t keylen,
535
    Error **errp)
536
{
537
    QCryptoGcryptRSA *rsa = g_new0(QCryptoGcryptRSA, 1);
538
    rsa->padding_alg = opt->padding_alg;
539
    rsa->hash_alg = opt->hash_alg;
540
    rsa->akcipher.driver = &gcrypt_rsa;
541

542
    switch (type) {
543
    case QCRYPTO_AKCIPHER_KEY_TYPE_PRIVATE:
544
        if (qcrypto_gcrypt_parse_rsa_private_key(rsa, key, keylen, errp) != 0) {
545
            goto error;
546
        }
547
        break;
548

549
    case QCRYPTO_AKCIPHER_KEY_TYPE_PUBLIC:
550
        if (qcrypto_gcrypt_parse_rsa_public_key(rsa, key, keylen, errp) != 0) {
551
            goto error;
552
        }
553
        break;
554

555
    default:
556
        error_setg(errp, "Unknown akcipher key type %d", type);
557
        goto error;
558
    }
559

560
    return rsa;
561

562
error:
563
    qcrypto_gcrypt_rsa_free((QCryptoAkCipher *)rsa);
564
    return NULL;
565
}
566

567

568
bool qcrypto_akcipher_supports(QCryptoAkCipherOptions *opts)
569
{
570
    switch (opts->alg) {
571
    case QCRYPTO_AKCIPHER_ALG_RSA:
572
        switch (opts->u.rsa.padding_alg) {
573
        case QCRYPTO_RSA_PADDING_ALG_RAW:
574
            return true;
575

576
        case QCRYPTO_RSA_PADDING_ALG_PKCS1:
577
            switch (opts->u.rsa.hash_alg) {
578
            case QCRYPTO_HASH_ALG_MD5:
579
            case QCRYPTO_HASH_ALG_SHA1:
580
            case QCRYPTO_HASH_ALG_SHA256:
581
            case QCRYPTO_HASH_ALG_SHA512:
582
                return true;
583

584
            default:
585
                return false;
586
            }
587

588
        default:
589
            return false;
590
        }
591

592
    default:
593
        return true;
594
    }
595
}
596

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.