]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-dns-dnssec.c
resolved: when matching up RRSIG and DNSKEY RRs, use the RRSIG's signer name, not...
[thirdparty/systemd.git] / src / resolve / resolved-dns-dnssec.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2015 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <gcrypt.h>
23
24 #include "alloc-util.h"
25 #include "dns-domain.h"
26 #include "resolved-dns-dnssec.h"
27 #include "resolved-dns-packet.h"
28 #include "string-table.h"
29
30 /* Open question:
31 *
32 * How does the DNSSEC canonical form of a hostname with a label
33 * containing a dot look like, the way DNS-SD does it?
34 *
35 * TODO:
36 *
37 * - Iterative validation
38 * - NSEC proof of non-existance
39 * - NSEC3 proof of non-existance
40 * - Make trust anchor store read additional DS+DNSKEY data from disk
41 * - wildcard zones compatibility
42 * - multi-label zone compatibility
43 * - DNSSEC cname/dname compatibility
44 * - per-interface DNSSEC setting
45 * - DSA support
46 * - EC support?
47 *
48 * */
49
50 #define VERIFY_RRS_MAX 256
51 #define MAX_KEY_SIZE (32*1024)
52
53 /* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */
54 #define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE)
55
56 /*
57 * The DNSSEC Chain of trust:
58 *
59 * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone
60 * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree
61 * DS RRs are protected like normal RRs
62 *
63 * Example chain:
64 * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS
65 */
66
67 static bool dnssec_algorithm_supported(int algorithm) {
68 return IN_SET(algorithm,
69 DNSSEC_ALGORITHM_RSASHA1,
70 DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1,
71 DNSSEC_ALGORITHM_RSASHA256,
72 DNSSEC_ALGORITHM_RSASHA512);
73 }
74
75 static bool dnssec_digest_supported(int digest) {
76 return IN_SET(digest,
77 DNSSEC_DIGEST_SHA1,
78 DNSSEC_DIGEST_SHA256);
79 }
80
81 uint16_t dnssec_keytag(DnsResourceRecord *dnskey) {
82 const uint8_t *p;
83 uint32_t sum;
84 size_t i;
85
86 /* The algorithm from RFC 4034, Appendix B. */
87
88 assert(dnskey);
89 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
90
91 sum = (uint32_t) dnskey->dnskey.flags +
92 ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm);
93
94 p = dnskey->dnskey.key;
95
96 for (i = 0; i < dnskey->dnskey.key_size; i++)
97 sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i];
98
99 sum += (sum >> 16) & UINT32_C(0xFFFF);
100
101 return sum & UINT32_C(0xFFFF);
102 }
103
104 static int rr_compare(const void *a, const void *b) {
105 DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b;
106 size_t m;
107 int r;
108
109 /* Let's order the RRs according to RFC 4034, Section 6.3 */
110
111 assert(x);
112 assert(*x);
113 assert((*x)->wire_format);
114 assert(y);
115 assert(*y);
116 assert((*y)->wire_format);
117
118 m = MIN((*x)->wire_format_size, (*y)->wire_format_size);
119
120 r = memcmp((*x)->wire_format, (*y)->wire_format, m);
121 if (r != 0)
122 return r;
123
124 if ((*x)->wire_format_size < (*y)->wire_format_size)
125 return -1;
126 else if ((*x)->wire_format_size > (*y)->wire_format_size)
127 return 1;
128
129 return 0;
130 }
131
132 static int dnssec_rsa_verify(
133 const char *hash_algorithm,
134 const void *signature, size_t signature_size,
135 const void *data, size_t data_size,
136 const void *exponent, size_t exponent_size,
137 const void *modulus, size_t modulus_size) {
138
139 gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL;
140 gcry_mpi_t n = NULL, e = NULL, s = NULL;
141 gcry_error_t ge;
142 int r;
143
144 assert(hash_algorithm);
145
146 ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL);
147 if (ge != 0) {
148 r = -EIO;
149 goto finish;
150 }
151
152 ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL);
153 if (ge != 0) {
154 r = -EIO;
155 goto finish;
156 }
157
158 ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL);
159 if (ge != 0) {
160 r = -EIO;
161 goto finish;
162 }
163
164 ge = gcry_sexp_build(&signature_sexp,
165 NULL,
166 "(sig-val (rsa (s %m)))",
167 s);
168
169 if (ge != 0) {
170 r = -EIO;
171 goto finish;
172 }
173
174 ge = gcry_sexp_build(&data_sexp,
175 NULL,
176 "(data (flags pkcs1) (hash %s %b))",
177 hash_algorithm,
178 (int) data_size,
179 data);
180 if (ge != 0) {
181 r = -EIO;
182 goto finish;
183 }
184
185 ge = gcry_sexp_build(&public_key_sexp,
186 NULL,
187 "(public-key (rsa (n %m) (e %m)))",
188 n,
189 e);
190 if (ge != 0) {
191 r = -EIO;
192 goto finish;
193 }
194
195 ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp);
196 if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE)
197 r = 0;
198 else if (ge != 0) {
199 log_debug("RSA signature check failed: %s", gpg_strerror(ge));
200 r = -EIO;
201 } else
202 r = 1;
203
204 finish:
205 if (e)
206 gcry_mpi_release(e);
207 if (n)
208 gcry_mpi_release(n);
209 if (s)
210 gcry_mpi_release(s);
211
212 if (public_key_sexp)
213 gcry_sexp_release(public_key_sexp);
214 if (signature_sexp)
215 gcry_sexp_release(signature_sexp);
216 if (data_sexp)
217 gcry_sexp_release(data_sexp);
218
219 return r;
220 }
221
222 static void md_add_uint8(gcry_md_hd_t md, uint8_t v) {
223 gcry_md_write(md, &v, sizeof(v));
224 }
225
226 static void md_add_uint16(gcry_md_hd_t md, uint16_t v) {
227 v = htobe16(v);
228 gcry_md_write(md, &v, sizeof(v));
229 }
230
231 static void md_add_uint32(gcry_md_hd_t md, uint32_t v) {
232 v = htobe32(v);
233 gcry_md_write(md, &v, sizeof(v));
234 }
235
236 static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) {
237 usec_t expiration, inception, skew;
238
239 assert(rrsig);
240 assert(rrsig->key->type == DNS_TYPE_RRSIG);
241
242 if (realtime == USEC_INFINITY)
243 realtime = now(CLOCK_REALTIME);
244
245 expiration = rrsig->rrsig.expiration * USEC_PER_SEC;
246 inception = rrsig->rrsig.inception * USEC_PER_SEC;
247
248 if (inception > expiration)
249 return -EKEYREJECTED;
250
251 /* Permit a certain amount of clock skew of 10% of the valid
252 * time range. This takes inspiration from unbound's
253 * resolver. */
254 skew = (expiration - inception) / 10;
255 if (skew > SKEW_MAX)
256 skew = SKEW_MAX;
257
258 if (inception < skew)
259 inception = 0;
260 else
261 inception -= skew;
262
263 if (expiration + skew < expiration)
264 expiration = USEC_INFINITY;
265 else
266 expiration += skew;
267
268 return realtime < inception || realtime > expiration;
269 }
270
271 int dnssec_verify_rrset(
272 DnsAnswer *a,
273 DnsResourceKey *key,
274 DnsResourceRecord *rrsig,
275 DnsResourceRecord *dnskey,
276 usec_t realtime) {
277
278 uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX];
279 size_t exponent_size, modulus_size, hash_size;
280 void *exponent, *modulus, *hash;
281 DnsResourceRecord **list, *rr;
282 gcry_md_hd_t md = NULL;
283 size_t k, n = 0;
284 int r;
285
286 assert(key);
287 assert(rrsig);
288 assert(dnskey);
289 assert(rrsig->key->type == DNS_TYPE_RRSIG);
290 assert(dnskey->key->type == DNS_TYPE_DNSKEY);
291
292 /* Verifies the the RRSet matching the specified "key" in "a",
293 * using the signature "rrsig" and the key "dnskey". It's
294 * assumed the RRSIG and DNSKEY match. */
295
296 if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm))
297 return -EOPNOTSUPP;
298
299 if (a->n_rrs > VERIFY_RRS_MAX)
300 return -E2BIG;
301
302 r = dnssec_rrsig_expired(rrsig, realtime);
303 if (r < 0)
304 return r;
305 if (r > 0)
306 return DNSSEC_SIGNATURE_EXPIRED;
307
308 /* Collect all relevant RRs in a single array, so that we can look at the RRset */
309 list = newa(DnsResourceRecord *, a->n_rrs);
310
311 DNS_ANSWER_FOREACH(rr, a) {
312 r = dns_resource_key_equal(key, rr->key);
313 if (r < 0)
314 return r;
315 if (r == 0)
316 continue;
317
318 /* We need the wire format for ordering, and digest calculation */
319 r = dns_resource_record_to_wire_format(rr, true);
320 if (r < 0)
321 return r;
322
323 list[n++] = rr;
324 }
325
326 if (n <= 0)
327 return -ENODATA;
328
329 /* Bring the RRs into canonical order */
330 qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare);
331
332 /* OK, the RRs are now in canonical order. Let's calculate the digest */
333 switch (rrsig->rrsig.algorithm) {
334
335 case DNSSEC_ALGORITHM_RSASHA1:
336 case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1:
337 gcry_md_open(&md, GCRY_MD_SHA1, 0);
338 hash_size = 20;
339 break;
340
341 case DNSSEC_ALGORITHM_RSASHA256:
342 gcry_md_open(&md, GCRY_MD_SHA256, 0);
343 hash_size = 32;
344 break;
345
346 case DNSSEC_ALGORITHM_RSASHA512:
347 gcry_md_open(&md, GCRY_MD_SHA512, 0);
348 hash_size = 64;
349 break;
350
351 default:
352 assert_not_reached("Unknown digest");
353 }
354
355 if (!md)
356 return -EIO;
357
358 md_add_uint16(md, rrsig->rrsig.type_covered);
359 md_add_uint8(md, rrsig->rrsig.algorithm);
360 md_add_uint8(md, rrsig->rrsig.labels);
361 md_add_uint32(md, rrsig->rrsig.original_ttl);
362 md_add_uint32(md, rrsig->rrsig.expiration);
363 md_add_uint32(md, rrsig->rrsig.inception);
364 md_add_uint16(md, rrsig->rrsig.key_tag);
365
366 r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true);
367 if (r < 0)
368 goto finish;
369 gcry_md_write(md, wire_format_name, r);
370
371 for (k = 0; k < n; k++) {
372 size_t l;
373 rr = list[k];
374
375 r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true);
376 if (r < 0)
377 goto finish;
378 gcry_md_write(md, wire_format_name, r);
379
380 md_add_uint16(md, rr->key->type);
381 md_add_uint16(md, rr->key->class);
382 md_add_uint32(md, rrsig->rrsig.original_ttl);
383
384 assert(rr->wire_format_rdata_offset <= rr->wire_format_size);
385 l = rr->wire_format_size - rr->wire_format_rdata_offset;
386 assert(l <= 0xFFFF);
387
388 md_add_uint16(md, (uint16_t) l);
389 gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l);
390 }
391
392 hash = gcry_md_read(md, 0);
393 if (!hash) {
394 r = -EIO;
395 goto finish;
396 }
397
398 if (*(uint8_t*) dnskey->dnskey.key == 0) {
399 /* exponent is > 255 bytes long */
400
401 exponent = (uint8_t*) dnskey->dnskey.key + 3;
402 exponent_size =
403 ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) |
404 ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]);
405
406 if (exponent_size < 256) {
407 r = -EINVAL;
408 goto finish;
409 }
410
411 if (3 + exponent_size >= dnskey->dnskey.key_size) {
412 r = -EINVAL;
413 goto finish;
414 }
415
416 modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size;
417 modulus_size = dnskey->dnskey.key_size - 3 - exponent_size;
418
419 } else {
420 /* exponent is <= 255 bytes long */
421
422 exponent = (uint8_t*) dnskey->dnskey.key + 1;
423 exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0];
424
425 if (exponent_size <= 0) {
426 r = -EINVAL;
427 goto finish;
428 }
429
430 if (1 + exponent_size >= dnskey->dnskey.key_size) {
431 r = -EINVAL;
432 goto finish;
433 }
434
435 modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size;
436 modulus_size = dnskey->dnskey.key_size - 1 - exponent_size;
437 }
438
439 r = dnssec_rsa_verify(
440 gcry_md_algo_name(gcry_md_get_algo(md)),
441 rrsig->rrsig.signature, rrsig->rrsig.signature_size,
442 hash, hash_size,
443 exponent, exponent_size,
444 modulus, modulus_size);
445 if (r < 0)
446 goto finish;
447
448 r = r ? DNSSEC_VERIFIED : DNSSEC_INVALID;
449
450 finish:
451 gcry_md_close(md);
452 return r;
453 }
454
455 int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) {
456
457 assert(rrsig);
458 assert(dnskey);
459
460 /* Checks if the specified DNSKEY RR matches the key used for
461 * the signature in the specified RRSIG RR */
462
463 if (rrsig->key->type != DNS_TYPE_RRSIG)
464 return -EINVAL;
465
466 if (dnskey->key->type != DNS_TYPE_DNSKEY)
467 return 0;
468 if (dnskey->key->class != rrsig->key->class)
469 return 0;
470 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
471 return 0;
472 if (dnskey->dnskey.protocol != 3)
473 return 0;
474 if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm)
475 return 0;
476
477 if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag)
478 return 0;
479
480 return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer);
481 }
482
483 int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) {
484 assert(key);
485 assert(rrsig);
486
487 /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */
488
489 if (rrsig->key->type != DNS_TYPE_RRSIG)
490 return 0;
491 if (rrsig->key->class != key->class)
492 return 0;
493 if (rrsig->rrsig.type_covered != key->type)
494 return 0;
495
496 return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key));
497 }
498
499 int dnssec_verify_rrset_search(
500 DnsAnswer *a,
501 DnsResourceKey *key,
502 DnsAnswer *validated_dnskeys,
503 usec_t realtime) {
504
505 bool found_rrsig = false, found_dnskey = false;
506 DnsResourceRecord *rrsig;
507 int r;
508
509 assert(key);
510
511 /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */
512
513 if (!a || a->n_rrs <= 0)
514 return -ENODATA;
515
516 /* Iterate through each RRSIG RR. */
517 DNS_ANSWER_FOREACH(rrsig, a) {
518 DnsResourceRecord *dnskey;
519
520 r = dnssec_key_match_rrsig(key, rrsig);
521 if (r < 0)
522 return r;
523 if (r == 0)
524 continue;
525
526 found_rrsig = true;
527
528 DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) {
529
530 r = dnssec_rrsig_match_dnskey(rrsig, dnskey);
531 if (r < 0)
532 return r;
533 if (r == 0)
534 continue;
535
536 found_dnskey = true;
537
538 /* Take the time here, if it isn't set yet, so
539 * that we do all validations with the same
540 * time. */
541 if (realtime == USEC_INFINITY)
542 realtime = now(CLOCK_REALTIME);
543
544 /* Yay, we found a matching RRSIG with a matching
545 * DNSKEY, awesome. Now let's verify all entries of
546 * the RRSet against the RRSIG and DNSKEY
547 * combination. */
548
549 r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime);
550 if (r < 0 && r != EOPNOTSUPP)
551 return r;
552 if (r == DNSSEC_VERIFIED)
553 return DNSSEC_VERIFIED;
554
555 /* If the signature is invalid, or done using
556 an unsupported algorithm, let's try another
557 key and/or signature. After all they
558 key_tags and stuff are not unique, and
559 might be shared by multiple keys. */
560 }
561 }
562
563 if (found_dnskey)
564 return DNSSEC_INVALID;
565
566 if (found_rrsig)
567 return DNSSEC_MISSING_KEY;
568
569 return DNSSEC_NO_SIGNATURE;
570 }
571
572 int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) {
573 size_t c = 0;
574 int r;
575
576 /* Converts the specified hostname into DNSSEC canonicalized
577 * form. */
578
579 if (buffer_max < 2)
580 return -ENOBUFS;
581
582 for (;;) {
583 size_t i;
584
585 r = dns_label_unescape(&n, buffer, buffer_max);
586 if (r < 0)
587 return r;
588 if (r == 0)
589 break;
590 if (r > 0) {
591 int k;
592
593 /* DNSSEC validation is always done on the ASCII version of the label */
594 k = dns_label_apply_idna(buffer, r, buffer, buffer_max);
595 if (k < 0)
596 return k;
597 if (k > 0)
598 r = k;
599 }
600
601 if (buffer_max < (size_t) r + 2)
602 return -ENOBUFS;
603
604 /* The DNSSEC canonical form is not clear on what to
605 * do with dots appearing in labels, the way DNS-SD
606 * does it. Refuse it for now. */
607
608 if (memchr(buffer, '.', r))
609 return -EINVAL;
610
611 for (i = 0; i < (size_t) r; i ++) {
612 if (buffer[i] >= 'A' && buffer[i] <= 'Z')
613 buffer[i] = buffer[i] - 'A' + 'a';
614 }
615
616 buffer[r] = '.';
617
618 buffer += r + 1;
619 c += r + 1;
620
621 buffer_max -= r + 1;
622 }
623
624 if (c <= 0) {
625 /* Not even a single label: this is the root domain name */
626
627 assert(buffer_max > 2);
628 buffer[0] = '.';
629 buffer[1] = 0;
630
631 return 1;
632 }
633
634 return (int) c;
635 }
636
637 int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) {
638 gcry_md_hd_t md = NULL;
639 char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX];
640 void *result;
641 int r;
642
643 assert(dnskey);
644 assert(ds);
645
646 /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */
647
648 if (dnskey->key->type != DNS_TYPE_DNSKEY)
649 return -EINVAL;
650 if (ds->key->type != DNS_TYPE_DS)
651 return -EINVAL;
652 if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0)
653 return -EKEYREJECTED;
654 if (dnskey->dnskey.protocol != 3)
655 return -EKEYREJECTED;
656
657 if (!dnssec_algorithm_supported(dnskey->dnskey.algorithm))
658 return -EOPNOTSUPP;
659 if (!dnssec_digest_supported(ds->ds.digest_type))
660 return -EOPNOTSUPP;
661
662 if (dnskey->dnskey.algorithm != ds->ds.algorithm)
663 return 0;
664 if (dnssec_keytag(dnskey) != ds->ds.key_tag)
665 return 0;
666
667 switch (ds->ds.digest_type) {
668
669 case DNSSEC_DIGEST_SHA1:
670
671 if (ds->ds.digest_size != 20)
672 return 0;
673
674 gcry_md_open(&md, GCRY_MD_SHA1, 0);
675 break;
676
677 case DNSSEC_DIGEST_SHA256:
678
679 if (ds->ds.digest_size != 32)
680 return 0;
681
682 gcry_md_open(&md, GCRY_MD_SHA256, 0);
683 break;
684
685 default:
686 assert_not_reached("Unknown digest");
687 }
688
689 if (!md)
690 return -EIO;
691
692 r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name));
693 if (r < 0)
694 goto finish;
695
696 gcry_md_write(md, owner_name, r);
697 md_add_uint16(md, dnskey->dnskey.flags);
698 md_add_uint8(md, dnskey->dnskey.protocol);
699 md_add_uint8(md, dnskey->dnskey.algorithm);
700 gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size);
701
702 result = gcry_md_read(md, 0);
703 if (!result) {
704 r = -EIO;
705 goto finish;
706 }
707
708 r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0;
709
710 finish:
711 gcry_md_close(md);
712 return r;
713 }
714
715 static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
716 [DNSSEC_NO] = "no",
717 [DNSSEC_TRUST] = "trust",
718 [DNSSEC_YES] = "yes",
719 };
720 DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);