~apreiml/hare-ssh

0e3024d6daf037283eec3f5701efb2c8a515657c — Drew DeVault 1 year, 8 months ago 323383d
Flesh out more kex/cipher initialization

This is half-baked because I think some serious refactoring is in order
3 files changed, 84 insertions(+), 22 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 +60 -8
@@ 1,10 1,23 @@
use crypto::aes;
use crypto::cipher;
use io;

export type cipherinitfn = fn(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher;

// XXX: Might be good to have different types for the cipher vtable and its
// implementation state, so that these fields need not be present. Same applies
// to kex.
export type cipher = struct {
	name: str,
	keysz: size,
	blksz: size,

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

// Returns the name of this cipher.


@@ 79,26 92,65 @@ export fn cipher_lookup(name: str) const nullable *cipher = {
	return null;
};

fn aes256_ctr_init(kex: *kex) *cipher = {
	abort(); // TODO
type aes_ctr = struct {
	cipher,
	ct: aes::ct64_block,
	ctr: cipher::ctr_stream,
	ctrbuf: [aes::CTR_BUFSIZE]u8,
};

fn aes256_ctr_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	let cipher = alloc(aes_ctr { ... });
	let iv: [aes::BLOCKSIZE]u8 = [0...];
	let key: [32]u8 = [0...];
	const shared = kex_shared(kex);
	kex_hash(client, kex, hash_type::IV_CLIENT_SERVER, shared, iv);
	kex_hash(client, kex, hash_type::ENCRYPT_CLIENT_SERVER, shared, key);
	cipher.ct = aes::ct64();
	cipher.ctr = cipher::ctr(handle, &cipher.ct, iv, cipher.ctrbuf);
	return cipher;
};

fn aes192_ctr_init(kex: *kex) *cipher = {
fn aes192_ctr_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	abort(); // TODO
};

fn aes128_ctr_init(kex: *kex) *cipher = {
fn aes128_ctr_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	abort(); // TODO
};

fn aes256_cbc_init(kex: *kex) *cipher = {
fn aes256_cbc_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	abort(); // TODO
};

fn aes192_cbc_init(kex: *kex) *cipher = {
fn aes192_cbc_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	abort(); // TODO
};

fn aes128_cbc_init(kex: *kex) *cipher = {
fn aes128_cbc_init(
	handle: io::handle,
	client: *client,
	kex: *kex,
) *cipher = {
	abort(); // TODO
};

M net/ssh/client.ha => net/ssh/client.ha +5 -3
@@ 194,13 194,15 @@ 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 shared: [128]u8 = [0...];
	assert(kex_keysz(kex) <= len(shared));
	kex.derivekey(kex, client, shared)?;
	kex.derivekey(kex, client)?;

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

	// XXX: This is wrong; need to rethink how we handle the
	// incoming/outgoing connection handle
	cipher.init(client.conn, client, kex);
	abort(); // TODO
};


M net/ssh/kex.ha => net/ssh/kex.ha +19 -11
@@ 12,17 12,17 @@ use strings;
export type kex = struct {
	name: str,
	keysz: size,
	// TODO: Separate kex interface and implementation
	shared: []u8,

	// Initializes state for a key exchange algorithm.
	init: *fn() *kex,

	// 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),
	// Derives a key using the key exchange algorithm and store it in the
	// kex object's internal state. 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) (void | error),

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


@@ 42,6 42,13 @@ export fn kex_keysz(kex: *kex) size = {
	return kex.keysz;
};

// Returns the shared key from a kex object. The kex operation must have been
// completed before calling this function.
export fn kex_shared(kex: *kex) []u8 = {
	assert(len(kex.shared) == 0);
	return kex.shared;
};

// Ordered by preference
const kextable: [_]*kex = [
	&curve25519_sha256,


@@ 67,6 74,7 @@ type hashinitfn = fn() *hash::hash;
const curve25519_sha256: kex = kex {
	name = "curve25519-sha256",
	keysz = 32,
	shared = [],
	init = &curve25519_sha256_init,
	derivekey = &curve25519_sha256_derivekey,
	hashinit = &curve25519_sha256_hashinit,


@@ 97,7 105,6 @@ fn curve25519_sha256_init() *kex = {
fn curve25519_sha256_derivekey(
	kex: *kex,
	client: *client,
	out: []u8,
) (void | error) = {
	const kex = kex: *kex_curve25519_sha256;
	switch (kex.state) {


@@ 172,7 179,8 @@ fn curve25519_sha256_derivekey(
			abort("invalid exchange key sig"); // TODO: Return error
		};

		out[..32] = shared[..];
		kex.kex.shared = alloc([0...], 32);
		kex.kex.shared = shared[..];
	};
};



@@ 184,7 192,7 @@ fn curve25519_sha256_finish(kex: *kex) void = {
	free(kex);
};

type kex_hash_type = enum u8 {
type hash_type = enum u8 {
	IV_CLIENT_SERVER = 'A',
	IV_SERVER_CLIENT = 'B',
	ENCRYPT_CLIENT_SERVER = 'C',


@@ 197,7 205,7 @@ type kex_hash_type = enum u8 {
fn kex_hash(
	client: const *client,
	kex: const *kex,
	hashtype: kex_hash_type,
	hashtype: hash_type,
	shared: const []u8,
	out: []u8,
) void = {