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 /src | |
parent | ca3ebf1e59eb00ad101ba8b26e5185db4d785610 (diff) |
GNS: Add EDKEY support.
GNS and GNSRECORD can now handle EdDSA keys
in addition to the existing ECDSA scheme.
See also LSD0001.
Diffstat (limited to 'src')
-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 |
9 files changed, 889 insertions, 185 deletions
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 ( } - ssize_t ecdsa_symmetric_encrypt ( const void *block, @@ -92,6 +91,34 @@ ecdsa_symmetric_encrypt ( } +enum GNUNET_GenericReturnValue +eddsa_symmetric_decrypt ( + const void *block, + size_t size, + const unsigned char *key, + const unsigned char *nonce, + void *result) +{ + if (0 != crypto_secretbox_open_easy (result, block, size, nonce, key)) + { + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +eddsa_symmetric_encrypt ( + const void *block, + size_t size, + const unsigned char *key, + const unsigned char *nonce, + void *result) +{ + crypto_secretbox_easy (result, block, size, nonce, key); + return GNUNET_OK; +} + /** * Derive session key and iv from label and public key. @@ -131,6 +158,42 @@ derive_block_aes_key (unsigned char *ctr, /** + * Derive session key and iv from label and public key. + * + * @param nonce initialization vector to initialize + * @param skey session key to initialize + * @param label label to use for KDF + * @param pub public key to use for KDF + */ +static void +derive_block_xsalsa_key (unsigned char *nonce, + unsigned char *key, + const char *label, + uint64_t exp, + const struct GNUNET_CRYPTO_EddsaPublicKey *pub) +{ + static const char ctx_key[] = "gns-aes-ctx-key"; + static const char ctx_iv[] = "gns-aes-ctx-iv"; + + GNUNET_CRYPTO_kdf (key, crypto_secretbox_KEYBYTES, + ctx_key, strlen (ctx_key), + pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey), + label, strlen (label), + NULL, 0); + memset (nonce, 0, crypto_secretbox_NONCEBYTES); + /** 16 byte nonce **/ + GNUNET_CRYPTO_kdf (nonce, (crypto_secretbox_NONCEBYTES - sizeof (exp)), + ctx_iv, strlen (ctx_iv), + pub, sizeof(struct GNUNET_CRYPTO_EddsaPublicKey), + label, strlen (label), + NULL, 0); + /** Expiration time 64 bit. **/ + memcpy (nonce + (crypto_secretbox_NONCEBYTES - sizeof (exp)), + &exp, sizeof (exp)); +} + + +/** * Sign name and records * * @param key the private key @@ -246,6 +309,119 @@ block_create_ecdsa (const struct GNUNET_CRYPTO_EcdsaPrivateKey *key, /** + * Sign name and records (EDDSA version) + * + * @param key the private key + * @param pkey associated public key + * @param expire block expiration + * @param label the name for the records + * @param rd record data + * @param rd_count number of records + * @return NULL on error (block too large) + */ +static struct GNUNET_GNSRECORD_Block * +block_create_eddsa (const struct GNUNET_CRYPTO_EddsaPrivateKey *key, + const struct GNUNET_CRYPTO_EddsaPublicKey *pkey, + struct GNUNET_TIME_Absolute expire, + const char *label, + const struct GNUNET_GNSRECORD_Data *rd, + unsigned int rd_count) +{ + ssize_t payload_len = GNUNET_GNSRECORD_records_get_size (rd_count, + rd); + struct GNUNET_GNSRECORD_Block *block; + struct GNUNET_GNSRECORD_EddsaBlock *edblock; + struct GNUNET_CRYPTO_EddsaPrivateScalar dkey; + unsigned char nonce[crypto_secretbox_NONCEBYTES]; + unsigned char skey[crypto_secretbox_KEYBYTES]; + struct GNUNET_GNSRECORD_Data rdc[GNUNET_NZL (rd_count)]; + uint32_t rd_count_nbo; + struct GNUNET_TIME_Absolute now; + + if (payload_len < 0) + { + GNUNET_break (0); + return NULL; + } + if (payload_len > GNUNET_GNSRECORD_MAX_BLOCK_SIZE) + { + GNUNET_break (0); + return NULL; + } + /* convert relative to absolute times */ + now = GNUNET_TIME_absolute_get (); + for (unsigned int i = 0; i < rd_count; i++) + { + rdc[i] = rd[i]; + if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)) + { + struct GNUNET_TIME_Relative t; + + /* encrypted blocks must never have relative expiration times, convert! */ + rdc[i].flags &= ~GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; + t.rel_value_us = rdc[i].expiration_time; + rdc[i].expiration_time = GNUNET_TIME_absolute_add (now, t).abs_value_us; + } + } + /* serialize */ + rd_count_nbo = htonl (rd_count); + { + char payload[sizeof(uint32_t) + payload_len]; + + GNUNET_memcpy (payload, + &rd_count_nbo, + sizeof(uint32_t)); + GNUNET_assert (payload_len == + GNUNET_GNSRECORD_records_serialize (rd_count, + rdc, + payload_len, + &payload[sizeof(uint32_t) + ])); + block = GNUNET_malloc (sizeof(struct GNUNET_GNSRECORD_Block) + + sizeof(uint32_t) + + payload_len + + crypto_secretbox_MACBYTES); + edblock = &block->eddsa_block; + block->type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY); + edblock->purpose.size = htonl (sizeof(uint32_t) + + payload_len + + sizeof(struct + GNUNET_CRYPTO_EccSignaturePurpose) + + sizeof(struct GNUNET_TIME_AbsoluteNBO) + + crypto_secretbox_MACBYTES); + edblock->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN); + edblock->expiration_time = GNUNET_TIME_absolute_hton (expire); + /* encrypt and sign */ + GNUNET_CRYPTO_eddsa_private_key_derive (key, + label, + "gns", + &dkey); + // FIXME: We may want a key_get_public_from_private_scalar function + struct GNUNET_CRYPTO_EddsaPublicKey test; + crypto_scalarmult_ed25519_base_noclamp (test.q_y, + dkey.s); + edblock->derived_key = test; + derive_block_xsalsa_key (nonce, + skey, + label, + edblock->expiration_time.abs_value_us__, + pkey); + GNUNET_break (GNUNET_OK == + eddsa_symmetric_encrypt (payload, + payload_len + + sizeof(uint32_t), + skey, + nonce, + &edblock[1])); + } + GNUNET_CRYPTO_eddsa_sign_with_scalar (&dkey, + &edblock->purpose, + &edblock->signature); + return block; +} + + +/** * Sign name and records * * @param key the private key @@ -263,6 +439,7 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key, unsigned int rd_count) { struct GNUNET_CRYPTO_EcdsaPublicKey pkey; + struct GNUNET_CRYPTO_EddsaPublicKey edkey; switch (ntohl (key->type)) { @@ -275,6 +452,15 @@ GNUNET_GNSRECORD_block_create (const struct GNUNET_IDENTITY_PrivateKey *key, label, rd, rd_count); + case GNUNET_GNSRECORD_TYPE_EDKEY: + GNUNET_CRYPTO_eddsa_key_get_public (&key->eddsa_key, + &edkey); + return block_create_eddsa (&key->eddsa_key, + &edkey, + expire, + label, + rd, + rd_count); default: GNUNET_assert (0); } @@ -319,33 +505,45 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey, unsigned int rd_count) { const struct GNUNET_CRYPTO_EcdsaPrivateKey *key; + struct GNUNET_CRYPTO_EddsaPublicKey edpubkey; - if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (pkey->type)) + if (GNUNET_IDENTITY_TYPE_ECDSA == ntohl (pkey->type)) { - return NULL; // FIXME - } - key = &pkey->ecdsa_key; + key = &pkey->ecdsa_key; #define CSIZE 64 - static struct KeyCacheLine cache[CSIZE]; - struct KeyCacheLine *line; + static struct KeyCacheLine cache[CSIZE]; + struct KeyCacheLine *line; - line = &cache[(*(unsigned int *) key) % CSIZE]; - if (0 != memcmp (&line->key, - key, - sizeof(*key))) + line = &cache[(*(unsigned int *) key) % CSIZE]; + if (0 != memcmp (&line->key, + key, + sizeof(*key))) + { + /* cache miss, recompute */ + line->key = *key; + GNUNET_CRYPTO_ecdsa_key_get_public (key, + &line->pkey); + } +#undef CSIZE + return block_create_ecdsa (key, + &line->pkey, + expire, + label, + rd, + rd_count); + } + else if (GNUNET_IDENTITY_TYPE_EDDSA == ntohl (pkey->type)) { - /* cache miss, recompute */ - line->key = *key; - GNUNET_CRYPTO_ecdsa_key_get_public (key, - &line->pkey); + GNUNET_CRYPTO_eddsa_key_get_public (&pkey->eddsa_key, + &edpubkey); + return block_create_eddsa (&pkey->eddsa_key, + &edpubkey, + expire, + label, + rd, + rd_count); } -#undef CSIZE - return block_create_ecdsa (key, - &line->pkey, - expire, - label, - rd, - rd_count); + return NULL; } @@ -359,21 +557,23 @@ GNUNET_GNSRECORD_block_create2 (const struct GNUNET_IDENTITY_PrivateKey *pkey, enum GNUNET_GenericReturnValue GNUNET_GNSRECORD_block_verify (const struct GNUNET_GNSRECORD_Block *block) { - const struct GNUNET_CRYPTO_EcdsaPublicKey *key; - const struct GNUNET_GNSRECORD_EcdsaBlock *ecblock; - - if (GNUNET_GNSRECORD_TYPE_PKEY != ntohl (block->type)) + switch (ntohl (block->type)) { - GNUNET_break (0); + case GNUNET_GNSRECORD_TYPE_PKEY: + return GNUNET_CRYPTO_ecdsa_verify_ ( + GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, + &block->ecdsa_block.purpose, + &block->ecdsa_block.signature, + &block->ecdsa_block.derived_key); + case GNUNET_GNSRECORD_TYPE_EDKEY: + return GNUNET_CRYPTO_eddsa_verify_ ( + GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, + &block->eddsa_block.purpose, + &block->eddsa_block.signature, + &block->eddsa_block.derived_key); + default: return GNUNET_NO; } - ecblock = &block->ecdsa_block; - key = &ecblock->derived_key; - - return GNUNET_CRYPTO_ecdsa_verify_ (GNUNET_SIGNATURE_PURPOSE_GNS_RECORD_SIGN, - &ecblock->purpose, - &ecblock->signature, - key); } @@ -505,6 +705,134 @@ block_decrypt_ecdsa (const struct GNUNET_GNSRECORD_EcdsaBlock *block, } +enum GNUNET_GenericReturnValue +block_decrypt_eddsa (const struct GNUNET_GNSRECORD_EddsaBlock *block, + const struct + GNUNET_CRYPTO_EddsaPublicKey *zone_key, + const char *label, + GNUNET_GNSRECORD_RecordCallback proc, + void *proc_cls) +{ + size_t payload_len = ntohl (block->purpose.size) + - sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) + - sizeof(struct GNUNET_TIME_AbsoluteNBO); + unsigned char nonce[crypto_secretbox_NONCEBYTES]; + unsigned char key[crypto_secretbox_KEYBYTES]; + + if (ntohl (block->purpose.size) < + sizeof(struct GNUNET_CRYPTO_EccSignaturePurpose) + + sizeof(struct GNUNET_TIME_AbsoluteNBO)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + derive_block_xsalsa_key (nonce, + key, + label, + block->expiration_time.abs_value_us__, + zone_key); + { + char payload[payload_len]; + uint32_t rd_count; + + GNUNET_break (GNUNET_OK == + eddsa_symmetric_decrypt (&block[1], payload_len, + key, nonce, + payload)); + GNUNET_memcpy (&rd_count, + payload, + sizeof(uint32_t)); + rd_count = ntohl (rd_count); + if (rd_count > 2048) + { + /* limit to sane value */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + { + struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL (rd_count)]; + unsigned int j; + struct GNUNET_TIME_Absolute now; + + if (GNUNET_OK != + GNUNET_GNSRECORD_records_deserialize (payload_len - sizeof(uint32_t), + &payload[sizeof(uint32_t)], + rd_count, + rd)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* hide expired records */ + now = GNUNET_TIME_absolute_get (); + j = 0; + for (unsigned int i = 0; i < rd_count; i++) + { + if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)) + { + /* encrypted blocks must never have relative expiration times, skip! */ + GNUNET_break_op (0); + continue; + } + + if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD)) + { + int include_record = GNUNET_YES; + /* Shadow record, figure out if we have a not expired active record */ + for (unsigned int k = 0; k < rd_count; k++) + { + if (k == i) + continue; + if (rd[i].expiration_time < now.abs_value_us) + include_record = GNUNET_NO; /* Shadow record is expired */ + if ((rd[k].record_type == rd[i].record_type) && + (rd[k].expiration_time >= now.abs_value_us) && + (0 == (rd[k].flags & GNUNET_GNSRECORD_RF_SHADOW_RECORD))) + { + include_record = GNUNET_NO; /* We have a non-expired, non-shadow record of the same type */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Ignoring shadow record\n"); + break; + } + } + if (GNUNET_YES == include_record) + { + rd[i].flags ^= GNUNET_GNSRECORD_RF_SHADOW_RECORD; /* Remove Flag */ + if (j != i) + rd[j] = rd[i]; + j++; + } + } + else if (rd[i].expiration_time >= now.abs_value_us) + { + /* Include this record */ + if (j != i) + rd[j] = rd[i]; + j++; + } + else + { + struct GNUNET_TIME_Absolute at; + + at.abs_value_us = rd[i].expiration_time; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Excluding record that expired %s (%llu ago)\n", + GNUNET_STRINGS_absolute_time_to_string (at), + (unsigned long long) rd[i].expiration_time + - now.abs_value_us); + } + } + rd_count = j; + if (NULL != proc) + proc (proc_cls, + rd_count, + (0 != rd_count) ? rd : NULL); + } + } + return GNUNET_OK; +} + + /** * Decrypt block. * @@ -524,17 +852,17 @@ GNUNET_GNSRECORD_block_decrypt (const struct GNUNET_GNSRECORD_Block *block, GNUNET_GNSRECORD_RecordCallback proc, void *proc_cls) { - const struct GNUNET_CRYPTO_EcdsaPublicKey *key; - - if (GNUNET_IDENTITY_TYPE_ECDSA != ntohl (zone_key->type)) + switch (ntohl (zone_key->type)) { - return GNUNET_NO; + case GNUNET_IDENTITY_TYPE_ECDSA: + return block_decrypt_ecdsa (&block->ecdsa_block, + &zone_key->ecdsa_key, label, proc, proc_cls); + case GNUNET_IDENTITY_TYPE_EDDSA: + return block_decrypt_eddsa (&block->eddsa_block, + &zone_key->eddsa_key, label, proc, proc_cls); + default: + return GNUNET_SYSERR; } - key = &zone_key->ecdsa_key; - - return block_decrypt_ecdsa (&block->ecdsa_block, - key, label, proc, proc_cls); - } @@ -555,6 +883,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct switch (ntohl (zone->type)) { case GNUNET_GNSRECORD_TYPE_PKEY: + case GNUNET_GNSRECORD_TYPE_EDKEY: GNUNET_IDENTITY_key_get_public (zone, &pub); @@ -570,6 +899,7 @@ GNUNET_GNSRECORD_query_from_private_key (const struct /** * Calculate the DHT query for a given @a label in a given @a zone. + * FIXME: We may want to plugin-ize this at some point. * * @param pub public key of the zone * @param label label of the record @@ -595,6 +925,16 @@ GNUNET_GNSRECORD_query_from_public_key (const struct sizeof (pd.ecdsa_key), query); break; + case GNUNET_GNSRECORD_TYPE_EDKEY: + pd.type = pub->type; + GNUNET_CRYPTO_eddsa_public_key_derive (&pub->eddsa_key, + label, + "gns", + &pd.eddsa_key); + GNUNET_CRYPTO_hash (&pd.eddsa_key, + sizeof (pd.eddsa_key), + query); + break; default: GNUNET_assert (0); } 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) + ntohl (block->ecdsa_block.purpose.size) /* Length of signed data */ - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */ break; + case GNUNET_GNSRECORD_TYPE_EDKEY: + return sizeof (uint32_t) /* zone type */ + + sizeof (block->eddsa_block) /* EddsaBlock */ + + ntohl (block->eddsa_block.purpose.size) /* Length of signed data */ + - sizeof (block->ecdsa_block.purpose); /* Purpose already in EcdsaBlock */ + default: return 0; } @@ -354,6 +360,8 @@ GNUNET_GNSRECORD_block_get_expiration (const struct { case GNUNET_GNSRECORD_TYPE_PKEY: return GNUNET_TIME_absolute_ntoh (block->ecdsa_block.expiration_time); + case GNUNET_GNSRECORD_TYPE_EDKEY: + return GNUNET_TIME_absolute_ntoh (block->eddsa_block.expiration_time); default: GNUNET_break (0); /* Hopefully we never get here, but we might */ } @@ -373,6 +381,11 @@ GNUNET_GNSRECORD_query_from_block (const struct GNUNET_GNSRECORD_Block *block, sizeof (block->ecdsa_block.derived_key), query); return GNUNET_OK; + case GNUNET_GNSRECORD_TYPE_EDKEY: + GNUNET_CRYPTO_hash (&block->eddsa_block.derived_key, + sizeof (block->eddsa_block.derived_key), + query); + return GNUNET_OK; default: return GNUNET_SYSERR; } @@ -394,6 +407,10 @@ GNUNET_GNSRECORD_record_to_identity_key (const struct GNUNET_GNSRECORD_Data *rd, key->type = htonl (rd->record_type); memcpy (&key->ecdsa_key, rd->data, sizeof (key->ecdsa_key)); return GNUNET_OK; + case GNUNET_GNSRECORD_TYPE_EDKEY: + key->type = htonl (rd->record_type); + memcpy (&key->eddsa_key, rd->data, sizeof (key->eddsa_key)); + return GNUNET_OK; default: return GNUNET_SYSERR; } 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, return -1; } // Do not pad PKEY - if (GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type) + if ((GNUNET_GNSRECORD_TYPE_PKEY == rd->record_type) || + (GNUNET_GNSRECORD_TYPE_EDKEY == rd->record_type)) return ret; /** * 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, res = 0; } - static void -run (void *cls, - char *const *args, - const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) +test_with_type (struct GNUNET_IDENTITY_PrivateKey *privkey) { struct GNUNET_GNSRECORD_Block *block; struct GNUNET_IDENTITY_PublicKey pubkey; struct GNUNET_HashCode query_pub; struct GNUNET_HashCode query_priv; struct GNUNET_TIME_Absolute expire = GNUNET_TIME_absolute_get (); - struct GNUNET_IDENTITY_PrivateKey privkey; - privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY); - GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key); /* get public key */ - GNUNET_IDENTITY_key_get_public (&privkey, + GNUNET_IDENTITY_key_get_public (privkey, &pubkey); /* test query derivation */ - GNUNET_GNSRECORD_query_from_private_key (&privkey, + GNUNET_GNSRECORD_query_from_private_key (privkey, "testlabel", &query_priv); GNUNET_GNSRECORD_query_from_public_key (&pubkey, @@ -129,7 +122,7 @@ run (void *cls, /* Create block */ GNUNET_assert (NULL != (block = - GNUNET_GNSRECORD_block_create (&privkey, + GNUNET_GNSRECORD_block_create (privkey, expire, s_name, s_rd, @@ -146,6 +139,27 @@ run (void *cls, } + +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_IDENTITY_PrivateKey privkey; + struct GNUNET_IDENTITY_PrivateKey privkey_ed; + + + privkey.type = htonl (GNUNET_GNSRECORD_TYPE_PKEY); + GNUNET_CRYPTO_ecdsa_key_create (&privkey.ecdsa_key); + test_with_type (&privkey); + + privkey_ed.type = htonl (GNUNET_GNSRECORD_TYPE_EDKEY); + GNUNET_CRYPTO_eddsa_key_create (&privkey_ed.eddsa_key); + test_with_type(&privkey_ed); +} + + int main (int argc, char *argv[]) { 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 /** + * Private ECC scalar encoded for transmission. To be used only for EdDSA + * signatures. + */ +struct GNUNET_CRYPTO_EddsaPrivateScalar +{ + /** + * s is the expandedprivate 512-bit scalar of a private key. + */ + unsigned char s[512 / 8]; +}; + + +/** * @brief type for session keys */ struct GNUNET_CRYPTO_SymmetricSessionKey @@ -1907,6 +1920,71 @@ GNUNET_CRYPTO_ecdsa_public_key_derive ( /** + * @ingroup crypto + * Derive a private scalar from a given private key and a label. + * Essentially calculates a private key 'h = H(l,P) * d mod n' + * where n is the size of the ECC group and P is the public + * key associated with the private key 'd'. + * The result is the derived private _scalar_, not the private + * key as for EdDSA we cannot derive before we hash the + * private key. + * + * @param priv original private key + * @param label label to use for key deriviation + * @param context additional context to use for HKDF of 'h'; + * typically the name of the subsystem/application + * @param result derived private scalar + */ +void +GNUNET_CRYPTO_eddsa_private_key_derive ( + const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, + const char *label, + const char *context, + struct GNUNET_CRYPTO_EddsaPrivateScalar *result); + + +/** + * @ingroup crypto + * Derive a public key from a given public key and a label. + * Essentially calculates a public key 'V = H(l,P) * P'. + * + * @param pub original public key + * @param label label to use for key deriviation + * @param context additional context to use for HKDF of 'h'. + * typically the name of the subsystem/application + * @param result where to write the derived public key + */ +void +GNUNET_CRYPTO_eddsa_public_key_derive ( + const struct GNUNET_CRYPTO_EddsaPublicKey *pub, + const char *label, + const char *context, + struct GNUNET_CRYPTO_EddsaPublicKey *result); + + +/** + * This is a signature function for EdDSA which takes the + * secret scalar sk instead of the private seed which is + * usually the case for crypto APIs. We require this functionality + * in order to use derived private keys for signatures we + * cannot calculate the inverse of a sk to find the seed + * efficiently. + * + * The resulting signature is a standard EdDSA signature + * which can be verified using the usual APIs. + * + * @param sk the secret scalar + * @param purp the signature purpose + * @param sig the resulting signature + */ +void +GNUNET_CRYPTO_eddsa_sign_with_scalar ( + const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv, + const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + struct GNUNET_CRYPTO_EddsaSignature *sig); + + +/** * Output the given MPI value to the given buffer in network * byte order. The MPI @a val may not be negative. * 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 /* followed by encrypted data */ }; + +/** + * Information we have in an encrypted block with record data (i.e. in the DHT). + */ +struct GNUNET_GNSRECORD_EddsaBlock +{ + /** + * Derived key used for signing; hash of this is the query. + */ + struct GNUNET_CRYPTO_EddsaPublicKey derived_key; + + /** + * Signature of the block. + */ + struct GNUNET_CRYPTO_EddsaSignature signature; + + /** + * Number of bytes signed; also specifies the number of bytes + * of encrypted data that follow. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Expiration time of the block. + */ + struct GNUNET_TIME_AbsoluteNBO expiration_time; + + /* followed by encrypted data */ +}; + + struct GNUNET_GNSRECORD_Block { uint32_t type; @@ -204,7 +235,7 @@ struct GNUNET_GNSRECORD_Block union { struct GNUNET_GNSRECORD_EcdsaBlock ecdsa_block; - //struct GNUNET_GNSRECORD_EddsaBlock eddsa_block; + struct GNUNET_GNSRECORD_EddsaBlock eddsa_block; }; }; 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 = \ crypto_symmetric.c \ crypto_crc.c \ crypto_ecc.c \ + crypto_ecc_gnsrecord.c \ $(DLOG) \ crypto_ecc_setup.c \ 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, } -/** - * Derive the 'h' value for key derivation, where - * 'h = H(l,P)'. - * - * @param pub public key for deriviation - * @param label label for deriviation - * @param context additional context to use for HKDF of 'h'; - * typically the name of the subsystem/application - * @return h value - */ -static gcry_mpi_t -derive_h (const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, - const char *label, - const char *context) -{ - gcry_mpi_t h; - struct GNUNET_HashCode hc; - static const char *const salt = "key-derivation"; - - GNUNET_CRYPTO_kdf (&hc, - sizeof(hc), - salt, - strlen (salt), - pub, - sizeof(*pub), - label, - strlen (label), - context, - strlen (context), - NULL, - 0); - GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc)); - return h; -} - - -struct GNUNET_CRYPTO_EcdsaPrivateKey * -GNUNET_CRYPTO_ecdsa_private_key_derive ( - const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv, - const char *label, - const char *context) -{ - struct GNUNET_CRYPTO_EcdsaPublicKey pub; - struct GNUNET_CRYPTO_EcdsaPrivateKey *ret; - uint8_t dc[32]; - gcry_mpi_t h; - gcry_mpi_t x; - gcry_mpi_t d; - gcry_mpi_t n; - gcry_ctx_t ctx; - - GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); - - n = gcry_mpi_ec_get_mpi ("n", ctx, 1); - GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub); - - h = derive_h (&pub, label, context); - /* Convert to big endian for libgcrypt */ - for (size_t i = 0; i < 32; i++) - dc[i] = priv->d[31 - i]; - GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); - d = gcry_mpi_new (256); - gcry_mpi_mulm (d, h, x, n); - gcry_mpi_release (h); - gcry_mpi_release (x); - gcry_mpi_release (n); - gcry_ctx_release (ctx); - ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey); - GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); - /* Convert to big endian for libgcrypt */ - for (size_t i = 0; i < 32; i++) - ret->d[i] = dc[31 - i]; - sodium_memzero (dc, sizeof(dc)); - gcry_mpi_release (d); - return ret; -} - - -void -GNUNET_CRYPTO_ecdsa_public_key_derive ( - const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, - const char *label, - const char *context, - struct GNUNET_CRYPTO_EcdsaPublicKey *result) -{ - gcry_ctx_t ctx; - gcry_mpi_t q_y; - gcry_mpi_t h; - gcry_mpi_t n; - gcry_mpi_t h_mod_n; - gcry_mpi_point_t q; - gcry_mpi_point_t v; - - GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); - - /* obtain point 'q' from original public key. The provided 'q' is - compressed thus we first store it in the context and then get it - back as a (decompresssed) point. */ - q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); - GNUNET_assert (NULL != q_y); - GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); - gcry_mpi_release (q_y); - q = gcry_mpi_ec_get_point ("q", ctx, 0); - GNUNET_assert (q); - - /* calculate h_mod_n = h % n */ - h = derive_h (pub, label, context); - n = gcry_mpi_ec_get_mpi ("n", ctx, 1); - h_mod_n = gcry_mpi_new (256); - gcry_mpi_mod (h_mod_n, h, n); - /* calculate v = h_mod_n * q */ - v = gcry_mpi_point_new (0); - gcry_mpi_ec_mul (v, h_mod_n, q, ctx); - gcry_mpi_release (h_mod_n); - gcry_mpi_release (h); - gcry_mpi_release (n); - gcry_mpi_point_release (q); - - /* convert point 'v' to public key that we return */ - GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); - gcry_mpi_point_release (v); - q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); - GNUNET_assert (q_y); - GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); - gcry_mpi_release (q_y); - gcry_ctx_release (ctx); -} - - enum GNUNET_GenericReturnValue GNUNET_CRYPTO_eddsa_ecdh (const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, 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 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013, 2015 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file util/crypto_ecc_gnsrecord.c + * @brief public key cryptography (ECC) for GNS records (LSD0001) + * @author Christian Grothoff + * @author Florian Dold + * @author Martin Schanzenbach + */ +#include "platform.h" +#include <gcrypt.h> +#include <sodium.h> +#include "gnunet_crypto_lib.h" +#include "gnunet_strings_lib.h" + +#define CURVE "Ed25519" + +/** + * Derive the 'h' value for key derivation, where + * 'h = H(l,P)'. + * + * @param pub public key for deriviation + * @param pubsize the size of the public key + * @param label label for deriviation + * @param context additional context to use for HKDF of 'h'; + * typically the name of the subsystem/application + * @return h value + */ +static gcry_mpi_t +derive_h (const void *pub, + size_t pubsize, + const char *label, + const char *context) +{ + gcry_mpi_t h; + struct GNUNET_HashCode hc; + static const char *const salt = "key-derivation"; + + GNUNET_CRYPTO_kdf (&hc, + sizeof(hc), + salt, + strlen (salt), + pub, + pubsize, + label, + strlen (label), + context, + strlen (context), + NULL, + 0); + GNUNET_CRYPTO_mpi_scan_unsigned (&h, (unsigned char *) &hc, sizeof(hc)); + return h; +} + + +/** + * This is a signature function for EdDSA which takes the + * secret scalar sk instead of the private seed which is + * usually the case for crypto APIs. We require this functionality + * in order to use derived private keys for signatures we + * cannot calculate the inverse of a sk to find the seed + * efficiently. + * + * The resulting signature is a standard EdDSA signature + * which can be verified using the usual APIs. + * + * @param sk the secret scalar + * @param purp the signature purpose + * @param sig the resulting signature + */ +void +GNUNET_CRYPTO_eddsa_sign_with_scalar ( + const struct GNUNET_CRYPTO_EddsaPrivateScalar *priv, + const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose, + struct GNUNET_CRYPTO_EddsaSignature *sig) +{ + + crypto_hash_sha512_state hs; + unsigned char az[64]; + unsigned char nonce[64]; + unsigned char hram[64]; + unsigned char R[32]; + unsigned char pk[32]; + + crypto_hash_sha512_init (&hs); + + // crypto_hash_sha512 (az, sk, 32); DO NOT EXPAND, WE HAVE A KEY + memcpy (az, priv->s, 64); + crypto_scalarmult_ed25519_base_noclamp (pk, + priv->s); + crypto_hash_sha512_update (&hs, az + 32, 32); + + crypto_hash_sha512_update (&hs, (uint8_t*) purpose, ntohl (purpose->size)); + crypto_hash_sha512_final (&hs, nonce); + + // This effectively creates R || A in sig + memcpy (sig->s, pk, 32); + + unsigned char nonce_mod[64]; + crypto_core_ed25519_scalar_reduce (nonce_mod, nonce); + // nonce == r; r * G == R + crypto_scalarmult_ed25519_base_noclamp (R, nonce_mod); + memcpy (sig->r, R, sizeof (R)); + + // SHA512 (R | A | M) == k + crypto_hash_sha512_init (&hs); + crypto_hash_sha512_update (&hs, (uint8_t*) sig, 64); + crypto_hash_sha512_update (&hs, (uint8_t*) purpose, + ntohl (purpose->size)); + crypto_hash_sha512_final (&hs, hram); + + unsigned char hram_mod[64]; + crypto_core_ed25519_scalar_reduce (hram_mod, hram); + az[0] &= 248; + az[31] &= 127; + az[31] |= 64; + + unsigned char tmp[32]; + // r + k * s mod L == S + crypto_core_ed25519_scalar_mul (tmp, hram_mod, az); + crypto_core_ed25519_scalar_add (sig->s, tmp, nonce_mod); + + sodium_memzero (az, sizeof az); + sodium_memzero (nonce, sizeof nonce); +} + + +struct GNUNET_CRYPTO_EcdsaPrivateKey * +GNUNET_CRYPTO_ecdsa_private_key_derive ( + const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv, + const char *label, + const char *context) +{ + struct GNUNET_CRYPTO_EcdsaPublicKey pub; + struct GNUNET_CRYPTO_EcdsaPrivateKey *ret; + uint8_t dc[32]; + gcry_mpi_t h; + gcry_mpi_t x; + gcry_mpi_t d; + gcry_mpi_t n; + gcry_ctx_t ctx; + + GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); + + n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + GNUNET_CRYPTO_ecdsa_key_get_public (priv, &pub); + + h = derive_h (&pub, sizeof (pub), label, context); + /* Convert to big endian for libgcrypt */ + for (size_t i = 0; i < 32; i++) + dc[i] = priv->d[31 - i]; + GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); + d = gcry_mpi_new (256); + gcry_mpi_mulm (d, h, x, n); + gcry_mpi_release (h); + gcry_mpi_release (x); + gcry_mpi_release (n); + gcry_ctx_release (ctx); + ret = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey); + GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); + /* Convert to big endian for libgcrypt */ + for (size_t i = 0; i < 32; i++) + ret->d[i] = dc[31 - i]; + sodium_memzero (dc, sizeof(dc)); + gcry_mpi_release (d); + return ret; +} + + +void +GNUNET_CRYPTO_ecdsa_public_key_derive ( + const struct GNUNET_CRYPTO_EcdsaPublicKey *pub, + const char *label, + const char *context, + struct GNUNET_CRYPTO_EcdsaPublicKey *result) +{ + gcry_ctx_t ctx; + gcry_mpi_t q_y; + gcry_mpi_t h; + gcry_mpi_t n; + gcry_mpi_t h_mod_n; + gcry_mpi_point_t q; + gcry_mpi_point_t v; + + GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, CURVE)); + + /* obtain point 'q' from original public key. The provided 'q' is + compressed thus we first store it in the context and then get it + back as a (decompresssed) point. */ + q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); + GNUNET_assert (NULL != q_y); + GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); + gcry_mpi_release (q_y); + q = gcry_mpi_ec_get_point ("q", ctx, 0); + GNUNET_assert (q); + + /* calculate h_mod_n = h % n */ + h = derive_h (pub, sizeof (pub), label, context); + n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + h_mod_n = gcry_mpi_new (256); + gcry_mpi_mod (h_mod_n, h, n); + /* calculate v = h_mod_n * q */ + v = gcry_mpi_point_new (0); + gcry_mpi_ec_mul (v, h_mod_n, q, ctx); + gcry_mpi_release (h_mod_n); + gcry_mpi_release (h); + gcry_mpi_release (n); + gcry_mpi_point_release (q); + + /* convert point 'v' to public key that we return */ + GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); + gcry_mpi_point_release (v); + q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); + GNUNET_assert (q_y); + GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); + gcry_mpi_release (q_y); + gcry_ctx_release (ctx); +} + + +void +GNUNET_CRYPTO_eddsa_private_key_derive ( + const struct GNUNET_CRYPTO_EddsaPrivateKey *priv, + const char *label, + const char *context, + struct GNUNET_CRYPTO_EddsaPrivateScalar *result) +{ + struct GNUNET_CRYPTO_EddsaPublicKey pub; + uint8_t dc[32]; + unsigned char sk[64]; + gcry_mpi_t h; + gcry_mpi_t h_mod_n; + gcry_mpi_t x; + gcry_mpi_t d; + gcry_mpi_t n; + gcry_mpi_t a1; + gcry_mpi_t a2; + gcry_ctx_t ctx; + + GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); + + n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + GNUNET_CRYPTO_eddsa_key_get_public (priv, &pub); + crypto_hash_sha512 (sk, priv->d, 32); + sk[0] &= 248; + sk[31] &= 127; + sk[31] |= 64; + h = derive_h (&pub, sizeof (pub), label, context); + h_mod_n = gcry_mpi_new (256); + gcry_mpi_mod (h_mod_n, h, n); + /* Convert to big endian for libgcrypt */ + for (size_t i = 0; i < 32; i++) + dc[i] = sk[31 - i]; + GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); // a + a1 = gcry_mpi_new (256); + gcry_mpi_t eight = gcry_mpi_set_ui (NULL, 8); + gcry_mpi_div (a1, NULL, x, eight, 0); // a1 := a / 8 + a2 = gcry_mpi_new (256); + gcry_mpi_mulm (a2, h_mod_n, a1, n); // a2 := h * a1 mod n + d = gcry_mpi_new (256); + // gcry_mpi_mulm (d, a2, eight, n); // a' := a2 * 8 mod n + gcry_mpi_mul (d, a2, eight); // a' := a2 * 8 + gcry_mpi_release (h); + gcry_mpi_release (x); + gcry_mpi_release (n); + gcry_mpi_release (a1); + gcry_mpi_release (a2); + gcry_ctx_release (ctx); + GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); + memcpy (result->s, sk, sizeof (sk)); + /* Convert to little endian for libsodium */ + for (size_t i = 0; i < 32; i++) + result->s[i] = dc[31 - i]; + result->s[0] &= 248; + result->s[31] &= 127; + result->s[31] |= 64; + + sodium_memzero (dc, sizeof(dc)); + gcry_mpi_release (d); +} + + +void +GNUNET_CRYPTO_eddsa_public_key_derive ( + const struct GNUNET_CRYPTO_EddsaPublicKey *pub, + const char *label, + const char *context, + struct GNUNET_CRYPTO_EddsaPublicKey *result) +{ + gcry_ctx_t ctx; + gcry_mpi_t q_y; + gcry_mpi_t h; + gcry_mpi_t n; + gcry_mpi_t h_mod_n; + gcry_mpi_point_t q; + gcry_mpi_point_t v; + + GNUNET_assert (0 == gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); + + /* obtain point 'q' from original public key. The provided 'q' is + compressed thus we first store it in the context and then get it + back as a (decompresssed) point. */ + q_y = gcry_mpi_set_opaque_copy (NULL, pub->q_y, 8 * sizeof(pub->q_y)); + GNUNET_assert (NULL != q_y); + GNUNET_assert (0 == gcry_mpi_ec_set_mpi ("q", q_y, ctx)); + gcry_mpi_release (q_y); + q = gcry_mpi_ec_get_point ("q", ctx, 0); + GNUNET_assert (q); + + /* calculate h_mod_n = h % n */ + h = derive_h (pub, sizeof (*pub), label, context); + n = gcry_mpi_ec_get_mpi ("n", ctx, 1); + h_mod_n = gcry_mpi_new (256); + gcry_mpi_mod (h_mod_n, h, n); + + /* calculate v = h_mod_n * q */ + v = gcry_mpi_point_new (0); + gcry_mpi_ec_mul (v, h_mod_n, q, ctx); + gcry_mpi_release (h_mod_n); + gcry_mpi_release (h); + gcry_mpi_release (n); + gcry_mpi_point_release (q); + + /* convert point 'v' to public key that we return */ + GNUNET_assert (0 == gcry_mpi_ec_set_point ("q", v, ctx)); + gcry_mpi_point_release (v); + q_y = gcry_mpi_ec_get_mpi ("q@eddsa", ctx, 0); + GNUNET_assert (q_y); + GNUNET_CRYPTO_mpi_print_unsigned (result->q_y, sizeof(result->q_y), q_y); + gcry_mpi_release (q_y); + gcry_ctx_release (ctx); + +} |