implement Sshlib; include "sys.m"; sys: Sys; sprint: import sys;include "bufio.m"; bufio: Bufio; Iobuf: import bufio;include "env.m"; env: Env;include "string.m"; str: String;include "security.m"; random: Random;include "keyring.m"; kr: Keyring; IPint, RSAsk, RSApk, RSAsig, DSAsk, DSApk, DSAsig, DigestState: import kr;include "factotum.m"; fact: Factotum;include "encoding.m"; base16, base64: Encoding;include "util0.m"; util: Util0; readfile, hex, prefix, suffix, rev, l2a, max, min, warn, join, eq, g32i, g64, p32, p32i, p64: import util;include "sshfmt.m"; sshfmt: Sshfmt; Val: import sshfmt; Tbyte, Tbool, Tint, Tbig, Tnames, Tstr, Tmpint: import sshfmt; valbool, valbyte, valint, valbig, valmpint, valnames, valstr, valbytes, valbuf: import sshfmt;include "sshlib.m"; Padmin: con 4;Packetunitmin: con 8;Pktlenmin: con 16;Pktlenmax: con 35000; # from ssh rfc Dhexchangemin: con 1*1024;Dhexchangewant: con 1*1024; # 2*1024 is recommended, but it is too slowDhexchangemax: con 8*1024; Seqmax: con big 2**32; dhgroup1, dhgroup14: ref Dh; # what we support. these arrays are index by types in sshlib.m, keep them in sync!knownkex := array[] of { "diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group-exchange-sha1",};knownhostkey := array[] of { "ssh-dss", "ssh-rsa",};knownenc := array[] of { "none", "aes128-cbc", "aes192-cbc", "aes256-cbc", "idea-cbc", # untested "arcfour", "aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour128", "arcfour256", "3des-cbc", # "blowfish-cbc", # doesn't work};knownmac := array[] of { "none", "hmac-sha1", "hmac-sha1-96", "hmac-md5", "hmac-md5-96",};knowncompr := array[] of { "none",};knownauthmeth := array[] of { "publickey", "password",}; # what we want to do by default, first is preferreddefkex := array[] of {Dgroupexchange, Dgroup14, Dgroup1};defhostkey := array[] of {Hrsa, Hdss};defenc := array[] of {Eaes128cbc, Eaes192cbc, Eaes256cbc, Eaes128ctr, Eaes192ctr, Eaes256ctr, Earcfour128, Earcfour256, Earcfour, E3descbc};defmac := array[] of {Msha1_96, Msha1, Mmd5, Mmd5_96};defcompr := array[] of {Cnone};defauthmeth := array[] of {Apublickey, Apassword}; msgnames := array[] of {SSH_MSG_DISCONNECT => "disconnect",SSH_MSG_IGNORE => "ignore",SSH_MSG_UNIMPLEMENTED => "unimplemented",SSH_MSG_DEBUG => "debug",SSH_MSG_SERVICE_REQUEST => "service request",SSH_MSG_SERVICE_ACCEPT => "service accept",SSH_MSG_KEXINIT => "kex init",SSH_MSG_NEWKEYS => "new keys", SSH_MSG_KEXDH_INIT => "kexdh init",SSH_MSG_KEXDH_REPLY => "kexdh reply",SSH_MSG_KEXDH_GEX_INIT => "kexdh gex init",SSH_MSG_KEXDH_GEX_REPLY => "kexdh gex reply",SSH_MSG_KEXDH_GEX_REQUEST => "kexdh gex request", SSH_MSG_USERAUTH_REQUEST => "userauth request",SSH_MSG_USERAUTH_FAILURE => "userauth failure",SSH_MSG_USERAUTH_SUCCESS => "userauth success",SSH_MSG_USERAUTH_BANNER => "userauth banner", SSH_MSG_GLOBAL_REQUEST => "global request",SSH_MSG_REQUEST_SUCCESS => "request success",SSH_MSG_REQUEST_FAILURE => "request failure",SSH_MSG_CHANNEL_OPEN => "channel open",SSH_MSG_CHANNEL_OPEN_CONFIRMATION => "channel open confirmation",SSH_MSG_CHANNEL_OPEN_FAILURE => "open failure",SSH_MSG_CHANNEL_WINDOW_ADJUST => "window adjust",SSH_MSG_CHANNEL_DATA => "channel data",SSH_MSG_CHANNEL_EXTENDED_DATA => "channel extended data",SSH_MSG_CHANNEL_EOF => "channel eof",SSH_MSG_CHANNEL_CLOSE => "channel close",SSH_MSG_CHANNEL_REQUEST => "channel request",SSH_MSG_CHANNEL_SUCCESS => "channel success",SSH_MSG_CHANNEL_FAILURE => "channel failure",}; init(){ sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; bufio->open("/dev/null", Bufio->OREAD); env = load Env Env->PATH; str = load String String->PATH; random = load Random Random->PATH; kr = load Keyring Keyring->PATH; base16 = load Encoding Encoding->BASE16PATH; base64 = load Encoding Encoding->BASE64PATH; fact = load Factotum Factotum->PATH; fact->init(); util = load Util0 Util0->PATH; util->init(); sshfmt = load Sshfmt Sshfmt->PATH; sshfmt->init(); group1primestr := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"+ "FFFFFFFFFFFFFFFF"; group1prime := IPint.strtoip(group1primestr, 16); group1gen := IPint.inttoip(2); dhgroup1 = ref Dh (group1prime, group1gen, 1024); group14primestr := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF"; group14prime := IPint.strtoip(group14primestr, 16); group14gen := IPint.inttoip(2); dhgroup14 = ref Dh (group14prime, group14gen, 2048);} msgname(t: int): string{ if(t < 0 || t >= len msgnames || msgnames[t] == nil) return "unknown"; return msgnames[t];} handshake(fd: ref Sys->FD, addr: string, wantcfg: ref Cfg): (ref Sshc, string){ b := bufio->fopen(fd, Bufio->OREAD); if(b == nil) return (nil, sprint("bufio fopen: %r")); lident := "SSH-2.0-inferno0"; if(sys->fprint(fd, "%s\r\n", lident) < 0) return (nil, sprint("write handshake: %r")); rident: string; for(;;) { rident = b.gets('\n'); if(rident == nil || rident[len rident-1] != '\n') return (nil, sprint("eof before identification line")); if(!prefix("SSH-", rident)) continue; if(len rident > 255) return (nil, sprint("identification from remote too long, invalid")); rident = rident[:len rident-1]; if(suffix("\r", rident)) rident = rident[:len rident-1]; # note: rident (minus \n or \r\n) is used in key exchange, must be left as is (rversion, rname) := str->splitstrl(rident[len "SSH-":], "-"); if(rname == nil) return (nil, sprint("bad remote identification %#q, missing 'name'", rident)); rcomment: string; (rname, rcomment) = str->splitstrl(rname[1:], " "); say(sprint("have remote version %q, name %q, comment %q", rversion, rname, rcomment)); if(rversion != "2.0" && rversion != "1.99") return (nil, sprint("unsupported remote version %#q", rversion)); break; } nilkey := ref Keys (Cryptalg.new(Enone), Macalg.new(Enone)); c := ref Sshc ( fd, b, addr, nil, big 0, big 0, big 0, big 0, nilkey, nilkey, nil, nil, lident, rident, wantcfg, nil, nil, nil, 0, nil, nil, nil ); return (c, nil);} keyexchangestart(c: ref Sshc): ref Tssh{ say("keyexchangestart"); nilnames := valnames(nil); cookie := random->randombuf(Random->NotQuiteRandom, 16); vals := array[] of { valbuf(cookie), valnames(c.wantcfg.kex), valnames(c.wantcfg.hostkey), valnames(c.wantcfg.encout), valnames(c.wantcfg.encin), valnames(c.wantcfg.macout), valnames(c.wantcfg.macin), valnames(c.wantcfg.comprout), valnames(c.wantcfg.comprin), nilnames, nilnames, valbool(0), valint(0), }; tm := ref Tssh(big 0, SSH_MSG_KEXINIT, vals, 0, nil); c.kexstate |= Kexinitsent; nvals := array[1+len vals] of ref Val; nvals[0] = valbyte(byte SSH_MSG_KEXINIT); nvals[1:] = vals; c.clkexinit = sshfmt->pack(nvals, 0); return tm;} keyexchange(c: ref Sshc, m: ref Rssh): (int, int, list of ref Tssh, string){ { (notimpl, kexdone, tms) := xkeyexchange(c, m); return (notimpl, kexdone, tms, nil); } exception x { "kex:*" => return (0, 0, nil, x[len "kex:":]); "parse:*" => return (0, 0, nil, x[len "parse:":]); }} xparseall(m: ref Rssh, l: list of int): array of ref Val{ (v, err) := sshfmt->parseall(m.buf[1:], l); if(err != nil) raise "parse:"+err; return v;} kexerror(s: string){ raise "kex:"+s;} xkeyexchange(c: ref Sshc, m: ref Rssh): (int, int, list of ref Tssh){ case m.t { SSH_MSG_KEXINIT => kexmsg := list of {16, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tbool, Tint}; v := xparseall(m, kexmsg); c.srvkexinit = m.buf; o := 1; remcfg := ref Cfg ( nil, v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), nil ); if(dflag) { say("languages client to server: "+v[o++].text()); say("languages server to client: "+v[o++].text()); say("first kex packet follows: "+v[o++].text()); say("out config:"); say(c.wantcfg.text()); say("from remote:"); say(remcfg.text()); } err: string; (c.usecfg, err) = Cfg.match(c.wantcfg, remcfg); if(err != nil) kexerror(err); if(dflag) say("chosen config:\n"+c.usecfg.text()); c.auths = authmethods(c.usecfg.authmeth); (c.newtosrv, c.newfromsrv) = Keys.new(c.usecfg); case hd c.usecfg.kex { "diffie-hellman-group1-sha1" => c.kex = ref Kex (0, dhgroup1, nil, nil); "diffie-hellman-group14-sha1" => c.kex = ref Kex (0, dhgroup14, nil, nil); "diffie-hellman-group-exchange-sha1" => c.kex = ref Kex (1, nil, nil, nil); * => raise "internal error, unknown kex alg"; } tms: list of ref Tssh; c.kexstate |= Kexinitreceived; if((c.kexstate & Kexinitsent) == 0) tms = keyexchangestart(c)::nil; tm: ref Tssh; if(c.kex.new) { vals := array[] of {valint(Dhexchangemin), valint(Dhexchangewant), valint(Dhexchangemax)}; tm = ref Tssh (big 0, SSH_MSG_KEX_DH_GEX_REQUEST, vals, 0, nil); } else { gendh(c.kex); vals := array[] of {valmpint(c.kex.e)}; tm = ref Tssh (big 0, SSH_MSG_KEXDH_INIT, vals, 0, nil); } tms = tm::tms; return (0, 0, rev(tms)); SSH_MSG_NEWKEYS => xparseall(m, nil); say("server wants to use newkeys"); if((c.kexstate & Havenewkeys) == 0) kexerror("server wants to use new keys, but none are pending"); tms: list of ref Tssh; if((c.kexstate & Newkeyssent) == 0) { say("writing newkeys to remote"); tm := ref Tssh (big 0, SSH_MSG_NEWKEYS, nil, 0, nil); # pack now we still have the old keys tm.packed = packpacket(c, tm); tms = tm::nil; c.kexstate |= Newkeyssent; } say("now using new keys"); c.tosrv = c.newtosrv; c.fromsrv = c.newfromsrv; c.newtosrv = c.newfromsrv = nil; c.nkeypkts = c.nkeybytes = big 0; c.kexstate &= ~(Kexinitsent|Kexinitreceived|Newkeyssent|Newkeysreceived|Havenewkeys); return (0, 1, tms); SSH_MSG_KEXDH_INIT => kexerror("received SSH_MSG_KEXDH_INIT from server, invalid"); SSH_MSG_KEXDH_REPLY or SSH_MSG_KEXDH_GEX_INIT to # xxx is gex init valid? SSH_MSG_KEXDH_GEX_REQUEST => if((c.kexstate & (Kexinitsent|Kexinitreceived)) != (Kexinitsent|Kexinitreceived)) kexerror("kexdh messages but no kexinit in progress!"); if((c.kexstate & Havenewkeys) != 0) kexerror("kexhd message, but already Havenewkeys?"); if(c.kex.new && m.t == SSH_MSG_KEX_DH_GEX_REPLY || !c.kex.new && m.t == SSH_MSG_KEXDH_REPLY) { v := xparseall(m, list of {Tstr, Tmpint, Tstr}); #string server public host key and certificates (K_S) #mpint f #string signature of H srvksval := v[0]; srvfval := v[1]; srvsigh := v[2].getbytes(); srvks := srvksval.getbytes(); srvf := srvfval.getipint(); # C then # computes K = f^x mod p, H = hash(V_C || V_S || I_C || I_S || K_S # || e || f || K), and verifies the signature s on H. say("calculating key from f from remote"); key := srvf.expmod(c.kex.x, c.kex.dhgroup.prime); c.kex.x = nil; #say(sprint("key %s", key.iptostr(16))); hashbufs: list of array of byte; if(c.kex.new) hashbufs = list of { valstr(c.lident).pack(), valstr(c.rident).pack(), valbytes(c.clkexinit).pack(), valbytes(c.srvkexinit).pack(), srvksval.pack(), valint(Dhexchangemin).pack(), valint(Dhexchangewant).pack(), valint(Dhexchangemax).pack(), valmpint(c.kex.dhgroup.prime).pack(), valmpint(c.kex.dhgroup.gen).pack(), valmpint(c.kex.e).pack(), srvfval.pack(), valmpint(key).pack() }; else hashbufs = list of { valstr(c.lident).pack(), valstr(c.rident).pack(), valbytes(c.clkexinit).pack(), valbytes(c.srvkexinit).pack(), srvksval.pack(), valmpint(c.kex.e).pack(), srvfval.pack(), valmpint(key).pack() }; dhhash := sha1many(hashbufs); zero(c.clkexinit); c.clkexinit = nil; zero(c.srvkexinit); c.srvkexinit = nil; srvfval = nil; if(dflag) say(sprint("hash on dh %s", fingerprint(dhhash))); if(c.sessionid == nil) c.sessionid = dhhash; err := verifyhostkey(c, hd c.usecfg.hostkey, srvks, srvsigh, dhhash); if(err != nil) kexerror(err); # calculate session keys #Encryption keys MUST be computed as HASH, of a known value and K, as follows: #o Initial IV client to server: HASH(K || H || "A" || session_id) # (Here K is encoded as mpint and "A" as byte and session_id as raw # data. "A" means the single character A, ASCII 65). #o Initial IV server to client: HASH(K || H || "B" || session_id) #o Encryption key client to server: HASH(K || H || "C" || session_id) #o Encryption key server to client: HASH(K || H || "D" || session_id) #o Integrity key client to server: HASH(K || H || "E" || session_id) #o Integrity key server to client: HASH(K || H || "F" || session_id) keypack := valmpint(key).pack(); keybitsout := c.newtosrv.crypt.keybits; keybitsin := c.newfromsrv.crypt.keybits; macbitsout := c.newtosrv.mac.keybytes*8; macbitsin := c.newfromsrv.mac.keybytes*8; ivc2s := genkey(keybitsout, keypack, dhhash, "A", c.sessionid); ivs2c := genkey(keybitsin, keypack, dhhash, "B", c.sessionid); enckeyc2s := genkey(keybitsout, keypack, dhhash, "C", c.sessionid); enckeys2c := genkey(keybitsin, keypack, dhhash, "D", c.sessionid); mackeyc2s := genkey(macbitsout, keypack, dhhash, "E", c.sessionid); mackeys2c := genkey(macbitsin, keypack, dhhash, "F", c.sessionid); c.newtosrv.crypt.setup(enckeyc2s, ivc2s); c.newfromsrv.crypt.setup(enckeys2c, ivs2c); c.newtosrv.mac.setup(mackeyc2s); c.newfromsrv.mac.setup(mackeys2c); say("we want to use newkeys"); c.kexstate |= Havenewkeys|Newkeyssent; tm := ref Tssh (big 0, SSH_MSG_NEWKEYS, nil, 0, nil); return (0, 0, tm::nil); } else if(c.kex.new && m.t == SSH_MSG_KEX_DH_GEX_GROUP) { v := xparseall(m, list of {Tmpint, Tmpint}); prime := v[0].getipint(); gen := v[1].getipint(); # xxx should verify these values are sane. c.kex.dhgroup = ref Dh (prime, gen, prime.bits()); gendh(c.kex); vals := array[] of {valmpint(c.kex.e)}; tm := ref Tssh (big 0, SSH_MSG_KEX_DH_GEX_INIT, vals, 0, nil); return (0, 0, tm::nil); } else { kexerror(sprint("unexpected kex message, t %d, new %d", m.t, c.kex.new));Lines 1–500 of 1464. Load more
implement Sshlib; include "sys.m"; sys: Sys; sprint: import sys; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "env.m"; env: Env; include "string.m"; str: String; include "security.m"; random: Random; include "keyring.m"; kr: Keyring; IPint, RSAsk, RSApk, RSAsig, DSAsk, DSApk, DSAsig, DigestState: import kr; include "factotum.m"; fact: Factotum; include "encoding.m"; base16, base64: Encoding; include "util0.m"; util: Util0; readfile, hex, prefix, suffix, rev, l2a, max, min, warn, join, eq, g32i, g64, p32, p32i, p64: import util; include "sshfmt.m"; sshfmt: Sshfmt; Val: import sshfmt; Tbyte, Tbool, Tint, Tbig, Tnames, Tstr, Tmpint: import sshfmt; valbool, valbyte, valint, valbig, valmpint, valnames, valstr, valbytes, valbuf: import sshfmt; include "sshlib.m"; Padmin: con 4; Packetunitmin: con 8; Pktlenmin: con 16; Pktlenmax: con 35000; # from ssh rfc Dhexchangemin: con 1*1024; Dhexchangewant: con 1*1024; # 2*1024 is recommended, but it is too slow Dhexchangemax: con 8*1024; Seqmax: con big 2**32; dhgroup1, dhgroup14: ref Dh; # what we support. these arrays are index by types in sshlib.m, keep them in sync! knownkex := array[] of { "diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "diffie-hellman-group-exchange-sha1", }; knownhostkey := array[] of { "ssh-dss", "ssh-rsa", }; knownenc := array[] of { "none", "aes128-cbc", "aes192-cbc", "aes256-cbc", "idea-cbc", # untested "arcfour", "aes128-ctr", "aes192-ctr", "aes256-ctr", "arcfour128", "arcfour256", "3des-cbc", # "blowfish-cbc", # doesn't work }; knownmac := array[] of { "none", "hmac-sha1", "hmac-sha1-96", "hmac-md5", "hmac-md5-96", }; knowncompr := array[] of { "none", }; knownauthmeth := array[] of { "publickey", "password", }; # what we want to do by default, first is preferred defkex := array[] of {Dgroupexchange, Dgroup14, Dgroup1}; defhostkey := array[] of {Hrsa, Hdss}; defenc := array[] of {Eaes128cbc, Eaes192cbc, Eaes256cbc, Eaes128ctr, Eaes192ctr, Eaes256ctr, Earcfour128, Earcfour256, Earcfour, E3descbc}; defmac := array[] of {Msha1_96, Msha1, Mmd5, Mmd5_96}; defcompr := array[] of {Cnone}; defauthmeth := array[] of {Apublickey, Apassword}; msgnames := array[] of { SSH_MSG_DISCONNECT => "disconnect", SSH_MSG_IGNORE => "ignore", SSH_MSG_UNIMPLEMENTED => "unimplemented", SSH_MSG_DEBUG => "debug", SSH_MSG_SERVICE_REQUEST => "service request", SSH_MSG_SERVICE_ACCEPT => "service accept", SSH_MSG_KEXINIT => "kex init", SSH_MSG_NEWKEYS => "new keys", SSH_MSG_KEXDH_INIT => "kexdh init", SSH_MSG_KEXDH_REPLY => "kexdh reply", SSH_MSG_KEXDH_GEX_INIT => "kexdh gex init", SSH_MSG_KEXDH_GEX_REPLY => "kexdh gex reply", SSH_MSG_KEXDH_GEX_REQUEST => "kexdh gex request", SSH_MSG_USERAUTH_REQUEST => "userauth request", SSH_MSG_USERAUTH_FAILURE => "userauth failure", SSH_MSG_USERAUTH_SUCCESS => "userauth success", SSH_MSG_USERAUTH_BANNER => "userauth banner", SSH_MSG_GLOBAL_REQUEST => "global request", SSH_MSG_REQUEST_SUCCESS => "request success", SSH_MSG_REQUEST_FAILURE => "request failure", SSH_MSG_CHANNEL_OPEN => "channel open", SSH_MSG_CHANNEL_OPEN_CONFIRMATION => "channel open confirmation", SSH_MSG_CHANNEL_OPEN_FAILURE => "open failure", SSH_MSG_CHANNEL_WINDOW_ADJUST => "window adjust", SSH_MSG_CHANNEL_DATA => "channel data", SSH_MSG_CHANNEL_EXTENDED_DATA => "channel extended data", SSH_MSG_CHANNEL_EOF => "channel eof", SSH_MSG_CHANNEL_CLOSE => "channel close", SSH_MSG_CHANNEL_REQUEST => "channel request", SSH_MSG_CHANNEL_SUCCESS => "channel success", SSH_MSG_CHANNEL_FAILURE => "channel failure", }; init() { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; bufio->open("/dev/null", Bufio->OREAD); env = load Env Env->PATH; str = load String String->PATH; random = load Random Random->PATH; kr = load Keyring Keyring->PATH; base16 = load Encoding Encoding->BASE16PATH; base64 = load Encoding Encoding->BASE64PATH; fact = load Factotum Factotum->PATH; fact->init(); util = load Util0 Util0->PATH; util->init(); sshfmt = load Sshfmt Sshfmt->PATH; sshfmt->init(); group1primestr := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"+ "FFFFFFFFFFFFFFFF"; group1prime := IPint.strtoip(group1primestr, 16); group1gen := IPint.inttoip(2); dhgroup1 = ref Dh (group1prime, group1gen, 1024); group14primestr := "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF"; group14prime := IPint.strtoip(group14primestr, 16); group14gen := IPint.inttoip(2); dhgroup14 = ref Dh (group14prime, group14gen, 2048); } msgname(t: int): string { if(t < 0 || t >= len msgnames || msgnames[t] == nil) return "unknown"; return msgnames[t]; } handshake(fd: ref Sys->FD, addr: string, wantcfg: ref Cfg): (ref Sshc, string) { b := bufio->fopen(fd, Bufio->OREAD); if(b == nil) return (nil, sprint("bufio fopen: %r")); lident := "SSH-2.0-inferno0"; if(sys->fprint(fd, "%s\r\n", lident) < 0) return (nil, sprint("write handshake: %r")); rident: string; for(;;) { rident = b.gets('\n'); if(rident == nil || rident[len rident-1] != '\n') return (nil, sprint("eof before identification line")); if(!prefix("SSH-", rident)) continue; if(len rident > 255) return (nil, sprint("identification from remote too long, invalid")); rident = rident[:len rident-1]; if(suffix("\r", rident)) rident = rident[:len rident-1]; # note: rident (minus \n or \r\n) is used in key exchange, must be left as is (rversion, rname) := str->splitstrl(rident[len "SSH-":], "-"); if(rname == nil) return (nil, sprint("bad remote identification %#q, missing 'name'", rident)); rcomment: string; (rname, rcomment) = str->splitstrl(rname[1:], " "); say(sprint("have remote version %q, name %q, comment %q", rversion, rname, rcomment)); if(rversion != "2.0" && rversion != "1.99") return (nil, sprint("unsupported remote version %#q", rversion)); break; } nilkey := ref Keys (Cryptalg.new(Enone), Macalg.new(Enone)); c := ref Sshc ( fd, b, addr, nil, big 0, big 0, big 0, big 0, nilkey, nilkey, nil, nil, lident, rident, wantcfg, nil, nil, nil, 0, nil, nil, nil ); return (c, nil); } keyexchangestart(c: ref Sshc): ref Tssh { say("keyexchangestart"); nilnames := valnames(nil); cookie := random->randombuf(Random->NotQuiteRandom, 16); vals := array[] of { valbuf(cookie), valnames(c.wantcfg.kex), valnames(c.wantcfg.hostkey), valnames(c.wantcfg.encout), valnames(c.wantcfg.encin), valnames(c.wantcfg.macout), valnames(c.wantcfg.macin), valnames(c.wantcfg.comprout), valnames(c.wantcfg.comprin), nilnames, nilnames, valbool(0), valint(0), }; tm := ref Tssh(big 0, SSH_MSG_KEXINIT, vals, 0, nil); c.kexstate |= Kexinitsent; nvals := array[1+len vals] of ref Val; nvals[0] = valbyte(byte SSH_MSG_KEXINIT); nvals[1:] = vals; c.clkexinit = sshfmt->pack(nvals, 0); return tm; } keyexchange(c: ref Sshc, m: ref Rssh): (int, int, list of ref Tssh, string) { { (notimpl, kexdone, tms) := xkeyexchange(c, m); return (notimpl, kexdone, tms, nil); } exception x { "kex:*" => return (0, 0, nil, x[len "kex:":]); "parse:*" => return (0, 0, nil, x[len "parse:":]); } } xparseall(m: ref Rssh, l: list of int): array of ref Val { (v, err) := sshfmt->parseall(m.buf[1:], l); if(err != nil) raise "parse:"+err; return v; } kexerror(s: string) { raise "kex:"+s; } xkeyexchange(c: ref Sshc, m: ref Rssh): (int, int, list of ref Tssh) { case m.t { SSH_MSG_KEXINIT => kexmsg := list of {16, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tnames, Tbool, Tint}; v := xparseall(m, kexmsg); c.srvkexinit = m.buf; o := 1; remcfg := ref Cfg ( nil, v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), v[o++].getnames(), nil ); if(dflag) { say("languages client to server: "+v[o++].text()); say("languages server to client: "+v[o++].text()); say("first kex packet follows: "+v[o++].text()); say("out config:"); say(c.wantcfg.text()); say("from remote:"); say(remcfg.text()); } err: string; (c.usecfg, err) = Cfg.match(c.wantcfg, remcfg); if(err != nil) kexerror(err); if(dflag) say("chosen config:\n"+c.usecfg.text()); c.auths = authmethods(c.usecfg.authmeth); (c.newtosrv, c.newfromsrv) = Keys.new(c.usecfg); case hd c.usecfg.kex { "diffie-hellman-group1-sha1" => c.kex = ref Kex (0, dhgroup1, nil, nil); "diffie-hellman-group14-sha1" => c.kex = ref Kex (0, dhgroup14, nil, nil); "diffie-hellman-group-exchange-sha1" => c.kex = ref Kex (1, nil, nil, nil); * => raise "internal error, unknown kex alg"; } tms: list of ref Tssh; c.kexstate |= Kexinitreceived; if((c.kexstate & Kexinitsent) == 0) tms = keyexchangestart(c)::nil; tm: ref Tssh; if(c.kex.new) { vals := array[] of {valint(Dhexchangemin), valint(Dhexchangewant), valint(Dhexchangemax)}; tm = ref Tssh (big 0, SSH_MSG_KEX_DH_GEX_REQUEST, vals, 0, nil); } else { gendh(c.kex); vals := array[] of {valmpint(c.kex.e)}; tm = ref Tssh (big 0, SSH_MSG_KEXDH_INIT, vals, 0, nil); } tms = tm::tms; return (0, 0, rev(tms)); SSH_MSG_NEWKEYS => xparseall(m, nil); say("server wants to use newkeys"); if((c.kexstate & Havenewkeys) == 0) kexerror("server wants to use new keys, but none are pending"); tms: list of ref Tssh; if((c.kexstate & Newkeyssent) == 0) { say("writing newkeys to remote"); tm := ref Tssh (big 0, SSH_MSG_NEWKEYS, nil, 0, nil); # pack now we still have the old keys tm.packed = packpacket(c, tm); tms = tm::nil; c.kexstate |= Newkeyssent; } say("now using new keys"); c.tosrv = c.newtosrv; c.fromsrv = c.newfromsrv; c.newtosrv = c.newfromsrv = nil; c.nkeypkts = c.nkeybytes = big 0; c.kexstate &= ~(Kexinitsent|Kexinitreceived|Newkeyssent|Newkeysreceived|Havenewkeys); return (0, 1, tms); SSH_MSG_KEXDH_INIT => kexerror("received SSH_MSG_KEXDH_INIT from server, invalid"); SSH_MSG_KEXDH_REPLY or SSH_MSG_KEXDH_GEX_INIT to # xxx is gex init valid? SSH_MSG_KEXDH_GEX_REQUEST => if((c.kexstate & (Kexinitsent|Kexinitreceived)) != (Kexinitsent|Kexinitreceived)) kexerror("kexdh messages but no kexinit in progress!"); if((c.kexstate & Havenewkeys) != 0) kexerror("kexhd message, but already Havenewkeys?"); if(c.kex.new && m.t == SSH_MSG_KEX_DH_GEX_REPLY || !c.kex.new && m.t == SSH_MSG_KEXDH_REPLY) { v := xparseall(m, list of {Tstr, Tmpint, Tstr}); #string server public host key and certificates (K_S) #mpint f #string signature of H srvksval := v[0]; srvfval := v[1]; srvsigh := v[2].getbytes(); srvks := srvksval.getbytes(); srvf := srvfval.getipint(); # C then # computes K = f^x mod p, H = hash(V_C || V_S || I_C || I_S || K_S # || e || f || K), and verifies the signature s on H. say("calculating key from f from remote"); key := srvf.expmod(c.kex.x, c.kex.dhgroup.prime); c.kex.x = nil; #say(sprint("key %s", key.iptostr(16))); hashbufs: list of array of byte; if(c.kex.new) hashbufs = list of { valstr(c.lident).pack(), valstr(c.rident).pack(), valbytes(c.clkexinit).pack(), valbytes(c.srvkexinit).pack(), srvksval.pack(), valint(Dhexchangemin).pack(), valint(Dhexchangewant).pack(), valint(Dhexchangemax).pack(), valmpint(c.kex.dhgroup.prime).pack(), valmpint(c.kex.dhgroup.gen).pack(), valmpint(c.kex.e).pack(), srvfval.pack(), valmpint(key).pack() }; else hashbufs = list of { valstr(c.lident).pack(), valstr(c.rident).pack(), valbytes(c.clkexinit).pack(), valbytes(c.srvkexinit).pack(), srvksval.pack(), valmpint(c.kex.e).pack(), srvfval.pack(), valmpint(key).pack() }; dhhash := sha1many(hashbufs); zero(c.clkexinit); c.clkexinit = nil; zero(c.srvkexinit); c.srvkexinit = nil; srvfval = nil; if(dflag) say(sprint("hash on dh %s", fingerprint(dhhash))); if(c.sessionid == nil) c.sessionid = dhhash; err := verifyhostkey(c, hd c.usecfg.hostkey, srvks, srvsigh, dhhash); if(err != nil) kexerror(err); # calculate session keys #Encryption keys MUST be computed as HASH, of a known value and K, as follows: #o Initial IV client to server: HASH(K || H || "A" || session_id) # (Here K is encoded as mpint and "A" as byte and session_id as raw # data. "A" means the single character A, ASCII 65). #o Initial IV server to client: HASH(K || H || "B" || session_id) #o Encryption key client to server: HASH(K || H || "C" || session_id) #o Encryption key server to client: HASH(K || H || "D" || session_id) #o Integrity key client to server: HASH(K || H || "E" || session_id) #o Integrity key server to client: HASH(K || H || "F" || session_id) keypack := valmpint(key).pack(); keybitsout := c.newtosrv.crypt.keybits; keybitsin := c.newfromsrv.crypt.keybits; macbitsout := c.newtosrv.mac.keybytes*8; macbitsin := c.newfromsrv.mac.keybytes*8; ivc2s := genkey(keybitsout, keypack, dhhash, "A", c.sessionid); ivs2c := genkey(keybitsin, keypack, dhhash, "B", c.sessionid); enckeyc2s := genkey(keybitsout, keypack, dhhash, "C", c.sessionid); enckeys2c := genkey(keybitsin, keypack, dhhash, "D", c.sessionid); mackeyc2s := genkey(macbitsout, keypack, dhhash, "E", c.sessionid); mackeys2c := genkey(macbitsin, keypack, dhhash, "F", c.sessionid); c.newtosrv.crypt.setup(enckeyc2s, ivc2s); c.newfromsrv.crypt.setup(enckeys2c, ivs2c); c.newtosrv.mac.setup(mackeyc2s); c.newfromsrv.mac.setup(mackeys2c); say("we want to use newkeys"); c.kexstate |= Havenewkeys|Newkeyssent; tm := ref Tssh (big 0, SSH_MSG_NEWKEYS, nil, 0, nil); return (0, 0, tm::nil); } else if(c.kex.new && m.t == SSH_MSG_KEX_DH_GEX_GROUP) { v := xparseall(m, list of {Tmpint, Tmpint}); prime := v[0].getipint(); gen := v[1].getipint(); # xxx should verify these values are sane. c.kex.dhgroup = ref Dh (prime, gen, prime.bits()); gendh(c.kex); vals := array[] of {valmpint(c.kex.e)}; tm := ref Tssh (big 0, SSH_MSG_KEX_DH_GEX_INIT, vals, 0, nil); return (0, 0, tm::nil); } else { kexerror(sprint("unexpected kex message, t %d, new %d", m.t, c.kex.new));