code wiki / (root) / nx_ssh_lib.nx

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}