parsav  crypt.t at [0814075eff]

File crypt.t artifact 2e175d9d09 part of check-in 0814075eff


-- vim: ft=terra
local const = {
	keybits = 2048;
	sighash = constant(uint32, lib.md.MBEDTLS_MD_SHA256);
}
const.keybits_c = constant(uint32,const.keybits);
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
const.maxdersz = const.maxpemsz -- FIXME this is a safe value but obvs not the correct one

local ctx = lib.pk.mbedtls_pk_context
terra ctx:free() lib.pk.mbedtls_pk_free(self) end

local struct hashalg { id: uint8 bytes: intptr }
local m = {
	pemfile = int8[const.maxpemsz];
	derfile = uint8[const.maxdersz];
	const = const;
	algsz = {
		sha1 =   160/8;
		sha256 = 256/8;
		sha512 = 512/8;
		sha384 = 384/8;
		sha224 = 224/8;
	}
}
m.alg = {
	sha1 =   `hashalg {id = lib.md.MBEDTLS_MD_SHA1;   bytes = m.algsz.sha1};
	sha256 = `hashalg {id = lib.md.MBEDTLS_MD_SHA256; bytes = m.algsz.sha256};
	sha512 = `hashalg {id = lib.md.MBEDTLS_MD_SHA512; bytes = m.algsz.sha512};
	sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = m.algsz.sha384};
	sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = m.algsz.sha224};
	-- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!!
};
local callbacks = {}
if config.feat.randomizer == 'kern' then
	local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
	terra m.spray(dest: &uint8, sz: intptr): int
		return rnd(dest, sz, 0)
	end
elseif config.feat.randomizer == 'devfs' then
	terra m.spray(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 m.spray(dest: &uint8, sz: intptr): int
		for i=0,sz do dest[i] = [uint8](rnd()) end
		return sz
	end
end

m.random = macro(function(typ, from, to)
	local ty = typ:astype()
	from = from or 0
	to = to or ty:max()
	return quote
		var v: ty
		m.spray([&uint8](&v), sizeof(ty))
		v = v % (to - from) + from -- only works with unsigned!!
	in v end
end)

terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr)
	return m.spray(dest,sz) end

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

local binblob = lib.mem.ptr(uint8)
terra m.der(pub: bool, key: &ctx, buf: &uint8): binblob
	var ofs: ptrdiff
	if pub then
		ofs = lib.pk.mbedtls_pk_write_pubkey_der(key, buf, const.maxdersz)
	else
		ofs = lib.pk.mbedtls_pk_write_key_der(key, buf, const.maxdersz)
	end

	if ofs == lib.asn1.MBEDTLS_ERR_ASN1_BUF_TOO_SMALL then
		lib.warn("BUG: bad buffer size for lib.crypt.der")
		return binblob.null()
	elseif ofs == lib.pk.MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE then
		lib.warn("mbedtls broken! asymmetric-key crypto unsupported")
		return binblob.null()
	elseif ofs == lib.pk.MBEDTLS_ERR_RSA_BAD_INPUT_DATA then
		lib.warn("bad RSA input to lib.crypt.der")
		return binblob.null()
	elseif ofs < 0 then
		var num: int8[21]
		var sz = lib.str.bfmt(num, "%lld", ofs)
		lib.warn("unknown MBEDTLS error ", {&num[0], sz});
		return binblob.null()
	end

	return binblob {
		ptr = buf + (const.maxdersz - ofs);
		ct = ofs;
	}
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.pk_genrsa(pk: &ctx)
-- fuck you arm for making requiring this inane pipe-through-cat
-- what is wrong with you demented bastards
	var noise: uint8[2048]
	m.spray(noise,2048)
	var rng: lib.drbg.mbedtls_ctr_drbg_context
	var entropy: lib.entropy.mbedtls_entropy_context
	lib.drbg.mbedtls_ctr_drbg_init(&rng)
	lib.entropy.mbedtls_entropy_init(&entropy)
	lib.drbg.mbedtls_ctr_drbg_seed(&rng, lib.entropy.mbedtls_entropy_func, &entropy, &noise[0], 2048)
	var rsa = lib.pk.pk2rsa(@pk)
	lib.rsa.mbedtls_rsa_init(rsa)
	var e = lib.rsa.mbedtls_rsa_gen_key(rsa, lib.drbg.mbedtls_ctr_drbg_random, &rng, const.keybits_c, 3)
	if e < 0 then
		var buf: int8[22]
		lib.bail("BUG: could not buf-generate RSA keypair; mbedtls reports error ",
			lib.math.decstrs(e, &buf[21]))
	end
	if lib.rsa.mbedtls_rsa_check_privkey(rsa) < 0 then
		lib.bail("BUG: generated bad RSA keypair")
	end
end

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

	var pk: ctx
	lib.pk.mbedtls_pk_init(&pk)
	if lib.pk.mbedtls_pk_setup(&pk, lib.pk.mbedtls_pk_info_from_type(lib.pk.MBEDTLS_PK_RSA)) < 0 then
		lib.bail 'pk context setup failed'
	end
	m.pk_genrsa(&pk)
	return pk
end


local binblob = lib.mem.ptr(uint8)
terra m.loadpriv(buf: binblob): lib.stat(ctx)
	lib.dbg('parsing saved private key')

	var pk: ctx
	lib.pk.mbedtls_pk_init(&pk)
	var rt = lib.pk.mbedtls_pk_parse_key(&pk,
		[&uint8](buf.ptr), [intptr](buf.ct),
		nil, 0,
		callbacks.randomize, nil)
	if rt == 0 then
		return [lib.stat(ctx)] { ok = true, val = pk }
	else
		lib.pk.mbedtls_pk_free(&pk)
		return [lib.stat(ctx)] { ok = false, error = rt }
	end
end

terra m.loadpub(buf: binblob): lib.stat(ctx)
	lib.dbg('parsing saved key')

	var pk: ctx
	lib.pk.mbedtls_pk_init(&pk)
	var rt = lib.pk.mbedtls_pk_parse_public_key(&pk, buf.ptr, buf.ct)
	if rt == 0 then
		return [lib.stat(ctx)] { ok = true, val = pk }
	else
		lib.pk.mbedtls_pk_free(&pk)
		return [lib.stat(ctx)] { ok = false, error = rt }
	end
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, sizeof([hash.type]),
		[&uint8](sig.ptr), 2048, &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: &uint8, 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
terra m.hmac(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8), buf: &uint8)
	lib.md.mbedtls_md_hmac(
			lib.md.mbedtls_md_info_from_type(alg.id), 
			key.ptr, key.ct,
			[&uint8](txt.ptr), txt.ct,
			buf) -- sz(buf) >= hash output size
end

terra m.hmaca(alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8))
	var buf = lib.mem.heapa(uint8, alg.bytes)
	m.hmac(alg, key, txt, buf.ptr)
	return buf
end

terra m.hmacp(p: &lib.mem.pool, alg: hashalg, key: lib.mem.ptr(uint8), txt: lib.mem.ptr(int8))
	var buf = p:alloc(uint8, alg.bytes)
	m.hmac(alg, key, txt, buf.ptr)
	return buf
end

terra m.hotp(key: &(uint8[10]), counter: uint64)
	var hmac: uint8[20]
	var ctr = [lib.mem.ptr(int8)]{ptr = [&int8](&counter), ct = 8}
	m.hmac(m.alg.sha1,
		[lib.mem.ptr(uint8)]{ptr = [&uint8](key), ct = 10},
		ctr, hmac)
	
	var ofs = hmac[19] and 0x0F
	var p: uint8[4]
	for i=0,4 do p[i] = hmac[ofs + i] end

	return (@[&uint32](&p)) and 0x7FFFFFFF -- one hopes it's that easy
end

local splitwords = macro(function(str)
	local words = {}
	for w in str:asvalue():gmatch('(%g+)') do words[#words + 1] = w end
	return `arrayof(lib.str.t, [words])
end)

terra m.cryptogram(a: &lib.str.acc, len: intptr)
	var words = splitwords [[
		alpha beta gamma delta epsilon psi eta nu omicron omega
		red crimson green verdant golden silver blue cyan navy
		carnelian opal sapphire amethyst ruby jade emerald
		chalice peacock cabernet windmill saxony tunnel waterspout
	]]
	for i = 0, len do
		a:ppush(words[m.random(intptr,0,[words.type.N])]):lpush '-'
	end
	a:ipush(m.random(uint32,0,99999))
end

return m