Source file dnssec.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
open Dns

let src = Logs.Src.create "dnssec" ~doc:"DNS Security"
module Log = (val Logs.src_log src : Logs.LOG)

let ( let* ) = Result.bind

module KM = Map.Make(struct type t = Rr_map.k let compare = Rr_map.comparek end)

let pp_km_name_rr_map ppf rrs =
  List.iter (fun (name, (rr_map, _)) ->
      Fmt.(list ~sep:(any "@.") string) ppf
        (List.map (Rr_map.text_b name) (Rr_map.bindings rr_map)))
    (Domain_name.Map.bindings rrs)

let guard a e = if a then Ok () else Error e

let root_ds =
  (* <KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
  <KeyTag>20326</KeyTag>
  <Algorithm>8</Algorithm>
  <DigestType>2</DigestType>
  <Digest>
  E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
  </Digest>
  <PublicKey>
AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTOiW1vkIbzxeF3+/4RgWOq7HrxRixHlFlExOLAJr5emLvN7SWXgnLh4+B5xQlNVz8Og8kvArMtNROxVQuCaSnIDdD5LKyWbRd2n9WGe2R8PzgCmr3EgVLrjyBxWezF0jLHwVN8efS3rCj/EWgvIWgb9tarpVUDK/b58Da+sqqls3eNbuv7pr+eoZG+SrDK6nWeL3c6H5Apxz7LjVc1uTIdsIXxuOLYA4/ilBmSVIzuDWfdRUfhHdY6+cn8HFRm+2hM8AnXGXws9555KrUB5qihylGa8subX2Nn6UwNR1AkUTV74bU=
  </PublicKey>
  <Flags>257</Flags>
  </KeyDigest>
  <KeyDigest id="Kmyv6jo" validFrom="2024-07-18T00:00:00+00:00">
  <KeyTag>38696</KeyTag>
  <Algorithm>8</Algorithm>
  <DigestType>2</DigestType>
  <Digest>
  683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16
  </Digest>
  <PublicKey>
  AwEAAa96jeuknZlaeSrvyAJj6ZHv28hhOKkx3rLGXVaC6rXTsDc449/cidltpkyGwCJNnOAlFNKF2jBosZBU5eeHspaQWOmOElZsjICMQMC3aeHbGiShvZsx4wMYSjH8e7Vrhbu6irwCzVBApESjbUdpWWmEnhathWu1jo+siFUiRAAxm9qyJNg/wOZqqzL/dL/q8PkcRU5oUKEpUge71M3ej2/7CPqpdVwuMoTvoB+ZOT4YeGyxMvHmbrxlFzGOHOijtzN+u1TQNatX2XBuzZNQ1K+s2CXkPIZo7s6JgZyvaBevYtxPvYLw4z9mR7K2vaF18UYH9Z9GNUUeayffKC73PYc=
  </PublicKey>
  <Flags>257</Flags>
  </KeyDigest>
  *)
  let ds2017 = {
    Ds.key_tag = 20326 ;
    algorithm = Dnskey.RSA_SHA256 ;
    digest_type = SHA256 ;
    digest = Ohex.decode "E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D" ;
  }
  and ds2024 = {
    Ds.key_tag = 38696 ;
    algorithm = Dnskey.RSA_SHA256 ;
    digest_type = SHA256 ;
    digest = Ohex.decode "683D2D0ACB8C9B712A1948B27F741219298D0A450D612C483AF444A4C0FB2B16" ;
  }
  in
  Rr_map.Ds_set.(add ds2024 (singleton ds2017))

type pub = [
  | `P256 of Mirage_crypto_ec.P256.Dsa.pub
  | `P384 of Mirage_crypto_ec.P384.Dsa.pub
  | `ED25519 of Mirage_crypto_ec.Ed25519.pub
  | `RSA of Mirage_crypto_pk.Rsa.pub
]

let pp_pub ppf = function
  | `P256 _ -> Fmt.string ppf "P256"
  | `P384 _ -> Fmt.string ppf "P384"
  | `ED25519 _ -> Fmt.string ppf "ED25519"
  | `RSA k -> Fmt.pf ppf "RSA %d bits" (Mirage_crypto_pk.Rsa.pub_bits k)

(* used by DS, RFC 4034 section 5.1.4 *)
let digest algorithm owner dnskey =
  let digest : type a. a Digestif.hash -> (string, _) result = fun h ->
    let res = Digestif.digest_string h (Dnskey.digest_prep owner dnskey) in
    Ok (Digestif.to_raw_string h res) in
  match algorithm with
  | Ds.SHA1 -> digest Digestif.SHA1
  | Ds.SHA256 -> digest Digestif.SHA256
  | Ds.SHA384 -> digest Digestif.SHA384
  | dt ->
    Error (`Extended (`Unsupported_Ds_digest,
                      Some (Fmt.str "DS %a: unkown digest type: %a"
                              Domain_name.pp owner Ds.pp_digest_type dt)))

let dnskey_to_pk req_dom { Dnskey.algorithm ; key ; _ } =
  let map_ec_err r =
    Result.map_error (fun e -> `Msg (Fmt.to_to_string Mirage_crypto_ec.pp_error e)) r
  in
  match algorithm with
  | Dnskey.RSA_SHA1 | Dnskey.RSASHA1_NSEC3_SHA1 | Dnskey.RSA_SHA256 | Dnskey.RSA_SHA512 ->
    (* described in RFC 3110 *)
    let* () = if String.length key > 0 then Ok () else Error (`Msg "key data too short") in
    let e_len = String.get_int8 key 0 in
    let data = String.sub key 1 (String.length key - 1) in
    let* () = if String.length key > (e_len + 1) then Ok () else Error (`Msg "key data too short") in
    let e = String.sub data 0 e_len
    and n = String.sub data e_len (String.length data - e_len) in
    let e = Mirage_crypto_pk.Z_extra.of_octets_be e
    and n = Mirage_crypto_pk.Z_extra.of_octets_be n
    in
    let* pub = Mirage_crypto_pk.Rsa.pub ~e ~n in
    Ok (`RSA pub)
  | Dnskey.P256_SHA256 ->
    let four = String.make 1 '\004' in
    let* pub = map_ec_err (Mirage_crypto_ec.P256.Dsa.pub_of_octets (four ^ key)) in
    Ok (`P256 pub)
  | Dnskey.P384_SHA384 ->
    let four = String.make 1 '\004' in
    let* pub = map_ec_err (Mirage_crypto_ec.P384.Dsa.pub_of_octets (four ^ key)) in
    Ok (`P384 pub)
  | Dnskey.ED25519 ->
    let* pub = map_ec_err (Mirage_crypto_ec.Ed25519.pub_of_octets key) in
    Ok (`ED25519 pub)
  | MD5 | SHA1 | SHA224 | SHA256 | SHA384 | SHA512 | Unknown _ ->
    Error (`Extended (`Unsupported_Dnskey_algorithm,
                      Some (Fmt.str "%a DNSKEY unsupported algorithm: %a"
                              Domain_name.pp req_dom Dnskey.pp_algorithm algorithm)))

let verify : type a . Ptime.t -> pub -> [`raw] Domain_name.t -> Rrsig.t ->
  a Rr_map.key -> a ->
  ([`raw] Domain_name.t * [`raw] Domain_name.t, [> `Msg of string | `Extended of Extended_error.t ]) result =
  fun now key name rrsig t v ->
  (* from RFC 4034 section 3.1.8.1 *)
  Log.debug (fun m -> m "verifying for %a (with %a / %a)" Domain_name.pp name
    pp_pub key
    Dnskey.pp_algorithm rrsig.Rrsig.algorithm);
  let* algorithm =
    match rrsig.Rrsig.algorithm with
    | Dnskey.RSA_SHA1 -> Ok `SHA1
    | Dnskey.RSASHA1_NSEC3_SHA1 -> Ok `SHA1
    | Dnskey.RSA_SHA256 -> Ok `SHA256
    | Dnskey.RSA_SHA512 -> Ok `SHA512
    | Dnskey.P256_SHA256 -> Ok `SHA256
    | Dnskey.P384_SHA384 -> Ok `SHA384
    | Dnskey.ED25519 -> Ok `SHA512
    | a ->
      let msg =
        Fmt.str "unsupported signature algorithm %a" Dnskey.pp_algorithm a
      in
      Error (`Extended (`Other, Some msg))
  in
  let digest data =
    match rrsig.Rrsig.algorithm with
    | Dnskey.RSA_SHA1 -> Digestif.SHA1.(digest_string data |> to_raw_string)
    | Dnskey.RSASHA1_NSEC3_SHA1 -> Digestif.SHA1.(digest_string data |> to_raw_string)
    | Dnskey.RSA_SHA256 -> Digestif.SHA256.(digest_string data |> to_raw_string)
    | Dnskey.RSA_SHA512 -> Digestif.SHA512.(digest_string data |> to_raw_string)
    | Dnskey.P256_SHA256 -> Digestif.SHA256.(digest_string data |> to_raw_string)
    | Dnskey.P384_SHA384 -> Digestif.SHA384.(digest_string data |> to_raw_string)
    | Dnskey.ED25519 -> Digestif.SHA512.(digest_string data |> to_raw_string)
    | _ -> assert false (* NOTE(dinosaure): prevent by [algorithm] and [let*]. *)
  in
  let* () =
    guard (Ptime.is_later ~than:now rrsig.Rrsig.signature_expiration)
      (`Extended (`Signature_expired, None))
  in
  let* () =
    guard (Ptime.is_later ~than:rrsig.Rrsig.signature_inception now)
      (`Extended (`Signature_not_yet_valid, None))
  in
  let* (used_name, data) = Rr_map.prep_for_sig name rrsig t v in
  let hashed () = digest data in
  let ok_if_true p =
    if p then
      Ok (used_name, rrsig.Rrsig.signer_name)
    else
      Error (`Msg "signature verification failed")
  in
  match key with
  | `P256 key ->
    let signature =
      String.sub rrsig.Rrsig.signature 0 32,
      String.sub rrsig.Rrsig.signature 32 (String.length rrsig.Rrsig.signature - 32) in
    ok_if_true (Mirage_crypto_ec.P256.Dsa.verify ~key signature (hashed ()))
  | `P384 key ->
    let signature =
      String.sub rrsig.Rrsig.signature 0 48,
      String.sub rrsig.Rrsig.signature 48 (String.length rrsig.Rrsig.signature - 48) in
    ok_if_true (Mirage_crypto_ec.P384.Dsa.verify ~key signature (hashed ()))
  | `ED25519 key ->
    let msg = data in
    ok_if_true (Mirage_crypto_ec.Ed25519.verify ~key rrsig.Rrsig.signature ~msg)
  | `RSA key ->
    let hashp = ( = ) algorithm
    and msg = `Message data
    and signature = rrsig.Rrsig.signature
    in
    ok_if_true (Mirage_crypto_pk.Rsa.PKCS1.verify ~hashp ~key ~signature msg)

let filter_ds_if_sha2_present ds_set =
  (* RFC 4509 - drop SHA1 DS if SHA2 DS are present *)
  if Rr_map.Ds_set.exists (fun ds ->
      match ds.Ds.digest_type with
      | Ds.SHA256 | Ds.SHA384 -> true | _ -> false)
      ds_set
  then
    Rr_map.Ds_set.filter
      (fun ds -> not (ds.Ds.digest_type = SHA1))
      ds_set
  else
    ds_set

let validate_ds zone dnskeys ds =
  let* used_dnskey =
    let key_signing_keys =
      Rr_map.Dnskey_set.filter (fun dnsk ->
          Dnskey.F.mem `Secure_entry_point dnsk.Dnskey.flags &&
          Dnskey.key_tag dnsk = ds.Ds.key_tag)
        dnskeys
    in
    if Rr_map.Dnskey_set.cardinal key_signing_keys = 1 then
      Ok (Rr_map.Dnskey_set.choose key_signing_keys)
    else
      Error (`Msg (string_of_int (Rr_map.Dnskey_set.cardinal key_signing_keys) ^ " key signing keys for " ^ string_of_int ds.key_tag))
  in
  let* dgst = digest ds.Ds.digest_type zone used_dnskey in
  if String.equal ds.Ds.digest dgst then begin
    Log.debug (fun m -> m "Found DNSKEY for DS for zone %a (key tag %u)"
                  Domain_name.pp zone ds.Ds.key_tag);
    Ok used_dnskey
  end else
    Error (`Msg "key signing key couldn't be validated")

let validate_rrsig_keys now dnskeys rrsigs requested_domain t v =
  Log.debug (fun m -> m "validating for %a typ %a"
                 Domain_name.pp requested_domain
                 Rr_map.ppk (K t));
  let keys_rrsigs =
    Rr_map.Dnskey_set.fold (fun key acc ->
        let key_tag = Dnskey.key_tag key in
        let matching =
          Rr_map.Rrsig_set.filter (fun rr -> rr.Rrsig.key_tag = key_tag) rrsigs
        in
        Rr_map.Rrsig_set.fold (fun rr acc -> (key, rr) :: acc) matching acc)
      dnskeys []
  in
  Log.debug (fun m -> m "found %d key-rrsig pairs" (List.length keys_rrsigs));
  let verify_signature (key, rrsig) =
    let* pkey = dnskey_to_pk requested_domain key in
    verify now pkey requested_domain rrsig t v
  in
  match List.partition Result.is_ok (List.map verify_signature keys_rrsigs) with
  | r :: _, _ -> r
  | [], e :: _ -> e
  | [], [] -> Error (`Msg "no key-rrsig pair found")

let find_soa auth =
  match
    Domain_name.Map.fold (fun k (rr_map, kms) acc ->
        match Rr_map.(find Soa rr_map) with
        | Some soa -> Some (Domain_name.raw k, soa, KM.find (K Soa) kms)
        | None -> acc)
      auth None
  with
  | None -> Error (`Msg "no SOA in authority")
  | Some (name, soa, used_name) ->
    if Domain_name.equal name used_name then
      Ok (name, soa)
    else
      Error (`Msg (Fmt.str "SOA owner %a differs from used name %a"
                     Domain_name.pp name Domain_name.pp used_name))

let is_name_in_chain ~soa_name ~name ~owner nsec =
  (* for the last NSEC entry, next_domain is zone itself (thus = soa_name) *)
  let next_owner = (snd nsec).Nsec.next_domain in
  Domain_name.(compare owner name < 0 &&
                  (compare name next_owner < 0 ||
                   compare soa_name next_owner = 0))

let name_in_chain ~soa_name ~name ~owner nsec =
  if is_name_in_chain ~soa_name ~name ~owner nsec then
    Ok ()
  else
    Error (`Msg (Fmt.str "name not in chain: owner %a, name %a, next owner %a (soa %a)"
                   Domain_name.pp owner
                   Domain_name.pp name
                   Domain_name.pp (snd nsec).Nsec.next_domain
                   Domain_name.pp soa_name))

let nsec_chain ~soa_name name auth =
  let matches =
    Domain_name.Map.filter (fun owner rr_map ->
        match Rr_map.find Nsec (fst rr_map) with
        | Some nsec ->
          Log.debug (fun m -> m "is domain name %a in chain %a (to %a)?"
                         Domain_name.pp name Domain_name.pp owner
                         Domain_name.pp (snd nsec).Nsec.next_domain);
          is_name_in_chain ~soa_name ~name ~owner nsec
        | None -> false)
      auth
  in
  if Domain_name.Map.cardinal matches = 1 then
    let owner, rrs = Domain_name.Map.choose matches in
    let nsec = Rr_map.get Nsec (fst rrs) in
    let used_name = KM.find (K Nsec) (snd rrs) in
    if Domain_name.equal used_name owner then
      Ok (owner, nsec)
    else
      Error (`Msg (Fmt.str "used_name %a is not owner %a in NSEC %a"
                     Domain_name.pp used_name Domain_name.pp owner
                     Nsec.pp (snd nsec)))
  else
    Error (`Msg (Fmt.str "couldn't find nsec chain record covering %a in %a"
                   Domain_name.pp name pp_km_name_rr_map auth))

let is_ent name ~owner nsec =
  Domain_name.is_subdomain ~domain:name ~subdomain:(snd nsec).Nsec.next_domain &&
    Domain_name.compare owner name < 0

let wildcard_non_existence ~soa_name name auth =
  Log.debug (fun m -> m "wildcard non-existence %a (soa %a)"
               Domain_name.pp name Domain_name.pp soa_name);
  (* for non-existing wildcard NSEC: its owner must be between
     <name> and <soa_name> *)
  let rec proof_wildcard_absence name =
    Log.debug (fun m -> m "proof_wildcards with %a" Domain_name.pp name);
    if Domain_name.equal soa_name name then
      Ok ()
    else
      match nsec_chain ~soa_name name auth with
      | Ok (owner, nsec) when is_ent name ~owner nsec -> Ok ()
      | _ ->
        let wc_name = Domain_name.(prepend_label_exn (drop_label_exn name) "*") in
        Log.debug (fun m -> m "proof_wildcard_absence %a, wc_name %a"
                     Domain_name.pp name
                     Domain_name.pp wc_name);
        if Domain_name.Map.exists (fun _owner (rr_map, kms) ->
              match Rr_map.find Nsec rr_map with
              | Some nsec ->
                let owner = KM.find (K Nsec) kms in
                is_name_in_chain ~soa_name ~name:wc_name ~owner nsec
              | None -> false)
            auth
        then
          proof_wildcard_absence (Domain_name.drop_label_exn wc_name)
        else
          Error (`Msg (Fmt.str "no denial of existence for %a found"
                         Domain_name.pp wc_name))
  in
  proof_wildcard_absence name

let nsec3_hash salt iterations name =
  let cs_name = Rr_map.canonical_encoded_name name in
  let rec more = function
    | 0 -> Digestif.SHA1.(digest_string (cs_name ^ salt) |> to_raw_string)
    | k -> Digestif.SHA1.(digest_string ((more (k - 1)) ^ salt) |> to_raw_string)
  in
  more iterations

let nsec3_hashed_name salt iterations ~soa_name name =
  let h = nsec3_hash salt iterations name in
  Domain_name.prepend_label_exn soa_name (Base32.encode h)

let nsec3_rrs auth =
  let nsec3_map =
    (* filter out any non-nsec3 rrs and those where label_count doesn't match *)
    Domain_name.Map.filter (fun name (rr_map, kms) ->
        Rr_map.exists (function
          | B (Nsec3, (_, nsec3)) ->
            begin match nsec3.Nsec3.flags with
              | Some `Opt_out | None -> true
              | Some `Unknown _ -> false
            end
          | _ -> false) rr_map &&
        Domain_name.equal name (KM.find (K Nsec3) kms))
      auth
  in
  if Domain_name.Map.is_empty nsec3_map then
    Error (`Msg "no NSEC3 resource record found")
  else begin
    Log.debug (fun m -> m "nsec3 non-existence %d" (Domain_name.Map.cardinal nsec3_map));
    let Nsec3.{ iterations ; salt ; _ } =
      let _, (rrs, _) = Domain_name.Map.choose nsec3_map in
      snd (Rr_map.get Nsec3 rrs)
    in
    if iterations > 150 then
      Error (`Msg "NSEC3 iterations greater than 150, ignoring")
    else
      Ok (nsec3_map, salt, iterations)
  end

let nsec3_closest_encloser nsec3_map salt iterations ~soa_name name =
  let rec find_it chop name =
    let hashed_name = nsec3_hashed_name ~soa_name salt iterations name in
    match Domain_name.Map.find hashed_name nsec3_map with
    | Some (rrs, _) -> Ok (chop, name, Rr_map.get Nsec3 rrs)
    | None ->
      let* parent = Domain_name.drop_label name in
      let chopped = Domain_name.get_label_exn name 0 in
      find_it chopped parent
  in
  let* (last_chop, closest_encloser, closest_encloser_nsec) = find_it "" name in
  Log.debug (fun m -> m "last chop %s closest encloser %a (hashed %a)"
                last_chop Domain_name.pp closest_encloser
                Domain_name.pp (nsec3_hashed_name ~soa_name salt iterations closest_encloser));
  (* 8.3: DNAME bit must not be set, and NS may only be set if SOA bit is set *)
  (* TODO DNAME *)
  let* () =
    let types = (snd closest_encloser_nsec).Nsec3.types in
    if Bit_map.mem (Rr_map.to_int Ns) types then
      if not (Bit_map.mem (Rr_map.to_int Soa) types) then
        Error (`Msg (Fmt.str "nsec3 with NS but not SOA %a %a"
                       Domain_name.pp closest_encloser
                       Nsec3.pp (snd closest_encloser_nsec)))
      else
        (* RFC 5155 8.9: presence of NS implies absence of DNAME *)
        Ok ()
    else if Bit_map.mem (*DNAME*)39 types then
      Error (`Msg (Fmt.str "nsec3 with DNAME %a %a"
                     Domain_name.pp closest_encloser
                     Nsec3.pp (snd closest_encloser_nsec)))
    else
      Ok ()
  in
  (* verify existence of nsec3 where owner < next_closer < next_owner_hashed *)
  let next_closer = Domain_name.prepend_label_exn closest_encloser last_chop in
  let next_closer_hashed = nsec3_hashed_name ~soa_name salt iterations next_closer in
  Ok (closest_encloser, next_closer, next_closer_hashed)

let nsec3_between nsec3_map ~soa_name hashed_name =
  Log.debug (fun m -> m "nsec3 between %a" Domain_name.pp hashed_name);
  let m =
    Domain_name.Map.filter (fun name (rrs, _) ->
        if Domain_name.compare name hashed_name < 0 then begin
          Log.debug (fun m -> m "(%a) yes %a" Domain_name.pp hashed_name
                        Domain_name.pp name);
          let _, nsec3 = Rr_map.get Nsec3 rrs in
          let hashed_next_owner =
            Domain_name.prepend_label_exn soa_name
              (Base32.encode nsec3.Nsec3.next_owner_hashed)
          in
          Log.debug (fun m -> m "(%a) comparing with %a: %d"
                        Domain_name.pp hashed_name
                        Domain_name.pp hashed_next_owner
                        (Domain_name.compare hashed_name hashed_next_owner));
          Domain_name.compare hashed_name hashed_next_owner < 0
        end else
          false)
      nsec3_map
  in
  if Domain_name.Map.cardinal m = 1 then
    Ok (Domain_name.Map.choose m)
  else begin
    Log.debug (fun m -> m "nsec3 between %a no" Domain_name.pp hashed_name);
    Error (`Msg (Fmt.str "no NSEC3 with owner < %a < next_owner_hashed"
                   Domain_name.pp hashed_name))
  end

let nsec3_non_existence name ~soa_name auth =
  Log.debug (fun m -> m "nsec3 non-existence %a (zone %a)"
                Domain_name.pp name Domain_name.pp soa_name);
  let* (nsec3_map, salt, iterations) = nsec3_rrs auth in
  let* (closest_encloser, _next_closer, hashed_next_closer) =
    nsec3_closest_encloser nsec3_map salt iterations ~soa_name name
  in
  let* (_, (rrs, _)) = nsec3_between nsec3_map ~soa_name hashed_next_closer in
  let nsec_next_closer = Rr_map.get Nsec3 rrs in
  let opt_out =
    match (snd nsec_next_closer).Nsec3.flags with
    | Some `Opt_out -> true
    | Some `Unknown _ | None -> false
  in
  Log.debug (fun m -> m "next_closer %a proved, opt out %B"
                Domain_name.pp hashed_next_closer opt_out);
  (* TODO 8.5 and 8.6!? *)
  if opt_out then
    Ok nsec_next_closer
  else
    (* verify existence of nsec3 where owner < wc < next_owner_hashed *)
    let wc = Domain_name.prepend_label_exn closest_encloser "*" in
    let hashed_wc = nsec3_hashed_name ~soa_name salt iterations wc in
    let* _ = nsec3_between nsec3_map ~soa_name hashed_wc in
    Ok nsec_next_closer

let nsec3_chain ~soa_name ~wc_name ~name auth =
  Log.debug (fun m -> m "nsec3 chain soa %a wc %a name %a"
                Domain_name.pp soa_name Domain_name.pp wc_name
                Domain_name.pp name);
  let closest_encloser = Domain_name.drop_label_exn wc_name in
  let next_closer =
    let lbl_idx = Domain_name.count_labels closest_encloser in
    let lbl = Domain_name.get_label_exn ~rev:true name lbl_idx in
    Domain_name.prepend_label_exn closest_encloser lbl
  in
  Log.debug (fun m -> m "next_closer %a" Domain_name.pp next_closer);
  let* (nsec3_map, salt, iterations) = nsec3_rrs auth in
  let hashed_next_closer =
    nsec3_hashed_name ~soa_name salt iterations next_closer
  in
  nsec3_between nsec3_map ~soa_name hashed_next_closer

let nsec_non_existence name ~soa_name auth =
  let* _ = nsec_chain ~soa_name name auth in
  wildcard_non_existence ~soa_name name auth

let no_domain name auth =
  (* no domain:
     - a SOA from a parent (zone), plus RRSIG
     - an NSEC for non-existing wildcard, plus rrsig
     - a NSEC <prev domain> .. <next-domain>, plus rrsig
     -> ensure requested_domain is between these domains *)
  let* (soa_name, soa) = find_soa auth in
  let* () =
    if Domain_name.is_subdomain ~subdomain:name ~domain:soa_name then
      Ok ()
    else
      Error (`Msg (Fmt.str "question %a is not subdomain of SOA %a"
                     Domain_name.pp name Domain_name.pp soa_name))
  in
  match
    nsec_non_existence name ~soa_name auth,
    nsec3_non_existence name ~soa_name auth
  with
  | Ok (), _ | _, Ok _ -> Ok (soa_name, soa)
  | Error _ as e, _ -> e

let nsec_no_data ~soa_name name k auth =
  match Domain_name.Map.find name auth with
  | Some (rr_map, kms) when Rr_map.mem Nsec rr_map ->
    let nsec = Rr_map.get Nsec rr_map
    and nsec_owner = KM.find (K Nsec) kms
    in
    let* () =
      if Domain_name.equal nsec_owner name then
        Ok ()
      else
        Error (`Msg (Fmt.str "nsec owner %a is not name %a"
                       Domain_name.pp nsec_owner
                       Domain_name.pp name))
    in
    if Bit_map.mem (Rr_map.to_int k) (snd nsec).Nsec.types then
      Error (`Msg (Fmt.str "nsec claims type %a to be present" Rr_map.ppk (K k)))
    else if Bit_map.mem (Rr_map.to_int Cname) (snd nsec).Nsec.types then
      Error (`Msg (Fmt.str "nsec claims CNAME to be present"))
    else
      Ok ()
  | _ ->
    (* nsec in chain ++ wildcard nsec *)
    let* _ = nsec_chain ~soa_name name auth in
    let rec find_wc name =
      if Domain_name.is_subdomain ~domain:soa_name ~subdomain:name then
        let wc_name = Domain_name.prepend_label_exn name "*" in
        Log.debug (fun m -> m "looking for %a" Domain_name.pp wc_name);
        match Domain_name.Map.find wc_name auth with
        | Some (rr_map, kms) when Rr_map.mem Nsec rr_map ->
          let nsec = Rr_map.get Nsec rr_map
          and nsec_owner = KM.find (K Nsec) kms
          in
          Ok (wc_name, nsec, nsec_owner)
        | _ ->
          let* name = Domain_name.drop_label name in
          find_wc name
      else
        Error (`Msg "no wildcard nsec found")
    in
    match find_wc name with
    | Ok (wc_name, wc_nsec, wc_nsec_owner) ->
      let* () =
        if Domain_name.equal wc_nsec_owner wc_name then
          Ok ()
        else
          Error (`Msg (Fmt.str "bad wildcard nsec, wc_name %a nsec_owner %a"
                         Domain_name.pp wc_name Domain_name.pp wc_nsec_owner))
      in
      if Bit_map.mem (Rr_map.to_int k) (snd wc_nsec).Nsec.types then
        Error (`Msg (Fmt.str "nsec claims type %a to be present" Rr_map.ppk (K k)))
      else
        Ok ()
    | Error _ ->
      wildcard_non_existence ~soa_name name auth

let nsec3_no_data ~soa_name name k auth =
  Log.debug (fun m -> m "nsec3 no data %a (zone %a)"
                Domain_name.pp name Domain_name.pp soa_name);
  let* (nsec3_map, salt, iterations) = nsec3_rrs auth in
  let hashed_name = nsec3_hashed_name ~soa_name salt iterations name in
  match Domain_name.Map.find hashed_name nsec3_map with
  | Some (rr_map, _) ->
    let _, nsec3 = Rr_map.get Nsec3 rr_map in
    if Bit_map.mem (Rr_map.to_int k) nsec3.Nsec3.types then
      Error (`Msg (Fmt.str "nsec3 claims type %a to be present" Rr_map.ppk (K k)))
    else if Bit_map.mem (Rr_map.to_int Cname) nsec3.Nsec3.types then
      Error (`Msg (Fmt.str "nsec3 claims type Cname to be present"))
    else
      Ok ()
  | None ->
    let* (_closest_encloser, _next_closer, hashed_next_closer) =
      nsec3_closest_encloser nsec3_map salt iterations ~soa_name name
    in
    let* (_, (rrs, _)) = nsec3_between nsec3_map ~soa_name hashed_next_closer in
    let nsec_next_closer = Rr_map.get Nsec3 rrs in
    let opt_out =
      match (snd nsec_next_closer).Nsec3.flags with
      | Some `Opt_out -> true
      | Some `Unknown _ | None -> false
    in
    Log.debug (fun m -> m "next_closer %a proved, opt out %B"
                  Domain_name.pp hashed_next_closer opt_out);
    if opt_out then
      Ok ()
    else
      Error (`Msg "no NSEC3, and next_closer has no opt-out")

let no_data name k auth =
  (* no data:
     - SOA + RRSIG
     - (NSEC for name (and not for type = k) OR wildcard NSEC) + RRSIG
  *)
  let* (soa_name, soa) = find_soa auth in
  let* () =
    if Domain_name.is_subdomain ~subdomain:name ~domain:soa_name then
      Ok ()
    else
      Error (`Msg (Fmt.str "name %a is not a subdomain of soa %a"
                     Domain_name.pp name Domain_name.pp soa_name))
  in
  match
    nsec_no_data ~soa_name name k auth,
    nsec3_no_data ~soa_name name k auth
  with
  | Ok (), _ | _, Ok () -> Ok (soa_name, soa)
  | Error _ as e, _ -> e

let has_delegation name_rr_map name =
  let rrs =
    Domain_name.Map.filter (fun owner_name rrs ->
        Domain_name.is_subdomain ~domain:owner_name ~subdomain:name &&
        Rr_map.mem Ns rrs) name_rr_map
  in
  Log.debug (fun m -> m "has_delegation with %d in %a"
                 (Domain_name.Map.cardinal rrs)
                 Name_rr_map.pp name_rr_map);
  if Domain_name.Map.cardinal rrs = 1 then
    Some (Domain_name.Map.choose rrs)
  else
    None

let validate_delegation signer_name auth (zname, rrs) =
  let _, ns = Rr_map.get Ns rrs in
  match Domain_name.Map.find zname auth with
  | Some (rrs, kms) when Rr_map.mem Ds rrs ->
    let ds = snd (Rr_map.get Ds rrs) in
    let used_name = KM.find (K Ds) kms in
    if not (Domain_name.equal used_name zname) then
      Error (`Msg (Fmt.str "owner %a of DS %a does not match used name %a"
                     Domain_name.pp zname
                     Fmt.(list ~sep:(any ", ") Ds.pp)
                     (Rr_map.Ds_set.elements ds)
                     Domain_name.pp used_name))
    else
      Ok (`Signed_delegation (zname, ns, ds))
  | Some (rrs, kms) when Rr_map.mem Nsec rrs ->
    let nsec = snd (Rr_map.get Nsec rrs) in
    let used_name = KM.find (K Nsec) kms in
    if not (Domain_name.equal used_name zname) then
      Error (`Msg (Fmt.str "owner %a of Nsec %a does not match used name %a"
                     Domain_name.pp zname
                     Nsec.pp nsec
                     Domain_name.pp used_name))
    else if
      (not (Bit_map.mem (Rr_map.to_int Ds) nsec.Nsec.types)) &&
      Bit_map.mem (Rr_map.to_int Ns) nsec.Nsec.types
    then
      Ok (`Unsigned_delegation (zname, ns))
    else
      Error (`Msg (Fmt.str "NSEC present for %a (%a), but either has DS or no NS bits"
                     Domain_name.pp zname Nsec.pp nsec))
  | _ ->
    let soa_name = Option.value ~default:Domain_name.root signer_name in
    let* nsec3 = nsec3_non_existence zname ~soa_name auth in
    if (snd nsec3).Nsec3.flags = Some `Opt_out then
      Ok (`Unsigned_delegation (zname, ns))
    else
      Error (`Msg (Fmt.str "NSEC3 for closest encloser %a present %a, but not opt-out"
                     Domain_name.pp zname
                     Nsec3.pp (snd nsec3)))

let maybe_validate_wildcard_answer signer_name auth kms name k =
  let used_name = KM.find (K k) kms in
  if Domain_name.equal used_name name then
    Ok ()
  else begin
    (* RFC 4035 5.3.4 - verify in authority the wildcard-expanded
       positive reply (no direct match) *)
    (* RFC 5155 8.8 - there's a candidate closest encloser for qname
       (the used_name without "*") - need to verify existence of a nsec3
       covering next_closer name to qname *)
    (match signer_name with
     | None -> Log.warn (fun m -> m "no signer name provided")
     | Some _ -> ());
    let soa_name = Option.value ~default:Domain_name.root signer_name in
    match
      nsec_chain ~soa_name name auth,
      nsec3_chain ~soa_name ~wc_name:used_name ~name auth
    with
    | Ok _, _ | _, Ok _ -> Ok ()
    | Error _ as e, _ -> e
  end

let validate_answer :
  type a. ?signer_name:[`raw] Domain_name.t ->
  [`raw] Domain_name.t -> a Rr_map.rr ->
  (Rr_map.t * [`raw] Domain_name.t KM.t) Domain_name.Map.t ->
  (Rr_map.t * [`raw] Domain_name.t KM.t) Domain_name.Map.t ->
  Name_rr_map.t ->
  (a,
   [> `Cname of [`raw] Domain_name.t
   | `Unsigned_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t
   | `Signed_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t * Rr_map.Ds_set.t
   | `No_data of [`raw] Domain_name.t * Soa.t
   | `Msg of string ]) result =
  fun ?signer_name name k answer auth raw_auth ->
  Log.debug (fun m -> m "validating %a (%a)"
                 Domain_name.pp name Rr_map.ppk (K k));
  match Domain_name.Map.find name answer with
  | None ->
    (* left are two options: no data OR delegation *)
    Option.fold
      ~none:(
        let* (soa_name, soa) = no_data name k auth in
        Log.debug (fun m -> m "validated no data");
        Error (`No_data (soa_name, soa)))
      ~some:(fun x ->
          let* r =  validate_delegation signer_name auth x in
          Error r)
      (has_delegation raw_auth name)
  | Some (rr_map, kms) ->
    match Rr_map.find k rr_map with
    | Some rrs ->
      let* () = maybe_validate_wildcard_answer signer_name auth kms name k in
      Ok rrs
    | None ->
      match Rr_map.find Cname rr_map with
      | None ->
        let* (soa_name, soa) = no_data name k auth in
        Log.debug (fun m -> m "validated no data");
        Error (`No_data (soa_name, soa))
      | Some rr ->
        let* () = maybe_validate_wildcard_answer signer_name auth kms name Cname in
        Log.info (fun m -> m "verified CNAME to %a" Domain_name.pp (snd rr));
        Error (`Cname (snd rr))

type err = [
  | `Cname of [ `raw ] Domain_name.t
  | `Unsigned_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t
  | `Signed_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t * Rr_map.Ds_set.t
  | `No_data of [ `raw ] Domain_name.t * Dns.Soa.t
  | `No_domain of [ `raw ] Domain_name.t * Dns.Soa.t
  | `Msg of string
]

let pp_err ppf = function
  | `Cname alias -> Fmt.pf ppf "cname %a" Domain_name.pp alias
  | `Unsigned_delegation (owner, ns) ->
    Fmt.pf ppf "unsigned delegation of %a to %a"
      Domain_name.pp owner
      Fmt.(list ~sep:(any ", ") Domain_name.pp)
      (Domain_name.Host_set.elements ns)
  | `Signed_delegation (owner, ns, ds) ->
    Fmt.pf ppf "signed delegation of %a to %a (DS %a)"
      Domain_name.pp owner
      Fmt.(list ~sep:(any ", ") Domain_name.pp)
      (Domain_name.Host_set.elements ns)
      Fmt.(list ~sep:(any ", ") Ds.pp)
      (Rr_map.Ds_set.elements ds)
  | `No_data (name, soa) ->
    Fmt.pf ppf "no data %a %a" Domain_name.pp name Soa.pp soa
  | `No_domain (name, soa) ->
    Fmt.pf ppf "no domain %a %a" Domain_name.pp name Soa.pp soa
  | `Msg m -> Fmt.pf ppf "error %s" m

let fold_option a b =
  match a, b with
  | None, None -> None
  | Some a, None -> Some a
  | None, Some b -> Some b
  | Some a, Some b ->
    if not (Domain_name.equal a b) then
      Log.warn (fun m -> m "different signer names %a and %a"
                   Domain_name.pp a Domain_name.pp b);
    Some a

(* to avoid missing a signature check, and also checking the signature
   multiple times, first verify all signatures in the map *)
let check_signatures now dnskeys map =
  (* the result is again a map, but with an additional nesting to track the
     used name (wildcard signatures) *)
  Domain_name.Map.fold (fun name rr_map (signer_name, acc) ->
      let rrsig_ttl, rrsigs =
        Option.value ~default:(0l, Rr_map.Rrsig_set.empty)
          (Rr_map.find Rrsig rr_map)
      in
      let signer_name, rrs = Rr_map.fold (fun b ((signer_name, (rrs, names)) as acc) ->
          match b with
          | B (Rr_map.Rrsig, _) -> acc
          | B (k, v) ->
            let int = Rr_map.to_int k in
            let rrsigs =
              Rr_map.Rrsig_set.filter
                (fun rrsig -> rrsig.Rrsig.type_covered = int)
                rrsigs
            in
            if Rr_map.Rrsig_set.is_empty rrsigs then
              Log.warn (fun m -> m "couldn't find RRSIG for %a %a"
                           Domain_name.pp name Rr_map.pp_b b);
            match validate_rrsig_keys now dnskeys rrsigs name k v with
            | Ok (used_name, signer_name') ->
              let signer = fold_option signer_name (Some signer_name') in
              let rrs = Rr_map.add k v rrs in
              let rrs =
                Rr_map.update Rrsig (function
                    | None -> Some (rrsig_ttl, rrsigs)
                    | Some (_, s) ->
                      Some (rrsig_ttl, Rr_map.Rrsig_set.union s rrsigs))
                  rrs
              in
              signer, (rrs, KM.add (Rr_map.K k) used_name names)
            | Error `Msg msg ->
              Log.warn (fun m -> m "RRSIG verification for %a %a failed: %s"
                           Domain_name.pp name Rr_map.pp_b b msg);
              acc
            | Error `Extended e ->
              Log.warn (fun m -> m "RRSIG verification for %a %a failed: %a"
                           Domain_name.pp name Rr_map.pp_b b
                           Extended_error.pp e);
              acc)
          rr_map (signer_name, (Rr_map.empty, KM.empty))
      in
      signer_name,
      if Rr_map.is_empty (fst rrs) then
        acc
      else
        Domain_name.Map.add name rrs acc)
    map (None, Domain_name.Map.empty)

let verify_reply : type a. ?fuel:int -> ?follow_cname:bool ->
  Ptime.t -> Rr_map.Dnskey_set.t -> [`raw] Domain_name.t -> a Rr_map.rr ->
  Packet.reply ->
  (a,
   [> `Cname of [ `raw ] Domain_name.t
   | `Unsigned_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t
   | `Signed_delegation of [`raw] Domain_name.t * Domain_name.Host_set.t * Rr_map.Ds_set.t
   | `No_data of [ `raw ] Domain_name.t * Dns.Soa.t
   | `No_domain of [ `raw ] Domain_name.t * Dns.Soa.t
   | `Msg of string ]) result =
  fun ?(fuel = 20) ?(follow_cname = true) now dnskeys name k reply ->
  Log.debug (fun m -> m "verifying %a (%a)"
                Domain_name.pp name Rr_map.ppk (K k));
  match reply with
  | `Answer (answer, authority) ->
    let signer_name, signed_answer = check_signatures now dnskeys answer
    and signer_name2, signed_authority = check_signatures now dnskeys authority
    in
    let signer_name = fold_option signer_name signer_name2 in
    begin
      let rec more ~fuel name =
        if fuel = 0 then
          Error (`Msg "too many CNAME redirections")
        else
          match validate_answer ?signer_name name k signed_answer signed_authority authority with
          | Error `Cname other when follow_cname ->
            more ~fuel:(fuel - 1) other
          | r -> r
      in
      more ~fuel name
    end
  | `Rcode_error (NXDomain, Query, Some (answer, authority)) ->
    let signer_name, _answer = check_signatures now dnskeys answer
    and signer_name2, authority = check_signatures now dnskeys authority
    in
    let _signer_name = fold_option signer_name signer_name2 in
    let* (soa_name, soa) = no_domain name authority in
    Error (`No_domain (soa_name, soa))
  | r ->
    Error (`Msg (Fmt.str "unexpected reply: %a" Packet.pp_reply r))

let remove_km map =
  Domain_name.Map.fold
    (fun name (rrs, _) acc -> Domain_name.Map.add name rrs acc)
    map Domain_name.Map.empty

let verify_packet now dnskeys packet =
  let qname = fst packet.Packet.question in
  let* data =
    match packet.Packet.data with
    | `Answer (answer, authority) ->
      let signer_name, signed_answer = check_signatures now dnskeys answer
      and signer_name2, signed_auth = check_signatures now dnskeys authority
      in
      let signer_name = fold_option signer_name signer_name2 in
      let ans = remove_km signed_answer and auth = remove_km signed_auth in
      begin match Domain_name.Map.find qname signed_answer with
        | None ->
          Option.fold
            ~none:(
              match snd packet.question with
              | `K K k ->
                let* _ = no_data qname k signed_auth in
                Ok (`Answer (ans, auth))
              | _ -> Error (`Msg "qtype is not a valid typ"))
            ~some:(fun (zname, rrs) ->
                (* add (unsigned!) rrs back into auth *)
                let* _ = validate_delegation signer_name signed_auth (zname, rrs) in
                let ns_rr = Rr_map.get Ns rrs in
                let auth =
                  Domain_name.Map.update zname
                    (function
                      | None -> Some Rr_map.(singleton Ns ns_rr)
                      | Some rrs -> Some Rr_map.(add Ns ns_rr rrs))
                    auth
                in
                Ok (`Answer (ans, auth))
                )
            (has_delegation authority qname)
        | Some (rrs, kms) ->
          begin match snd packet.question with
            | `K K k ->
              begin match Rr_map.find k rrs, Rr_map.find Cname rrs with
                | None, None ->
                  let* _ = no_data qname k signed_auth in
                  Ok (`Answer (ans, auth))
                | Some _, _ ->
                  let* _ = maybe_validate_wildcard_answer signer_name signed_auth kms qname k in
                  Ok (`Answer (ans, auth))
                | _, Some _ ->
                  let* _ = maybe_validate_wildcard_answer signer_name signed_auth kms qname Cname in
                  Ok (`Answer (ans, auth))
              end
            | _ -> Error (`Msg "qtype is not a valid typ")
          end
      end
    | `Rcode_error (Rcode.NXDomain, Query, Some (answer, authority)) ->
      let signer_name, signed_answer = check_signatures now dnskeys answer
      and signer_name2, signed_authority = check_signatures now dnskeys authority
      in
      let _signer_name = fold_option signer_name signer_name2 in
      let* _ = no_domain qname signed_authority in
      let answer = remove_km signed_answer and auth = remove_km signed_authority in
      Ok (`Rcode_error (Rcode.NXDomain, Opcode.Query, Some (answer, auth)))
    | `Rcode_error (rc, op, Some (ans, aut)) ->
      let signer_name, signed_answer = check_signatures now dnskeys ans
      and signer_name2, signed_authority = check_signatures now dnskeys aut
      in
      let _signer_name = fold_option signer_name signer_name2 in
      let answer = remove_km signed_answer and auth = remove_km signed_authority in
      Ok (`Rcode_error (rc, op, Some (answer, auth)))
    | `Rcode_error (rc, op, None) -> Ok (`Rcode_error (rc, op, None))
    | x -> Ok x
  in
  Ok (Packet.create ~additional:packet.additional ?edns:packet.edns
        ?tsig:packet.tsig packet.header packet.question data)