~sircmpwn/hare-ssh

4d475c1c1aa418d05e9a38f16c6d61f4505fa4f7 — Armin Preiml 6 months ago 9fe392e
update to memio
M cmd/ssh-agent/main.ha => cmd/ssh-agent/main.ha +5 -5
@@ 1,6 1,6 @@
// NOTICE: This code is for testing & demonstration purposes only.
use bytes;
use bufio;
use memio;
use errors;
use format::ssh;
use io;


@@ 120,12 120,12 @@ fn handle_add_ident(
	msg: *agent::add_identity,
	agent: *agent::agent,
) (void | agent::error) = {
	let sink = bufio::dynamic(io::mode::WRITE);
	let sink = memio::dynamic();
	ssh::encode_pubkey(&sink, msg.key)!;
	append(state.identities, identity {
		comment = strings::dup(msg.comment),
		privkey = msg.key,
		pubkey = bufio::buffer(&sink),
		pubkey = memio::buffer(&sink),
	});
	const answer: agent::message = agent::agent_success;
	agent::writemsg(agent, &answer)?;


@@ 154,12 154,12 @@ fn handle_sign_request(
		return;
	};

	let buf = bufio::dynamic(io::mode::WRITE);
	let buf = memio::dynamic();
	defer io::close(&buf)!;
	ssh::sign(&buf, key.privkey, msg.data)!;

	const answer: agent::message = agent::sign_response {
		signature = bufio::buffer(&buf),
		signature = memio::buffer(&buf),
	};
	agent::writemsg(agent, &answer)?;
	log::printfln("Signed challenge with key {}", key.comment);

M cmd/sshkey/main.ha => cmd/sshkey/main.ha +2 -2
@@ 1,4 1,4 @@
use bufio;
use memio;
use encoding::hex;
use encoding::base64;
use fmt;


@@ 38,7 38,7 @@ export fn main() void = {
		tty::noecho(&termios)!;

		fmt::errorf("Enter passphrase: ")!;
		const pass = bufio::scanline(tty)!;
		const pass = memio::scanline(tty)!;
		tty::termios_restore(&termios);
		const pass = match (pass) {
		case io::EOF =>

M format/ssh/+test/key.ha => format/ssh/+test/key.ha +6 -6
@@ 1,30 1,30 @@
use bufio;
use memio;
use bytes;
use io;
use strings;

@test fn encode_pubkeystr() void = {
	const reader = bufio::fixed(testkey, io::mode::READ);
	const reader = memio::fixed(testkey);
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	const private = decodeprivate(&sshkey)!;

	let buf = bufio::dynamic(io::mode::WRITE);
	let buf = memio::dynamic();
	defer io::close(&buf)!;
	encode_pubkeystr(&buf, private)!;
	assert(bytes::equal(bufio::buffer(&buf), strings::toutf8(testkey_pub)));
	assert(bytes::equal(memio::buffer(&buf), strings::toutf8(testkey_pub)));
};

const testkey_pub: str = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJhFq0avPAyp4BZrFbnSIj7YUMeiha1CaRfMUuCJYaOG sircmpwn@taiga";

fn copykey(sink: *key, src: *key) void = {
	let pubbuf = bufio::dynamic(io::mode::RDWR);
	let pubbuf = memio::dynamic();
	defer io::close(&pubbuf)!;
	key_encoderawpub(src, &pubbuf)!;
	io::seek(&pubbuf, 0, io::whence::SET)!;
	key_decoderawpub(sink, &pubbuf)!;

	let privbuf = bufio::dynamic(io::mode::RDWR);
	let privbuf = memio::dynamic();
	defer io::close(&privbuf)!;
	key_encoderawpriv(src, &privbuf)!;
	io::seek(&privbuf, 0, io::whence::SET)!;

M format/ssh/+test/rawkey.ha => format/ssh/+test/rawkey.ha +2 -2
@@ 1,9 1,9 @@
use bufio;
use memio;
use bytes;
use io;

@test fn ed25519_rawkeys() void = {
	const reader = bufio::fixed(testkey, io::mode::READ);
	const reader = memio::fixed(testkey);
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	const private = decodeprivate(&sshkey)!;

M format/ssh/+test/rsakeys.ha => format/ssh/+test/rsakeys.ha +9 -9
@@ 1,4 1,4 @@
use bufio;
use memio;
use bytes;
use encoding::hex;
use io;


@@ 6,7 6,7 @@ use os;
use strings;

@test fn decodersaprivate() void = {
	const reader = bufio::fixed(strings::toutf8(rsatestkey), io::mode::READ);
	const reader = memio::fixed(strings::toutf8(rsatestkey));
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	assert(sshkey.cipher == "none");


@@ 16,11 16,11 @@ use strings;

	let priv = decodeprivate(&sshkey)!;

	let pubbuf = bufio::dynamic(io::mode::WRITE);
	let pubbuf = memio::dynamic();
	defer io::close(&pubbuf)!;
	encode_pubkeystr(&pubbuf, priv)!;

	assert(bytes::equal(strings::toutf8(rsapukeystr), bufio::buffer(&pubbuf)));
	assert(bytes::equal(strings::toutf8(rsapukeystr), memio::buffer(&pubbuf)));
};

const rsatestkey: str = `-----BEGIN OPENSSH PRIVATE KEY-----


@@ 112,23 112,23 @@ const rsasig: [_]u8 = [
];

@test fn rsasign() void = {
	const reader = bufio::fixed(strings::toutf8(rsatestkey), io::mode::READ);
	const reader = memio::fixed(strings::toutf8(rsatestkey));
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);

	let sigbuf = bufio::fixed(rsasig, io::mode::READ);
	let sigbuf = memio::fixed(rsasig);
	let key = decodeprivate(&sshkey)!;
	verify(&sigbuf, key, rsamsg)!;

	let newsig = bufio::dynamic(io::mode::WRITE);
	let newsig = memio::dynamic();
	defer io::close(&newsig)!;
	sign(&newsig, key, rsamsg)!;

	assert(bytes::equal(rsasig, bufio::buffer(&newsig)));
	assert(bytes::equal(rsasig, memio::buffer(&newsig)));
};

@test fn rsa_rawkeys() void = {
	const reader = bufio::fixed(strings::toutf8(rsatestkey), io::mode::READ);
	const reader = memio::fixed(strings::toutf8(rsatestkey));
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	const private = decodeprivate(&sshkey)!;

M format/ssh/+test/sign.ha => format/ssh/+test/sign.ha +3 -3
@@ 1,17 1,17 @@
use bufio;
use memio;
use bytes;
use io;
use strings;

@test fn sign() void = {
	const reader = bufio::fixed(testkey, io::mode::READ);
	const reader = memio::fixed(testkey);
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	assert(!isencrypted(&sshkey));
	const skey = decodeprivate(&sshkey)!;

	const msg = strings::toutf8("hello world");
	let buf = bufio::dynamic(io::mode::RDWR);
	let buf = memio::dynamic();
	defer io::close(&buf)!;
	sign(&buf, skey, msg)!;


M format/ssh/+test/sshprivkey.ha => format/ssh/+test/sshprivkey.ha +3 -3
@@ 1,10 1,10 @@
use bufio;
use memio;
use bytes;
use io;
use strings;

@test fn decodesshprivate() void = {
	const reader = bufio::fixed(testkey, io::mode::READ);
	const reader = memio::fixed(testkey);
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	assert(sshkey.cipher == "none");


@@ 19,7 19,7 @@ use strings;
};

@test fn decrypt() void = {
	const reader = bufio::fixed(testkey_encrypted, io::mode::READ);
	const reader = memio::fixed(testkey_encrypted);
	const sshkey = decodesshprivate(&reader)!;
	defer sshprivkey_finish(&sshkey);
	assert(sshkey.cipher == "aes256-ctr");

M format/ssh/key.ha => format/ssh/key.ha +2 -2
@@ 1,9 1,9 @@
use bufio;
use bytes;
use encoding::base64;
use endian;
use fmt;
use io;
use memio;

export type decodepubfunc = fn(key: *key, buf: io::handle) (void | error);
export type decodeprivfunc = fn(key: *key, buf: io::handle) (void | error);


@@ 44,7 44,7 @@ export type key = struct {
// when they're done using it.
export fn decodeprivate(src: *sshprivkey) (*key | error) = {
	assert(!isencrypted(src));
	const buf = bufio::fixed(src.privkey, io::mode::READ);
	const buf = memio::fixed(src.privkey);

	let verify: [8]u8 = [0...];
	io::read(&buf, verify)!;

M format/ssh/sshprivkey.ha => format/ssh/sshprivkey.ha +3 -3
@@ 1,4 1,4 @@
use bufio;
use memio;
use bytes;
use encoding::pem;
use endian;


@@ 99,7 99,7 @@ export fn decrypt(key: *sshprivkey, pass: []u8) (void | error) = {
		bytes::zero(ckey);
		free(ckey);
	};
	let kdfbuf = bufio::fixed(key.kdf, io::mode::READ);
	let kdfbuf = memio::fixed(key.kdf);

	switch (key.kdfname) {
	case "bcrypt" =>


@@ 111,7 111,7 @@ export fn decrypt(key: *sshprivkey, pass: []u8) (void | error) = {
		return badcipher;
	};

	let secretbuf = bufio::fixed(key.privkey, io::mode::READ);
	let secretbuf = memio::fixed(key.privkey);
	const cipher = cipher.init(&secretbuf,
		ckey[..cipher.keylen], ckey[cipher.keylen..]);
	defer cipher_free(cipher);

M net/ssh/agent/agent.ha => net/ssh/agent/agent.ha +5 -5
@@ 1,4 1,4 @@
use bufio;
use memio;
use io;
use os;



@@ 17,7 17,7 @@ export fn strerror(err: error) const str = {

export type agent = struct {
	fd: io::handle,
	buf: bufio::memstream,
	buf: memio::stream,
};

// Creates a new SSH agent for the given socket. The user must call


@@ 25,7 25,7 @@ export type agent = struct {
export fn new(fd: io::handle) agent = {
	return agent {
		fd = fd,
		buf = bufio::dynamic(io::mode::WRITE),
		buf = memio::dynamic(),
	};
};



@@ 46,11 46,11 @@ export fn readmsg(agent: *agent) (message | io::EOF | void | error) = {
		return io::EOF;
	};
	io::write(&agent.buf, buf[..z])!;
	const msg = match (parse(bufio::buffer(&agent.buf))?) {
	const msg = match (parse(memio::buffer(&agent.buf))?) {
	case size =>
		return;
	case let msg: message =>
		bufio::reset(&agent.buf);
		memio::reset(&agent.buf);
		yield msg;
	};
	return msg;

M net/ssh/agent/proto.ha => net/ssh/agent/proto.ha +4 -4
@@ 1,7 1,7 @@
use bufio;
use endian;
use format::ssh;
use io;
use memio;
use strings;
use types;



@@ 23,7 23,7 @@ export fn parse(msg: []u8) (message | size | invalid) = {
	};

	const mtype = msg[4];
	const buf = bufio::fixed(msg[5..], io::mode::READ);
	const buf = memio::fixed(msg[5..]);
	switch (mtype) {
	case messagetype::REQUEST_IDENTITIES =>
		return request_identities;


@@ 171,7 171,7 @@ fn readslice(src: io::handle) ([]u8 | invalid) = {
export fn serialize(msg: *message) []u8 = {
	// TODO: This might be better if we wrote to io::empty to get the size
	// instead of dynamically allocating a buffer.
	const buf = bufio::dynamic(io::mode::WRITE);
	const buf = memio::dynamic();
	// TODO: Match overhaul
	// TODO: Flesh all of these out
	const mtype = match (*msg) {


@@ 210,7 210,7 @@ export fn serialize(msg: *message) []u8 = {
		yield messagetype::EXTENSION_FAILURE;
	};

	let buf = bufio::buffer(&buf);
	let buf = memio::buffer(&buf);
	insert(buf[0], mtype);

	assert(len(buf) <= types::U32_MAX, "message exceeds maximum length");

M net/ssh/client.ha => net/ssh/client.ha +6 -6
@@ 1,10 1,10 @@
use bufio;
use bytes;
use crypto::random;
use endian;
use errors;
use fmt;
use io;
use memio;
use os;
use strings;
use types;


@@ 239,10 239,10 @@ fn client_read(client: *client) (packet | io::EOF | error) = {
// Encodes a packet into the write buffer and performs the initial write.
fn client_send(client: *client, pkt: *packet) (void | error) = {
	// TODO: reuse wbuf here
	let buf = bufio::dynamic(io::mode::WRITE);
	let buf = memio::dynamic();
	encode(&buf, pkt)!;

	const buffer = bufio::buffer(&buf);
	const buffer = memio::buffer(&buf);
	if (len(client.exchash) == 0 && buffer[0] == SSH_MSG_KEXINIT) {
		client.client_kexinit = alloc(buffer...);
	};


@@ 254,7 254,7 @@ fn client_send(client: *client, pkt: *packet) (void | error) = {
	case null =>
		yield;
	};
	const payloadlen = len(bufio::buffer(&buf));
	const payloadlen = len(memio::buffer(&buf));
	let padding = blksz - ((payloadlen + 5) % blksz);
	if (padding < 4) {
		padding = 4;


@@ 265,7 265,7 @@ fn client_send(client: *client, pkt: *packet) (void | error) = {
	io::writeall(&buf, padbuf[..padding])!;

	free(client.wbuf);
	client.wbuf = bufio::buffer(&buf);
	client.wbuf = memio::buffer(&buf);

	assert(padding < types::U8_MAX);
	insert(client.wbuf[0], padding: u8);


@@ 311,7 311,7 @@ fn client_decode(client: *client) (packet | error) = {
	};

	// TODO: Verify MAC
	const reader = bufio::fixed(rbuf, io::mode::READ);
	const reader = memio::fixed(rbuf);
	const pkt = decode(&reader)?;
	client.rptr = msglen;
	client.serverseq += 1;

M net/ssh/kex.ha => net/ssh/kex.ha +6 -5
@@ 1,4 1,4 @@
use bufio;
use memio;
use crypto::math;
use crypto::random;
use crypto::sha256;


@@ 7,6 7,7 @@ use errors;
use format::ssh;
use hash;
use io;
use memio;
use strings;

export type kex = struct {


@@ 127,7 128,7 @@ fn curve25519_sha256_derivekey(

		// TODO: Verify the server's host key (against e.g. known_hosts)
		const pkt = pkt as kex_ecdh_reply;
		const reader = bufio::fixed(pkt.k_s, io::mode::READ);
		const reader = memio::fixed(pkt.k_s);
		const hostkey = match (ssh::decodepublic(&reader)) {
		case let key: *ssh::key =>
			yield key;


@@ 149,7 150,7 @@ fn curve25519_sha256_derivekey(

		if (len(client.exchash) == 0) {
			// TODO: Move me to a helper function
			let buf = bufio::dynamic(io::mode::WRITE);
			let buf = memio::dynamic();
			defer io::close(&buf)!;
			writestr(&buf, strings::rtrim(client.client_version, '\r', '\n'))!;
			writestr(&buf, strings::rtrim(client.server_version, '\r', '\n'))!;


@@ 164,12 165,12 @@ fn curve25519_sha256_derivekey(

			const sha = sha256::sha256();
			let exchash: [sha256::SIZE]u8 = [0...];
			hash::write(&sha, bufio::buffer(&buf));
			hash::write(&sha, memio::buffer(&buf));
			hash::sum(&sha, exchash);
			client.exchash = alloc(exchash...);
		};

		const sig = bufio::fixed(pkt.sig, io::mode::READ);
		const sig = memio::fixed(pkt.sig);
		// TODO other keytype support
		match (ssh::verify(&sig, hostkey: *ssh::ed25519key, client.exchash)) {
		case void =>

M net/ssh/packet.ha => net/ssh/packet.ha +4 -4
@@ 1,4 1,4 @@
use bufio;
use memio;
use io;

// An SSH packet.


@@ 63,7 63,7 @@ export type kex_ecdh_reply = struct {
};

// Decodes an SSH packet.
fn decode(src: *bufio::memstream) (packet | protoerror) = {
fn decode(src: *memio::stream) (packet | protoerror) = {
	switch (readbyte(src)?) {
	case SSH_MSG_KEXINIT =>
		return kexinit_decode(src);


@@ 89,7 89,7 @@ fn encode(out: io::handle, pkt: *packet) (void | error) = {
	};
};

fn kexinit_decode(src: *bufio::memstream) (packet | protoerror) = {
fn kexinit_decode(src: *memio::stream) (packet | protoerror) = {
	let pkt = kexinit { ... };
	match (io::readall(src, pkt.cookie)!) {
	case io::EOF =>


@@ 133,7 133,7 @@ fn kex_ecdh_init_encode(out: io::handle, pkt: kex_ecdh_init) (void | error) = {
	writeslice(out, pkt.q_c)?;
};

fn kex_ecdh_reply_decode(src: *bufio::memstream) (packet | protoerror) = {
fn kex_ecdh_reply_decode(src: *memio::stream) (packet | protoerror) = {
	let pkt = kex_ecdh_reply { ... };
	pkt.k_s = readslice(src)?;
	pkt.q_s = readslice(src)?;

M net/ssh/proto.ha => net/ssh/proto.ha +13 -10
@@ 1,13 1,16 @@
use bufio;
use memio;
use endian;
use io;
use strings;
use types;

fn readbyte(src: *bufio::memstream) (u8 | protoerror) = {
	match (bufio::scanbyte(src)!) {
	case let u: u8 =>
		return u;
fn readbyte(src: *memio::stream) (u8 | protoerror) = {
	let b: [1]u8 = [0];
	match (io::read(src, b)!) {
	case let s: size =>
		assert(s == 1);
		return b[0];
	case io::EOF =>
		return protoerror;
	};


@@ 17,7 20,7 @@ fn writebyte(out: io::handle, val: u8) (void | io::error) = {
	io::write(out, [val])?;
};

fn readbool(src: *bufio::memstream) (bool | protoerror) = {
fn readbool(src: *memio::stream) (bool | protoerror) = {
	return readbyte(src)? != 0;
};



@@ 25,7 28,7 @@ fn writebool(out: io::handle, val: bool) (void | io::error) = {
	return writebyte(out, if (val) 1 else 0)?;
};

fn readu32(src: *bufio::memstream) (u32 | protoerror) = {
fn readu32(src: *memio::stream) (u32 | protoerror) = {
	let buf: [4]u8 = [0...];
	match (io::read(src, buf)!) {
	case io::EOF =>


@@ 44,7 47,7 @@ fn writeu32(out: io::handle, val: u32) (void | io::error) = {
	io::writeall(out, buf)?;
};

fn readslice(src: *bufio::memstream) ([]u8 | protoerror) = {
fn readslice(src: *memio::stream) ([]u8 | protoerror) = {
	const length = readu32(src)?;
	if (length > MAX_PACKETSIZE) {
		return protoerror;


@@ 52,7 55,7 @@ fn readslice(src: *bufio::memstream) ([]u8 | protoerror) = {
	if (length == 0) {
		return [];
	};
	match (bufio::borrowedread(src, length)) {
	match (memio::borrowedread(src, length)) {
	case io::EOF =>
		return protoerror;
	case let buf: []u8 =>


@@ 69,7 72,7 @@ fn writeslice(out: io::handle, slice: []u8) (void | io::error) = {
	io::writeall(out, slice)?;
};

fn readstr(src: *bufio::memstream) (str | protoerror) = {
fn readstr(src: *memio::stream) (str | protoerror) = {
	const slice = readslice(src)?;
	match (strings::fromutf8(slice)) {
	case let s: str =>


@@ 83,7 86,7 @@ fn writestr(out: io::handle, s: str) (void | io::error) = {
	return writeslice(out, strings::toutf8(s));
};

fn readnamelist(src: *bufio::memstream) ([]str | protoerror) = {
fn readnamelist(src: *memio::stream) ([]str | protoerror) = {
	const list = readstr(src)?;
	const tok = strings::tokenize(list, ",");
	let items: []str = [];