~apreiml/hare-ssh

804f683b60ef7799f9c6030c6351c16d47a6ba5e — Armin Preiml 1 year, 10 months ago ec25df7
add key vtable to support multiple keys
M cmd/sshkey/main.ha => cmd/sshkey/main.ha +13 -5
@@ 57,9 57,17 @@ export fn main() void = {
	};

	const privkey = ssh::decodeprivate(&key)!;
	const pub = base64::encodestr(&base64::std_encoding, privkey.pubkey);
	defer free(pub);
	const priv = base64::encodestr(&base64::std_encoding, privkey.privkey);
	defer free(priv);
	fmt::printfln("ed25519 key, pub: {}, priv: {}", pub, priv)!;
	defer ssh::key_free(privkey);

	switch (ssh::keytype(privkey)) {
	case "ssh-ed25519" =>
		let privkey = privkey: *ssh::ed25519key;
		const pub = base64::encodestr(&base64::std_encoding, privkey.pubkey);
		defer free(pub);
		const priv = base64::encodestr(&base64::std_encoding, privkey.privkey);
		defer free(priv);
		fmt::printfln("ed25519 key, pub: {}, priv: {}", pub, priv)!;
	case =>
		abort("details not implemented");
	};
};

M format/ssh/+test/key.ha => format/ssh/+test/key.ha +1 -1
@@ 11,7 11,7 @@ use strings;

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


M format/ssh/+test/sign.ha => format/ssh/+test/sign.ha +3 -3
@@ 13,12 13,12 @@ use strings;
	const msg = strings::toutf8("hello world");
	let buf = bufio::dynamic(io::mode::RDWR);
	defer io::close(&buf)!;
	sign(&buf, &skey, msg)!;
	sign(&buf, skey, msg)!;

	io::seek(&buf, 0, io::whence::SET)!;
	assert(verify(&buf, &skey, msg) is void);
	assert(verify(&buf, skey, msg) is void);

	const msg = strings::toutf8("goodbye world");
	io::seek(&buf, 0, io::whence::SET)!;
	assert(verify(&buf, &skey, msg) is badsig);
	assert(verify(&buf, skey, msg) is badsig);
};

M format/ssh/+test/sshprivkey.ha => format/ssh/+test/sshprivkey.ha +2 -0
@@ 14,6 14,7 @@ use strings;

	const private = decodeprivate(&sshkey)!;
	assert(private.comment == "sircmpwn@taiga");
	let private = private: *ed25519key;
	assert(bytes::equal(private.privkey, testkey_privkey));
};



@@ 31,6 32,7 @@ use strings;

	const private = decodeprivate(&sshkey)!;
	assert(private.comment == "sircmpwn@taiga");
	let private = private: *ed25519key;
	assert(bytes::equal(private.privkey, testkey_encrypted_privkey));
};


A format/ssh/ed25519.ha => format/ssh/ed25519.ha +101 -0
@@ 0,0 1,101 @@
use bytes;
use crypto::ed25519;
use io;

// An ed25519 key pair.
export type ed25519key = struct {
	key,
	pubkey: ed25519::pubkey,
	privkey: ed25519::privkey,
};

const ed25519_vtable: key_vtable = key_vtable {
	keytype = "ssh-ed25519",
	decodepub = &ed25519_decodepub,
	decodepriv = &ed25519_decodepriv,
	encodepub = &ed25519_encodepub,
	sign = &ed25519_sign,
	verify = &ed25519_verify,
	finish = &ed25519_finish,
	...
};

export fn new_ed25519key() ed25519key = {
	return ed25519key {
		vtable = &ed25519_vtable,
		...
	};
};

fn ed25519_decodepub(key: *key, buf: io::handle) (void | error) = {
	let key = key: *ed25519key;
	const l = readu32(buf)?;
	if (l != ed25519::PUBKEYSZ) {
		return invalid;
	};
	io::readall(buf, key.pubkey)?;
};

fn ed25519_decodepriv(key: *key, buf: io::handle) (void | error) = {
	let key = key: *ed25519key;
	ed25519_decodepub(key, buf)?;

	const l = readu32(buf)?;
	if (l != ed25519::PRIVKEYSZ) {
		return invalid;
	};
	io::readall(buf, key.privkey)?;

	// Sanity check
	const pubkey = ed25519::privkey_getpubkey(&key.privkey);
	if (!bytes::equal(pubkey, key.pubkey)) {
		return invalid;
	};

	key.comment = readstr(buf)?;
};

fn ed25519_encodepub(key: *const key, sink: io::handle) (void | io::error) = {
	let key = key: *ed25519key;
	writeslice(sink, key.pubkey)?;
};

fn ed25519_sign(
	key: *const key,
	sink: io::handle,
	msg: []u8,
) (void | io::error) = {
	let key = key: *ed25519key;
	const signature = ed25519::sign(&key.privkey, msg);
	writestr(sink, "ssh-ed25519")?;
	writeslice(sink, signature)?;
};

fn ed25519_verify(
	key: *const key,
	source: io::handle,
	msg: []u8,
) (void | error) = {
	let key = key: *ed25519key;
	const sigtype = readstr(source)?;
	defer free(sigtype);
	if (sigtype != keytype(key)) {
		return badsig;
	};
	const sig = readslice(source)?;
	defer free(sig);

	if (len(sig) != ed25519::SIGNATURESZ) {
		return badsig;
	};

	const sig = sig: *[*]u8: *[ed25519::SIGNATURESZ]u8;
	if (!ed25519::verify(&key.pubkey, msg, sig)) {
		return badsig;
	};
};

fn ed25519_finish(key: *key) void = {
	let key = key: *ed25519key;
	bytes::zero(key.privkey);
};

M format/ssh/key.ha => format/ssh/key.ha +45 -57
@@ 1,21 1,40 @@
use bufio;
use bytes;
use crypto::ed25519;
use encoding::base64;
use endian;
use fmt;
use io;

// An SSH key. For public keys, the private bits are not filled in.
export type key = ed25519key; // TODO: Tagged union me
export type decodepubfunc = fn(key: *key, buf: io::handle) (void | error);
export type decodeprivfunc = fn(key: *key, buf: io::handle) (void | error);
export type encodepubfunc = fn(key: *const key, sink: io::handle) (void | io::error);
export type signfunc = fn(key: *const key, sink: io::handle, msg: []u8) (void | io::error);
export type verifyfunc = fn(key: *const key, source: io::handle, msg: []u8) (void | error);
export type finishfunc = fn(key: *key) void;

export type key_vtable = struct {
	keytype: const str,
	decodepub: *decodepubfunc,
	decodepriv: *decodeprivfunc,
	encodepub: *encodepubfunc,
	sign: *signfunc,
	verify: *verifyfunc,
	finish: *finishfunc,
};

// Decodes the private key from an OpenSSH private key file and returns the
// decoded key. The key must be unencrypted, see [[isencrypted]] and [[decrypt]]
// for details.
// A SSH key.
export type key = struct {
	vtable: *key_vtable,
	comment: str,
};

// Decodes the private key from an OpenSSH private key file or from raw key
// bytes and returns the decoded key. The key must be unencrypted, see
// [[isencrypted]] and [[decrypt]] for details.
//
// The caller must call [[key_finish]] to securely wipe the key from memory
// The caller must call [[key_free]] to securely wipe the key from memory
// when they're done using it.
export fn decodeprivate(src: *sshprivkey) (key | error) = {
export fn decodeprivate(src: *sshprivkey) (*key | error) = {
	assert(!isencrypted(src));
	const buf = bufio::fixed(src.privkey, io::mode::READ);



@@ 30,37 49,37 @@ export fn decodeprivate(src: *sshprivkey) (key | error) = {
	const keytype = readstr(&buf)?;
	defer free(keytype);

	switch (keytype) {
	let key: *key = switch (keytype) {
	case "ssh-ed25519" =>
		let key = ed25519key { ... };
		decode_ed25519_sk(&key, &buf)?;
		return key;
		yield alloc(new_ed25519key());
	case =>
		return badcipher;
	};

	key.vtable.decodepriv(key, &buf)?;
	return key;
};

// Decodes a public key in the SSH public key format.
export fn decodepublic(sink: io::handle) (key | error) = {
export fn decodepublic(sink: io::handle) (*key | error) = {
	const keytype = readstr(sink)?;
	defer free(keytype);

	switch (keytype) {
	let key: *key = switch (keytype) {
	case "ssh-ed25519" =>
		let key = ed25519key { ... };
		decode_ed25519_pk(&key, sink)?;
		return key;
		yield alloc(new_ed25519key());
	case =>
		return badcipher;
	};
	key.vtable.decodepub(key, sink)?;
	return key;
};

// Encodes a private key's public component using the SSH wire format, writing
// it into the given stream.
export fn encode_pubkey(sink: io::handle, key: *key) (void | io::error) = {
export fn encode_pubkey(sink: io::handle, key: *const key) (void | io::error) = {
	writestr(sink, keytype(key))?;
	// TODO: Other key types
	writeslice(sink, key.pubkey)?;
	key.vtable.encodepub(key, sink)?;
};

// Encodes a private key's public component using the SSH string format, writing


@@ 75,47 94,16 @@ export fn encode_pubkeystr(sink: io::handle, key: *key) (void | io::error) = {
	};
};

// Securely wipes data associated with a private key from memory.
export fn key_finish(key: *key) void = {
	bytes::zero(key.privkey);
// Securely wipes data associated with a private key from memory and frees the
// key resource.
export fn key_free(key: *key) void = {
	key.vtable.finish(key);
	free(key.comment);
	free(key);
};

// Returns the SSH key type string for this key.
export fn keytype(key: *key) const str = {
	// TODO: Other key types
	return "ssh-ed25519";
	return key.vtable.keytype;
};

// An ed25519 key pair.
export type ed25519key = struct {
	pubkey: ed25519::pubkey,
	privkey: ed25519::privkey,
	comment: str,
};

fn decode_ed25519_pk(key: *ed25519key, buf: io::handle) (void | error) = {
	const l = readu32(buf)?;
	if (l != ed25519::PUBKEYSZ) {
		return invalid;
	};
	io::readall(buf, key.pubkey)?;
};

fn decode_ed25519_sk(key: *ed25519key, buf: io::handle) (void | error) = {
	decode_ed25519_pk(key, buf)?;

	const l = readu32(buf)?;
	if (l != ed25519::PRIVKEYSZ) {
		return invalid;
	};
	io::readall(buf, key.privkey)?;

	// Sanity check
	const pubkey = ed25519::privkey_getpubkey(&key.privkey);
	if (!bytes::equal(pubkey, key.pubkey)) {
		return invalid;
	};

	key.comment = readstr(buf)?;
};

M format/ssh/sign.ha => format/ssh/sign.ha +2 -20
@@ 8,9 8,7 @@ export fn sign(
	key: *key,
	msg: []u8,
) (void | io::error) = {
	const signature = ed25519::sign(&key.privkey, msg);
	writestr(sink, "ssh-ed25519")?;
	writeslice(sink, signature)?;
	key.vtable.sign(key, sink, msg)?;
};

// Reads an SSH wire signature from the provided I/O handle and verifies that it


@@ 21,21 19,5 @@ export fn verify(
	key: *key,
	msg: []u8,
) (void | error) = {
	const sigtype = readstr(source)?;
	defer free(sigtype);
	if (sigtype != keytype(key)) {
		return badsig;
	};
	const sig = readslice(source)?;
	defer free(sig);

	assert(sigtype == "ssh-ed25519"); // TODO: other key types
	if (len(sig) != ed25519::SIGNATURESZ) {
		return badsig;
	};

	const sig = sig: *[*]u8: *[ed25519::SIGNATURESZ]u8;
	if (!ed25519::verify(&key.pubkey, msg, sig)) {
		return badsig;
	};
	key.vtable.verify(key, source, msg)?;
};

M net/ssh/kex.ha => net/ssh/kex.ha +4 -2
@@ 112,12 112,13 @@ fn curve25519_sha256_getkey(
		const pkt = pkt as kex_ecdh_reply;
		const reader = bufio::fixed(pkt.k_s, io::mode::READ);
		const hostkey = match (ssh::decodepublic(&reader)) {
		case let key: ssh::key =>
		case let key: *ssh::key =>
			yield key;
		case =>
			// XXX: Return something more specific here?
			return protoerror;
		};

		// TODO: Update me when more key types are supported
		let shared: [32]u8 = [0...];
		let q_s: [32]u8 = [0...];


@@ 152,7 153,8 @@ fn curve25519_sha256_getkey(
		};

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