package otr

  1. Overview
  2. Docs

Source file otr_parser.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
open Cstruct
open Rresult
open Astring

open Otr_packet

type error =
  | Unknown of string
  | Underflow
  | LeadingZero

let maybe a = if a = "" then None else Some a

(* parse query string *)
let parse_query str =
  let parse_v acc = function
    | '2' -> `V2 :: acc
    | '3' -> `V3 :: acc
    | _ -> acc
  in
  let parse idx =
    let _, left = String.span ~max:idx str in
    match String.cut ~sep:"?" left with
    | None -> ([], None)
    | Some (vs, post) ->
      let versions = String.fold_left parse_v [] vs in
      (List.rev versions, maybe post)
  in
  match String.get str 0, String.get str 1 with
  | '?', 'v' -> Ok (parse 2)
  | 'v', _ -> Ok (parse 1)
  | _ -> Error (Unknown "no usable version found")

let mark_match sep data =
  match String.cut ~sep data with
  | Some (pre, post) -> Ok (maybe pre, post)
  | None -> Error (Unknown "parse failed")

type ret = [
  | `Data of Cstruct.t
  | `ParseError of string
  | `Error of string
  | `PlainTag of Otr_state.version list * string option
  | `Query of Otr_state.version list
  | `String of string
  | `Fragment_v2 of (int * int) * string
  | `Fragment_v3 of (int32 * int32) * (int * int) * string
]

let parse_data data =
  match String.cut ~sep:"." data with
  | None -> Error (Unknown "empty OTR message")
  | Some (data, rest) ->
    let b64data = Cstruct.of_string data in
    match Nocrypto.Base64.decode b64data with
    | None -> Error (Unknown "bad base64 data")
    | Some x -> Ok (x, maybe rest)

let parse_plain_tag data =
  let rec find_mark str acc =
    if String.length str < 8 then
      (List.rev acc, maybe str)
    else
      let tag, rest = String.span ~max:8 str in
      if tag = Otr_state.tag_v2 then
        find_mark rest (`V2 :: acc)
      else if tag = Otr_state.tag_v3 then
        find_mark rest (`V3 :: acc)
      else
        find_mark rest acc
  in
  find_mark data []

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

let parse_fragment data =
  match String.cuts ~sep:"," data with
  | k :: n :: piece :: rest ->
    let k = int_of_string k in
    let n = int_of_string n in
    guard
      (k > 0 && k <= 65535)
      (Unknown "k must be between 0 and 65535") >>= fun () ->
    guard
      (n > 0 && n <= 65535)
      (Unknown "n must be between 0 and 65535") >>= fun () ->
    guard
      (k <= n)
      (Unknown "k must be smaller or equal to n") >>= fun () ->
    guard
      (String.length piece > 0)
      (Unknown "fragment must be of non-zero size") >>= fun () ->
    guard
      (String.length (String.concat ~sep:"" rest) = 0)
      (Unknown "too many elements") >>= fun () ->
    Ok ((k, n), piece)
  | _ -> Error (Unknown "invalid fragment")

let parse_fragment_v3 data =
  match String.cut ~sep:"|" data with
  | Some (sender_instance, data) ->
    ( match String.cut ~sep:"," data with
      | Some (receiver_instance, data) ->
        let sender_instance = Scanf.sscanf sender_instance "%lx" (fun x -> x) in
        let receiver_instance = Scanf.sscanf receiver_instance "%lx" (fun x -> x) in
        parse_fragment data >>| fun (kn, piece) ->
        ((sender_instance, receiver_instance), kn, piece)
      | None -> Error (Unknown "invalid fragment (receiver_instance)"))
  | None -> Error (Unknown "invalid fragment (sender_instance)")

let classify_input bytes =
  let open Otr_state in
  match mark_match otr_v2_frag bytes with
  | Ok (pre, data) ->
    begin match parse_fragment data with
      | Ok data when pre = None -> `Fragment_v2 data
      | Ok _ -> `ParseError "Malformed v2 fragment (predata)"
      | Error _ -> `ParseError "Malformed v2 fragment"
    end
  | Error _ -> match mark_match otr_v3_frag bytes with
    | Ok (pre, data) ->
      begin match parse_fragment_v3 data with
        | Ok data when pre = None -> `Fragment_v3 data
        | Ok _ -> `ParseError "Malformed v3 fragment (predata)"
        | Error _ -> `ParseError "Malformed v3 fragment"
      end
    | Error _ -> match mark_match otr_mark bytes with
      | Ok (pre, data) ->
        begin match parse_data data with
          | Ok (data, post) when pre = None && post = None -> `Data data
          | Ok _ -> `ParseError "Malformed OTR data (pre/postdata)"
          | Error _ -> `ParseError "Malformed OTR data message"
        end
      | Error _ -> match mark_match otr_err_mark bytes with
        | Ok (pre, data) when pre = None -> `Error data
        | Ok _ -> `ParseError "Malformed Error received (predata)"
        | Error _ ->  match mark_match otr_prefix bytes with
          | Ok (pre, data) ->
            begin match parse_query data with
              | Ok (versions, _) when pre = None -> `Query versions
              | Ok _ -> `ParseError "Malformed OTR query (pre/postdata)"
              | Error _ -> `ParseError "Malformed OTR query"
            end
          | Error _ -> match mark_match tag_prefix bytes with
            | Ok (pre, data) ->
              begin match parse_plain_tag data with
                | (versions, None) -> `PlainTag (versions, pre)
                | _ -> `ParseError "Malformed Tag (postdata)"
              end
            | Error _ -> `String bytes


(* real OTR data parsing *)
let decode_data buf =
  guard (len buf >= 4) Underflow >>= fun () ->
  let size = BE.get_uint32 buf 0 in
  let intsize = Int32.to_int size in
  guard (len buf >= 4 + intsize) Underflow >>| fun () ->
  (sub buf 4 intsize, shift buf (4 + intsize))

let parse_gy data =
  decode_data data >>= fun (gy, rst) ->
  guard (len rst = 0) Underflow >>= fun () ->
  guard (get_uint8 gy 0 <> 0) LeadingZero >>| fun () ->
  gy


let version_of_int = function
  | 2 -> Ok `V2
  | 3 -> Ok `V3
  | _ -> Error (Unknown "version")

let parse_header bytes =
  guard (len bytes >= 3) Underflow >>= fun () ->
  version_of_int (BE.get_uint16 bytes 0) >>= fun version ->
  let typ = get_uint8 bytes 2 in
  R.of_option
    ~none:(fun () -> Error (Unknown "message type"))
    (int_to_message_type typ) >>= fun typ ->
  match version with
  | `V2 -> Ok (version, typ, None, shift bytes 3)
  | `V3 ->
    guard (len bytes >= 11) Underflow >>| fun () ->
    let mine = BE.get_uint32 bytes 3
    and thei = BE.get_uint32 bytes 7
    in
    (version, typ, Some (mine, thei), shift bytes 11)

let parse_signature_data buf =
  guard (len buf >= 2) Underflow >>= fun () ->
  let tag, buf = split buf 2 in
  guard (BE.get_uint16 tag 0 = 0) (Unknown "key tag != 0") >>= fun () ->
  decode_data buf >>= fun (p, buf) ->
  guard (get_uint8 p 0 <> 0) LeadingZero >>= fun () ->
  decode_data buf >>= fun (q, buf) ->
  guard (get_uint8 q 0 <> 0) LeadingZero >>= fun () ->
  decode_data buf >>= fun (gg, buf) ->
  guard (get_uint8 gg 0 <> 0) LeadingZero >>= fun () ->
  decode_data buf >>= fun (y, buf) ->
  guard (get_uint8 y 0 <> 0) LeadingZero >>= fun () ->
  let key = Otr_crypto.OtrDsa.pub ~p ~q ~gg ~y in
  guard (len buf = 44) (Unknown "signature lengh") >>| fun () ->
  let keyida = BE.get_uint32 buf 0 in
  let buf = shift buf 4 in
  let siga = split buf 20 in
  (key, keyida, siga)

let parse_reveal buf =
  decode_data buf >>= fun (r, buf) ->
  decode_data buf >>= fun (enc_data, mac) ->
  guard (len mac = 20) (Unknown "wrong mac length") >>| fun () ->
  (r, enc_data, mac)

let parse_dh_commit buf =
  decode_data buf >>= fun (gxenc, buf) ->
  decode_data buf >>= fun (hgx, buf) ->
  guard ((len buf = 0) && (len hgx = 32)) (Unknown "bad dh_commit") >>| fun () ->
  (gxenc, hgx)

let parse_data_body buf =
  guard (len buf >= 9) Underflow >>= fun () ->
  let flags = get_uint8 buf 0
  and s_keyid = BE.get_uint32 buf 1
  and r_keyid = BE.get_uint32 buf 5
  in
  decode_data (shift buf 9) >>= fun (dh_y, buf) ->
  guard (get_uint8 dh_y 0 <> 0) LeadingZero >>= fun () ->
  guard (len buf >= 8) Underflow >>= fun () ->
  let ctr = BE.get_uint64 buf 0 in
  decode_data (shift buf 8) >>= fun (encdata, buf) ->
  guard (len buf >= 20) Underflow >>= fun () ->
  let mac = sub buf 0 20 in
  decode_data (shift buf 20) >>= fun (reveal, buf) ->
  guard (len buf = 0) Underflow >>| fun () ->
  let flags = if flags = 1 then true else false in
  (flags, s_keyid, r_keyid, dh_y, ctr, encdata, mac, reveal)

let parse_data buf =
  parse_header buf >>= fun (version, typ, instances, buf) ->
  guard (typ = DATA) (Unknown "type") >>= fun () ->
  parse_data_body buf >>| fun (flags, s_keyid, r_keyid, dh_y, ctr, encdata, mac, reveal) ->
  (version, instances, flags, s_keyid, r_keyid, dh_y, ctr, encdata, mac, reveal)

let parse_tlv buf =
  guard (len buf >= 4) Underflow >>= fun () ->
  let typ = BE.get_uint16 buf 0 in
  let l = BE.get_uint16 buf 2 in
  guard (len buf >= 4 + l) Underflow >>| fun () ->
  (int_to_tlv_type typ, sub buf 4 l, shift buf (4 + l))

let parse_datas buf n =
  let rec p_data buf acc = function
    | 0 when len buf = 0 -> Ok (List.rev acc)
    | 0 -> Error Underflow
    | n ->
      decode_data buf >>= fun (x, buf) ->
      guard (get_uint8 x 0 <> 0) LeadingZero >>= fun () ->
      p_data buf (x :: acc) (pred n)
  in
  guard (len buf >= 4) Underflow >>= fun () ->
  let cnt = BE.get_uint32 buf 0 in
  if cnt = Int32.of_int n then
    p_data (shift buf 4) [] n
  else
    Error Underflow
OCaml

Innovation. Community. Security.