diff options
author | Martin Schanzenbach <mschanzenbach@posteo.de> | 2021-05-01 22:05:15 +0200 |
---|---|---|
committer | Martin Schanzenbach <mschanzenbach@posteo.de> | 2021-05-02 10:39:55 +0200 |
commit | 572f4d6f7b19dec42d571829384ac9cd356bb092 (patch) | |
tree | cbe8bf3ae413a1aa3b71beffcbe7ce9bb83fe03f | |
parent | ca3ebf1e59eb00ad101ba8b26e5185db4d785610 (diff) | |
download | gnunet-572f4d6f7b19dec42d571829384ac9cd356bb092.tar.gz gnunet-572f4d6f7b19dec42d571829384ac9cd356bb092.zip |
GNS: Add EDKEY support.
GNS and GNSRECORD can now handle EdDSA keys
in addition to the existing ECDSA scheme.
See also LSD0001.
-rw-r--r-- | po/POTFILES.in | 2 | ||||
-rw-r--r-- | src/gnsrecord/gnsrecord_crypto.c | 426 | ||||
-rw-r--r-- | src/gnsrecord/gnsrecord_misc.c | 17 | ||||
-rw-r--r-- | src/gnsrecord/gnsrecord_serialization.c | 3 | ||||
-rw-r--r-- | src/gnsrecord/test_gnsrecord_crypto.c | 36 | ||||
-rw-r--r-- | src/include/gnunet_crypto_lib.h | 78 | ||||
-rw-r--r-- | src/include/gnunet_gnsrecord_lib.h | 33 | ||||
-rw-r--r-- | src/util/Makefile.am | 1 | ||||
-rw-r--r-- | src/util/crypto_ecc.c | 129 | ||||
-rw-r--r-- | src/util/crypto_ecc_gnsrecord.c | 351 |
10 files changed, 891 insertions, 185 deletions
diff --git a/po/POTFILES.in b/po/POTFILES.in index 3416e6c84..996b8bda2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in | |||
@@ -313,6 +313,7 @@ src/reclaim/plugin_rest_reclaim.c | |||
313 | src/reclaim/reclaim_api.c | 313 | src/reclaim/reclaim_api.c |
314 | src/reclaim/reclaim_attribute.c | 314 | src/reclaim/reclaim_attribute.c |
315 | src/reclaim/reclaim_credential.c | 315 | src/reclaim/reclaim_credential.c |
316 | src/reclaim/test.c | ||
316 | src/regex/gnunet-daemon-regexprofiler.c | 317 | src/regex/gnunet-daemon-regexprofiler.c |
317 | src/regex/gnunet-regex-profiler.c | 318 | src/regex/gnunet-regex-profiler.c |
318 | src/regex/gnunet-regex-simulation-profiler.c | 319 | src/regex/gnunet-regex-simulation-profiler.c |
@@ -517,6 +518,7 @@ src/util/crypto_abe.c | |||
517 | src/util/crypto_crc.c | 518 | src/util/crypto_crc.c |
518 | src/util/crypto_ecc.c | 519 | src/util/crypto_ecc.c |
519 | src/util/crypto_ecc_dlog.c | 520 | src/util/crypto_ecc_dlog.c |
521 | src/util/crypto_ecc_gnsrecord.c | ||
520 | src/util/crypto_ecc_setup.c | 522 | src/util/crypto_ecc_setup.c |
521 | src/util/crypto_hash.c | 523 | src/util/crypto_hash.c |
522 | src/util/crypto_hash_file.c | 524 | src/util/crypto_hash_file.c |
diff --git a/src/gnsrecord/gnsrecord_crypto.c b/src/gnsrecord/gnsrecord_crypto.c index 9c551a936..289f0e885 100644 --- a/src/gnsrecord/gnsrecord_crypto.c +++ b/src/gnsrecord/gnsrecord_crypto.c | |||
@@ -64,7 +64,6 @@ ecdsa_symmetric_decrypt ( | |||
64 | } | 64 | } |
65 | 65 | ||
66 | 66 | ||
67 | |||
68 | ssize_t | 67 | ssize_t |
69 | ecdsa_symmetric_encrypt ( | 68 | ecdsa_symmetric_encrypt ( |
70 | const void *block, | 69 | const void *block, |
@@ -92,6 +91,34 @@ ecdsa_symmetric_encrypt ( | |||
92 | } | 91 | } |
93 | 92 | ||
94 | 93 | ||
94 | enum GNUNET_GenericReturnValue | ||
95 | eddsa_symmetric_decrypt ( | ||
96 | const void *block, | ||
97 | size_t size, | ||
98 | const unsigned char *key, | ||
99 | const unsigned char *nonce, | ||
100 | void *result) | ||
101 | { | ||
102 | if (0 != crypto_secretbox_open_easy (result, block, size, nonce, key)) | ||
103 | { | ||
104 | return GNUNET_SYSERR; | ||
105 | } | ||
106 | return GNUNET_OK; | ||
107 | } | ||
108 | |||
109 | |||
110 | enum GNUNET_GenericReturnValue | ||
111 | eddsa_symmetric_encrypt ( | ||
112 | const void *block, | ||
113 | size_t size, | ||
114 | const unsigned char *key, | ||
115 | const unsigned char *nonce, | ||
116 | void *result) | ||
117 | { | ||
118 | crypto_secretbox_easy (result, block, size, nonce, key); | ||
119 | return GNUNET_OK; | ||
120 | } | ||
121 | |||
95 | 122 | ||
96 | /** | 123 | /** |
97 | * Derive session key and iv from label and public key. | 124 | * Derive session key and iv from label and public key. |
@@ -131,6 +158,42 @@ derive_block_aes_key (unsigned char *ctr, | |||
131 | 158 | ||
132 | 159 | ||
133 | /** | 160 | /** |
161 | * Derive session key and iv from label and public key. | ||
162 | * | ||
163 | * @param nonce initialization vector to initialize | ||
164 | * @param skey session key to initialize | ||
165 | * @param label label to use for KDF | ||
166 | * @param pub public key to use for KDF | ||
167 | */ | ||
168 | static void | ||
169 | derive_block_xsalsa_key (unsigned char *nonce, | ||
170 | unsigned char *key, | ||
171 | const char *label, | ||
172 | uint64_t exp, | ||
173 | const struct GNUNET_CRYPTO_EddsaPublicKey *pub) | ||
174 | { | ||
175 | static const char ctx_key[] = "gns-aes-ctx-key"; | ||
176 | static const char ctx_iv[] = "gns-aes-ctx-iv"; | ||
177 | |||
178 | GNUNET_CRYPTO_kdf (key, crypto_secretbox_KEYBYTES, | ||
179 | ctx_key, strlen (ctx_key), | ||
180 | pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey), | ||
181 | label, strlen (label), | ||
182 | NULL, 0); | ||
183 | memset (nonce, 0, crypto_secretbox_NONCEBYTES); | ||
184 | /** 16 byte nonce **/ | ||
185 | GNUNET_CRYPTO_kdf (nonce, (crypto_secretbox_NONCEBYTES - sizeof (exp)), | ||
186 | ctx_iv, strlen (ctx_iv), | ||
187 | pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey), | ||
188 | label, strlen (label), | ||
189 | NULL, 0); | ||
190 | /** Expiration time 64 bit. **/ | ||
191 | memcpy (nonce + (crypto_secretbox_NONCEBYTES - sizeof (exp)), | ||
192 | &exp, sizeof (exp)); | ||
193 | } | ||
194 | |||
195 | |||
196 | /** | ||
134 | * Sign name and records | 197 | * Sign name and records |
135 | * | 198 | * |
136 | * @param key the private key | 199 | * @param key the private key |
@@ -246,6 +309,119 @@ block_create_ecdsa (const struct GNUNET_CRYPTO_EcdsaPrivateKey *key, | |||
246 | 309 | ||
247 | 310 | ||
248 | /** | 311 | /** |
312 | * Sign name and records (EDDSA version) | ||
313 | * | ||
314 | * @param key the private key | ||
315 | * @param pkey associated public key | ||
316 | * @param expire block expiration | ||
317 | * @param label the name for the records | ||
318 | * @param rd record data | ||
319 | * @param rd_count number of records | ||
320 | * @return NULL on error (block too large) | ||
321 | */ | ||
322 | static struct GNUNET_GNSRECORD_Block * | ||
323 | block_create_eddsa (const struct GNUNET_CRYPTO_EddsaPrivateKey *key, | ||
324 | const struct GNUNET_CRYPTO_EddsaPublicKey *pkey, | ||
325 | struct GNUNET_TIME_Absolute expire, | ||
326 | const char *label, | ||
327 | const struct GNUNET_GNSRECORD_Data *rd, | ||
328 | unsigned int rd_count) | ||
329 | { | ||
330 | ssize_t payload_len = GNUNET_GNSRECORD_records_get_size (rd_count, | ||
331 | rd); | ||
332 | struct GNUNET_GNSRECORD_Block *block; | ||
333 | struct GNUNET_GNSRECORD_EddsaBlock *edblock; | ||
334 | struct GNUNET_CRYPTO_EddsaPrivateScalar dkey; | ||
335 | unsigned char nonce[crypto_secretbox_NONCEBYTES]; | ||
336 | unsigned char skey[crypto_secretbox_KEYBYTES]; | ||
337 | struct GNUNET_GNSRECORD_Data rdc[GNUNET_NZL (rd_count)]; | ||
338 | uint32_t rd_count_nbo; | ||
339 | struct GNUNET_TIME_Absolute now; | ||
340 | |||
341 | if (payload_len < 0) | ||
342 | { | ||
343 | GNUNET_break (0); | ||
344 | return NULL; | ||
345 | } | ||
346 | if (payload_len > GNUNET_GNSRECORD_MAX_BLOCK_SIZE) | ||
347 | { | ||
348 | GNUNET_break (0); | ||
349 | return NULL; | ||
350 | } | ||
351 | /* convert relative to absolute times */ | ||
352 | now = GNUNET_TIME_absolute_get (); | ||
353 | for (unsigned int i = 0; i < rd_count; i++) | ||
354 | { | ||
355 | rdc[i] = rd[i]; | ||
356 | if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)) | ||
357 | { | ||
358 | struct GNUNET_TIME_Relative t; | ||
359 | |||
360 | /* encrypted blocks must never have relative expiration times, convert! */ | ||
361 | rdc[i].flags &= ~GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; | ||
362 | t.rel_value_us = rdc[i].expiration_time; | ||
363 | rdc[i].expiration_time = GNUNET_TIME_absolute_add (now, t).abs_value_us; | ||
364 | } | ||
365 | } | ||
366 | /* serialize */ | ||
367 | rd_count_nbo = htonl (rd_count); | ||
368 | { | ||
369 | char payload[sizeof(uint32_t) + payload_len]; | ||
370 | |||
371 | GNUNET_memcpy (payload, | ||
372 | &rd_count_nbo, | ||
373 | sizeof(uint32_t)); | ||
374 | GNUNET_assert (payload_len == | ||
375 | GNUNET_GNSRECORD_records_serialize (rd_count, | ||
376 | rdc, | ||
377 | payload_len, | ||
378 | &payload[sizeof(uint32_t) | ||
379 | ])); | ||
380 | block = GNUNET_malloc (sizeof(struct GNUNET_GNSRECORD_Block) | ||
381 | + sizeof(uint32_t) | ||
382 | + payload_len | ||
383 | + crypto_secretbox_MACBYTES); | ||
384 | edblock = &block->eddsa_block; | ||
385 | block->type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY); | ||
386 | edblock->purpose.size = htonl (sizeof(uint32_t) | ||
387 | + payload_len | ||
388 | + sizeof(struct | ||
389 | GNUNET_CRYPTO_EccSignaturePurpose) | ||
390 | + sizeof(struct GNUNET_TIME_AbsoluteNBO) | ||
391 | + crypto_secretbox_MACBYTES); | ||
392 | edblock->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN); | ||
393 | edblock->expiration_time = GNUNET_TIME_absolute_hton (expire); | ||
394 | /* encrypt and sign */ | ||
395 | GNUNET_CRYPTO_eddsa_private_key_derive (key, | ||
396 | label, | ||
397 | "gns", | ||
398 | &dkey); | ||
399 | // FIXME: We may want a key_get_public_from_private_scalar function | ||
400 | struct GNUNET_CRYPTO_EddsaPublicKey test; | ||
401 | crypto_scalarmult_ed25519_base_noclamp (test.q_y, | ||
402 | dkey.s); | ||
403 | edblock->derived_key = test; | ||
404 | derive_block_xsalsa_key (nonce, | ||
405 | skey, | ||
406 | label, | ||
407 | edblock->expiration_time.abs_value_us__, | ||
408 | pkey); | ||
409 | GNUNET_break (GNUNET_OK == | ||
410 | eddsa_symmetric_encrypt (payload, | ||
411 | payload_len | ||
412 | + sizeof(uint32_t), | ||
413 | skey, | ||
414 | nonce, | ||
415 | &edblock[1])); | ||
416 | } | ||
417 | GNUNET_CRYPTO_eddsa_sign_with_scalar (&dkey, | ||
418 | &edblock->purpose, | ||
419 | &edblock->signature); | ||
420 | return block; | ||
421 | } | ||
422 | |||
423 | |||
424 | /** | ||
249 | * Sign name and records | 425 | * Sign name and records |
250 | * | 426 | * |
251 | * @param key the private key | 427 | * @param key the private key |
@@ -263,6 +439,7 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key, | |||
263 | unsigned int rd_count) | 439 | unsigned int rd_count) |
264 | { | 440 | { |
265 | struct GNUNET_CRYPTO_EcdsaPublicKey pkey; | 441 | struct GNUNET_CRYPTO_EcdsaPublicKey pkey; |
442 | struct GNUNET_CRYPTO_EddsaPublicKey edkey; | ||
266 | 443 | ||
267 | switch (ntohl (key->type)) | 444 | switch (ntohl (key->type)) |
268 | { | 445 | { |
@@ -275,6 +452,15 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key, | |||
275 | label, | 452 | label, |
276 | rd, | 453 | rd, |
277 | rd_count); | 454 | rd_count); |
455 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
456 | GNUNET_CRYPTO_eddsa_key_get_public (&key->eddsa_key, | ||
457 | &edkey); | ||
458 | return block_create_eddsa (&key->eddsa_key, | ||
459 | &edkey, | ||
460 | expire, | ||
461 | label, | ||
462 | rd, | ||
463 | rd_count); | ||
278 | default: | 464 | default: |
279 | GNUNET_assert (0); | 465 | GNUNET_assert (0); |
280 | } | 466 | } |
@@ -319,33 +505,45 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey, | |||
319 | unsigned int rd_count) | 505 | unsigned int rd_count) |
320 | { | 506 | { |
321 | const struct GNUNET_CRYPTO_EcdsaPrivateKey *key; | 507 | const struct GNUNET_CRYPTO_EcdsaPrivateKey *key; |
508 | struct GNUNET_CRYPTO_EddsaPublicKey edpubkey; | ||
322 | 509 | ||
323 | if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (pkey->type)) | 510 | if (GNUNET_IDENTITY_TYPE_ECDSA == ntohl (pkey->type)) |
324 | { | 511 | { |
325 | return NULL; // FIXME | 512 | key = &pkey->ecdsa_key; |
326 | } | ||
327 | key = &pkey->ecdsa_key; | ||
328 | #define CSIZE 64 | 513 | #define CSIZE 64 |
329 | static struct KeyCacheLine cache[CSIZE]; | 514 | static struct KeyCacheLine cache[CSIZE]; |
330 | struct KeyCacheLine *line; | 515 | struct KeyCacheLine *line; |
331 | 516 | ||
332 | line = &cache[(*(unsigned int *) key) % CSIZE]; | 517 | line = &cache[(*(unsigned int *) key) % CSIZE]; |
333 | if (0 != memcmp (&line->key, | 518 | if (0 != memcmp (&line->key, |
334 | key, | 519 | key, |
335 | sizeof(*key))) | 520 | sizeof(*key))) |
521 | { | ||
522 | /* cache miss, recompute */ | ||
523 | line->key = *key; | ||
524 | GNUNET_CRYPTO_ecdsa_key_get_public (key, | ||
525 | &line->pkey); | ||
526 | } | ||
527 | #undef CSIZE | ||
528 | return block_create_ecdsa (key, | ||
529 | &line->pkey, | ||
530 | expire, | ||
531 | label, | ||
532 | rd, | ||
533 | rd_count); | ||
534 | } | ||
535 | else if (GNUNET_IDENTITY_TYPE_EDDSA == ntohl (pkey->type)) | ||
336 | { | 536 | { |
337 | /* cache miss, recompute */ | 537 | GNUNET_CRYPTO_eddsa_key_get_public (&pkey->eddsa_key, |
338 | line->key = *key; | 538 | &edpubkey); |
339 | GNUNET_CRYPTO_ecdsa_key_get_public (key, | 539 | return block_create_eddsa (&pkey->eddsa_key, |
340 | &line->pkey); | 540 | &edpubkey, |
541 | expire, | ||
542 | label, | ||
543 | rd, | ||
544 | rd_count); | ||
341 | } | 545 | } |
342 | #undef CSIZE | 546 | return NULL; |
343 | return block_create_ecdsa (key, | ||
344 | &line->pkey, | ||
345 | expire, | ||
346 | label, | ||
347 | rd, | ||
348 | rd_count); | ||
349 | } | 547 | } |
350 | 548 | ||
351 | 549 | ||
@@ -359,21 +557,23 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey, | |||
359 | enum GNUNET_GenericReturnValue | 557 | enum GNUNET_GenericReturnValue |
360 | GNUNET_GNSRECORD_block_verify (const struct GNUNET_GNSRECORD_Block *block) | 558 | GNUNET_GNSRECORD_block_verify (const struct GNUNET_GNSRECORD_Block *block) |
361 | { | 559 | { |
362 | const struct GNUNET_CRYPTO_EcdsaPublicKey *key; | 560 | switch (ntohl (block->type)) |
363 | const struct GNUNET_GNSRECORD_EcdsaBlock *ecblock; | ||
364 | |||
365 | if (GNUNET_GNSRECORD_TYPE_PKEY != ntohl (block->type)) | ||
366 | { | 561 | { |
367 | GNUNET_break (0); | 562 | case GNUNET_GNSRECORD_TYPE_PKEY: |
563 | return GNUNET_CRYPTO_ecdsa_verify_ ( | ||
564 | GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, | ||
565 | &block->ecdsa_block.purpose, | ||
566 | &block->ecdsa_block.signature, | ||
567 | &block->ecdsa_block.derived_key); | ||
568 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
569 | return GNUNET_CRYPTO_eddsa_verify_ ( | ||
570 | GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, | ||
571 | &block->eddsa_block.purpose, | ||
572 | &block->eddsa_block.signature, | ||
573 | &block->eddsa_block.derived_key); | ||
574 | default: | ||
368 | return GNUNET_NO; | 575 | return GNUNET_NO; |
369 | } | 576 | } |
370 | ecblock = &block->ecdsa_block; | ||
371 | key = &ecblock->derived_key; | ||
372 | |||
373 | return GNUNET_CRYPTO_ecdsa_verify_ (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, | ||
374 | &ecblock->purpose, | ||
375 | &ecblock->signature, | ||
376 | key); | ||
377 | } | 577 | } |
378 | 578 | ||
379 | 579 | ||
@@ -505,6 +705,134 @@ block_decrypt_ecdsa (const struct GNUNET_GNSRECORD_EcdsaBlock *block, | |||
505 | } | 705 | } |
506 | 706 | ||
507 | 707 | ||
708 | enum GNUNET_GenericReturnValue | ||
709 | block_decrypt_eddsa (const struct GNUNET_GNSRECORD_EddsaBlock *block, | ||
710 | const struct | ||
711 | GNUNET_CRYPTO_EddsaPublicKey *zone_key, | ||
712 | const char *label, | ||
713 | GNUNET_GNSRECORD_RecordCallback proc, | ||
714 | void *proc_cls) | ||
715 | { | ||
716 | size_t payload_len = ntohl (block->purpose.size) | ||
717 | - sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) | ||
718 | - sizeof(struct GNUNET_TIME_AbsoluteNBO); | ||
719 | unsigned char nonce[crypto_secretbox_NONCEBYTES]; | ||
720 | unsigned char key[crypto_secretbox_KEYBYTES]; | ||
721 | |||
722 | if (ntohl (block->purpose.size) < | ||
723 | sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) | ||
724 | + sizeof(struct GNUNET_TIME_AbsoluteNBO)) | ||
725 | { | ||
726 | GNUNET_break_op (0); | ||
727 | return GNUNET_SYSERR; | ||
728 | } | ||
729 | derive_block_xsalsa_key (nonce, | ||
730 | key, | ||
731 | label, | ||
732 | block->expiration_time.abs_value_us__, | ||
733 | zone_key); | ||
734 | { | ||
735 | char payload[payload_len]; | ||
736 | uint32_t rd_count; | ||
737 | |||
738 | GNUNET_break (GNUNET_OK == | ||
739 | eddsa_symmetric_decrypt (&block[1], payload_len, | ||
740 | key, nonce, | ||
741 | payload)); | ||
742 | GNUNET_memcpy (&rd_count, | ||
743 | payload, | ||
744 | sizeof(uint32_t)); | ||
745 | rd_count = ntohl (rd_count); | ||
746 | if (rd_count > 2048) | ||
747 | { | ||
748 | /* limit to sane value */ | ||
749 | GNUNET_break_op (0); | ||
750 | return GNUNET_SYSERR; | ||
751 | } | ||
752 | { | ||
753 | struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (rd_count)]; | ||
754 | unsigned int j; | ||
755 | struct GNUNET_TIME_Absolute now; | ||
756 | |||
757 | if (GNUNET_OK != | ||
758 | GNUNET_GNSRECORD_records_deserialize (payload_len - sizeof(uint32_t), | ||
759 | &payload[sizeof(uint32_t)], | ||
760 | rd_count, | ||
761 | rd)) | ||
762 | { | ||
763 | GNUNET_break_op (0); | ||
764 | return GNUNET_SYSERR; | ||
765 | } | ||
766 | /* hide expired records */ | ||
767 | now = GNUNET_TIME_absolute_get (); | ||
768 | j = 0; | ||
769 | for (unsigned int i = 0; i < rd_count; i++) | ||
770 | { | ||
771 | if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)) | ||
772 | { | ||
773 | /* encrypted blocks must never have relative expiration times, skip! */ | ||
774 | GNUNET_break_op (0); | ||
775 | continue; | ||
776 | } | ||
777 | |||
778 | if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD)) | ||
779 | { | ||
780 | int include_record = GNUNET_YES; | ||
781 | /* Shadow record, figure out if we have a not expired active record */ | ||
782 | for (unsigned int k = 0; k < rd_count; k++) | ||
783 | { | ||
784 | if (k == i) | ||
785 | continue; | ||
786 | if (rd[i].expiration_time < now.abs_value_us) | ||
787 | include_record = GNUNET_NO; /* Shadow record is expired */ | ||
788 | if ((rd[k].record_type == rd[i].record_type) && | ||
789 | (rd[k].expiration_time >= now.abs_value_us) && | ||
790 | (0 == (rd[k].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD))) | ||
791 | { | ||
792 | include_record = GNUNET_NO; /* We have a non-expired, non-shadow record of the same type */ | ||
793 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
794 | "Ignoring shadow record\n"); | ||
795 | break; | ||
796 | } | ||
797 | } | ||
798 | if (GNUNET_YES == include_record) | ||
799 | { | ||
800 | rd[i].flags ^= GNUNET_GNSRECORD_RF_SHADOW_RECORD; /* Remove Flag */ | ||
801 | if (j != i) | ||
802 | rd[j] = rd[i]; | ||
803 | j++; | ||
804 | } | ||
805 | } | ||
806 | else if (rd[i].expiration_time >= now.abs_value_us) | ||
807 | { | ||
808 | /* Include this record */ | ||
809 | if (j != i) | ||
810 | rd[j] = rd[i]; | ||
811 | j++; | ||
812 | } | ||
813 | else | ||
814 | { | ||
815 | struct GNUNET_TIME_Absolute at; | ||
816 | |||
817 | at.abs_value_us = rd[i].expiration_time; | ||
818 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
819 | "Excluding record that expired %s (%llu ago)\n", | ||
820 | GNUNET_STRINGS_absolute_time_to_string (at), | ||
821 | (unsigned long long) rd[i].expiration_time | ||
822 | - now.abs_value_us); | ||
823 | } | ||
824 | } | ||
825 | rd_count = j; | ||
826 | if (NULL != proc) | ||
827 | proc (proc_cls, | ||
828 | rd_count, | ||
829 | (0 != rd_count) ? rd : NULL); | ||
830 | } | ||
831 | } | ||
832 | return GNUNET_OK; | ||
833 | } | ||
834 | |||
835 | |||
508 | /** | 836 | /** |
509 | * Decrypt block. | 837 | * Decrypt block. |
510 | * | 838 | * |
@@ -524,17 +852,17 @@ GNUNET_GNSRECORD_block_decrypt (const struct GNUNET_GNSRECORD_Block *block, | |||
524 | GNUNET_GNSRECORD_RecordCallback proc, | 852 | GNUNET_GNSRECORD_RecordCallback proc, |
525 | void *proc_cls) | 853 | void *proc_cls) |
526 | { | 854 | { |
527 | const struct GNUNET_CRYPTO_EcdsaPublicKey *key; | 855 | switch (ntohl (zone_key->type)) |
528 | |||
529 | if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (zone_key->type)) | ||
530 | { | 856 | { |
531 | return GNUNET_NO; | 857 | case GNUNET_IDENTITY_TYPE_ECDSA: |
858 | return block_decrypt_ecdsa (&block->ecdsa_block, | ||
859 | &zone_key->ecdsa_key, label, proc, proc_cls); | ||
860 | case GNUNET_IDENTITY_TYPE_EDDSA: | ||
861 | return block_decrypt_eddsa (&block->eddsa_block, | ||
862 | &zone_key->eddsa_key, label, proc, proc_cls); | ||
863 | default: | ||
864 | return GNUNET_SYSERR; | ||
532 | } | 865 | } |
533 | key = &zone_key->ecdsa_key; | ||
534 | |||
535 | return block_decrypt_ecdsa (&block->ecdsa_block, | ||
536 | key, label, proc, proc_cls); | ||
537 | |||
538 | } | 866 | } |
539 | 867 | ||
540 | 868 | ||
@@ -555,6 +883,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct | |||
555 | switch (ntohl (zone->type)) | 883 | switch (ntohl (zone->type)) |
556 | { | 884 | { |
557 | case GNUNET_GNSRECORD_TYPE_PKEY: | 885 | case GNUNET_GNSRECORD_TYPE_PKEY: |
886 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
558 | 887 | ||
559 | GNUNET_IDENTITY_key_get_public (zone, | 888 | GNUNET_IDENTITY_key_get_public (zone, |
560 | &pub); | 889 | &pub); |
@@ -570,6 +899,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct | |||
570 | 899 | ||
571 | /** | 900 | /** |
572 | * Calculate the DHT query for a given @a label in a given @a zone. | 901 | * Calculate the DHT query for a given @a label in a given @a zone. |
902 | * FIXME: We may want to plugin-ize this at some point. | ||
573 | * | 903 | * |
574 | * @param pub public key of the zone | 904 | * @param pub public key of the zone |
575 | * @param label label of the record | 905 | * @param label label of the record |
@@ -595,6 +925,16 @@ GNUNET_GNSRECORD_query_from_public_key (const struct | |||
595 | sizeof (pd.ecdsa_key), | 925 | sizeof (pd.ecdsa_key), |
596 | query); | 926 | query); |
597 | break; | 927 | break; |
928 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
929 | pd.type = pub->type; | ||
930 | GNUNET_CRYPTO_eddsa_public_key_derive (&pub->eddsa_key, | ||
931 | label, | ||
932 | "gns", | ||
933 | &pd.eddsa_key); | ||
934 | GNUNET_CRYPTO_hash (&pd.eddsa_key, | ||
935 | sizeof (pd.eddsa_key), | ||
936 | query); | ||
937 | break; | ||
598 | default: | 938 | default: |
599 | GNUNET_assert (0); | 939 | GNUNET_assert (0); |
600 | } | 940 | } |
diff --git a/src/gnsrecord/gnsrecord_misc.c b/src/gnsrecord/gnsrecord_misc.c index b907eed27..2fe315bd8 100644 --- a/src/gnsrecord/gnsrecord_misc.c +++ b/src/gnsrecord/gnsrecord_misc.c | |||
@@ -338,6 +338,12 @@ GNUNET_GNSRECORD_block_get_size (const struct GNUNET_GNSRECORD_Block *block) | |||
338 | + ntohl (block->ecdsa_block.purpose.size) /* Length of signed data */ | 338 | + ntohl (block->ecdsa_block.purpose.size) /* Length of signed data */ |
339 | - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */ | 339 | - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */ |
340 | break; | 340 | break; |
341 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
342 | return sizeof (uint32_t) /* zone type */ | ||
343 | + sizeof (block->eddsa_block) /* EddsaBlock */ | ||
344 | + ntohl (block->eddsa_block.purpose.size) /* Length of signed data */ | ||
345 | - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */ | ||
346 | |||
341 | default: | 347 | default: |
342 | return 0; | 348 | return 0; |
343 | } | 349 | } |
@@ -354,6 +360,8 @@ GNUNET_GNSRECORD_block_get_expiration (const struct | |||
354 | { | 360 | { |
355 | case GNUNET_GNSRECORD_TYPE_PKEY: | 361 | case GNUNET_GNSRECORD_TYPE_PKEY: |
356 | return GNUNET_TIME_absolute_ntoh (block->ecdsa_block.expiration_time); | 362 | return GNUNET_TIME_absolute_ntoh (block->ecdsa_block.expiration_time); |
363 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
364 | return GNUNET_TIME_absolute_ntoh (block->eddsa_block.expiration_time); | ||
357 | default: | 365 | default: |
358 | GNUNET_break (0); /* Hopefully we never get here, but we might */ | 366 | GNUNET_break (0); /* Hopefully we never get here, but we might */ |
359 | } | 367 | } |
@@ -373,6 +381,11 @@ GNUNET_GNSRECORD_query_from_block (const struct GNUNET_GNSRECORD_Block *block, | |||
373 | sizeof (block->ecdsa_block.derived_key), | 381 | sizeof (block->ecdsa_block.derived_key), |
374 | query); | 382 | query); |
375 | return GNUNET_OK; | 383 | return GNUNET_OK; |
384 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
385 | GNUNET_CRYPTO_hash (&block->eddsa_block.derived_key, | ||
386 | sizeof (block->eddsa_block.derived_key), | ||
387 | query); | ||
388 | return GNUNET_OK; | ||
376 | default: | 389 | default: |
377 | return GNUNET_SYSERR; | 390 | return GNUNET_SYSERR; |
378 | } | 391 | } |
@@ -394,6 +407,10 @@ GNUNET_GNSRECORD_record_to_identity_key (const struct GNUNET_GNSRECORD_Data *rd, | |||
394 | key->type = htonl (rd->record_type); | 407 | key->type = htonl (rd->record_type); |
395 | memcpy (&key->ecdsa_key, rd->data, sizeof (key->ecdsa_key)); | 408 | memcpy (&key->ecdsa_key, rd->data, sizeof (key->ecdsa_key)); |
396 | return GNUNET_OK; | 409 | return GNUNET_OK; |
410 | case GNUNET_GNSRECORD_TYPE_EDKEY: | ||
411 | key->type = htonl (rd->record_type); | ||
412 | memcpy (&key->eddsa_key, rd->data, sizeof (key->eddsa_key)); | ||
413 | return GNUNET_OK; | ||
397 | default: | 414 | default: |
398 | return GNUNET_SYSERR; | 415 | return GNUNET_SYSERR; |
399 | } | 416 | } |
diff --git a/src/gnsrecord/gnsrecord_serialization.c b/src/gnsrecord/gnsrecord_serialization.c index 42665e662..cb6957605 100644 --- a/src/gnsrecord/gnsrecord_serialization.c +++ b/src/gnsrecord/gnsrecord_serialization.c | |||
@@ -124,7 +124,8 @@ GNUNET_GNSRECORD_records_get_size (unsigned int rd_count, | |||
124 | return -1; | 124 | return -1; |
125 | } | 125 | } |
126 | // Do not pad PKEY | 126 | // Do not pad PKEY |
127 | if (GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type) | 127 | if ((GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type) || |
128 | (GNUNET_GNSRECORD_TYPE_EDKEY == rd->record_type)) | ||
128 | return ret; | 129 | return ret; |
129 | /** | 130 | /** |
130 | * Efficiently round up to the next | 131 | * Efficiently round up to the next |
diff --git a/src/gnsrecord/test_gnsrecord_crypto.c b/src/gnsrecord/test_gnsrecord_crypto.c index d541f3076..9394f562d 100644 --- a/src/gnsrecord/test_gnsrecord_crypto.c +++ b/src/gnsrecord/test_gnsrecord_crypto.c | |||
@@ -92,29 +92,22 @@ rd_decrypt_cb (void *cls, | |||
92 | res = 0; | 92 | res = 0; |
93 | } | 93 | } |
94 | 94 | ||
95 | |||
96 | static void | 95 | static void |
97 | run (void *cls, | 96 | test_with_type (struct GNUNET_IDENTITY_PrivateKey *privkey) |
98 | char *const *args, | ||
99 | const char *cfgfile, | ||
100 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
101 | { | 97 | { |
102 | struct GNUNET_GNSRECORD_Block *block; | 98 | struct GNUNET_GNSRECORD_Block *block; |
103 | struct GNUNET_IDENTITY_PublicKey pubkey; | 99 | struct GNUNET_IDENTITY_PublicKey pubkey; |
104 | struct GNUNET_HashCode query_pub; | 100 | struct GNUNET_HashCode query_pub; |
105 | struct GNUNET_HashCode query_priv; | 101 | struct GNUNET_HashCode query_priv; |
106 | struct GNUNET_TIME_Absolute expire = GNUNET_TIME_absolute_get (); | 102 | struct GNUNET_TIME_Absolute expire = GNUNET_TIME_absolute_get (); |
107 | struct GNUNET_IDENTITY_PrivateKey privkey; | ||
108 | 103 | ||
109 | 104 | ||
110 | privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY); | ||
111 | GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key); | ||
112 | /* get public key */ | 105 | /* get public key */ |
113 | GNUNET_IDENTITY_key_get_public (&privkey, | 106 | GNUNET_IDENTITY_key_get_public (privkey, |
114 | &pubkey); | 107 | &pubkey); |
115 | 108 | ||
116 | /* test query derivation */ | 109 | /* test query derivation */ |
117 | GNUNET_GNSRECORD_query_from_private_key (&privkey, | 110 | GNUNET_GNSRECORD_query_from_private_key (privkey, |
118 | "testlabel", | 111 | "testlabel", |
119 | &query_priv); | 112 | &query_priv); |
120 | GNUNET_GNSRECORD_query_from_public_key (&pubkey, | 113 | GNUNET_GNSRECORD_query_from_public_key (&pubkey, |
@@ -129,7 +122,7 @@ run (void *cls, | |||
129 | 122 | ||
130 | /* Create block */ | 123 | /* Create block */ |
131 | GNUNET_assert (NULL != (block = | 124 | GNUNET_assert (NULL != (block = |
132 | GNUNET_GNSRECORD_block_create (&privkey, | 125 | GNUNET_GNSRECORD_block_create (privkey, |
133 | expire, | 126 | expire, |
134 | s_name, | 127 | s_name, |
135 | s_rd, | 128 | s_rd, |
@@ -146,6 +139,27 @@ run (void *cls, | |||
146 | } | 139 | } |
147 | 140 | ||
148 | 141 | ||
142 | |||
143 | static void | ||
144 | run (void *cls, | ||
145 | char *const *args, | ||
146 | const char *cfgfile, | ||
147 | const struct GNUNET_CONFIGURATION_Handle *cfg) | ||
148 | { | ||
149 | struct GNUNET_IDENTITY_PrivateKey privkey; | ||
150 | struct GNUNET_IDENTITY_PrivateKey privkey_ed; | ||
151 | |||
152 | |||
153 | privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY); | ||
154 | GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key); | ||
155 | test_with_type (&privkey); | ||
156 | |||
157 | privkey_ed.type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY); | ||
158 | GNUNET_CRYPTO_eddsa_key_create (&privkey_ed.eddsa_key); | ||
159 | test_with_type(&privkey_ed); | ||
160 | } | ||
161 | |||
162 | |||
149 | int | 163 | int |
150 | main (int argc, char *argv[]) | 164 | main (int argc, char *argv[]) |
151 | { | 165 | { |
diff --git a/src/include/gnunet_crypto_lib.h b/src/include/gnunet_crypto_lib.h index 43cdfdfac..a334b50d0 100644 --- a/src/include/gnunet_crypto_lib.h +++ b/src/include/gnunet_crypto_lib.h | |||
@@ -275,6 +275,19 @@ struct GNUNET_CRYPTO_EddsaPrivateKey | |||
275 | 275 | ||
276 | 276 | ||
277 | /** | 277 | /** |
278 | * Private ECC scalar encoded for transmission. To be used only for EdDSA | ||
279 | * signatures. | ||
280 | */ | ||
281 | struct GNUNET_CRYPTO_EddsaPrivateScalar | ||
282 | { | ||
283 | /** | ||
284 | * s is the expandedprivate 512-bit scalar of a private key. | ||
285 | */ | ||
286 | unsigned char s[512 / 8]; | ||
287 | }; | ||
288 | |||
289 | |||
290 | /** | ||
278 | * @brief type for session keys | 291 | * @brief type for session keys |
279 | */ | 292 | */ |
280 | struct GNUNET_CRYPTO_SymmetricSessionKey | 293 | struct GNUNET_CRYPTO_SymmetricSessionKey |
@@ -1907,6 +1920,71 @@ GNUNET_CRYPTO_ecdsa_public_key_derive ( | |||
1907 | 1920 | ||
1908 | 1921 | ||
1909 | /** | 1922 | /** |
1923 | * @ingroup crypto | ||
1924 | * Derive a private scalar from a given private key and a label. | ||
1925 | * Essentially calculates a private key 'h = H(l,P) * d mod n' | ||
1926 | * where n is the size of the ECC group and P is the public | ||
1927 | * key associated with the private key 'd'. | ||
1928 | * The result is the derived private _scalar_, not the private | ||
1929 | * key as for EdDSA we cannot derive before we hash the | ||
1930 | * private key. | ||
1931 | * | ||
1932 | * @param priv original private key | ||
1933 | * @param label label to use for key deriviation | ||
1934 | * @param context additional context to use for HKDF of 'h'; | ||
1935 | * typically the name of the subsystem/application | ||
1936 | * @param result derived private scalar | ||
1937 | */ | ||
1938 | void | ||
1939 | GNUNET_CRYPTO_eddsa_private_key_derive ( | ||
1940 | const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, | ||
1941 | const char *label, | ||
1942 | const char *context, | ||
1943 | struct GNUNET_CRYPTO_EddsaPrivateScalar *result); | ||
1944 | |||
1945 | |||
1946 | /** | ||
1947 | * @ingroup crypto | ||
1948 | * Derive a public key from a given public key and a label. | ||
1949 | * Essentially calculates a public key 'V = H(l,P) * P'. | ||
1950 | * | ||
1951 | * @param pub original public key | ||
1952 | * @param label label to use for key deriviation | ||
1953 | * @param context additional context to use for HKDF of 'h'. | ||
1954 | * typically the name of the subsystem/application | ||
1955 | * @param result where to write the derived public key | ||
1956 | */ | ||
1957 | void | ||
1958 | GNUNET_CRYPTO_eddsa_public_key_derive ( | ||
1959 | const struct GNUNET_CRYPTO_EddsaPublicKey *pub, | ||
1960 | const char *label, | ||
1961 | const char *context, | ||
1962 | struct GNUNET_CRYPTO_EddsaPublicKey *result); | ||
1963 | |||
1964 | |||
1965 | /** | ||
1966 | * This is a signature function for EdDSA which takes the | ||
1967 | * secret scalar sk instead of the private seed which is | ||
1968 | * usually the case for crypto APIs. We require this functionality | ||
1969 | * in order to use derived private keys for signatures we | ||
1970 | * cannot calculate the inverse of a sk to find the seed | ||
1971 | * efficiently. | ||
1972 | * | ||
1973 | * The resulting signature is a standard EdDSA signature | ||
1974 | * which can be verified using the usual APIs. | ||
1975 | * | ||
1976 | * @param sk the secret scalar | ||
1977 | * @param purp the signature purpose | ||
1978 | * @param sig the resulting signature | ||
1979 | */ | ||
1980 | void | ||
1981 | GNUNET_CRYPTO_eddsa_sign_with_scalar ( | ||
1982 | const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv, | ||
1983 | const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, | ||
1984 | struct GNUNET_CRYPTO_EddsaSignature *sig); | ||
1985 | |||
1986 | |||
1987 | /** | ||
1910 | * Output the given MPI value to the given buffer in network | 1988 | * Output the given MPI value to the given buffer in network |
1911 | * byte order. The MPI @a val may not be negative. | 1989 | * byte order. The MPI @a val may not be negative. |
1912 | * | 1990 | * |
diff --git a/src/include/gnunet_gnsrecord_lib.h b/src/include/gnunet_gnsrecord_lib.h index 61cbac2ca..5afb3f253 100644 --- a/src/include/gnunet_gnsrecord_lib.h +++ b/src/include/gnunet_gnsrecord_lib.h | |||
@@ -197,6 +197,37 @@ struct GNUNET_GNSRECORD_EcdsaBlock | |||
197 | /* followed by encrypted data */ | 197 | /* followed by encrypted data */ |
198 | }; | 198 | }; |
199 | 199 | ||
200 | |||
201 | /** | ||
202 | * Information we have in an encrypted block with record data (i.e. in the DHT). | ||
203 | */ | ||
204 | struct GNUNET_GNSRECORD_EddsaBlock | ||
205 | { | ||
206 | /** | ||
207 | * Derived key used for signing; hash of this is the query. | ||
208 | */ | ||
209 | struct GNUNET_CRYPTO_EddsaPublicKey derived_key; | ||
210 | |||
211 | /** | ||
212 | * Signature of the block. | ||
213 | */ | ||
214 | struct GNUNET_CRYPTO_EddsaSignature signature; | ||
215 | |||
216 | /** | ||
217 | * Number of bytes signed; also specifies the number of bytes | ||
218 | * of encrypted data that follow. | ||
219 | */ | ||
220 | struct GNUNET_CRYPTO_EccSignaturePurpose purpose; | ||
221 | |||
222 | /** | ||
223 | * Expiration time of the block. | ||
224 | */ | ||
225 | struct GNUNET_TIME_AbsoluteNBO expiration_time; | ||
226 | |||
227 | /* followed by encrypted data */ | ||
228 | }; | ||
229 | |||
230 | |||
200 | struct GNUNET_GNSRECORD_Block | 231 | struct GNUNET_GNSRECORD_Block |
201 | { | 232 | { |
202 | uint32_t type; | 233 | uint32_t type; |
@@ -204,7 +235,7 @@ struct GNUNET_GNSRECORD_Block | |||
204 | union | 235 | union |
205 | { | 236 | { |
206 | struct GNUNET_GNSRECORD_EcdsaBlock ecdsa_block; | 237 | struct GNUNET_GNSRECORD_EcdsaBlock ecdsa_block; |
207 | //struct GNUNET_GNSRECORD_EddsaBlock eddsa_block; | 238 | struct GNUNET_GNSRECORD_EddsaBlock eddsa_block; |
208 | }; | 239 | }; |
209 | }; | 240 | }; |
210 | 241 | ||
diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 72fd37bde..09620aee0 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am | |||
@@ -61,6 +61,7 @@ libgnunetutil_la_SOURCES = \ | |||
61 | crypto_symmetric.c \ | 61 | crypto_symmetric.c \ |
62 | crypto_crc.c \ | 62 | crypto_crc.c \ |
63 | crypto_ecc.c \ | 63 | crypto_ecc.c \ |
64 | crypto_ecc_gnsrecord.c \ | ||
64 | $(DLOG) \ | 65 | $(DLOG) \ |
65 | crypto_ecc_setup.c \ | 66 | crypto_ecc_setup.c \ |
66 | crypto_hash.c \ | 67 | crypto_hash.c \ |
diff --git a/src/util/crypto_ecc.c b/src/util/crypto_ecc.c index efbf2ee17..5b1b579ec 100644 --- a/src/util/crypto_ecc.c +++ b/src/util/crypto_ecc.c | |||
@@ -714,135 +714,6 @@ GNUNET_CRYPTO_ecc_ecdh (const struct GNUNET_CRYPTO_EcdhePrivateKey *priv, | |||
714 | } | 714 | } |
715 | 715 | ||
716 | 716 | ||
717 | /** | ||
718 | * Derive the 'h' value for key derivation, where | ||
719 | * 'h = H(l,P)'. | ||
720 | * | ||
721 | * @param pub public key for deriviation | ||
722 | * @param label label for deriviation | ||
723 | * @param context additional context to use for HKDF of 'h'; | ||
724 | * typically the name of the subsystem/application | ||
725 | * @return h value | ||
726 | */ | ||
727 | static gcry_mpi_t | ||
728 | derive_h (const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, | ||
729 | const char *label, | ||
730 | const char *context) | ||
731 | { | ||
732 | gcry_mpi_t h; | ||
733 | struct GNUNET_HashCode hc; | ||
734 | static const char *const salt = "key-derivation"; | ||
735 | |||
736 | GNUNET_CRYPTO_kdf (&hc, | ||
737 | sizeof(hc), | ||
738 | salt, | ||
739 | strlen (salt), | ||
740 | pub, | ||
741 | sizeof(*pub), | ||
742 | label, | ||
743 | strlen (label), | ||
744 | context, | ||
745 | strlen (context), | ||
746 | NULL, | ||
747 | 0); | ||
748 | GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc)); | ||
749 | return h; | ||
750 | } | ||
751 | |||
752 | |||
753 | struct GNUNET_CRYPTO_EcdsaPrivateKey * | ||
754 | GNUNET_CRYPTO_ecdsa_private_key_derive ( | ||
755 | const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv, | ||
756 | const char *label, | ||
757 | const char *context) | ||
758 | { | ||
759 | struct GNUNET_CRYPTO_EcdsaPublicKey pub; | ||
760 | struct GNUNET_CRYPTO_EcdsaPrivateKey *ret; | ||
761 | uint8_t dc[32]; | ||
762 | gcry_mpi_t h; | ||
763 | gcry_mpi_t x; | ||
764 | gcry_mpi_t d; | ||
765 | gcry_mpi_t n; | ||
766 | gcry_ctx_t ctx; | ||
767 | |||
768 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); | ||
769 | |||
770 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
771 | GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub); | ||
772 | |||
773 | h = derive_h (&pub, label, context); | ||
774 | /* Convert to big endian for libgcrypt */ | ||
775 | for (size_t i = 0; i < 32; i++) | ||
776 | dc[i] = priv->d[31 - i]; | ||
777 | GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); | ||
778 | d = gcry_mpi_new (256); | ||
779 | gcry_mpi_mulm (d, h, x, n); | ||
780 | gcry_mpi_release (h); | ||
781 | gcry_mpi_release (x); | ||
782 | gcry_mpi_release (n); | ||
783 | gcry_ctx_release (ctx); | ||
784 | ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey); | ||
785 | GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); | ||
786 | /* Convert to big endian for libgcrypt */ | ||
787 | for (size_t i = 0; i < 32; i++) | ||
788 | ret->d[i] = dc[31 - i]; | ||
789 | sodium_memzero (dc, sizeof(dc)); | ||
790 | gcry_mpi_release (d); | ||
791 | return ret; | ||
792 | } | ||
793 | |||
794 | |||
795 | void | ||
796 | GNUNET_CRYPTO_ecdsa_public_key_derive ( | ||
797 | const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, | ||
798 | const char *label, | ||
799 | const char *context, | ||
800 | struct GNUNET_CRYPTO_EcdsaPublicKey *result) | ||
801 | { | ||
802 | gcry_ctx_t ctx; | ||
803 | gcry_mpi_t q_y; | ||
804 | gcry_mpi_t h; | ||
805 | gcry_mpi_t n; | ||
806 | gcry_mpi_t h_mod_n; | ||
807 | gcry_mpi_point_t q; | ||
808 | gcry_mpi_point_t v; | ||
809 | |||
810 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); | ||
811 | |||
812 | /* obtain point 'q' from original public key. The provided 'q' is | ||
813 | compressed thus we first store it in the context and then get it | ||
814 | back as a (decompresssed) point. */ | ||
815 | q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); | ||
816 | GNUNET_assert (NULL != q_y); | ||
817 | GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); | ||
818 | gcry_mpi_release (q_y); | ||
819 | q = gcry_mpi_ec_get_point ("q", ctx, 0); | ||
820 | GNUNET_assert (q); | ||
821 | |||
822 | /* calculate h_mod_n = h % n */ | ||
823 | h = derive_h (pub, label, context); | ||
824 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
825 | h_mod_n = gcry_mpi_new (256); | ||
826 | gcry_mpi_mod (h_mod_n, h, n); | ||
827 | /* calculate v = h_mod_n * q */ | ||
828 | v = gcry_mpi_point_new (0); | ||
829 | gcry_mpi_ec_mul (v, h_mod_n, q, ctx); | ||
830 | gcry_mpi_release (h_mod_n); | ||
831 | gcry_mpi_release (h); | ||
832 | gcry_mpi_release (n); | ||
833 | gcry_mpi_point_release (q); | ||
834 | |||
835 | /* convert point 'v' to public key that we return */ | ||
836 | GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); | ||
837 | gcry_mpi_point_release (v); | ||
838 | q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); | ||
839 | GNUNET_assert (q_y); | ||
840 | GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); | ||
841 | gcry_mpi_release (q_y); | ||
842 | gcry_ctx_release (ctx); | ||
843 | } | ||
844 | |||
845 | |||
846 | enum GNUNET_GenericReturnValue | 717 | enum GNUNET_GenericReturnValue |
847 | GNUNET_CRYPTO_eddsa_ecdh (const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, | 718 | GNUNET_CRYPTO_eddsa_ecdh (const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, |
848 | const struct GNUNET_CRYPTO_EcdhePublicKey *pub, | 719 | const struct GNUNET_CRYPTO_EcdhePublicKey *pub, |
diff --git a/src/util/crypto_ecc_gnsrecord.c b/src/util/crypto_ecc_gnsrecord.c new file mode 100644 index 000000000..6689a21f1 --- /dev/null +++ b/src/util/crypto_ecc_gnsrecord.c | |||
@@ -0,0 +1,351 @@ | |||
1 | /* | ||
2 | This file is part of GNUnet. | ||
3 | Copyright (C) 2012, 2013, 2015 GNUnet e.V. | ||
4 | |||
5 | GNUnet is free software: you can redistribute it and/or modify it | ||
6 | under the terms of the GNU Affero General Public License as published | ||
7 | by the Free Software Foundation, either version 3 of the License, | ||
8 | or (at your option) any later version. | ||
9 | |||
10 | GNUnet is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | Affero General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU Affero General Public License | ||
16 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | SPDX-License-Identifier: AGPL3.0-or-later | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file util/crypto_ecc_gnsrecord.c | ||
23 | * @brief public key cryptography (ECC) for GNS records (LSD0001) | ||
24 | * @author Christian Grothoff | ||
25 | * @author Florian Dold | ||
26 | * @author Martin Schanzenbach | ||
27 | */ | ||
28 | #include "platform.h" | ||
29 | #include <gcrypt.h> | ||
30 | #include <sodium.h> | ||
31 | #include "gnunet_crypto_lib.h" | ||
32 | #include "gnunet_strings_lib.h" | ||
33 | |||
34 | #define CURVE "Ed25519" | ||
35 | |||
36 | /** | ||
37 | * Derive the 'h' value for key derivation, where | ||
38 | * 'h = H(l,P)'. | ||
39 | * | ||
40 | * @param pub public key for deriviation | ||
41 | * @param pubsize the size of the public key | ||
42 | * @param label label for deriviation | ||
43 | * @param context additional context to use for HKDF of 'h'; | ||
44 | * typically the name of the subsystem/application | ||
45 | * @return h value | ||
46 | */ | ||
47 | static gcry_mpi_t | ||
48 | derive_h (const void *pub, | ||
49 | size_t pubsize, | ||
50 | const char *label, | ||
51 | const char *context) | ||
52 | { | ||
53 | gcry_mpi_t h; | ||
54 | struct GNUNET_HashCode hc; | ||
55 | static const char *const salt = "key-derivation"; | ||
56 | |||
57 | GNUNET_CRYPTO_kdf (&hc, | ||
58 | sizeof(hc), | ||
59 | salt, | ||
60 | strlen (salt), | ||
61 | pub, | ||
62 | pubsize, | ||
63 | label, | ||
64 | strlen (label), | ||
65 | context, | ||
66 | strlen (context), | ||
67 | NULL, | ||
68 | 0); | ||
69 | GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc)); | ||
70 | return h; | ||
71 | } | ||
72 | |||
73 | |||
74 | /** | ||
75 | * This is a signature function for EdDSA which takes the | ||
76 | * secret scalar sk instead of the private seed which is | ||
77 | * usually the case for crypto APIs. We require this functionality | ||
78 | * in order to use derived private keys for signatures we | ||
79 | * cannot calculate the inverse of a sk to find the seed | ||
80 | * efficiently. | ||
81 | * | ||
82 | * The resulting signature is a standard EdDSA signature | ||
83 | * which can be verified using the usual APIs. | ||
84 | * | ||
85 | * @param sk the secret scalar | ||
86 | * @param purp the signature purpose | ||
87 | * @param sig the resulting signature | ||
88 | */ | ||
89 | void | ||
90 | GNUNET_CRYPTO_eddsa_sign_with_scalar ( | ||
91 | const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv, | ||
92 | const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, | ||
93 | struct GNUNET_CRYPTO_EddsaSignature *sig) | ||
94 | { | ||
95 | |||
96 | crypto_hash_sha512_state hs; | ||
97 | unsigned char az[64]; | ||
98 | unsigned char nonce[64]; | ||
99 | unsigned char hram[64]; | ||
100 | unsigned char R[32]; | ||
101 | unsigned char pk[32]; | ||
102 | |||
103 | crypto_hash_sha512_init (&hs); | ||
104 | |||
105 | // crypto_hash_sha512 (az, sk, 32); DO NOT EXPAND, WE HAVE A KEY | ||
106 | memcpy (az, priv->s, 64); | ||
107 | crypto_scalarmult_ed25519_base_noclamp (pk, | ||
108 | priv->s); | ||
109 | crypto_hash_sha512_update (&hs, az + 32, 32); | ||
110 | |||
111 | crypto_hash_sha512_update (&hs, (uint8_t*) purpose, ntohl (purpose->size)); | ||
112 | crypto_hash_sha512_final (&hs, nonce); | ||
113 | |||
114 | // This effectively creates R || A in sig | ||
115 | memcpy (sig->s, pk, 32); | ||
116 | |||
117 | unsigned char nonce_mod[64]; | ||
118 | crypto_core_ed25519_scalar_reduce (nonce_mod, nonce); | ||
119 | // nonce == r; r * G == R | ||
120 | crypto_scalarmult_ed25519_base_noclamp (R, nonce_mod); | ||
121 | memcpy (sig->r, R, sizeof (R)); | ||
122 | |||
123 | // SHA512 (R | A | M) == k | ||
124 | crypto_hash_sha512_init (&hs); | ||
125 | crypto_hash_sha512_update (&hs, (uint8_t*) sig, 64); | ||
126 | crypto_hash_sha512_update (&hs, (uint8_t*) purpose, | ||
127 | ntohl (purpose->size)); | ||
128 | crypto_hash_sha512_final (&hs, hram); | ||
129 | |||
130 | unsigned char hram_mod[64]; | ||
131 | crypto_core_ed25519_scalar_reduce (hram_mod, hram); | ||
132 | az[0] &= 248; | ||
133 | az[31] &= 127; | ||
134 | az[31] |= 64; | ||
135 | |||
136 | unsigned char tmp[32]; | ||
137 | // r + k * s mod L == S | ||
138 | crypto_core_ed25519_scalar_mul (tmp, hram_mod, az); | ||
139 | crypto_core_ed25519_scalar_add (sig->s, tmp, nonce_mod); | ||
140 | |||
141 | sodium_memzero (az, sizeof az); | ||
142 | sodium_memzero (nonce, sizeof nonce); | ||
143 | } | ||
144 | |||
145 | |||
146 | struct GNUNET_CRYPTO_EcdsaPrivateKey * | ||
147 | GNUNET_CRYPTO_ecdsa_private_key_derive ( | ||
148 | const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv, | ||
149 | const char *label, | ||
150 | const char *context) | ||
151 | { | ||
152 | struct GNUNET_CRYPTO_EcdsaPublicKey pub; | ||
153 | struct GNUNET_CRYPTO_EcdsaPrivateKey *ret; | ||
154 | uint8_t dc[32]; | ||
155 | gcry_mpi_t h; | ||
156 | gcry_mpi_t x; | ||
157 | gcry_mpi_t d; | ||
158 | gcry_mpi_t n; | ||
159 | gcry_ctx_t ctx; | ||
160 | |||
161 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); | ||
162 | |||
163 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
164 | GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub); | ||
165 | |||
166 | h = derive_h (&pub, sizeof (pub), label, context); | ||
167 | /* Convert to big endian for libgcrypt */ | ||
168 | for (size_t i = 0; i < 32; i++) | ||
169 | dc[i] = priv->d[31 - i]; | ||
170 | GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); | ||
171 | d = gcry_mpi_new (256); | ||
172 | gcry_mpi_mulm (d, h, x, n); | ||
173 | gcry_mpi_release (h); | ||
174 | gcry_mpi_release (x); | ||
175 | gcry_mpi_release (n); | ||
176 | gcry_ctx_release (ctx); | ||
177 | ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey); | ||
178 | GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); | ||
179 | /* Convert to big endian for libgcrypt */ | ||
180 | for (size_t i = 0; i < 32; i++) | ||
181 | ret->d[i] = dc[31 - i]; | ||
182 | sodium_memzero (dc, sizeof(dc)); | ||
183 | gcry_mpi_release (d); | ||
184 | return ret; | ||
185 | } | ||
186 | |||
187 | |||
188 | void | ||
189 | GNUNET_CRYPTO_ecdsa_public_key_derive ( | ||
190 | const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, | ||
191 | const char *label, | ||
192 | const char *context, | ||
193 | struct GNUNET_CRYPTO_EcdsaPublicKey *result) | ||
194 | { | ||
195 | gcry_ctx_t ctx; | ||
196 | gcry_mpi_t q_y; | ||
197 | gcry_mpi_t h; | ||
198 | gcry_mpi_t n; | ||
199 | gcry_mpi_t h_mod_n; | ||
200 | gcry_mpi_point_t q; | ||
201 | gcry_mpi_point_t v; | ||
202 | |||
203 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); | ||
204 | |||
205 | /* obtain point 'q' from original public key. The provided 'q' is | ||
206 | compressed thus we first store it in the context and then get it | ||
207 | back as a (decompresssed) point. */ | ||
208 | q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); | ||
209 | GNUNET_assert (NULL != q_y); | ||
210 | GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); | ||
211 | gcry_mpi_release (q_y); | ||
212 | q = gcry_mpi_ec_get_point ("q", ctx, 0); | ||
213 | GNUNET_assert (q); | ||
214 | |||
215 | /* calculate h_mod_n = h % n */ | ||
216 | h = derive_h (pub, sizeof (pub), label, context); | ||
217 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
218 | h_mod_n = gcry_mpi_new (256); | ||
219 | gcry_mpi_mod (h_mod_n, h, n); | ||
220 | /* calculate v = h_mod_n * q */ | ||
221 | v = gcry_mpi_point_new (0); | ||
222 | gcry_mpi_ec_mul (v, h_mod_n, q, ctx); | ||
223 | gcry_mpi_release (h_mod_n); | ||
224 | gcry_mpi_release (h); | ||
225 | gcry_mpi_release (n); | ||
226 | gcry_mpi_point_release (q); | ||
227 | |||
228 | /* convert point 'v' to public key that we return */ | ||
229 | GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); | ||
230 | gcry_mpi_point_release (v); | ||
231 | q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); | ||
232 | GNUNET_assert (q_y); | ||
233 | GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); | ||
234 | gcry_mpi_release (q_y); | ||
235 | gcry_ctx_release (ctx); | ||
236 | } | ||
237 | |||
238 | |||
239 | void | ||
240 | GNUNET_CRYPTO_eddsa_private_key_derive ( | ||
241 | const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, | ||
242 | const char *label, | ||
243 | const char *context, | ||
244 | struct GNUNET_CRYPTO_EddsaPrivateScalar *result) | ||
245 | { | ||
246 | struct GNUNET_CRYPTO_EddsaPublicKey pub; | ||
247 | uint8_t dc[32]; | ||
248 | unsigned char sk[64]; | ||
249 | gcry_mpi_t h; | ||
250 | gcry_mpi_t h_mod_n; | ||
251 | gcry_mpi_t x; | ||
252 | gcry_mpi_t d; | ||
253 | gcry_mpi_t n; | ||
254 | gcry_mpi_t a1; | ||
255 | gcry_mpi_t a2; | ||
256 | gcry_ctx_t ctx; | ||
257 | |||
258 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); | ||
259 | |||
260 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
261 | GNUNET_CRYPTO_eddsa_key_get_public (priv, &pub); | ||
262 | crypto_hash_sha512 (sk, priv->d, 32); | ||
263 | sk[0] &= 248; | ||
264 | sk[31] &= 127; | ||
265 | sk[31] |= 64; | ||
266 | h = derive_h (&pub, sizeof (pub), label, context); | ||
267 | h_mod_n = gcry_mpi_new (256); | ||
268 | gcry_mpi_mod (h_mod_n, h, n); | ||
269 | /* Convert to big endian for libgcrypt */ | ||
270 | for (size_t i = 0; i < 32; i++) | ||
271 | dc[i] = sk[31 - i]; | ||
272 | GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); // a | ||
273 | a1 = gcry_mpi_new (256); | ||
274 | gcry_mpi_t eight = gcry_mpi_set_ui (NULL, 8); | ||
275 | gcry_mpi_div (a1, NULL, x, eight, 0); // a1 := a / 8 | ||
276 | a2 = gcry_mpi_new (256); | ||
277 | gcry_mpi_mulm (a2, h_mod_n, a1, n); // a2 := h * a1 mod n | ||
278 | d = gcry_mpi_new (256); | ||
279 | // gcry_mpi_mulm (d, a2, eight, n); // a' := a2 * 8 mod n | ||
280 | gcry_mpi_mul (d, a2, eight); // a' := a2 * 8 | ||
281 | gcry_mpi_release (h); | ||
282 | gcry_mpi_release (x); | ||
283 | gcry_mpi_release (n); | ||
284 | gcry_mpi_release (a1); | ||
285 | gcry_mpi_release (a2); | ||
286 | gcry_ctx_release (ctx); | ||
287 | GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); | ||
288 | memcpy (result->s, sk, sizeof (sk)); | ||
289 | /* Convert to little endian for libsodium */ | ||
290 | for (size_t i = 0; i < 32; i++) | ||
291 | result->s[i] = dc[31 - i]; | ||
292 | result->s[0] &= 248; | ||
293 | result->s[31] &= 127; | ||
294 | result->s[31] |= 64; | ||
295 | |||
296 | sodium_memzero (dc, sizeof(dc)); | ||
297 | gcry_mpi_release (d); | ||
298 | } | ||
299 | |||
300 | |||
301 | void | ||
302 | GNUNET_CRYPTO_eddsa_public_key_derive ( | ||
303 | const struct GNUNET_CRYPTO_EddsaPublicKey *pub, | ||
304 | const char *label, | ||
305 | const char *context, | ||
306 | struct GNUNET_CRYPTO_EddsaPublicKey *result) | ||
307 | { | ||
308 | gcry_ctx_t ctx; | ||
309 | gcry_mpi_t q_y; | ||
310 | gcry_mpi_t h; | ||
311 | gcry_mpi_t n; | ||
312 | gcry_mpi_t h_mod_n; | ||
313 | gcry_mpi_point_t q; | ||
314 | gcry_mpi_point_t v; | ||
315 | |||
316 | GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); | ||
317 | |||
318 | /* obtain point 'q' from original public key. The provided 'q' is | ||
319 | compressed thus we first store it in the context and then get it | ||
320 | back as a (decompresssed) point. */ | ||
321 | q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); | ||
322 | GNUNET_assert (NULL != q_y); | ||
323 | GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); | ||
324 | gcry_mpi_release (q_y); | ||
325 | q = gcry_mpi_ec_get_point ("q", ctx, 0); | ||
326 | GNUNET_assert (q); | ||
327 | |||
328 | /* calculate h_mod_n = h % n */ | ||
329 | h = derive_h (pub, sizeof (*pub), label, context); | ||
330 | n = gcry_mpi_ec_get_mpi ("n", ctx, 1); | ||
331 | h_mod_n = gcry_mpi_new (256); | ||
332 | gcry_mpi_mod (h_mod_n, h, n); | ||
333 | |||
334 | /* calculate v = h_mod_n * q */ | ||
335 | v = gcry_mpi_point_new (0); | ||
336 | gcry_mpi_ec_mul (v, h_mod_n, q, ctx); | ||
337 | gcry_mpi_release (h_mod_n); | ||
338 | gcry_mpi_release (h); | ||
339 | gcry_mpi_release (n); | ||
340 | gcry_mpi_point_release (q); | ||
341 | |||
342 | /* convert point 'v' to public key that we return */ | ||
343 | GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); | ||
344 | gcry_mpi_point_release (v); | ||
345 | q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); | ||
346 | GNUNET_assert (q_y); | ||
347 | GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); | ||
348 | gcry_mpi_release (q_y); | ||
349 | gcry_ctx_release (ctx); | ||
350 | |||
351 | } | ||