nx_ssh_lib.nx source
↩ module page · 497 lines · 21395 B
1// nx_ssh_lib.nx -- shared core of the sovereign SSH-2 client: wire helpers,
2// binary-packet framing, the curve25519-sha256 KEX (fills an SshState with
3// the exchange hash H/session_id + mpint(K)), and the
4// chacha20-poly1305@openssh.com encrypted transport. Composed by
5// nx_ssh_kex (KEX proof) and nx_ssh_client (full session).
6//
7// Bits-up: our own X25519 / Ed25519 / SHA-256 / ChaCha20 / Poly1305.
8//
9// license_tier: ORIGINAL
10
11import "nx_syscalls.nx"
12import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
13import "nx_x25519_ephemeral.nx"
14import "nx_ed25519_signature.nx"
15import "nx_sha256.nx"
16import "nx_chacha20.nx"
17import "nx_poly1305.nx"
18import "nx_base64.nx"
19const SSH_MAGIC_4096: i64 = 4096
20const SSH_MAGIC_1024: i64 = 1024
21const SSH_MAGIC_65536: i64 = 65536
22const SSH_MAGIC_1048576: i64 = 1048576
23const SSH_MAGIC_16384: i64 = 16384
24const SSH_MAGIC_8192: i64 = 8192
25
26// Per-session state after KEX.
27struct SshState {
28 fd: i64,
29 h: *u8, // 32-byte exchange hash == session_id (first KEX)
30 kmp: *u8, // mpint(K) WIRE bytes (u32 len + value)
31 kmp_len: i64,
32 verified: i64, // 1 if host-key signature verified
33 // chacha20-poly1305@openssh.com keys (64B each: K_2=[0..32] len, K_1=[32..64] payload)
34 c2s: *u8, // client->server key material (we encrypt)
35 s2c: *u8, // server->client key material (we decrypt)
36 seq_c2s: i64,
37 seq_s2c: i64,
38}
39const SSH_STATE_BYTES: i64 = 96
40
41func ssh_P(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 }
42func ssh_puts(s: *u8) -> i64 {
43 var n: i64 = 0
44 while s[n] != 0 as u8 { n = n + 1 }
45 sys_write(1, s, n); return 0
46}
47func ssh_phex(b: *u8, n: i64) -> i64 {
48 let hx: *u8 = "0123456789abcdef" as *u8
49 var i: i64 = 0
50 while i < n {
51 let o: *u8 = sys_mmap(2)
52 o[0] = hx[(b[i] as i64 >> 4) & 0xf]; o[1] = hx[(b[i] as i64) & 0xf]
53 sys_write(1, o, 2); i = i + 1
54 }
55 return 0
56}
57func ssh_sockaddr(sa: *u8, ip: i64, port: i64) -> i64 {
58 sa[0] = (AF_INET & 0xff) as u8; sa[1] = ((AF_INET >> 8) & 0xff) as u8
59 sa[2] = ((port >> 8) & 0xff) as u8; sa[3] = (port & 0xff) as u8
60 sa[4] = ((ip >> 24) & 0xff) as u8; sa[5] = ((ip >> 16) & 0xff) as u8
61 sa[6] = ((ip >> 8) & 0xff) as u8; sa[7] = (ip & 0xff) as u8
62 var p: i64 = 8
63 while p < 16 { sa[p] = 0 as u8; p = p + 1 }
64 return 0
65}
66func ssh_u32be(b: *u8, off: i64) -> i64 {
67 return ((b[off] as i64) << 24) | ((b[off+1] as i64) << 16) | ((b[off+2] as i64) << 8) | (b[off+3] as i64)
68}
69func ssh_read_full(fd: i64, dst: *u8, n: i64) -> i64 {
70 var got: i64 = 0
71 while got < n {
72 let r: i64 = sys_read(fd, (dst + got) as *u8, n - got)
73 if r <= 0 { return got }
74 got = got + r
75 }
76 return got
77}
78func ssh_read_line(fd: i64, dst: *u8, max: i64) -> i64 {
79 var k: i64 = 0
80 while k < max {
81 let r: i64 = sys_read(fd, (dst + k) as *u8, 1)
82 if r <= 0 { return k }
83 if dst[k] == 10 as u8 { return k + 1 }
84 k = k + 1
85 }
86 return k
87}
88func ssh_put_u32(b: *u8, idx: i64, v: i64) -> i64 {
89 b[idx] = ((v >> 24) & 0xff) as u8; b[idx+1] = ((v >> 16) & 0xff) as u8
90 b[idx+2] = ((v >> 8) & 0xff) as u8; b[idx+3] = (v & 0xff) as u8
91 return idx + 4
92}
93func ssh_put_byte(b: *u8, idx: i64, v: i64) -> i64 { b[idx] = (v & 0xff) as u8; return idx + 1 }
94func ssh_put_bytes(b: *u8, idx: i64, s: *u8, n: i64) -> i64 {
95 var i: i64 = 0
96 while i < n { b[idx+i] = s[i]; i = i + 1 }
97 return idx + n
98}
99func ssh_put_str(b: *u8, idx: i64, s: *u8, n: i64) -> i64 {
100 let j: i64 = ssh_put_u32(b, idx, n)
101 return ssh_put_bytes(b, j, s, n)
102}
103// plaintext (pre-NEWKEYS) packet framing.
104func ssh_send_packet(fd: i64, payload: *u8, plen: i64) -> i64 {
105 var padlen: i64 = 8 - ((5 + plen) % 8)
106 if padlen < 4 { padlen = padlen + 8 }
107 let pktlen: i64 = 1 + plen + padlen
108 let out: *u8 = sys_mmap(plen + padlen + 16)
109 ssh_put_u32(out, 0, pktlen)
110 out[4] = (padlen & 0xff) as u8
111 ssh_put_bytes(out, 5, payload, plen)
112 var j: i64 = 0
113 while j < padlen { out[5 + plen + j] = 0 as u8; j = j + 1 }
114 sys_write(fd, out, 5 + plen + padlen)
115 return 0
116}
117func ssh_read_packet(fd: i64, payload_out: *u8) -> i64 {
118 let hdr: *u8 = sys_mmap(8)
119 if ssh_read_full(fd, hdr, 4) != 4 { return 0 - 1 }
120 let pktlen: i64 = ssh_u32be(hdr, 0)
121 let body: *u8 = sys_mmap(pktlen + 16)
122 if ssh_read_full(fd, body, pktlen) != pktlen { return 0 - 1 }
123 let padlen: i64 = body[0] as i64
124 let plen: i64 = pktlen - padlen - 1
125 ssh_put_bytes(payload_out, 0, (body + 1) as *u8, plen)
126 return plen
127}
128func ssh_h_str(ctx: *Sha256, src: *u8, n: i64) -> i64 {
129 let t: *u8 = sys_mmap(4); ssh_put_u32(t, 0, n); sha256_update(ctx, t, 4)
130 sha256_update(ctx, src, n); return 0
131}
132func ssh_build_mpint(K: *u8, n: i64, out: *u8) -> i64 {
133 var i: i64 = 0
134 while i < n { if K[i] != 0 as u8 { break } i = i + 1 }
135 if i == n { return 0 }
136 var o: i64 = 0
137 if (K[i] as i64 & 0x80) != 0 { out[o] = 0 as u8; o = o + 1 }
138 while i < n { out[o] = K[i]; o = o + 1; i = i + 1 }
139 return o
140}
141
142// ===== KEX: version exchange + curve25519-sha256, fills st. rc 0 = ok =====
143func ssh_kex(st: *SshState) -> i64 {
144 let fd: i64 = st.fd
145 let VC: *u8 = "SSH-2.0-NishiSSH_0.1" as *u8
146 let vclen: i64 = 20
147 sys_write(fd, VC, vclen); sys_write(fd, "\r\n" as *u8, 2)
148 let vsline: *u8 = sys_mmap(256)
149 let vsraw: i64 = ssh_read_line(fd, vsline, 255)
150 var vslen: i64 = vsraw
151 while vslen > 0 {
152 if vsline[vslen-1] == 13 as u8 { vslen = vslen - 1 }
153 else { if vsline[vslen-1] == 10 as u8 { vslen = vslen - 1 } else { break } }
154 }
155
156 let IS: *u8 = sys_mmap(SSH_MAGIC_4096)
157 let islen: i64 = ssh_read_packet(fd, IS)
158 if islen < 0 { return 0 - 1 }
159 if IS[0] != 20 as u8 { return 0 - 2 }
160
161 let IC: *u8 = sys_mmap(SSH_MAGIC_1024)
162 var ic: i64 = 0
163 ic = ssh_put_byte(IC, ic, 20)
164 let cookie: *u8 = sys_mmap(16); rand_bytes(cookie, 16); ic = ssh_put_bytes(IC, ic, cookie, 16)
165 ic = ssh_put_str(IC, ic, "curve25519-sha256" as *u8, 17)
166 ic = ssh_put_str(IC, ic, "ssh-ed25519" as *u8, 11)
167 ic = ssh_put_str(IC, ic, "chacha20-poly1305@openssh.com" as *u8, 29)
168 ic = ssh_put_str(IC, ic, "chacha20-poly1305@openssh.com" as *u8, 29)
169 ic = ssh_put_str(IC, ic, "hmac-sha2-256" as *u8, 13)
170 ic = ssh_put_str(IC, ic, "hmac-sha2-256" as *u8, 13)
171 ic = ssh_put_str(IC, ic, "none" as *u8, 4)
172 ic = ssh_put_str(IC, ic, "none" as *u8, 4)
173 ic = ssh_put_u32(IC, ic, 0); ic = ssh_put_u32(IC, ic, 0)
174 ic = ssh_put_byte(IC, ic, 0); ic = ssh_put_u32(IC, ic, 0)
175 ssh_send_packet(fd, IC, ic)
176
177 let eph_priv: *u8 = sys_mmap(32); let QC: *u8 = sys_mmap(32)
178 x25519_gen_keypair(eph_priv, QC)
179 let KI: *u8 = sys_mmap(64); var ki: i64 = 0
180 ki = ssh_put_byte(KI, ki, 30); ki = ssh_put_str(KI, ki, QC, 32)
181 ssh_send_packet(fd, KI, ki)
182
183 let REP: *u8 = sys_mmap(SSH_MAGIC_4096)
184 let rlen: i64 = ssh_read_packet(fd, REP)
185 if rlen < 0 { return 0 - 3 }
186 if REP[0] != 31 as u8 { return 0 - 4 }
187 var off: i64 = 1
188 let kslen: i64 = ssh_u32be(REP, off); off = off + 4
189 let KS: *u8 = (REP + off) as *u8; off = off + kslen
190 let qslen: i64 = ssh_u32be(REP, off); off = off + 4
191 let QS: *u8 = (REP + off) as *u8; off = off + qslen
192 let siglen: i64 = ssh_u32be(REP, off); off = off + 4
193 let SIG: *u8 = (REP + off) as *u8
194
195 let K: *u8 = sys_mmap(32)
196 x25519(eph_priv, QS, K)
197 let mp: *u8 = sys_mmap(40); let mplen: i64 = ssh_build_mpint(K, 32, mp)
198 // store mpint(K) WIRE bytes (u32 len + value) for the hash + key derivation
199 let kmp: *u8 = sys_mmap(48); let kmp_len: i64 = ssh_put_str(kmp, 0, mp, mplen)
200
201 let ctx: *Sha256 = sys_mmap(256) as *Sha256
202 sha256_init(ctx)
203 ssh_h_str(ctx, VC, vclen)
204 ssh_h_str(ctx, vsline, vslen)
205 ssh_h_str(ctx, IC, ic)
206 ssh_h_str(ctx, IS, islen)
207 ssh_h_str(ctx, KS, kslen)
208 ssh_h_str(ctx, QC, 32)
209 ssh_h_str(ctx, QS, qslen)
210 sha256_update(ctx, kmp, kmp_len) // mpint(K) wire bytes
211 let H: *u8 = sys_mmap(32); sha256_final(ctx, H)
212
213 var ko: i64 = 0
214 let kt: i64 = ssh_u32be(KS, ko); ko = ko + 4 + kt
215 let publen: i64 = ssh_u32be(KS, ko); ko = ko + 4
216 let PUB: *u8 = (KS + ko) as *u8
217 var so: i64 = 0
218 let sct: i64 = ssh_u32be(SIG, so); so = so + 4 + sct
219 let sraw_len: i64 = ssh_u32be(SIG, so); so = so + 4
220 let SIGRAW: *u8 = (SIG + so) as *u8
221
222 st.h = H
223 st.kmp = kmp
224 st.kmp_len = kmp_len
225 st.verified = 0
226 if ed25519_verify_full(PUB, H, 32, SIGRAW) == NX_ED25519_SIG_OK { st.verified = 1 }
227 return 0
228}
229
230// ===== key derivation (RFC 4253 7.2, hash=SHA-256) =====
231// K_X = HASH(mpint(K) || H || letter || session_id), extended by
232// HASH(mpint(K) || H || K_so_far) until `need` bytes.
233func ssh_derive_key(st: *SshState, letter: i64, need: i64, out: *u8) -> i64 {
234 let ctx: *Sha256 = sys_mmap(256) as *Sha256
235 sha256_init(ctx)
236 sha256_update(ctx, st.kmp, st.kmp_len)
237 sha256_update(ctx, st.h, 32)
238 let lb: *u8 = sys_mmap(1); lb[0] = (letter & 0xff) as u8; sha256_update(ctx, lb, 1)
239 sha256_update(ctx, st.h, 32) // session_id == H
240 sha256_final(ctx, out)
241 var have: i64 = 32
242 while have < need {
243 let c2: *Sha256 = sys_mmap(256) as *Sha256
244 sha256_init(c2)
245 sha256_update(c2, st.kmp, st.kmp_len)
246 sha256_update(c2, st.h, 32)
247 sha256_update(c2, out, have)
248 sha256_final(c2, (out + have) as *u8)
249 have = have + 32
250 }
251 return 0
252}
253// derive the two 64-byte chacha20-poly1305 keys (C=c2s, D=s2c) + reset seqs.
254func ssh_derive_session(st: *SshState) -> i64 {
255 let c2s: *u8 = sys_mmap(64); ssh_derive_key(st, 67, 64, c2s) // 'C'
256 let s2c: *u8 = sys_mmap(64); ssh_derive_key(st, 68, 64, s2c) // 'D'
257 st.c2s = c2s; st.s2c = s2c
258 return 0
259}
260
261// 8-byte nonce for chacha20-poly1305@openssh.com = the packet sequence
262// number, big-endian (the original DJB ChaCha20 nonce, words 14-15).
263func ssh_seqbuf(seqnr: i64, out8: *u8) -> i64 {
264 var s: i64 = 7
265 while s >= 0 { out8[7 - s] = ((seqnr >> (s * 8)) & 0xff) as u8; s = s - 1 }
266 return 0
267}
268
269// ===== chacha20-poly1305@openssh.com transport =====
270// Per OpenSSH chachapoly_init: K_1 = key64[0..32] (main_ctx) keys the payload
271// (counter 1) and the Poly1305 one-time key (counter 0); K_2 = key64[32..64]
272// (header_ctx) encrypts the 4-byte length. (Order is the CODE's, not the
273// PROTOCOL doc's K_1/K_2 prose.)
274func ssh_enc_send(st: *SshState, payload: *u8, plen: i64) -> i64 {
275 let fd: i64 = st.fd
276 let K1: *u8 = st.c2s; let K2: *u8 = (st.c2s + 32) as *u8
277 let nz: *u8 = sys_mmap(8); ssh_seqbuf(st.seq_c2s, nz)
278 var padlen: i64 = 8 - ((1 + plen) % 8)
279 if padlen < 4 { padlen = padlen + 8 }
280 let pktlen: i64 = 1 + plen + padlen
281 let pt: *u8 = sys_mmap(pktlen + 16)
282 pt[0] = (padlen & 0xff) as u8
283 ssh_put_bytes(pt, 1, payload, plen)
284 var z: i64 = 0
285 while z < padlen { pt[1 + plen + z] = 0 as u8; z = z + 1 }
286 let ksL: *u8 = sys_mmap(64); chacha20_block_djb(K2, 0, nz, ksL)
287 let encL: *u8 = sys_mmap(4)
288 encL[0] = (((pktlen >> 24) & 0xff) ^ (ksL[0] as i64)) as u8
289 encL[1] = (((pktlen >> 16) & 0xff) ^ (ksL[1] as i64)) as u8
290 encL[2] = (((pktlen >> 8) & 0xff) ^ (ksL[2] as i64)) as u8
291 encL[3] = ((pktlen & 0xff) ^ (ksL[3] as i64)) as u8
292 let ct: *u8 = sys_mmap(pktlen + 16); chacha20_djb(K1, 1, nz, pt, pktlen, ct)
293 let pk: *u8 = sys_mmap(64); chacha20_block_djb(K1, 0, nz, pk)
294 let macin: *u8 = sys_mmap(pktlen + 32)
295 ssh_put_bytes(macin, 0, encL, 4); ssh_put_bytes(macin, 4, ct, pktlen)
296 let tag: *u8 = sys_mmap(16); poly1305_mac(pk, macin, 4 + pktlen, tag)
297 let wire: *u8 = sys_mmap(pktlen + 32)
298 ssh_put_bytes(wire, 0, encL, 4); ssh_put_bytes(wire, 4, ct, pktlen); ssh_put_bytes(wire, 4 + pktlen, tag, 16)
299 sys_write(fd, wire, 4 + pktlen + 16)
300 st.seq_c2s = st.seq_c2s + 1
301 return 0
302}
303// returns payload length, or <0 on error (-2 = MAC fail).
304func ssh_enc_recv(st: *SshState, payload_out: *u8) -> i64 {
305 let fd: i64 = st.fd
306 let K1: *u8 = st.s2c; let K2: *u8 = (st.s2c + 32) as *u8
307 let nz: *u8 = sys_mmap(8); ssh_seqbuf(st.seq_s2c, nz)
308 let encL: *u8 = sys_mmap(4)
309 if ssh_read_full(fd, encL, 4) != 4 { return 0 - 1 }
310 let ksL: *u8 = sys_mmap(64); chacha20_block_djb(K2, 0, nz, ksL)
311 let pktlen: i64 = (((encL[0] as i64) ^ (ksL[0] as i64)) << 24) | (((encL[1] as i64) ^ (ksL[1] as i64)) << 16) | (((encL[2] as i64) ^ (ksL[2] as i64)) << 8) | ((encL[3] as i64) ^ (ksL[3] as i64))
312 if pktlen <= 0 { return 0 - 1 }
313 if pktlen > SSH_MAGIC_65536 { return 0 - 1 }
314 let ct: *u8 = sys_mmap(pktlen + 16)
315 if ssh_read_full(fd, ct, pktlen + 16) != pktlen + 16 { return 0 - 1 }
316 let pk: *u8 = sys_mmap(64); chacha20_block_djb(K1, 0, nz, pk)
317 let macin: *u8 = sys_mmap(pktlen + 32)
318 ssh_put_bytes(macin, 0, encL, 4); ssh_put_bytes(macin, 4, ct, pktlen)
319 let tag: *u8 = sys_mmap(16); poly1305_mac(pk, macin, 4 + pktlen, tag)
320 if poly1305_tag_equal(tag, (ct + pktlen) as *u8) != 1 { return 0 - 2 }
321 let pt: *u8 = sys_mmap(pktlen + 16); chacha20_djb(K1, 1, nz, ct, pktlen, pt)
322 let padlen: i64 = pt[0] as i64
323 let plen: i64 = pktlen - padlen - 1
324 ssh_put_bytes(payload_out, 0, (pt + 1) as *u8, plen)
325 st.seq_s2c = st.seq_s2c + 1
326 return plen
327}
328
329// build the ssh-ed25519 public-key blob: string("ssh-ed25519")||string(pub32).
330func ssh_ed25519_blob(pub32: *u8, out: *u8) -> i64 {
331 var b: i64 = 0
332 b = ssh_put_str(out, b, "ssh-ed25519" as *u8, 11)
333 b = ssh_put_str(out, b, pub32, 32)
334 return b
335}
336// print the authorized_keys line for our identity (ssh-ed25519 <b64> nishi-ssh).
337func ssh_print_authkeys(pub32: *u8) -> i64 {
338 let blob: *u8 = sys_mmap(64); let bl: i64 = ssh_ed25519_blob(pub32, blob)
339 let b64: *u8 = sys_mmap(128); let n: i64 = b64_encode(blob, bl, b64)
340 ssh_puts("ssh-ed25519 " as *u8); sys_write(1, b64, n); ssh_puts(" nishi-ssh\n" as *u8)
341 return 0
342}
343
344// connect + KEX + NEWKEYS + key derivation + SERVICE_REQUEST(ssh-userauth),
345// leaving st ready for a userauth attempt. rc 0 = ready.
346func ssh_open_session(st: *SshState, ip: i64) -> i64 {
347 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0)
348 if fd < 0 { return 0 - 1 }
349 let sa: *u8 = sys_mmap(16); ssh_sockaddr(sa, ip, 22)
350 if nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) != 0 { return 0 - 2 }
351 st.fd = fd
352 if ssh_kex(st) != 0 { return 0 - 3 }
353 if st.verified != 1 { return 0 - 4 }
354 let nk: *u8 = sys_mmap(64)
355 if ssh_read_packet(fd, nk) < 0 { return 0 - 5 }
356 if nk[0] != 21 as u8 { return 0 - 6 }
357 let mynk: *u8 = sys_mmap(8); mynk[0] = 21 as u8; ssh_send_packet(fd, mynk, 1)
358 ssh_derive_session(st)
359 st.seq_c2s = 3; st.seq_s2c = 3
360 let sr: *u8 = sys_mmap(64); var m: i64 = 0
361 m = ssh_put_byte(sr, m, 5); m = ssh_put_str(sr, m, "ssh-userauth" as *u8, 12)
362 ssh_enc_send(st, sr, m)
363 let rep: *u8 = sys_mmap(512)
364 if ssh_enc_recv(st, rep) < 0 { return 0 - 7 }
365 if rep[0] != 6 as u8 { return 0 - 8 }
366 return 0
367}
368
369// ===== userauth: password =====
370// returns 1 = SUCCESS, 0 = FAILURE, <0 = transport error. Handles a
371// USERAUTH_BANNER (53) arriving before the result.
372func ssh_userauth_password(st: *SshState, user: *u8, ulen: i64, pw: *u8, pwlen: i64) -> i64 {
373 let req: *u8 = sys_mmap(512); var n: i64 = 0
374 n = ssh_put_byte(req, n, 50)
375 n = ssh_put_str(req, n, user, ulen)
376 n = ssh_put_str(req, n, "ssh-connection" as *u8, 14)
377 n = ssh_put_str(req, n, "password" as *u8, 8)
378 n = ssh_put_byte(req, n, 0) // FALSE: not a password change
379 n = ssh_put_str(req, n, pw, pwlen)
380 ssh_enc_send(st, req, n)
381 let rep: *u8 = sys_mmap(512)
382 var tries: i64 = 0
383 while tries < 4 {
384 let rl: i64 = ssh_enc_recv(st, rep)
385 if rl < 0 { return 0 - 1 }
386 if rep[0] == 52 as u8 { return 1 } // USERAUTH_SUCCESS
387 if rep[0] == 51 as u8 { return 0 } // USERAUTH_FAILURE
388 tries = tries + 1 // 53 BANNER / other -> read next
389 }
390 return 0
391}
392
393// load (or generate-once + persist) our sovereign Ed25519 identity seed +
394// derive its public key. Shared by the SSH client and the fleet doctor.
395func ssh_load_identity(seed: *u8, pub: *u8) -> i64 {
396 let idpath: *u8 = "/mnt/c/Users/elder/.nishi/nx_ssh_id" as *u8
397 let lenbox: *i64 = sys_mmap(16) as *i64
398 let data: *u8 = sys_read_file(idpath, lenbox)
399 var have: i64 = 0
400 if (data as i64) != 0 { if lenbox[0] == 32 { ssh_put_bytes(seed, 0, data, 32); have = 1 } }
401 if have == 0 {
402 rand_bytes(seed, 32)
403 let wfd: i64 = sys_openat_wr(idpath, 384)
404 if wfd >= 0 { sys_write(wfd, seed, 32); sys_close(wfd) }
405 }
406 ed25519_pub_from_priv(seed, pub)
407 return 0
408}
409
410// ===== userauth: publickey (ed25519) =====
411// returns 1 = SUCCESS, 0 = FAILURE, <0 = transport error.
412func ssh_userauth_publickey(st: *SshState, user: *u8, ulen: i64, seed32: *u8, pub32: *u8) -> i64 {
413 let pkblob: *u8 = sys_mmap(64); let pb: i64 = ssh_ed25519_blob(pub32, pkblob)
414 // to-be-signed = string(session_id) || the request body (with have_sig=1).
415 let tbs: *u8 = sys_mmap(512); var tb: i64 = 0
416 tb = ssh_put_str(tbs, tb, st.h, 32)
417 tb = ssh_put_byte(tbs, tb, 50)
418 tb = ssh_put_str(tbs, tb, user, ulen)
419 tb = ssh_put_str(tbs, tb, "ssh-connection" as *u8, 14)
420 tb = ssh_put_str(tbs, tb, "publickey" as *u8, 9)
421 tb = ssh_put_byte(tbs, tb, 1)
422 tb = ssh_put_str(tbs, tb, "ssh-ed25519" as *u8, 11)
423 tb = ssh_put_str(tbs, tb, pkblob, pb)
424 let sig: *u8 = sys_mmap(64); ed25519_sign_full(seed32, tbs, tb, sig)
425 let sigblob: *u8 = sys_mmap(96); var sb: i64 = 0
426 sb = ssh_put_str(sigblob, sb, "ssh-ed25519" as *u8, 11)
427 sb = ssh_put_str(sigblob, sb, sig, 64)
428 let req: *u8 = sys_mmap(512); var rq: i64 = 0
429 rq = ssh_put_byte(req, rq, 50)
430 rq = ssh_put_str(req, rq, user, ulen)
431 rq = ssh_put_str(req, rq, "ssh-connection" as *u8, 14)
432 rq = ssh_put_str(req, rq, "publickey" as *u8, 9)
433 rq = ssh_put_byte(req, rq, 1)
434 rq = ssh_put_str(req, rq, "ssh-ed25519" as *u8, 11)
435 rq = ssh_put_str(req, rq, pkblob, pb)
436 rq = ssh_put_str(req, rq, sigblob, sb)
437 ssh_enc_send(st, req, rq)
438 let rep: *u8 = sys_mmap(512); let rl: i64 = ssh_enc_recv(st, rep)
439 if rl < 0 { return 0 - 1 }
440 if rep[0] == 52 as u8 { return 1 } // SSH_MSG_USERAUTH_SUCCESS
441 return 0 // SSH_MSG_USERAUTH_FAILURE / other
442}
443
444// ===== open a session channel, exec `cmd`, stream output to stdout =====
445func ssh_exec(st: *SshState, cmd: *u8, cmdlen: i64) -> i64 {
446 let co: *u8 = sys_mmap(64); var c: i64 = 0
447 c = ssh_put_byte(co, c, 90) // SSH_MSG_CHANNEL_OPEN
448 c = ssh_put_str(co, c, "session" as *u8, 7)
449 c = ssh_put_u32(co, c, 0) // our channel id = 0
450 c = ssh_put_u32(co, c, SSH_MAGIC_1048576) // initial window
451 c = ssh_put_u32(co, c, SSH_MAGIC_16384) // max packet
452 ssh_enc_send(st, co, c)
453 let rep: *u8 = sys_mmap(SSH_MAGIC_8192)
454 var rcid: i64 = 0 - 1
455 var guard: i64 = 0
456 while rcid < 0 {
457 if guard > 16 { return 0 - 1 }
458 let rl: i64 = ssh_enc_recv(st, rep)
459 if rl < 0 { return 0 - 1 }
460 if rep[0] == 91 as u8 { rcid = ssh_u32be(rep, 5) } // OPEN_CONFIRMATION -> server channel id
461 if rep[0] == 92 as u8 { return 0 - 2 } // OPEN_FAILURE
462 guard = guard + 1
463 }
464 let cr: *u8 = sys_mmap(SSH_MAGIC_4096); var q: i64 = 0 // SSH_MAGIC_4096 (was 256) so long exec commands (uci firewall batch) fit
465 q = ssh_put_byte(cr, q, 98) // SSH_MSG_CHANNEL_REQUEST
466 q = ssh_put_u32(cr, q, rcid) // server's channel id
467 q = ssh_put_str(cr, q, "exec" as *u8, 4)
468 q = ssh_put_byte(cr, q, 1) // want_reply
469 q = ssh_put_str(cr, q, cmd, cmdlen)
470 ssh_enc_send(st, cr, q)
471 // POLL-BOUNDED wait: ssh_enc_recv blocks forever if the server sends nothing, and a backgrounded
472 // remote daemon (e.g. our setsid'd supervisor) keeps the channel open so CHANNEL_CLOSE never comes.
473 // Poll with a 1s timeout; print output as it arrives; finish on CHANNEL_CLOSE OR ~15s of quiet
474 // (foreground command done, backgrounded process detached). A client must not hang on this.
475 let pfd: *u8 = sys_mmap(8)
476 let fdv: i64 = st.fd
477 pfd[0]=(fdv&0xff) as u8; pfd[1]=((fdv>>8)&0xff) as u8; pfd[2]=((fdv>>16)&0xff) as u8; pfd[3]=((fdv>>24)&0xff) as u8
478 pfd[4]=1 as u8; pfd[5]=0 as u8
479 var done: i64 = 0
480 var quiet: i64 = 0
481 while done == 0 {
482 let pr: i64 = sys_poll(pfd, 1, 1000)
483 if pr <= 0 { quiet = quiet + 1; if quiet >= 15 { done = 1 } }
484 else {
485 quiet = 0
486 let rl: i64 = ssh_enc_recv(st, rep)
487 if rl < 0 { done = 1 }
488 else {
489 let mt: i64 = rep[0] as i64
490 if mt == 94 { let dlen: i64 = ssh_u32be(rep, 5); sys_write(1, (rep + 9) as *u8, dlen) } // CHANNEL_DATA
491 if mt == 95 { let dl2: i64 = ssh_u32be(rep, 9); sys_write(1, (rep + 13) as *u8, dl2) } // EXTENDED_DATA (stderr)
492 if mt == 97 { done = 1 } // CHANNEL_CLOSE
493 }
494 }
495 }
496 return 0
497}