package dns-tsig

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file dns_tsig.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
(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *)

open Dns

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

let algorithm_to_nc = function
  | Tsig.SHA1 -> `SHA1
  | Tsig.SHA224 -> `SHA224
  | Tsig.SHA256 -> `SHA256
  | Tsig.SHA384 -> `SHA384
  | Tsig.SHA512 -> `SHA512

let compute_tsig name tsig ~key buf =
  let raw_name = Domain_name.raw name in
  let h = algorithm_to_nc tsig.Tsig.algorithm
  and data = Tsig.encode_raw raw_name tsig
  in
  Mirage_crypto.Hash.mac h ~key (Cstruct.append buf data)

let ( let* ) = Result.bind

let guard p err = if p then Ok () else Error err

(* TODO: should name compression be done?  atm it's convenient not to do it *)
let add_tsig ?max_size name tsig buf =
  Cstruct.BE.set_uint16 buf 10 (succ (Cstruct.BE.get_uint16 buf 10)) ;
  let tsig = Tsig.encode_full name tsig in
  match max_size with
  | Some x when x - Cstruct.length buf < Cstruct.length tsig -> None
  | _ -> Some (Cstruct.(append buf tsig))

let mac_to_prep = function
  | None -> Cstruct.create 0
  | Some mac ->
    let l = Cstruct.create 2 in
    Cstruct.BE.set_uint16 l 0 (Cstruct.length mac) ;
    Cstruct.append l mac

let sign ?mac ?max_size name tsig ~key p buf =
  match Base64.decode (Cstruct.to_string key.Dnskey.key) with
  | Error _ -> None
  | Ok key ->
    let key = Cstruct.of_string key in
    let prep = mac_to_prep mac in
    let mac = compute_tsig name tsig ~key (Cstruct.append prep buf) in
    let tsig = Tsig.with_mac tsig mac in
    (* RFC2845 Sec 3.1: if TSIG leads to truncation, alter message:
       - header stays (truncated = true)!
       - only question is preserved
       - _one_ additional, the TSIG itself *)
    match add_tsig ?max_size name tsig buf with
    | Some out -> Some (out, mac)
    | None ->
      match p.Packet.data with
      | #Packet.request ->
        Log.err (fun m -> m "dns_tsig sign: truncated, is a request, not doing anything") ;
        None
      | #Packet.reply as r ->
        Log.err (fun m -> m "dns_tsig sign: truncated reply %a, sending tsig error"
                    Packet.pp_reply r) ;
        let header =
          fst p.header, Packet.Flags.add `Truncation (snd p.header)
        in
        let rc = Packet.rcode_data r
        and op = Packet.opcode_data r
        in
        let p' = Packet.create header p.question (`Rcode_error (rc, op, None)) in
        let new_buf, off = Packet.encode `Udp p' in
        let tbs = Cstruct.sub new_buf 0 off in
        let mac = compute_tsig name tsig ~key (Cstruct.append prep tbs) in
        let tsig = Tsig.with_mac tsig mac in
        match add_tsig name tsig new_buf with
        | None ->
          Log.err (fun m -> m "dns_tsig sign failed query %a with tsig %a too big (max_size %a) truncated packet %a:@.%a"
                    Packet.pp p Tsig.pp tsig Packet.pp p'
                    Fmt.(option ~none:(any "none") int) max_size
                    Cstruct.hexdump_pp new_buf) ;
          None
        | Some out -> Some (out, mac)

let verify_raw ?mac now name ~key tsig tbs =
  let name = Domain_name.raw name in
  let* priv =
    Result.map_error
      (fun _ -> `Bad_key (name, tsig))
      (Base64.decode (Cstruct.to_string key.Dnskey.key))
  in
  let ac = Cstruct.BE.get_uint16 tbs 10 in
  Cstruct.BE.set_uint16 tbs 10 (pred ac) ;
  let prep = mac_to_prep mac in
  let priv = Cstruct.of_string priv in
  let computed = compute_tsig name tsig ~key:priv (Cstruct.append prep tbs) in
  let mac = tsig.Tsig.mac in
  let* () = guard (Cstruct.length mac = Cstruct.length computed) (`Bad_truncation (name, tsig)) in
  let* () = guard (Cstruct.equal computed mac) (`Invalid_mac (name, tsig)) in
  let* () = guard (Tsig.valid_time now tsig) (`Bad_timestamp (name, tsig, key)) in
  let* tsig =
    Option.to_result ~none:(`Bad_timestamp (name, tsig, key))
      (Tsig.with_signed tsig now)
  in
  Ok (tsig, mac)

let verify ?mac now p name ?key tsig tbs =
  let raw_name = Domain_name.raw name in
  match
    let* key =
      Option.to_result ~none:(`Bad_key (raw_name, tsig)) key
    in
    let* tsig, mac = verify_raw ?mac now raw_name ~key tsig tbs in
    Ok (tsig, mac, key)
  with
  | Ok x -> Ok x
  | Error e ->
    Log.err (fun m -> m "error %a while verifying %a" Tsig_op.pp_e e Packet.pp p);
    let answer = match p.Packet.data with
      | #Packet.reply -> None
      | #Packet.request as r ->
        (* now we prepare a reply for the request! *)
        (* TODO not clear which flags to preserve *)
        let header = fst p.Packet.header, Packet.Flags.empty
        and opcode = Packet.opcode_data r
        in
        (* TODO: edns *)
        let answer = Packet.create header p.question (`Rcode_error (Rcode.NotAuth, opcode, None)) in
        let err, max_size = Packet.encode `Udp answer in
        let or_err f err = match f err with None -> Some err | Some x -> Some x in
        match e with
        | `Bad_key (name, tsig) ->
          let tsig = Tsig.with_error (Tsig.with_mac tsig Cstruct.empty) Rcode.BadKey in
          or_err (add_tsig ~max_size name tsig) err
        | `Invalid_mac (name, tsig) ->
          let tsig = Tsig.with_error (Tsig.with_mac tsig Cstruct.empty) Rcode.BadVersOrSig in
          or_err (add_tsig ~max_size name tsig) err
        | `Bad_truncation (name, tsig) ->
          let tsig = Tsig.with_error (Tsig.with_mac tsig (Cstruct.create 0)) Rcode.BadTrunc in
          or_err (add_tsig ~max_size name tsig) err
        | `Bad_timestamp (name, tsig, key) ->
          let tsig = Tsig.with_error tsig Rcode.BadTime in
          match Tsig.with_other tsig (Some now) with
          | None -> Some err
          | Some tsig ->
            match sign ~max_size ~mac:tsig.Tsig.mac name tsig ~key answer err with
            | None -> Some err
            | Some (buf, _) -> Some buf
    in
    Error (e, answer)

type s = [ `Key_algorithm of Dnskey.t | `Tsig_creation | `Sign ]

let pp_s ppf = function
  | `Key_algorithm key -> Fmt.pf ppf "algorithm %a not supported for tsig" Dnskey.pp key
  | `Tsig_creation -> Fmt.pf ppf "failed to create tsig"
  | `Sign -> Fmt.pf ppf "failed to sign"

let encode_and_sign ?(proto = `Udp) ?mac p now key keyname =
  let b, _ = Packet.encode proto p in
  match Tsig.dnskey_to_tsig_algo key with
  | Error _ -> Error (`Key_algorithm key)
  | Ok algorithm -> match Tsig.tsig ~algorithm ~signed:now () with
    | None -> Error `Tsig_creation
    | Some tsig -> match sign ?mac (Domain_name.raw keyname) ~key tsig p b with
      | None -> Error `Sign
      | Some r -> Ok r

type e = [
  | `Decode of Packet.err
  | `Unsigned of Packet.t
  | `Crypto of Tsig_op.e
  | `Invalid_key of [ `raw ] Domain_name.t * [ `raw ] Domain_name.t
]

let pp_e ppf = function
  | `Decode err -> Fmt.pf ppf "decode %a" Packet.pp_err err
  | `Unsigned res -> Fmt.pf ppf "unsigned %a" Packet.pp res
  | `Crypto c -> Fmt.pf ppf "crypto %a" Tsig_op.pp_e c
  | `Invalid_key (key, used) ->
    Fmt.pf ppf "invalid key, expected %a, but %a was used"
      Domain_name.pp key Domain_name.pp used

let decode_and_verify now key keyname ?mac buf =
  let raw_keyname = Domain_name.raw keyname in
  match Packet.decode buf with
  | Error e -> Error (`Decode e)
  | Ok ({ Packet.tsig = None ; _ } as res) -> Error (`Unsigned res)
  | Ok ({ Packet.tsig = Some (name, tsig, tsig_off) ; _ } as res) when Domain_name.equal keyname name ->
    begin match verify_raw ?mac now raw_keyname ~key tsig (Cstruct.sub buf 0 tsig_off) with
      | Ok (_, mac) -> Ok (res, tsig, mac)
      | Error e -> Error (`Crypto e)
    end
  | Ok { Packet.tsig = Some (name, _, _) ; _ } -> Error (`Invalid_key (raw_keyname, name))
OCaml

Innovation. Community. Security.