package awa

  1. Overview
  2. Docs

Source file ssh.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
(*
 * Copyright (c) 2017 Christiano F. Haesbaert <haesbaert@haesbaert.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

open Sexplib.Conv
open Util

[%%cstruct
type pkt_hdr = {
  pkt_len: uint32_t;
  pad_len: uint8_t;
} [@@big_endian]]

let version_banner = "SSH-2.0-awa_ssh_0.1"
let max_pkt_len = 512 * 1024          (* 512KB should be enough *)
let max_len = 256 * 1024              (* 256KB for a field is enough *)
let channel_win_len =                 (* 4MB channel window *)
  Int32.of_int (4 * 1024 * 1000)
let channel_win_adj_threshold =       (* Refresh window if below 2MB *)
  Int32.of_int (2 * 1024 * 1000)
let channel_max_pkt_len =             (* Must be smaller than max_pkt_len *)
  Int32.of_int (64 * 1024)
let max_channels = 1024               (* 1024 maximum channels per connection *)

let min_dh, n, max_dh = 2048l, 3072l, 8192l

let guard_sshlen len =
  guard (len >= 0 && len <= max_len) (sprintf "Bad length: %d" len)

let guard_sshlen_exn len =
  match guard_sshlen len with Ok () -> () | Error e -> invalid_arg e

[%%cenum
type message_id =
  | MSG_DISCONNECT                [@id 1]
  | MSG_IGNORE                    [@id 2]
  | MSG_UNIMPLEMENTED             [@id 3]
  | MSG_DEBUG                     [@id 4]
  | MSG_SERVICE_REQUEST           [@id 5]
  | MSG_SERVICE_ACCEPT            [@id 6]
  | MSG_KEXINIT                   [@id 20]
  | MSG_NEWKEYS                   [@id 21]
  | MSG_KEX_0                     [@id 30]
  | MSG_KEX_1                     [@id 31]
  | MSG_KEX_2                     [@id 32]
  | MSG_KEX_3                     [@id 33]
  | MSG_KEX_4                     [@id 34]
  | MSG_USERAUTH_REQUEST          [@id 50]
  | MSG_USERAUTH_FAILURE          [@id 51]
  | MSG_USERAUTH_SUCCESS          [@id 52]
  | MSG_USERAUTH_BANNER           [@id 53]
  | MSG_USERAUTH_PK_OK            [@id 60]
  | MSG_GLOBAL_REQUEST            [@id 80]
  | MSG_REQUEST_SUCCESS           [@id 81]
  | MSG_REQUEST_FAILURE           [@id 82]
  | MSG_CHANNEL_OPEN              [@id 90]
  | MSG_CHANNEL_OPEN_CONFIRMATION [@id 91]
  | MSG_CHANNEL_OPEN_FAILURE      [@id 92]
  | MSG_CHANNEL_WINDOW_ADJUST     [@id 93]
  | MSG_CHANNEL_DATA              [@id 94]
  | MSG_CHANNEL_EXTENDED_DATA     [@id 95]
  | MSG_CHANNEL_EOF               [@id 96]
  | MSG_CHANNEL_CLOSE             [@id 97]
  | MSG_CHANNEL_REQUEST           [@id 98]
  | MSG_CHANNEL_SUCCESS           [@id 99]
  | MSG_CHANNEL_FAILURE           [@id 100]
  | MSG_VERSION                   [@id -1]
[@@uint8_t][@@sexp]]

type kexinit = {
  cookie                   : Cstruct_sexp.t;
  kex_algs                 : string list;
  server_host_key_algs     : string list;
  encryption_algs_ctos     : string list;
  encryption_algs_stoc     : string list;
  mac_algs_ctos            : string list;
  mac_algs_stoc            : string list;
  compression_algs_ctos    : string list;
  compression_algs_stoc    : string list;
  languages_ctos           : string list;
  languages_stoc           : string list;
  first_kex_packet_follows : bool;
  rawkex                   : Cstruct_sexp.t;   (* raw kexinit *)
} [@@deriving sexp]

[%%cenum
type disconnect_code =
  | DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT      [@id  1]
  | DISCONNECT_PROTOCOL_ERROR                   [@id  2]
  | DISCONNECT_KEY_EXCHANGE_FAILED              [@id  3]
  | DISCONNECT_RESERVED                         [@id  4]
  | DISCONNECT_MAC_ERROR                        [@id  5]
  | DISCONNECT_COMPRESSION_ERROR                [@id  6]
  | DISCONNECT_SERVICE_NOT_AVAILABLE            [@id  7]
  | DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED   [@id  8]
  | DISCONNECT_HOST_KEY_NOT_VERIFIABLE          [@id  9]
  | DISCONNECT_CONNECTION_LOST                  [@id 10]
  | DISCONNECT_BY_APPLICATION                   [@id 11]
  | DISCONNECT_TOO_MANY_CONNECTIONS             [@id 12]
  | DISCONNECT_AUTH_CANCELLED_BY_USER           [@id 13]
  | DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE   [@id 14]
  | DISCONNECT_ILLEGAL_USER_NAME                [@id 15]
[@@uint32_t][@@sexp]]

let int_to_disconnect_code code =
  match int_to_disconnect_code code with
  | Some disc -> disc
  | None -> DISCONNECT_PROTOCOL_ERROR (* Mock up *)

(* Channel open codes *)
[%%cenum
type channel_open_code =
  | OPEN_ADMINISTRATIVELY_PROHIBITED  [@id 1]
  | OPEN_CONNECT_FAILED               [@id 2]
  | OPEN_UNKNOWN_CHANNEL_TYPE         [@id 3]
  | OPEN_RESOURCE_SHORTAGE            [@id 4]
[@@uint32_t][@@sexp]]

type mpint = Z.t

let sexp_of_mpint mpint = sexp_of_string (Z.to_string mpint)

type global_request =
  | Tcpip_forward of (string * int32)
  | Cancel_tcpip_forward of (string * int32)
  | Unknown_request of string
[@@deriving sexp]

type channel_request =
  | Pty_req of (string * int32 * int32 * int32 * int32 * string)
  | X11_req of (bool * string * string * int32)
  | Env of (string * string)
  | Shell
  | Exec of string
  | Subsystem of string
  | Window_change of (int32 * int32 * int32 * int32)
  | Xon_xoff of bool
  | Signal of string
  | Exit_status of int32
  | Exit_signal of (string * bool * string * string)
  | Raw_data of Cstruct_sexp.t
[@@deriving sexp]

type channel_open =
  | Session
  | X11 of (string * int32)
  | Forwarded_tcpip of (string * int32 * string * int32)
  | Direct_tcpip of (string * int32 * string * int32)
  | Raw_data of Cstruct_sexp.t
[@@deriving sexp]

(*
 * Protocol Authentication
 *)
type password = string

let sexp_of_password _ = sexp_of_string "????"
let password_of_sexp _ = failwith "password_of_sexp: TODO"

type auth_method =
  | Pubkey of (Hostkey.pub * (Hostkey.alg * Cstruct_sexp.t) option)
  | Password of (password * password option)
  | Hostbased of (string * Cstruct_sexp.t * string * string * Cstruct_sexp.t) (* TODO *)
  | Authnone
[@@deriving sexp]

let auth_method_equal a b =
  match a, b with
  | Pubkey (key_a, signature_a),
    Pubkey (key_b, signature_b) ->
    let signature_match = match signature_a, signature_b with
      | Some (alga, sa), Some (algb, sb) -> alga = algb && Cstruct.equal sa sb
      | None, None -> true
      | _ -> false
    in
    key_a = key_b && signature_match
  | Password _, Password _ -> a = b
  | Hostbased (key_alg_a, key_blob_a, hostname_a, hostuser_a, hostsig_a),
    Hostbased (key_alg_b, key_blob_b, hostname_b, hostuser_b, hostsig_b) ->
    key_alg_a = key_alg_b && (Cstruct.equal key_blob_a key_blob_b) &&
    hostname_a = hostname_b && hostuser_a = hostuser_b &&
    (Cstruct.equal hostsig_a hostsig_b)
  | Authnone, Authnone -> true
  | _ -> false

type message =
  | Msg_disconnect of (disconnect_code * string * string)
  | Msg_ignore of string
  | Msg_unimplemented of int32
  | Msg_debug of (bool * string * string)
  | Msg_service_request of string
  | Msg_service_accept of string
  | Msg_kexinit of kexinit
  | Msg_newkeys
  | Msg_kexdh_reply of Hostkey.pub * mpint * (Hostkey.alg * Cstruct_sexp.t)
  | Msg_kexdh_init of mpint
  (* from RFC 5656 / 8731 *)
  | Msg_kexecdh_reply of Hostkey.pub * mpint * (Hostkey.alg * Cstruct_sexp.t)
  | Msg_kexecdh_init of mpint
  (* from RFC 4419 *)
  (* there's as well a Msg_kexdh_gex_request_old with only a single int32 *)
  | Msg_kexdh_gex_request of int32 * int32 * int32
  | Msg_kexdh_gex_group of mpint * mpint
  | Msg_kexdh_gex_init of mpint
  | Msg_kexdh_gex_reply of Hostkey.pub * mpint * (Hostkey.alg * Cstruct_sexp.t)
  | Msg_kex of message_id * Cstruct_sexp.t
  | Msg_userauth_request of (string * string * auth_method)
  | Msg_userauth_failure of (string list * bool)
  | Msg_userauth_success
  | Msg_userauth_banner of (string * string)
  | Msg_userauth_pk_ok of Hostkey.pub
  | Msg_global_request of (string * bool * global_request)
  | Msg_request_success of Cstruct_sexp.t option
  | Msg_request_failure
  | Msg_channel_open of (int32 * int32 * int32 * channel_open)
  | Msg_channel_open_confirmation of (int32 * int32 * int32 * int32 * Cstruct_sexp.t)
  | Msg_channel_open_failure of (int32 * int32 * string * string)
  | Msg_channel_window_adjust of (int32 * int32)
  | Msg_channel_data of (int32 * Cstruct_sexp.t)
  | Msg_channel_extended_data of (int32 * int32 * Cstruct_sexp.t)
  | Msg_channel_eof of int32
  | Msg_channel_close of int32
  | Msg_channel_request of (int32 * bool * channel_request)
  | Msg_channel_success of int32
  | Msg_channel_failure of int32
  | Msg_version of string       (* Mocked version *)
[@@deriving sexp_of]

let message_to_string msg =
  Sexplib.Sexp.to_string_hum (sexp_of_message msg)

let message_to_id = function
  | Msg_disconnect _               -> MSG_DISCONNECT
  | Msg_ignore _                   -> MSG_IGNORE
  | Msg_unimplemented _            -> MSG_UNIMPLEMENTED
  | Msg_debug _                    -> MSG_DEBUG
  | Msg_service_request _          -> MSG_SERVICE_REQUEST
  | Msg_service_accept _           -> MSG_SERVICE_ACCEPT
  | Msg_kexinit _                  -> MSG_KEXINIT
  | Msg_newkeys                    -> MSG_NEWKEYS
  | Msg_kexdh_init _               -> MSG_KEX_0
  | Msg_kexdh_reply _              -> MSG_KEX_1
  | Msg_kexecdh_init _             -> MSG_KEX_0
  | Msg_kexecdh_reply _            -> MSG_KEX_1
  | Msg_kexdh_gex_request _        -> MSG_KEX_4
  | Msg_kexdh_gex_group _          -> MSG_KEX_1
  | Msg_kexdh_gex_init _           -> MSG_KEX_2
  | Msg_kexdh_gex_reply _          -> MSG_KEX_3
  | Msg_kex (id, _)                -> id
  | Msg_userauth_request _         -> MSG_USERAUTH_REQUEST
  | Msg_userauth_failure _         -> MSG_USERAUTH_FAILURE
  | Msg_userauth_success           -> MSG_USERAUTH_SUCCESS
  | Msg_userauth_banner _          -> MSG_USERAUTH_BANNER
  | Msg_userauth_pk_ok _           -> MSG_USERAUTH_PK_OK
  | Msg_global_request _           -> MSG_GLOBAL_REQUEST
  | Msg_request_success _          -> MSG_REQUEST_SUCCESS
  | Msg_request_failure            -> MSG_REQUEST_FAILURE
  | Msg_channel_open _             -> MSG_CHANNEL_OPEN
  | Msg_channel_open_confirmation _-> MSG_CHANNEL_OPEN_CONFIRMATION
  | Msg_channel_open_failure _     -> MSG_CHANNEL_OPEN_FAILURE
  | Msg_channel_window_adjust _    -> MSG_CHANNEL_WINDOW_ADJUST
  | Msg_channel_data _             -> MSG_CHANNEL_DATA
  | Msg_channel_extended_data _    -> MSG_CHANNEL_EXTENDED_DATA
  | Msg_channel_eof _              -> MSG_CHANNEL_EOF
  | Msg_channel_close _            -> MSG_CHANNEL_CLOSE
  | Msg_channel_request _          -> MSG_CHANNEL_REQUEST
  | Msg_channel_success _          -> MSG_CHANNEL_SUCCESS
  | Msg_channel_failure _          -> MSG_CHANNEL_FAILURE
  | Msg_version _                  -> MSG_VERSION

let message_to_int msg = message_id_to_int (message_to_id msg)

let disconnect_msg code s =
  Msg_disconnect (code, s, "")