#include	"dat.h"
#include	"fns.h"
#include	"error.h"
#include	"interp.h"
#include	<isa.h>
#include	"runt.h"
#include "mp.h"
#include "libsec.h"
#include "../../libkeyring/keys.h"

/*
 * experimental version of signed modules
 */

enum
{
	Qdir,
	Qkey,
	Qctl,

	Maxkey = 2048
};

static Dirtab signdir[] =
{
	".",		{Qdir, 0, QTDIR},	0,	DMDIR|0555,
	"signerkey",	{Qkey},	0,			0644,
	"signerctl",	{Qctl},	0,			0600,
};

typedef struct Get Get;
struct Get {
	uchar*	p;
	uchar*	ep;
};

#define	G32(b)	((b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3])

static	int	vc(Get*);
static	int	vs(void*, int, Get*, int);
static Signerkey* findsignerkey(Skeyset*, char*, int, char*);
extern vlong		osusectime(void);

int
verifysigner(uchar *sign, int len, uchar *data, ulong ndata)
{
	Get sig;
	int alg;
	ulong issued, expires, now;
	int footprint, r, n;
	uchar buf[128], digest[SHA1dlen];
	DigestState *ds;
	volatile struct {mpint* b;} b;
	volatile struct {mpint* s;} s;
	SigAlgVec *sa;
	Signerkey *key;
	Skeyset *sigs;

	/* alg[1] issued[4] expires[4] footprint[2] signer[n] sig[m] */
	sigs = up->env->sigs;
	if(sigs == nil)
		return 1;	/* not enforcing signed modules */
	sig.p = sign;
	sig.ep = sign+len;
	alg = vc(&sig);
	if(alg != 2)
		return 0;	/* we do only SHA1/RSA */
	sa = findsigalg("rsa");
	if(sa == nil)
		return 0;
	if(vs(buf, sizeof(buf), &sig, 4) < 0)
		return 0;
	now = osusectime()/1000000;
	issued = G32(buf);
	if(vs(buf, sizeof(buf), &sig, 4) < 0)
		return 0;
	if(issued != 0 && now < issued)
		return 0;
	expires = G32(buf);
	if(expires != 0 && now >= expires)
		return 0;
	footprint = vc(&sig) << 8;
	footprint |= vc(&sig);
	if(footprint < 0)
		return 0;
	r = 0;
	b.b = nil;
	s.s = nil;
	qlock(&sigs->l);
	if(waserror())
		goto out;
	if((n = vs(buf, sizeof(buf)-NUMSIZE-1, &sig, -1)) < 0)	/* owner */
		goto out;
	buf[n] = 0;
	key = findsignerkey(sigs, sa->name, footprint, (char*)buf);
	if(key == nil)
		goto out;
	n += snprint((char*)buf+n, NUMSIZE, " %lud", expires);
	ds = sha1(buf, n, nil, nil);
	sha1(data, ndata, digest, ds);
	b.b = betomp(digest, SHA1dlen, nil);
	if(b.b == nil)
		goto out;
	s.s = betomp(sig.p, sig.ep-sig.p, nil);
	if(s.s == nil)
		goto out;
	r = (*sa->verify)(b.b, s.s, key->pk);
out:
	qunlock(&sigs->l);
	if(b.b != nil)
		mpfree(b.b);
	if(s.s != nil)
		mpfree(s.s);
	return r;
}

int
mustbesigned(char *path, uchar *code, ulong length, Dir *dir)
{
	USED(code); USED(length);
if(0)print("load %s: %d %C\n", path, up->env->sigs!=nil, dir==nil?'?':dir->type);
	/* allow only signed modules and those in #/; already loaded modules are reloaded from cache */
	return up->env->sigs != nil && (dir == nil || dir->type != '/');
}

static int
vc(Get *g)
{
	return g->p < g->ep? *g->p++: -1;
}

static int
vs(void *s, int lim, Get *g, int n)
{
	int nr;

	if(n < 0){
		if(g->p >= g->ep)
			return -1;
		n = *g->p++;
		lim--;
	}
	if(n > lim)
		return -1;
	nr = g->ep - g->p;
	if(n > nr)
		return -1;
	if(s != nil)
		memmove(s, g->p, n);
	g->p += n;
	return n;
}

static char*
cstring(char *str, char **strp)
{
	char *p, *s;
	int n;

	p = strchr(str, '\n');
	if(p == 0)
		p = str + strlen(str);
	n = p - str;
	s = malloc(n+1);
	if(s == nil)
		return nil;
	memmove(s, str, n);
	s[n] = 0;

	if(strp){
		if(*p)
			p++;
		*strp = p;
	}

	return s;
}

static SigAlgVec*
cstrtoalg(char *str, char **strp)
{
	int n;
	char *p, name[KNAMELEN];

	p = strchr(str, '\n');
	if(p == 0){
		p = str + strlen(str);
		if(strp)
			*strp = p;
	} else {
		if(strp)
			*strp = p+1;
	}

	n = p - str;
	if(n >= sizeof(name))
		return nil;
	strncpy(name, str, n);
	name[n] = 0;
	return findsigalg(name);
}

static Signerkey*
strtopk(char *buf)
{
	SigAlgVec *sa;
	char *p;
	Signerkey *key;

	key = malloc(sizeof(*key));
	if(key == nil)
		return nil;
	key->r.ref = 1;
	sa = cstrtoalg(buf, &p);
	if(sa == nil){
		free(key);
		return nil;
	}
	key->alg = sa;
	key->pkfree = sa->pkfree;
	key->owner = cstring(p, &p);
	if(key->owner == nil){
		free(key);
		return nil;
	}
	key->pk = (*sa->str2pk)(p, &p);
	if(key->pk == nil){
		free(key->owner);
		free(key);
		return nil;
	}
	return key;
}

static Signerkey*
findsignerkey(Skeyset *sigs, char *alg, int footprint, char *owner)
{
	int i;
	Signerkey *key;

	for(i=0; i<sigs->nkey; i++){
		key = sigs->keys[i];
		if(key->footprint == footprint &&
		   strcmp(alg, ((SigAlgVec*)key->alg)->name) == 0 &&
		   strcmp(key->owner, owner) == 0)
			return key;
	}
	return nil;
}

static Chan*
signattach(char *spec)
{
	return devattach(0x03A3, spec);	 /* L'Σ' */
}

static Walkqid*
signwalk(Chan *c, Chan *nc, char **name, int nname)
{
	return devwalk(c, nc, name, nname, signdir, nelem(signdir), devgen);
}

static int
signstat(Chan *c, uchar *db, int n)
{
	return devstat(c, db, n, signdir, nelem(signdir), devgen);
}

static Chan*
signopen(Chan *c, int omode)
{
	if(c->qid.type & QTDIR) {
		if(omode != OREAD)
			error(Eisdir);
		c->mode = openmode(omode);
		c->flag |= COPEN;
		c->offset = 0;
		return c;
	}

	switch((ulong)c->qid.path){
	case Qctl:
		if(!iseve())
			error(Eperm);
		break;

	case Qkey:
		if(omode != OREAD && !iseve())
			error(Eperm);
		break;
	}

	c->mode = openmode(omode);
	c->flag |= COPEN;
	c->offset = 0;
	return c;
}

static void
signclose(Chan *c)
{
	USED(c);
}

static long
signread(Chan *c, void *va, long n, vlong offset)
{
	char *buf, *p;
	SigAlgVec *sa;
	Skeyset *sigs;
	Signerkey *key;
	int i;

	if(c->qid.type & QTDIR)
		return devdirread(c, va, n, signdir, nelem(signdir), devgen);
	sigs = up->env->sigs;
	if(sigs == nil)
		return 0;
	switch((ulong)c->qid.path){
	case Qkey:
		buf = smalloc(Maxkey);
		if(waserror()){
			free(buf);
			nexterror();
		}
		qlock(&sigs->l);
		if(waserror()){
			qunlock(&sigs->l);
			nexterror();
		}
		p = buf;
		for(i=0; i<sigs->nkey; i++){
			key = sigs->keys[i];
			sa = key->alg;
			p = seprint(p, buf+Maxkey, "owner=%s alg=%s footprint=%ud expires=%lud\n",
				key->owner, sa->name, key->footprint, key->expires);
		}
		poperror();
		qunlock(&sigs->l);
		n = readstr(offset, va, n, buf);
		poperror();
		free(buf);
		return n;

	case Qctl:
		return readnum(offset, va, n, sigs->nkey, NUMSIZE);
	}
	return 0;
}

static long
signwrite(Chan *c, void *va, long n, vlong offset)
{
	char *buf;
	Skeyset *sigs;
	Signerkey *okey, *key;
	int i;

	if(c->qid.type & QTDIR)
		error(Eisdir);
	USED(offset);
	switch((ulong)c->qid.path){
	case Qkey:
		if(n >= Maxkey)
			error(Etoobig);
		buf = smalloc(Maxkey);
		if(waserror()){
			free(buf);
			nexterror();
		}
		memmove(buf, va, n);
		buf[n] = 0;

		key = strtopk(buf);
		if(key == nil)
			error("bad key syntax");
		poperror();
		free(buf);

		if(waserror()){
			freeskey(key);
			nexterror();
		}
		sigs = up->env->sigs;
		if(sigs == nil){
			sigs = malloc(sizeof(*sigs));
			if(sigs == nil)
				error(Enomem);
			sigs->r.ref = 1;
			up->env->sigs = sigs;
		}
		qlock(&sigs->l);
		if(waserror()){
			qunlock(&sigs->l);
			nexterror();
		}
		for(i=0; i<sigs->nkey; i++){
			okey = sigs->keys[i];
			if(strcmp(okey->owner, key->owner) == 0){
				/* replace existing key */
				sigs->keys[i] = key;
				freeskey(okey);
				break;
			}
		}
		if(i >= sigs->nkey){
			if(sigs->nkey >= nelem(sigs->keys))
				error("too many keys");
			sigs->keys[sigs->nkey++] = key;
		}
		poperror();
		qunlock(&sigs->l);
		poperror();	/* key */

		return n;
	case Qctl:
		error(Ebadctl);
		break;
	}
	return 0;
}

Dev signdevtab = {
	0x03A3,	/* L'Σ',	/* U+03A3 */
	"sign",

	devinit,
	signattach,
	signwalk,
	signstat,
	signopen,
	devcreate,
	signclose,
	signread,
	devbread,
	signwrite,
	devbwrite,
	devremove,
	devwstat
};
