]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
zonemd, loop over zone and canonicalize data, test call in unit test.
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 6 Oct 2020 15:07:24 +0000 (17:07 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Tue, 6 Oct 2020 15:07:24 +0000 (17:07 +0200)
services/authzone.c
services/authzone.h
testcode/unitauth.c
testcode/unitmain.c
testdata/zonemd.example1.zone [new file with mode: 0644]
validator/val_sigcrypt.c
validator/val_sigcrypt.h

index a26d1003abe2b5974543dc36cd154e1dd7d6ccf3..b4217bd55a13fb0a2301de8d20f28061cd6376b4 100644 (file)
@@ -68,6 +68,7 @@
 #include "sldns/keyraw.h"
 #include "validator/val_nsec3.h"
 #include "validator/val_secalgo.h"
+#include "validator/val_sigcrypt.h"
 #include <ctype.h>
 
 /** bytes to use for NSEC3 hash buffer. 20 for sha1 */
@@ -6965,3 +6966,366 @@ compare_serial(uint32_t a, uint32_t b)
                return 1;
        }
 }
+
+/** returns true if a zonemd hash algo is supported */
+static int zonemd_hashalgo_supported(int hashalgo)
+{
+       if(hashalgo == 1) return 1;
+       if(hashalgo == 2) return 1;
+       return 0;
+}
+
+/** returns true if a zonemd scheme is supported */
+static int zonemd_scheme_supported(int scheme)
+{
+       if(scheme == 1) return 1;
+       return 0;
+}
+
+/** initialize hash for hashing with zonemd hash algo */
+static void* zonemd_digest_init(int hashalgo)
+{
+       if(hashalgo == 1) {
+               /* sha384 */
+               //return secalgo_digest_start_sha384();
+       } else if(hashalgo == 2) {
+               /* sha512 */
+               //return secalgo_digest_start_sha512();
+       }
+       /* unknown hash algo */
+       return NULL;
+}
+
+/** add rrsets from node to the list */
+static size_t authdata_rrsets_to_list(struct auth_rrset** array,
+       size_t arraysize, struct auth_rrset* first)
+{
+       struct auth_rrset* rrset = first;
+       size_t num = 0;
+       while(rrset) {
+               if(num+1 >= arraysize)
+                       return num;
+               array[num] = rrset;
+               num++;
+               rrset = rrset->next;
+       }
+       return num;
+}
+
+/** compare rr list entries */
+static int rrlist_compare(const void* arg1, const void* arg2)
+{
+       struct auth_rrset* r1 = *(struct auth_rrset**)arg1;
+       struct auth_rrset* r2 = *(struct auth_rrset**)arg2;
+       uint16_t t1, t2;
+       if(r1 == NULL) t1 = LDNS_RR_TYPE_RRSIG;
+       else t1 = r1->type;
+       if(r2 == NULL) t2 = LDNS_RR_TYPE_RRSIG;
+       else t2 = r2->type;
+       if(t1 < t2)
+               return -1;
+       if(t1 > t2)
+               return 1;
+       return 0;
+}
+
+/** add type RRSIG to rr list if not one there already,
+ * this is to perform RRSIG collate processing at that point. */
+static void addrrsigtype_if_needed(struct auth_rrset** array,
+       size_t arraysize, size_t* rrnum, struct auth_data* node)
+{
+       if(az_domain_rrset(node, LDNS_RR_TYPE_RRSIG))
+               return; /* already one there */
+       if((*rrnum)+1 >= arraysize)
+               return; /* array too small? */
+       array[*rrnum] = NULL; /* nothing there, but need entry in list */
+       (*rrnum)++;
+}
+
+/** collate the RRs in an RRset using the simple scheme */
+static int zonemd_simple_rrset(struct auth_zone* z, void* hash,
+       struct auth_data* node, struct auth_rrset* rrset,
+       struct regional* region, struct sldns_buffer* buf, char** reason)
+{
+       /* canonicalize */
+       struct ub_packed_rrset_key key;
+       memset(&key, 0, sizeof(key));
+       key.entry.key = &key;
+       key.entry.data = rrset->data;
+       key.rk.dname = node->name;
+       key.rk.dname_len = node->namelen;
+       key.rk.type = htons(rrset->type);
+       key.rk.rrset_class = htons(z->dclass);
+       if(!rrset_canonicalize_to_buffer(region, buf, &key)) {
+               *reason = "out of memory";
+               return 0;
+       }
+       regional_free_all(region);
+
+       /* hash */
+       return 1;
+}
+
+/** count number of RRSIGs in a domain name rrset list */
+static size_t zonemd_simple_count_rrsig(struct auth_rrset* rrset,
+       struct auth_rrset** rrlist, size_t rrnum,
+       struct auth_zone* z, struct auth_data* node)
+{
+       size_t i, count = 0;
+       if(rrset) {
+               size_t j;
+               for(j = 0; j<rrset->data->count; j++) {
+                       if(rrsig_rdata_get_type_covered(rrset->data->
+                               rr_data[j], rrset->data->rr_len[j]) ==
+                               LDNS_RR_TYPE_ZONEMD &&
+                               query_dname_compare(z->name, node->name)==0) {
+                               /* omit RRSIGs over type ZONEMD at apex */
+                               continue;
+                       }
+                       count++;
+               }
+       }
+       for(i=0; i<rrnum; i++) {
+               if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
+                       query_dname_compare(z->name, node->name)==0) {
+                       /* omit RRSIGs over type ZONEMD at apex */
+                       continue;
+               }
+               count += (rrlist[i]?rrlist[i]->data->rrsig_count:0);
+       }
+       return count;
+}
+
+/** allocate sparse rrset data for the number of entries in tepm region */
+static int zonemd_simple_rrsig_allocs(struct regional* region,
+       struct packed_rrset_data* data, size_t count)
+{
+       data->rr_len = regional_alloc(region, sizeof(*data->rr_len) * count);
+       if(!data->rr_len) {
+               return 0;
+       }
+       data->rr_ttl = regional_alloc(region, sizeof(*data->rr_ttl) * count);
+       if(!data->rr_ttl) {
+               return 0;
+       }
+       data->rr_data = regional_alloc(region, sizeof(*data->rr_data) * count);
+       if(!data->rr_data) {
+               return 0;
+       }
+       return 1;
+}
+
+/** add the RRSIGs from the rrs in the domain into the data */
+static void add_rrlist_rrsigs_into_data(struct packed_rrset_data* data,
+       size_t* done, struct auth_rrset** rrlist, size_t rrnum,
+       struct auth_zone* z, struct auth_data* node)
+{
+       size_t i;
+       for(i=0; i<rrnum; i++) {
+               size_t j;
+               if(!rrlist[i])
+                       continue;
+               if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
+                       query_dname_compare(z->name, node->name)==0) {
+                       /* omit RRSIGs over type ZONEMD at apex */
+                       continue;
+               }
+               for(j = 0; j<rrlist[i]->data->rrsig_count; j++) {
+                       data->rr_len[*done] = rrlist[i]->data->rr_len[rrlist[i]->data->count + j];
+                       data->rr_ttl[*done] = rrlist[i]->data->rr_ttl[rrlist[i]->data->count + j];
+                       /* reference the rdata in the rrset, no need to
+                        * copy it, it is no longer need at the end of
+                        * the routine */
+                       data->rr_data[*done] = rrlist[i]->data->rr_data[rrlist[i]->data->count + j];
+                       (*done)++;
+               }
+       }
+}
+
+static void add_rrset_into_data(struct packed_rrset_data* data,
+       size_t* done, struct auth_rrset* rrset,
+       struct auth_zone* z, struct auth_data* node)
+{
+       if(rrset) {
+               size_t j;
+               for(j = 0; j<rrset->data->count; j++) {
+                       if(rrsig_rdata_get_type_covered(rrset->data->
+                               rr_data[j], rrset->data->rr_len[j]) ==
+                               LDNS_RR_TYPE_ZONEMD &&
+                               query_dname_compare(z->name, node->name)==0) {
+                               /* omit RRSIGs over type ZONEMD at apex */
+                               continue;
+                       }
+                       data->rr_len[*done] = rrset->data->rr_len[j];
+                       data->rr_ttl[*done] = rrset->data->rr_ttl[j];
+                       /* reference the rdata in the rrset, no need to
+                        * copy it, it is no longer need at the end of
+                        * the routine */
+                       data->rr_data[*done] = rrset->data->rr_data[j];
+                       (*done)++;
+               }
+       }
+}
+
+/** collate the RRSIGs using the simple scheme */
+static int zonemd_simple_rrsig(struct auth_zone* z, void* hash,
+       struct auth_data* node, struct auth_rrset* rrset,
+       struct auth_rrset** rrlist, size_t rrnum, struct regional* region,
+       struct sldns_buffer* buf, char** reason)
+{
+       /* the rrset pointer can be NULL, this means it is type RRSIG and
+        * there is no ordinary type RRSIG there.  The RRSIGs are stored
+        * with the RRsets in their data.
+        *
+        * The RRset pointer can be nonNULL. This happens if there is
+        * no RR that is covered by the RRSIG for the domain.  Then this
+        * RRSIG RR is stored in an rrset of type RRSIG. The other RRSIGs
+        * are stored in the rrset entries for the RRs in the rr list for
+        * the domain node.  We need to collate the rrset's data, if any, and
+        * the rrlist's rrsigs */
+       /* if this is the apex, omit RRSIGs that cover type ZONEMD */
+       /* build rrsig rrset */
+       size_t done = 0;
+       struct ub_packed_rrset_key key;
+       struct packed_rrset_data data;
+       memset(&key, 0, sizeof(key));
+       memset(&data, 0, sizeof(data));
+       key.entry.key = &key;
+       key.entry.data = &data;
+       key.rk.dname = node->name;
+       key.rk.dname_len = node->namelen;
+       key.rk.type = htons(rrset->type);
+       key.rk.rrset_class = htons(z->dclass);
+       data.count = zonemd_simple_count_rrsig(rrset, rrlist, rrnum, z, node);
+       if(!zonemd_simple_rrsig_allocs(region, &data, data.count)) {
+               *reason = "out of memory";
+               regional_free_all(region);
+               return 0;
+       }
+       /* all the RRSIGs stored in the other rrsets for this domain node */
+       add_rrlist_rrsigs_into_data(&data, &done, rrlist, rrnum, z, node);
+       /* plus the RRSIGs stored in an rrset of type RRSIG for this node */
+       add_rrset_into_data(&data, &done, rrset, z, node);
+
+       /* canonicalize */
+       if(!rrset_canonicalize_to_buffer(region, buf, &key)) {
+               *reason = "out of memory";
+               regional_free_all(region);
+               return 0;
+       }
+       regional_free_all(region);
+
+       /* hash */
+       return 1;
+}
+
+/** collate a domain's rrsets using the simple scheme */
+static int zonemd_simple_domain(struct auth_zone* z, void* hash,
+       struct auth_data* node, struct regional* region,
+       struct sldns_buffer* buf, char** reason)
+{
+       const size_t rrlistsize = 65536;
+       struct auth_rrset* rrlist[rrlistsize];
+       size_t i, rrnum = 0;
+       /* see if the domain is out of scope, the zone origin,
+        * that would be omitted */
+       if(!dname_subdomain_c(node->name, z->name))
+               return 1; /* continue */
+       /* loop over the rrsets in ascending order. */
+       rrnum = authdata_rrsets_to_list(rrlist, rrlistsize, node->rrsets);
+       addrrsigtype_if_needed(rrlist, rrlistsize, &rrnum, node);
+       qsort(rrlist, rrnum, sizeof(*rrlist), rrlist_compare);
+       for(i=0; i<rrnum; i++) {
+               if(rrlist[i] && rrlist[i]->type == LDNS_RR_TYPE_ZONEMD &&
+                       query_dname_compare(z->name, node->name) == 0) {
+                       /* omit type ZONEMD at apex */
+                       continue;
+               }
+               if(rrlist[i] == NULL || rrlist[i]->type ==
+                       LDNS_RR_TYPE_RRSIG) {
+                       if(!zonemd_simple_rrsig(z, hash, node, rrlist[i],
+                               rrlist, rrnum, region, buf, reason))
+                               return 0;
+               } else if(!zonemd_simple_rrset(z, hash, node, rrlist[i],
+                       region, buf, reason)) {
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+/** collate the zone using the simple scheme */
+static int zonemd_simple_collate(struct auth_zone* z, void* hash,
+       struct regional* region, struct sldns_buffer* buf, char** reason)
+{
+       /* our tree is sorted in canonical order, so we can just loop over
+        * the tree */
+       struct auth_data* n;
+       RBTREE_FOR(n, struct auth_data*, &z->data) {
+               if(!zonemd_simple_domain(z, hash, n, region, buf, reason))
+                       return 0;
+       }
+       return 1;
+}
+
+int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme,
+        int hashalgo, uint8_t* hash, size_t hashlen, size_t* resultlen,
+       struct regional* region, struct sldns_buffer* buf, char** reason)
+{
+       void* h = zonemd_digest_init(hashalgo);
+       if(!h) {
+               *reason = "digest init fail";
+               return 0;
+       }
+       if(scheme == 1) {
+               if(!zonemd_simple_collate(z, h, region, buf, reason)) {
+                       if(!*reason) *reason = "scheme simple collate fail";
+                       return 0;
+               }
+       }
+       /*
+       if(!zonemd_digest_finish(hashalgo, hash, hashlen, resultlen)) {
+               *reason = "digest finish fail";
+               return 0;
+       }
+       */
+       return 1;
+}
+
+int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme,
+       int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region,
+       struct sldns_buffer* buf, char** reason)
+{
+       uint8_t gen[512];
+       size_t genlen = 0;
+       if(!zonemd_hashalgo_supported(hashalgo)) {
+               *reason = "unsupported algorithm";
+               return 0;
+       }
+       if(!zonemd_scheme_supported(scheme)) {
+               *reason = "unsupported scheme";
+               return 0;
+       }
+       if(hashlen < 12) {
+               /* the ZONEMD draft requires digests to fail if too small */
+               *reason = "digest length too small, less than 12";
+               return 0;
+       }
+       /* generate digest */
+       if(!auth_zone_generate_zonemd_hash(z, scheme, hashalgo, gen,
+               sizeof(gen), &genlen, region, buf, reason)) {
+               /* reason filled in by zonemd hash routine */
+               return 0;
+       }
+       /* check digest length */
+       if(hashlen != genlen) {
+               *reason = "incorrect digest length";
+               return 0;
+       }
+       /* check digest */
+       if(memcmp(hash, gen, genlen) != 0) {
+               *reason = "incorrect digest";
+               return 0;
+       }
+       return 1;
+}
index 3d94f30d6202c38100802372300bd3934d528ea5..813fc0e8746eff92950e039ac1b7107736603275 100644 (file)
@@ -685,4 +685,39 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode,
  */
 int compare_serial(uint32_t a, uint32_t b);
 
+/**
+ * Generate ZONEMD digest for the auth zone.
+ * @param z: the auth zone to digest.
+ *     omits zonemd at apex and its RRSIG from the digest.
+ * @param scheme: the collation scheme to use.  Numbers as defined for ZONEMD.
+ * @param hashalgo: the hash algo, from the registry defined for ZONEMD type.
+ * @param hash: the result buffer.
+ * @param buflen: size of the result buffer, must be large enough. or the
+ *     routine fails.
+ * @param resultlen: size of the hash in the result buffer of the result.
+ * @param region: temp region for allocs during canonicalisation.
+ * @param buf: temp buffer during canonicalisation.
+ * @param reason: failure reason, returns a string, NULL on success.
+ * @return false on failure.
+ */
+int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme,
+       int hashalgo, uint8_t* hash, size_t buflen, size_t* resultlen,
+       struct regional* region, struct sldns_buffer* buf, char** reason);
+
+/**
+ * Check ZONEMD digest for the auth zone.
+ * @param z: auth zone to digest.
+ * @param scheme: zonemd scheme.
+ * @param hashalgo: zonemd hash algorithm.
+ * @param hash: the hash to check.
+ * @param buflen: length of hash buffer.
+ * @param region: temp region for allocs during canonicalisation.
+ * @param buf: temp buffer during canonicalisation.
+ * @param reason: string returned with failure reason.
+ * @return false on failure.
+ */
+int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme,
+       int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region,
+       struct sldns_buffer* buf, char** reason);
+
 #endif /* SERVICES_AUTHZONE_H */
index 4b3410c9ef7ce1ecacda21b9a25cfb77866912f1..184573ab6395651d8ef495f14f72db11ed5cbc1d 100644 (file)
@@ -517,8 +517,8 @@ del_tmp_file(char* fname)
 }
 
 /** Add zone from file for testing */
-static struct auth_zone*
-addzone(struct auth_zones* az, const char* name, char* fname)
+struct auth_zone*
+authtest_addzone(struct auth_zones* az, const char* name, char* fname)
 {
        struct auth_zone* z;
        size_t nmlen;
@@ -593,7 +593,7 @@ check_read_exact(const char* name, const char* zone)
 
        az = auth_zones_create();
        unit_assert(az);
-       z = addzone(az, name, fname);
+       z = authtest_addzone(az, name, fname);
        unit_assert(z);
        outf = create_tmp_file(NULL);
        if(!auth_zone_write_file(z, outf)) {
@@ -844,7 +844,7 @@ check_queries(const char* name, const char* zone, struct q_ans* queries)
        fname = create_tmp_file(zone);
        az = auth_zones_create();
        if(!az) fatal_exit("out of memory");
-       z = addzone(az, name, fname);
+       z = authtest_addzone(az, name, fname);
        if(!z) fatal_exit("could not read zone for queries test");
        del_tmp_file(fname);
 
index a42be424e9e9a35b623368b4b4f9f951fb01556e..df560a466391e107e44633d6bd82db02a9bfa035 100644 (file)
@@ -839,6 +839,52 @@ static void respip_test(void)
        respip_conf_actions_test();
 }
 
+#include "services/authzone.h"
+#include "util/data/dname.h"
+#include "util/regional.h"
+/** Add zone from file for testing */
+struct auth_zone* authtest_addzone(struct auth_zones* az, const char* name,
+       char* fname);
+/** zonemd unit tests */
+static void zonemd_test(void)
+{
+       uint8_t zonemd_hash[512];
+       struct auth_zones* az;
+       struct auth_zone* z;
+       int scheme = 1, hashalgo = 2;
+       size_t hashlen = 0;
+       int result;
+       char* reason = NULL;
+       struct regional* region = NULL;
+       struct sldns_buffer* buf = NULL;
+       unit_show_feature("zonemd");
+       region = regional_create();
+       unit_assert(region);
+       buf = sldns_buffer_new(65535);
+       unit_assert(buf);
+       az = auth_zones_create();
+       unit_assert(az);
+       z = authtest_addzone(az, "example.org", "testdata/zonemd.example1.zone");
+       unit_assert(z);
+
+       /* zonemd test on zone */
+       result = auth_zone_generate_zonemd_hash(z, scheme, hashalgo,
+               zonemd_hash, sizeof(zonemd_hash), &hashlen, region, buf,
+               &reason);
+       if(reason) printf("zonemd failure reason: %s\n", reason);
+       unit_assert(result);
+       if(1) {
+               char zname[255+1];
+               dname_str(z->name, zname);
+               printf("zonemd generated for %s in %s with scheme=%d, hashalgo=%d\n", zname, z->zonefile, scheme, hashalgo);
+               log_hex("digest", zonemd_hash, hashlen);
+       }
+
+       auth_zones_delete(az);
+       regional_destroy(region);
+       sldns_buffer_free(buf);
+}
+
 void unit_show_func(const char* file, const char* func)
 {
        printf("test %s:%s\n", file, func);
@@ -889,6 +935,7 @@ main(int argc, char* argv[])
                fatal_exit("could not init NSS");
 #endif /* HAVE_SSL or HAVE_NSS*/
        checklock_start();
+       zonemd_test();
        authzone_test();
        neg_test();
        rnd_test();
diff --git a/testdata/zonemd.example1.zone b/testdata/zonemd.example1.zone
new file mode 100644 (file)
index 0000000..b1a4489
--- /dev/null
@@ -0,0 +1,4 @@
+example.org. IN SOA ns.example.org. hostmaster.example.org. 200154054 28800 7200 604800 3600
+example.org. IN NS ns.example.org.
+www.example.org. IN A 127.0.0.1
+ns.example.org. IN A 127.0.0.1
index de730f681893b10d99c0e9c4b5a35ef57d9d9039..10cf2caee0e8db7526478f2dc686c4d6765dee20 100644 (file)
@@ -1199,6 +1199,59 @@ rrset_canonical(struct regional* region, sldns_buffer* buf,
        return 1;
 }
 
+int
+rrset_canonicalize_to_buffer(struct regional* region, sldns_buffer* buf,
+       struct ub_packed_rrset_key* k)
+{
+       struct rbtree_type* sortree = NULL;
+       struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data;
+       uint8_t* can_owner = NULL;
+       size_t can_owner_len = 0;
+       struct canon_rr* walk;
+       struct canon_rr* rrs;
+
+       sortree = (struct rbtree_type*)regional_alloc(region,
+               sizeof(rbtree_type));
+       if(!sortree)
+               return 0;
+       if(d->count > RR_COUNT_MAX)
+               return 0; /* integer overflow protection */
+       rrs = regional_alloc(region, sizeof(struct canon_rr)*d->count);
+       if(!rrs) {
+               return 0;
+       }
+       rbtree_init(sortree, &canonical_tree_compare);
+       canonical_sort(k, d, sortree, rrs);
+
+       sldns_buffer_clear(buf);
+       RBTREE_FOR(walk, struct canon_rr*, sortree) {
+               /* see if there is enough space left in the buffer */
+               if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4
+                       + d->rr_len[walk->rr_idx]) {
+                       log_err("verify: failed to canonicalize, "
+                               "rrset too big");
+                       return 0;
+               }
+               /* determine canonical owner name */
+               if(can_owner)
+                       sldns_buffer_write(buf, can_owner, can_owner_len);
+               else    {
+                       can_owner = sldns_buffer_current(buf);
+                       sldns_buffer_write(buf, k->rk.dname, k->rk.dname_len);
+                       query_dname_tolower(can_owner);
+                       can_owner_len = k->rk.dname_len;
+               }
+               sldns_buffer_write(buf, &k->rk.type, 2);
+               sldns_buffer_write(buf, &k->rk.rrset_class, 2);
+               sldns_buffer_write_u32(buf, d->rr_ttl[walk->rr_idx]);
+               sldns_buffer_write(buf, d->rr_data[walk->rr_idx],
+                       d->rr_len[walk->rr_idx]);
+               canonicalize_rdata(buf, k, d->rr_len[walk->rr_idx]);
+       }
+       sldns_buffer_flip(buf);
+       return 1;
+}
+
 /** pretty print rrsig error with dates */
 static void
 sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now)
index 755a1d6e126d588881934d1d692d75391f803e77..23ca1d91b7b5e9588835cd4b1d424901ad0ed011 100644 (file)
@@ -334,4 +334,16 @@ int canonical_tree_compare(const void* k1, const void* k2);
 int rrset_canonical_equal(struct regional* region,
        struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2);
 
+/**
+ * Canonicalize an rrset into the buffer.  For an auth zone record, so
+ * this does not use a signature, or the RRSIG TTL or the wildcard label
+ * count from the RRSIG.
+ * @param region: temporary region.
+ * @param buf: the buffer to use.
+ * @param k: the rrset to insert.
+ * @return false on alloc error.
+ */
+int rrset_canonicalize_to_buffer(struct regional* region,
+       struct sldns_buffer* buf, struct ub_packed_rrset_key* k);
+
 #endif /* VALIDATOR_VAL_SIGCRYPT_H */