-- vim: ft=terra
local const = {
	keybits = 2048;
	sighash = lib.md.MBEDTLS_MD_SHA256;
}
local err = {
	rawcode = terra(code: int)
		if code < 0 then code = -code end
		return code and 0xFF80
	end;
	toobig = -lib.pk.MBEDTLS_ERR_RSA_OUTPUT_TOO_LARGE;
}
const.maxpemsz = math.floor((const.keybits / 8)*6.4) + 128 -- idk why this formula works but it basically seems to

local ctx = lib.pk.mbedtls_pk_context

local m = {
	pemfile = uint8[const.maxpemsz];
}
local callbacks = {}
if config.feat.randomizer == 'kern' then
	local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
		return rnd(dest, sz, 0)
	end
elseif config.feat.randomizer == 'devfs' then
	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
		var gen = lib.io.open("/dev/urandom",0)
		lib.io.read(gen, dest, sz)
		lib.io.close(gen)
		return sz
	end
elseif config.feat.randomizer == 'libc' then
	local rnd = terralib.externfunction('rand', {} -> int);
	local srnd = terralib.externfunction('srand', uint -> int);
	local time = terralib.includec 'time.h'
	lib.init[#lib.init + 1] = quote srnd(time.time(nil)) end
	print '(warn) using libc soft-rand function for cryptographic purposes, this is very bad!'
	terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
		for i=0,sz do dest[i] = [uint8](rnd()) end
		return sz
	end
end

terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool
	if pub then
		return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
	else
		return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
	end
end

m.destroy = lib.dispatch {
	[ctx] = function(v) return `lib.pk.mbedtls_pk_free(&v) end;

	[false] = function(ptr) return `ptr:free() end;
}

terra m.genkp(): lib.pk.mbedtls_pk_context
	lib.dbg('generating new keypair')

	var pk: ctx
	lib.pk.mbedtls_pk_init(&pk)
	lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA))
	var rsa = [&lib.rsa.mbedtls_rsa_context](pk.pk_ctx)
	lib.rsa.mbedtls_rsa_gen_key(rsa, callbacks.randomize, nil, const.keybits, 65537)

	return pk
end

terra m.loadpriv(buf: &uint8, len: intptr): ctx
	lib.dbg('parsing saved keypair')

	var pk: ctx
	lib.pk.mbedtls_pk_init(&pk)
	lib.pk.mbedtls_pk_parse_key(&pk, buf, len + 1, nil, 0)
	return pk
end

terra m.sign(pk: &ctx, txt: rawstring, len: intptr)
	lib.dbg('signing message')
	var osz: intptr = 0
	var sig = lib.mem.heapa(int8, 2048)
	var hash: uint8[32]
	
	lib.dbg('(1/2) hashing message')
	if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(const.sighash), [&uint8](txt), len, hash) ~= 0 then
		lib.bail('could not hash message')
	end

	lib.dbg('(2/2) signing hash')
	var ret = lib.pk.mbedtls_pk_sign(pk, const.sighash, hash, 0, [&uint8](sig.ptr), &osz, callbacks.randomize, nil)
	if ret ~= 0 then lib.bail('could not sign message hash')
	else sig:resize(osz) end

	return sig
end

terra m.verify(pk: &ctx, txt: rawstring, len: intptr,
                        sig: rawstring, siglen: intptr): {bool, uint8}
	lib.dbg('verifying signature')
	var osz: intptr = 0
	var hash: uint8[64]

	-- there does not appear to be any way to extract the hash algorithm
	-- from the message, so we just have to try likely algorithms until
	-- we find one that fits or give up. a security level is attached
	-- to indicate our relative confidence in the hash; 0 == 'likely forgeable'
	var algs = array(
		-- common hashes
		{lib.md.MBEDTLS_MD_SHA256, 'sha256', 2},
		{lib.md.MBEDTLS_MD_SHA512, 'sha512', 3},
		{lib.md.MBEDTLS_MD_SHA1,   'sha1',   1},
		-- uncommon hashes
		{lib.md.MBEDTLS_MD_SHA384, 'sha384', 2},
		{lib.md.MBEDTLS_MD_SHA224, 'sha224', 2},
		-- bad hashes
		{lib.md.MBEDTLS_MD_MD5,   'md5', 0},
		{lib.md.MBEDTLS_MD_MD4,   'md4', 0},
		{lib.md.MBEDTLS_MD_MD2,   'md2', 0}
	)
	
	for i = 0, [algs.type.N] do
		var hk, aname, secl = algs[i]

		lib.dbg('(1/2) trying hash algorithm ',aname)
		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(hk), [&uint8](txt), len, hash) ~= 0 then
			lib.bail('could not hash message')
		end

		lib.dbg('(2/2) verifying hash')
		if lib.pk.mbedtls_pk_verify(pk, hk, hash, 0, [&uint8](sig), siglen) == 0 then
			return true, secl
		end
	end
	lib.dbg('all hash algorithms failed')
	return false, 0
end

return m
