Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
dns_tsig.ml1 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(* (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 :> Digestif.hash') and data = Tsig.encode_raw raw_name tsig in let module H = (val (Digestif.module_of_hash' h)) in H.hmac_string ~key (buf ^ data) |> H.to_raw_string 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 str = let buf = Bytes.of_string str in Bytes.set_uint16_be buf 10 (succ (Bytes.get_uint16_be buf 10)) ; let tsig = Tsig.encode_full name tsig in match max_size with | Some x when x - Bytes.length buf < String.length tsig -> None | _ -> Some (Bytes.unsafe_to_string buf ^ tsig) let mac_to_prep = function | None -> "" | Some mac -> let l = Bytes.create 2 in Bytes.set_uint16_be l 0 (String.length mac) ; Bytes.unsafe_to_string l ^ mac let sign ?mac ?max_size name tsig ~key p str = match Base64.decode key.Dnskey.key with | Error _ -> None | Ok key -> let prep = mac_to_prep mac in let mac = compute_tsig name tsig ~key (prep ^ str) 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 str 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 = String.sub new_buf 0 off in let mac = compute_tsig name tsig ~key (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 Ohex.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 key.Dnskey.key) in let ac = String.get_int16_be tbs 10 in let tbs = Bytes.of_string tbs in Bytes.set_int16_be tbs 10 (pred ac) ; let tbs = Bytes.unsafe_to_string tbs in let prep = mac_to_prep mac in let computed = compute_tsig name tsig ~key:priv (prep ^ tbs) in let mac = tsig.Tsig.mac in let* () = guard (String.length mac = String.length computed) (`Bad_truncation (name, tsig)) in let* () = guard (String.equal computed mac) (* Eqaf? *) (`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 : string option = 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 String.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 String.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 String.empty) 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 (String.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))