~apreiml/hare-ssh

323383d11af6f7a2fd2edfdf1e21a00d261f7df9 — Drew DeVault 1 year, 8 months ago 0550a9b
kex: implement key material generation

Also lays some groundwork for cipher initialization
3 files changed, 99 insertions(+), 9 deletions(-)

M net/ssh/cipher.ha
M net/ssh/client.ha
M net/ssh/kex.ha
M net/ssh/cipher.ha => net/ssh/cipher.ha +34 -0
@@ 2,6 2,9 @@ export type cipher = struct {
	name: str,
	keysz: size,
	blksz: size,

	// Initializes a cipher from a kex object.
	init: *fn(kex: *kex) *cipher,
};

// Returns the name of this cipher.


@@ 26,36 29,43 @@ const ciphertable: [_]cipher = [
	//	name = "chacha20-poly1305@openssh.com",
	//	keysz = 64,
	//	blksz = 8,
	//	init = &chacha20_poly1305_init,
	//},
	cipher {
		name = "aes256-ctr",
		keysz = 32,
		blksz = 16,
		init = &aes256_ctr_init,
	},
	cipher {
		name = "aes192-ctr",
		keysz = 24,
		blksz = 16,
		init = &aes192_ctr_init,
	},
	cipher {
		name = "aes128-ctr",
		keysz = 16,
		blksz = 16,
		init = &aes128_ctr_init,
	},
	cipher {
		name = "aes256-cbc",
		keysz = 32,
		blksz = 16,
		init = &aes256_cbc_init,
	},
	cipher {
		name = "aes192-cbc",
		keysz = 24,
		blksz = 16,
		init = &aes192_cbc_init,
	},
	cipher {
		name = "aes128-cbc",
		keysz = 16,
		blksz = 16,
		init = &aes128_cbc_init,
	},
];



@@ 68,3 78,27 @@ export fn cipher_lookup(name: str) const nullable *cipher = {
	};
	return null;
};

fn aes256_ctr_init(kex: *kex) *cipher = {
	abort(); // TODO
};

fn aes192_ctr_init(kex: *kex) *cipher = {
	abort(); // TODO
};

fn aes128_ctr_init(kex: *kex) *cipher = {
	abort(); // TODO
};

fn aes256_cbc_init(kex: *kex) *cipher = {
	abort(); // TODO
};

fn aes192_cbc_init(kex: *kex) *cipher = {
	abort(); // TODO
};

fn aes128_cbc_init(kex: *kex) *cipher = {
	abort(); // TODO
};

M net/ssh/client.ha => net/ssh/client.ha +3 -3
@@ 194,9 194,9 @@ fn client_keyexch(client: *client) (void | error) = {

	// TODO: Deal with incorrectly guessing the other side's algorithm
	const kex = client.kex as *kex;
	static let keybuf: [128]u8 = [0...];
	assert(kex_keysz(kex) <= len(keybuf));
	kex.getkey(kex, client, keybuf)?;
	static let shared: [128]u8 = [0...];
	assert(kex_keysz(kex) <= len(shared));
	kex.derivekey(kex, client, shared)?;

	const (cipher, mac) = client.nextalgos as (const *cipher, const *mac);
	fmt::errorfln("kex complete; set up cipher {} + {}",

M net/ssh/kex.ha => net/ssh/kex.ha +62 -6
@@ 15,10 15,18 @@ export type kex = struct {

	// Initializes state for a key exchange algorithm.
	init: *fn() *kex,
	// Retrieves the key from a key exchange algorithm. If the process is
	// not complete, [[errors::again]] is returned and the function should
	// be repeated.
	getkey: *fn(kex: *kex, client: *client, out: []u8) (void | error),

	// Derives a key using the key exchange algorithm, writing the shared
	// key to 'out', which must be be able to store at least as many bytes
	// as is required by the kex algorithm's keysize (see [[kex_keysz]]). If
	// the process is not complete, [[errors::again]] is returned and the
	// user should call the function again once the network socket is
	// readable.
	derivekey: *fn(kex: *kex, client: *client, out: []u8) (void | error),

	// Creates the hashing function for this key exchange algorithm.
	hashinit: *fn() *hash::hash,

	// Frees state associated with this key exchange algorithm.
	finish: *fn(kex: *kex) void,
};


@@ 54,11 62,14 @@ type ecdh_state = enum {
	REPLY,
};

type hashinitfn = fn() *hash::hash;

const curve25519_sha256: kex = kex {
	name = "curve25519-sha256",
	keysz = 32,
	init = &curve25519_sha256_init,
	getkey = &curve25519_sha256_getkey,
	derivekey = &curve25519_sha256_derivekey,
	hashinit = &curve25519_sha256_hashinit,
	finish = &curve25519_sha256_finish,
};



@@ 83,7 94,7 @@ fn curve25519_sha256_init() *kex = {
	return state;
};

fn curve25519_sha256_getkey(
fn curve25519_sha256_derivekey(
	kex: *kex,
	client: *client,
	out: []u8,


@@ 165,10 176,55 @@ fn curve25519_sha256_getkey(
	};
};

fn curve25519_sha256_hashinit() *hash::hash = {
	return alloc(sha256::sha256());
};

fn curve25519_sha256_finish(kex: *kex) void = {
	free(kex);
};

type kex_hash_type = enum u8 {
	IV_CLIENT_SERVER = 'A',
	IV_SERVER_CLIENT = 'B',
	ENCRYPT_CLIENT_SERVER = 'C',
	ENCRYPT_SERVER_CLIENT = 'D',
	MAC_CLIENT_SERVER = 'E',
	MAC_SERVER_CLIENT = 'F',
};

// Initialises an IV for a cipher using the output of a key exchange object.
fn kex_hash(
	client: const *client,
	kex: const *kex,
	hashtype: kex_hash_type,
	shared: const []u8,
	out: []u8,
) void = {
	const shared = shared[..kex.keysz];
	const hash = kex.hashinit();
	defer {
		hash::close(hash);
		free(hash);
	};

	writempint(hash, shared)!;
	// TODO: This parameter should change when we support renegotiation:
	io::writeall(hash, client.exchash)!;
	io::writeall(hash, [hashtype])!;
	io::writeall(hash, client.exchash)!;

	const buf = alloc([0u8...], hash::sz(hash));
	defer free(buf);
	hash::sum(hash, buf);

	// TODO: if this assertion is false we need to generate more key
	// material per https://www.rfc-editor.org/rfc/rfc4253#section-7.2
	assert(len(buf) <= len(out));

	out[..] = buf[..len(out)];
};

// Finds a suitable set of key exchange, cipher, and MAC algorithms which are
// compatible with the server's set.
fn findalgos(kexinit: *kexinit) ((const *kex, const *cipher, const *mac) | void) = {