Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-modern-client/libsec-ecc-ecdsa-primitives.patch

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


libsec: generic ECC arithmetic, secp256r1/secp384r1, ECDSA verify

Adds generic short-Weierstrass EC arithmetic, the NIST P-256 and
P-384 curves, ECDSA sign/verify, and X.509 glue to extract EC
public keys and verify ECDSA-signed certificates.  Stands alone:
no TLS or chain work is needed to use these primitives.

The build runs mpc(1) over .mp inputs to generate jacobian.c,
secp256r1.c, and secp384r1.c at compile time.  mpc(1) ships in
the 9legacy base distribution; no separate install.

ecc.c is the short-Weierstrass code from 9front libsec/port/ecc.c
with 9front's "os.h" replaced by <u.h> + <libc.h>.  The .mp files
are copied from 9front unchanged.  X509toECpub and
X509ecdsaverifydigest are simplified ports given 9legacy's
CertX509 is file-local.

RFC 6090 (EC primitives), 5480 (ECC SubjectPublicKeyInfo in
X.509), 5758 §3.2 (ECDSA in X.509).  Curve parameters and
sign/verify follow FIPS 186-4.

--- sys/include/libsec.h
+++ sys/include/libsec.h
@@ -128,6 +128,7 @@
 void	ccpoly_encrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs);
 int	ccpoly_decrypt(uchar *dat, ulong ndat, uchar *aad, ulong naad, uchar tag[16], Chachastate *cs);
 
+
 /*
  * Curve25519 (RFC 7748)
  */
@@ -137,6 +138,54 @@
 int	curve25519_dh_finish(uchar x[32], uchar y[32], uchar z[32]);
 
 /*
+ * Short-Weierstrass elliptic curves (secp256r1, secp384r1) + ECDSA.
+ * Used for TLS_ECDHE_ECDSA_* cipher suites and for validating certificate
+ * chains signed with ECDSA.
+ */
+typedef struct ECpoint{
+	int	inf;
+	mpint	*x;
+	mpint	*y;
+	mpint	*z;	/* nil when using affine coordinates */
+} ECpoint;
+
+typedef ECpoint ECpub;
+typedef struct ECpriv{
+	ECpoint;	/* anonymous — same fields as ECpub */
+	mpint	*d;
+} ECpriv;
+
+typedef struct ECdomain{
+	mpint	*p;
+	mpint	*a;
+	mpint	*b;
+	ECpoint	G;
+	mpint	*n;
+	mpint	*h;
+} ECdomain;
+
+void	ecdominit(ECdomain*, void (*init)(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h));
+void	ecdomfree(ECdomain*);
+
+void	ecassign(ECdomain*, ECpoint *old, ECpoint *new);
+void	ecadd(ECdomain*, ECpoint *a, ECpoint *b, ECpoint *s);
+void	ecmul(ECdomain*, ECpoint *a, mpint *k, ECpoint *s);
+ECpoint*	strtoec(ECdomain*, char*, char**, ECpoint*);
+ECpriv*	ecgen(ECdomain*, ECpriv*);
+int	ecverify(ECdomain*, ECpoint*);
+int	ecpubverify(ECdomain*, ECpub*);
+void	ecdsasign(ECdomain*, ECpriv*, uchar*, int, mpint*, mpint*);
+int	ecdsaverify(ECdomain*, ECpub*, uchar*, int, mpint*, mpint*);
+
+ECpub*	ecdecodepub(ECdomain *dom, uchar*, int);
+int	ecencodepub(ECdomain *dom, ECpub*, uchar*, int);
+void	ecpubfree(ECpub*);
+
+/* curve parameter initializers — compatible with ecdominit's init callback */
+void	secp256r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+void	secp384r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h);
+
+/*
  * DES definitions
  */
 
@@ -352,6 +401,8 @@
 char*		X509verify(uchar *cert, int ncert, RSApub *pk);
 void		X509dump(uchar *cert, int ncert);
 char*		rsapkcs1verify(RSApub *pk, int sigalg, uchar *msg, ulong msglen, uchar *sig, int siglen);
+ECpub*		X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom);
+char*		X509ecdsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, ECdomain *dom, ECpub *pub);
 
 /*
  * elgamal
--- sys/src/libsec/port/ecc.c
+++ sys/src/libsec/port/ecc.c
@@ -0,0 +1,616 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <ctype.h>
+
+extern void jacobian_affine(mpint *p,
+	mpint *X, mpint *Y, mpint *Z);
+extern void jacobian_dbl(mpint *p, mpint *a,
+	mpint *X1, mpint *Y1, mpint *Z1,
+	mpint *X3, mpint *Y3, mpint *Z3);
+extern void jacobian_add(mpint *p, mpint *a,
+	mpint *X1, mpint *Y1, mpint *Z1,
+	mpint *X2, mpint *Y2, mpint *Z2,
+	mpint *X3, mpint *Y3, mpint *Z3);
+
+void
+ecassign(ECdomain *dom, ECpoint *a, ECpoint *b)
+{
+	if((b->inf = a->inf) != 0)
+		return;
+	mpassign(a->x, b->x);
+	mpassign(a->y, b->y);
+	if(b->z != nil){
+		mpassign(a->z != nil ? a->z : mpone, b->z);
+		return;
+	}
+	if(a->z != nil){
+		b->z = mpcopy(a->z);
+		jacobian_affine(dom->p, b->x, b->y, b->z);
+		mpfree(b->z);
+		b->z = nil;
+	}
+}
+
+void
+ecadd(ECdomain *dom, ECpoint *a, ECpoint *b, ECpoint *s)
+{
+	if(a->inf && b->inf){
+		s->inf = 1;
+		return;
+	}
+	if(a->inf){
+		ecassign(dom, b, s);
+		return;
+	}
+	if(b->inf){
+		ecassign(dom, a, s);
+		return;
+	}
+
+	if(s->z == nil){
+		s->z = mpcopy(mpone);
+		ecadd(dom, a, b, s);
+		if(!s->inf)
+			jacobian_affine(dom->p, s->x, s->y, s->z);
+		mpfree(s->z);
+		s->z = nil;
+		return;
+	}
+
+	if(a == b)
+		jacobian_dbl(dom->p, dom->a,
+			a->x, a->y, a->z != nil ? a->z : mpone,
+			s->x, s->y, s->z);
+	else
+		jacobian_add(dom->p, dom->a,
+			a->x, a->y, a->z != nil ? a->z : mpone,
+			b->x, b->y, b->z != nil ? b->z : mpone,
+			s->x, s->y, s->z);
+	s->inf = mpcmp(s->z, mpzero) == 0;
+}
+
+void
+ecmul(ECdomain *dom, ECpoint *a, mpint *k, ECpoint *s)
+{
+	ECpoint ns, na;
+	mpint *l;
+
+	if(a->inf || mpcmp(k, mpzero) == 0){
+		s->inf = 1;
+		return;
+	}
+	ns.inf = 1;
+	ns.x = mpnew(0);
+	ns.y = mpnew(0);
+	ns.z = mpnew(0);
+	na.x = mpnew(0);
+	na.y = mpnew(0);
+	na.z = mpnew(0);
+	ecassign(dom, a, &na);
+	l = mpcopy(k);
+	l->sign = 1;
+	while(mpcmp(l, mpzero) != 0){
+		if(l->p[0] & 1)
+			ecadd(dom, &na, &ns, &ns);
+		ecadd(dom, &na, &na, &na);
+		mpright(l, 1, l);
+	}
+	if(k->sign < 0 && !ns.inf){
+		ns.y->sign = -1;
+		mpmod(ns.y, dom->p, ns.y);
+	}
+	ecassign(dom, &ns, s);
+	mpfree(ns.x);
+	mpfree(ns.y);
+	mpfree(ns.z);
+	mpfree(na.x);
+	mpfree(na.y);
+	mpfree(na.z);
+	mpfree(l);
+}
+
+int
+ecverify(ECdomain *dom, ECpoint *a)
+{
+	mpint *p, *q;
+	int r;
+
+	if(a->inf)
+		return 1;
+
+	assert(a->z == nil);	/* need affine coordinates */
+	p = mpnew(0);
+	q = mpnew(0);
+	mpmodmul(a->y, a->y, dom->p, p);
+	mpmodmul(a->x, a->x, dom->p, q);
+	mpmodadd(q, dom->a, dom->p, q);
+	mpmodmul(q, a->x, dom->p, q);
+	mpmodadd(q, dom->b, dom->p, q);
+	r = mpcmp(p, q);
+	mpfree(p);
+	mpfree(q);
+	return r == 0;
+}
+
+int
+ecpubverify(ECdomain *dom, ECpub *a)
+{
+	ECpoint p;
+	int r;
+
+	if(a->inf)
+		return 0;
+	if(!ecverify(dom, a))
+		return 0;
+	p.x = mpnew(0);
+	p.y = mpnew(0);
+	p.z = mpnew(0);
+	ecmul(dom, a, dom->n, &p);
+	r = p.inf;
+	mpfree(p.x);
+	mpfree(p.y);
+	mpfree(p.z);
+	return r;
+}
+
+static void
+fixnibble(uchar *a)
+{
+	if(*a >= 'a')
+		*a -= 'a'-10;
+	else if(*a >= 'A')
+		*a -= 'A'-10;
+	else
+		*a -= '0';
+}
+
+static int
+octet(char **s)
+{
+	uchar c, d;
+	
+	c = *(*s)++;
+	if(!isxdigit(c))
+		return -1;
+	d = *(*s)++;
+	if(!isxdigit(d))
+		return -1;
+	fixnibble(&c);
+	fixnibble(&d);
+	return (c << 4) | d;
+}
+
+static mpint*
+halfpt(ECdomain *dom, char *s, char **rptr, mpint *out)
+{
+	char *buf, *r;
+	int n;
+	mpint *ret;
+	
+	n = ((mpsignif(dom->p)+7)/8)*2;
+	if(strlen(s) < n)
+		return 0;
+	buf = malloc(n+1);
+	buf[n] = 0;
+	memcpy(buf, s, n);
+	ret = strtomp(buf, &r, 16, out);
+	*rptr = s + (r - buf);
+	free(buf);
+	return ret;
+}
+
+static int
+mpleg(mpint *a, mpint *b)
+{
+	int r, k;
+	mpint *m, *n, *t;
+	
+	r = 1;
+	m = mpcopy(a);
+	n = mpcopy(b);
+	for(;;){
+		if(mpcmp(m, n) > 0)
+			mpmod(m, n, m);
+		if(mpcmp(m, mpzero) == 0){
+			r = 0;
+			break;
+		}
+		if(mpcmp(m, mpone) == 0)
+			break;
+		k = mplowbits0(m);
+		if(k > 0){
+			if(k & 1)
+				switch(n->p[0] & 15){
+				case 3: case 5: case 11: case 13:
+					r = -r;
+				}
+			mpright(m, k, m);
+		}
+		if((n->p[0] & 3) == 3 && (m->p[0] & 3) == 3)
+			r = -r;
+		t = m;
+		m = n;
+		n = t;
+	}
+	mpfree(m);
+	mpfree(n);
+	return r;
+}
+
+static int
+mpsqrt(mpint *n, mpint *p, mpint *r)
+{
+	mpint *a, *t, *s, *xp, *xq, *yp, *yq, *zp, *zq, *N;
+
+	if(mpleg(n, p) == -1)
+		return 0;
+	a = mpnew(0);
+	t = mpnew(0);
+	s = mpnew(0);
+	N = mpnew(0);
+	xp = mpnew(0);
+	xq = mpnew(0);
+	yp = mpnew(0);
+	yq = mpnew(0);
+	zp = mpnew(0);
+	zq = mpnew(0);
+	for(;;){
+		for(;;){
+			mpnrand(p, genrandom, a);
+			if(mpcmp(a, mpzero) > 0)
+				break;
+		}
+		mpmul(a, a, t);
+		mpsub(t, n, t);
+		mpmod(t, p, t);
+		if(mpleg(t, p) == -1)
+			break;
+	}
+	mpadd(p, mpone, N);
+	mpright(N, 1, N);
+	mpmul(a, a, t);
+	mpsub(t, n, t);
+	mpassign(a, xp);
+	uitomp(1, xq);
+	uitomp(1, yp);
+	uitomp(0, yq);
+	while(mpcmp(N, mpzero) != 0){
+		if(N->p[0] & 1){
+			mpmul(xp, yp, zp);
+			mpmul(xq, yq, zq);
+			mpmul(zq, t, zq);
+			mpadd(zp, zq, zp);
+			mpmod(zp, p, zp);
+			mpmul(xp, yq, zq);
+			mpmul(xq, yp, s);
+			mpadd(zq, s, zq);
+			mpmod(zq, p, yq);
+			mpassign(zp, yp);
+		}
+		mpmul(xp, xp, zp);
+		mpmul(xq, xq, zq);
+		mpmul(zq, t, zq);
+		mpadd(zp, zq, zp);
+		mpmod(zp, p, zp);
+		mpmul(xp, xq, zq);
+		mpadd(zq, zq, zq);
+		mpmod(zq, p, xq);
+		mpassign(zp, xp);
+		mpright(N, 1, N);
+	}
+	if(mpcmp(yq, mpzero) != 0)
+		abort();
+	mpassign(yp, r);
+	mpfree(a);
+	mpfree(t);
+	mpfree(s);
+	mpfree(N);
+	mpfree(xp);
+	mpfree(xq);
+	mpfree(yp);
+	mpfree(yq);
+	mpfree(zp);
+	mpfree(zq);
+	return 1;
+}
+
+ECpoint*
+strtoec(ECdomain *dom, char *s, char **rptr, ECpoint *ret)
+{
+	int allocd, o;
+	mpint *r;
+
+	allocd = 0;
+	if(ret == nil){
+		allocd = 1;
+		ret = mallocz(sizeof(*ret), 1);
+		if(ret == nil)
+			return nil;
+		ret->x = mpnew(0);
+		ret->y = mpnew(0);
+	}
+	ret->inf = 0;
+	o = 0;
+	switch(octet(&s)){
+	case 0:
+		ret->inf = 1;
+		break;
+	case 3:
+		o = 1;
+	case 2:
+		if(halfpt(dom, s, &s, ret->x) == nil)
+			goto err;
+		r = mpnew(0);
+		mpmul(ret->x, ret->x, r);
+		mpadd(r, dom->a, r);
+		mpmul(r, ret->x, r);
+		mpadd(r, dom->b, r);
+		if(!mpsqrt(r, dom->p, r)){
+			mpfree(r);
+			goto err;
+		}
+		if((r->p[0] & 1) != o)
+			mpsub(dom->p, r, r);
+		mpassign(r, ret->y);
+		mpfree(r);
+		if(!ecverify(dom, ret))
+			goto err;
+		break;
+	case 4:
+		if(halfpt(dom, s, &s, ret->x) == nil)
+			goto err;
+		if(halfpt(dom, s, &s, ret->y) == nil)
+			goto err;
+		if(!ecverify(dom, ret))
+			goto err;
+		break;
+	}
+	if(ret->z != nil && !ret->inf)
+		mpassign(mpone, ret->z);
+	return ret;
+
+err:
+	if(rptr)
+		*rptr = s;
+	if(allocd){
+		mpfree(ret->x);
+		mpfree(ret->y);
+		free(ret);
+	}
+	return nil;
+}
+
+ECpriv*
+ecgen(ECdomain *dom, ECpriv *p)
+{
+	if(p == nil){
+		p = mallocz(sizeof(*p), 1);
+		if(p == nil)
+			return nil;
+		p->x = mpnew(0);
+		p->y = mpnew(0);
+		p->d = mpnew(0);
+	}
+	for(;;){
+		mpnrand(dom->n, genrandom, p->d);
+		if(mpcmp(p->d, mpzero) > 0)
+			break;
+	}
+	ecmul(dom, &dom->G, p->d, p);
+	return p;
+}
+
+void
+ecdsasign(ECdomain *dom, ECpriv *priv, uchar *dig, int len, mpint *r, mpint *s)
+{
+	ECpriv tmp;
+	mpint *E, *t;
+
+	tmp.x = mpnew(0);
+	tmp.y = mpnew(0);
+	tmp.z = nil;
+	tmp.d = mpnew(0);
+	E = betomp(dig, len, nil);
+	t = mpnew(0);
+	if(mpsignif(dom->n) < 8*len)
+		mpright(E, 8*len - mpsignif(dom->n), E);
+	for(;;){
+		ecgen(dom, &tmp);
+		mpmod(tmp.x, dom->n, r);
+		if(mpcmp(r, mpzero) == 0)
+			continue;
+		mpmul(r, priv->d, s);
+		mpadd(E, s, s);
+		mpinvert(tmp.d, dom->n, t);
+		mpmodmul(s, t, dom->n, s);
+		if(mpcmp(s, mpzero) != 0)
+			break;
+	}
+	mpfree(t);
+	mpfree(E);
+	mpfree(tmp.x);
+	mpfree(tmp.y);
+	mpfree(tmp.d);
+}
+
+int
+ecdsaverify(ECdomain *dom, ECpub *pub, uchar *dig, int len, mpint *r, mpint *s)
+{
+	mpint *E, *t, *u1, *u2;
+	ECpoint R, S;
+	int ret;
+
+	if(mpcmp(r, mpone) < 0 || mpcmp(s, mpone) < 0 || mpcmp(r, dom->n) >= 0 || mpcmp(r, dom->n) >= 0)
+		return 0;
+	E = betomp(dig, len, nil);
+	if(mpsignif(dom->n) < 8*len)
+		mpright(E, 8*len - mpsignif(dom->n), E);
+	t = mpnew(0);
+	u1 = mpnew(0);
+	u2 = mpnew(0);
+	R.x = mpnew(0);
+	R.y = mpnew(0);
+	R.z = mpnew(0);
+	S.x = mpnew(0);
+	S.y = mpnew(0);
+	S.z = mpnew(0);
+	mpinvert(s, dom->n, t);
+	mpmodmul(E, t, dom->n, u1);
+	mpmodmul(r, t, dom->n, u2);
+	ecmul(dom, &dom->G, u1, &R);
+	ecmul(dom, pub, u2, &S);
+	ecadd(dom, &R, &S, &R);
+	ret = 0;
+	if(!R.inf){
+		jacobian_affine(dom->p, R.x, R.y, R.z);
+		mpmod(R.x, dom->n, t);
+		ret = mpcmp(r, t) == 0;
+	}
+	mpfree(E);
+	mpfree(t);
+	mpfree(u1);
+	mpfree(u2);
+	mpfree(R.x);
+	mpfree(R.y);
+	mpfree(R.z);
+	mpfree(S.x);
+	mpfree(S.y);
+	mpfree(S.z);
+	return ret;
+}
+
+static char *code = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+
+void
+base58enc(uchar *src, char *dst, int len)
+{
+	mpint *n, *r, *b;
+	char *sdst, t;
+	
+	sdst = dst;
+	n = betomp(src, len, nil);
+	b = uitomp(58, nil);
+	r = mpnew(0);
+	while(mpcmp(n, mpzero) != 0){
+		mpdiv(n, b, n, r);
+		*dst++ = code[mptoui(r)];
+	}
+	for(; *src == 0; src++)
+		*dst++ = code[0];
+	*dst-- = 0;
+	while(dst > sdst){
+		t = *sdst;
+		*sdst++ = *dst;
+		*dst-- = t;
+	}
+}
+
+int
+base58dec(char *src, uchar *dst, int len)
+{
+	mpint *n, *b, *r;
+	char *t;
+	
+	n = mpnew(0);
+	r = mpnew(0);
+	b = uitomp(58, nil);
+	for(; *src; src++){
+		t = strchr(code, *src);
+		if(t == nil){
+			mpfree(n);
+			mpfree(r);
+			mpfree(b);
+			werrstr("invalid base58 char");
+			return -1;
+		}
+		uitomp(t - code, r);
+		mpmul(n, b, n);
+		mpadd(n, r, n);
+	}
+	mptober(n, dst, len);
+	mpfree(n);
+	mpfree(r);
+	mpfree(b);
+	return 0;
+}
+
+void
+ecdominit(ECdomain *dom, void (*init)(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h))
+{
+	memset(dom, 0, sizeof(*dom));
+	dom->p = mpnew(0);
+	dom->a = mpnew(0);
+	dom->b = mpnew(0);
+	dom->G.x = mpnew(0);
+	dom->G.y = mpnew(0);
+	dom->n = mpnew(0);
+	dom->h = mpnew(0);
+	if(init){
+		(*init)(dom->p, dom->a, dom->b, dom->G.x, dom->G.y, dom->n, dom->h);
+		dom->p = mpfield(dom->p);
+	}
+}
+
+void
+ecdomfree(ECdomain *dom)
+{
+	if(dom == nil || dom->p == nil)
+		return;	/* idempotent: no-op on zeroed or already-freed domain */
+	mpfree(dom->p);
+	mpfree(dom->a);
+	mpfree(dom->b);
+	mpfree(dom->G.x);
+	mpfree(dom->G.y);
+	mpfree(dom->n);
+	mpfree(dom->h);
+	memset(dom, 0, sizeof(*dom));
+}
+
+int
+ecencodepub(ECdomain *dom, ECpub *pub, uchar *data, int len)
+{
+	int n;
+
+	n = (mpsignif(dom->p)+7)/8;
+	if(len < 1 + 2*n)
+		return 0;
+	len = 1 + 2*n;
+	data[0] = 0x04;
+	mptober(pub->x, data+1, n);
+	mptober(pub->y, data+1+n, n);
+	return len;
+}
+
+ECpub*
+ecdecodepub(ECdomain *dom, uchar *data, int len)
+{
+	ECpub *pub;
+	int n;
+
+	n = (mpsignif(dom->p)+7)/8;
+	if(len != 1 + 2*n || data[0] != 0x04)
+		return nil;
+	pub = mallocz(sizeof(*pub), 1);
+	if(pub == nil)
+		return nil;
+	pub->x = betomp(data+1, n, nil);
+	pub->y = betomp(data+1+n, n, nil);
+	if(!ecpubverify(dom, pub)){
+		ecpubfree(pub);
+		pub = nil;
+	}
+	return pub;
+}
+
+void
+ecpubfree(ECpub *p)
+{
+	if(p == nil)
+		return;
+	mpfree(p->x); p->x = nil;
+	mpfree(p->y); p->y = nil;
+	mpfree(p->z); p->z = nil;
+	free(p);
+}
--- sys/src/libsec/port/jacobian.mp
+++ sys/src/libsec/port/jacobian.mp
@@ -0,0 +1,60 @@
+# Elliptic curve group operations in jacobian coordinates:
+#	x=X/Z^2
+#	x=Y/Z^3
+
+jacobian_new(x,y,z, X,Y,Z) {
+	X = x;
+	Y = y;
+	Z = z;
+}
+jacobian_inf(X,Y,Z) {
+	X,Y,Z = jacobian_new(0,1,0);
+}
+jacobian_affine(p, X,Y,Z) mod(p) {
+	if(Z != 0) {
+		ZZ = Z^2;
+		ZZZ = ZZ*Z;
+		X = X / ZZ;
+		Y = Y / ZZZ;
+		Z = 1;
+	}
+}
+jacobian_dbl(p,a, X1,Y1,Z1, X3,Y3,Z3) mod(p) {
+	if(Y1 == 0) {
+		X3,Y3,Z3 = jacobian_inf();
+	} else {
+		XX = X1^2;
+		YY = Y1^2;
+		YYYY = YY^2;
+		ZZ = Z1^2;
+		S = 2*((X1+YY)^2-XX-YYYY);
+		M = 3*XX+a*ZZ^2;
+		Z3 = (Y1+Z1)^2-YY-ZZ;	
+		X3 = M^2-2*S;
+		Y3 = M*(S-X3)-8*YYYY;
+	}
+}
+jacobian_add(p,a, X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3) mod(p) {
+	Z1Z1 = Z1^2;
+	Z2Z2 = Z2^2;
+	U1 = X1*Z2Z2;
+	U2 = X2*Z1Z1;
+	S1 = Y1*Z2*Z2Z2;
+	S2 = Y2*Z1*Z1Z1;
+	if(U1 == U2) {
+		if(S1 != S2) {
+			X3,Y3,Z3 = jacobian_inf();
+		} else {
+			X3,Y3,Z3 = jacobian_dbl(p,a, X1,Y1,Z1);
+		}
+	} else {
+		H = U2-U1;
+		I = (2*H)^2;
+		J = H*I;
+		r = 2*(S2-S1);
+		V = U1*I;
+		X3 = r^2-J-2*V;
+		Y3 = r*(V-X3)-2*S1*J;
+		Z3 = ((Z1+Z2)^2-Z1Z1-Z2Z2)*H;
+	}
+}
--- sys/src/libsec/port/mkfile
+++ sys/src/libsec/port/mkfile
@@ -5,6 +5,7 @@
 CFILES = des.c desmodes.c desECB.c desCBC.c des3ECB.c des3CBC.c\
 	aes.c aes_gcm.c blowfish.c chacha.c \
 	curve25519.c curve25519_dh.c\
+	ecc.c jacobian.c secp256r1.c secp384r1.c\
 	hmac.c md5.c md5block.c md4.c sha1.c sha1block.c\
 	sha2_64.c sha2_128.c sha2block64.c sha2block128.c\
 	sha1pickle.c md5pickle.c\
@@ -24,6 +25,11 @@
 	ccpoly.c\
 	tsmemcmp.c\
 
+# jacobian.c is generated from jacobian.mp by mpc(1) at build time.
+# secp256r1.c and secp384r1.c are hand-written (9legacy's mpc mis-parses
+# large hex literals and the 2^N power operator used in 9front's .mp).
+CLEANFILES=jacobian.c
+
 ALLOFILES=${CFILES:%.c=%.$O}
 
 # cull things in the per-machine directories from this list
@@ -38,6 +44,13 @@
 	$CFILES\
 
 </sys/src/cmd/mksyslib
+
+# generate .c from .mp (mpc) for curve/jacobian parameter tables
+%.c:D:	%.mp
+	echo '#include <u.h>' > $target
+	echo '#include <libc.h>' >> $target
+	echo '#include <mp.h>' >> $target
+	mpc $prereq >> $target
 
 $O.rsatest: rsatest.$O
 	$LD -o $target $prereq
--- sys/src/libsec/port/secp256r1.c
+++ sys/src/libsec/port/secp256r1.c
@@ -0,0 +1,27 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+
+/*
+ * NIST P-256 (secp256r1) curve parameters.
+ *
+ * Hand-written to avoid 9legacy's mpc(1) mis-parsing large hex literals
+ * and the "2^N" power operator — the 9front .mp source relies on both,
+ * and 9legacy's mpc silently produces garbage for them.
+ *
+ * Reference: SEC 2 v2.0 §2.4.2 / NIST SP 800-186 §3.2.1.3.
+ */
+void
+secp256r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h)
+{
+	/* p = 2^256 - 2^224 + 2^192 + 2^96 - 1 */
+	strtomp("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", nil, 16, p);
+	/* a = p - 3 */
+	uitomp(3, a);
+	mpsub(p, a, a);
+	strtomp("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", nil, 16, b);
+	strtomp("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", nil, 16, x);
+	strtomp("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", nil, 16, y);
+	strtomp("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", nil, 16, n);
+	uitomp(1, h);
+}
--- sys/src/libsec/port/secp384r1.c
+++ sys/src/libsec/port/secp384r1.c
@@ -0,0 +1,24 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+
+/*
+ * NIST P-384 (secp384r1) curve parameters.  See secp256r1.c for the
+ * reason this file is hand-written rather than generated from a .mp.
+ *
+ * Reference: SEC 2 v2.0 §2.5.1 / NIST SP 800-186 §3.2.1.4.
+ */
+void
+secp384r1(mpint *p, mpint *a, mpint *b, mpint *x, mpint *y, mpint *n, mpint *h)
+{
+	/* p = 2^384 - 2^128 - 2^96 + 2^32 - 1 */
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", nil, 16, p);
+	/* a = p - 3 */
+	uitomp(3, a);
+	mpsub(p, a, a);
+	strtomp("B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", nil, 16, b);
+	strtomp("AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", nil, 16, x);
+	strtomp("3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F", nil, 16, y);
+	strtomp("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", nil, 16, n);
+	uitomp(1, h);
+}
--- sys/src/libsec/port/x509.c
+++ sys/src/libsec/port/x509.c
@@ -1569,6 +1569,7 @@
 	char*	validity_end;
 	char*	subject;
 	int	publickey_alg;
+	int	curve;		/* CURVE_* index when publickey_alg == ALG_ecPublicKey, else -1 */
 	Bytes*	publickey;
 	int	signature_alg;
 	Bytes*	signature;
@@ -1592,6 +1593,11 @@
 	ALG_sha384,
 	ALG_sha512,
 	ALG_sha224,
+	ALG_ecPublicKey,
+	ALG_sha1WithECDSA,
+	ALG_sha256WithECDSA,
+	ALG_sha384WithECDSA,
+	ALG_sha512WithECDSA,
 	NUMALGS
 };
 typedef struct Ints9 {
@@ -1614,6 +1620,11 @@
 static Ints9 oid_sha384 ={9, 2, 16, 840, 1, 101, 3, 4, 2, 2 };
 static Ints9 oid_sha512 ={9, 2, 16, 840, 1, 101, 3, 4, 2, 3 };
 static Ints9 oid_sha224 ={9, 2, 16, 840, 1, 101, 3, 4, 2, 4 };
+static Ints9 oid_ecPublicKey = {6, 1, 2, 840, 10045, 2, 1 };
+static Ints9 oid_sha1WithECDSA = {6, 1, 2, 840, 10045, 4, 1 };
+static Ints9 oid_sha256WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 2 };
+static Ints9 oid_sha384WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 3 };
+static Ints9 oid_sha512WithECDSA = {7, 1, 2, 840, 10045, 4, 3, 4 };
 static Ints *alg_oid_tab[NUMALGS+1] = {
 	(Ints*)&oid_rsaEncryption,
 	(Ints*)&oid_md2WithRSAEncryption,
@@ -1631,10 +1642,41 @@
 	(Ints*)&oid_sha384,
 	(Ints*)&oid_sha512,
 	(Ints*)&oid_sha224,
+	(Ints*)&oid_ecPublicKey,
+	(Ints*)&oid_sha1WithECDSA,
+	(Ints*)&oid_sha256WithECDSA,
+	(Ints*)&oid_sha384WithECDSA,
+	(Ints*)&oid_sha512WithECDSA,
 	nil
 };
-static DigestFun digestalg[NUMALGS+1] = { md5, md5, md5, md5, sha1, sha1, sha2_256, sha2_384, sha2_512, sha2_224, md5, sha1, sha2_256, sha2_384, sha2_512, sha2_224, nil };
+static DigestFun digestalg[NUMALGS+1] = {
+	md5, md5, md5, md5, sha1, sha1, sha2_256, sha2_384, sha2_512, sha2_224,
+	md5, sha1, sha2_256, sha2_384, sha2_512, sha2_224,
+	nil,                       /* ecPublicKey — key alg, no digest */
+	sha1, sha2_256, sha2_384, sha2_512,  /* ECDSA signature algs */
+	nil
+};
 
+/* Named curve OIDs (parameters of an ecPublicKey SubjectPublicKeyInfo) */
+static Ints9 oid_secp256r1 = {7, 1, 2, 840, 10045, 3, 1, 7};
+static Ints9 oid_secp384r1 = {5, 1, 3, 132, 0, 34};
+
+enum {
+	CURVE_secp256r1,
+	CURVE_secp384r1,
+	NUMCURVES
+};
+static Ints *namedcurves_oid_tab[NUMCURVES+1] = {
+	(Ints*)&oid_secp256r1,
+	(Ints*)&oid_secp384r1,
+	nil
+};
+/* init callback per curve, indexed by CURVE_* */
+static void (*namedcurves[NUMCURVES])(mpint*, mpint*, mpint*, mpint*, mpint*, mpint*, mpint*) = {
+	secp256r1,
+	secp384r1,
+};
+
 static void
 freecert(CertX509* c)
 {
@@ -1729,6 +1771,24 @@
 	return oid_lookup(oid, alg_oid_tab);
 }
 
+/*
+ * For an ecPublicKey AlgorithmIdentifier, the parameters slot holds the
+ * namedCurve OID.  Returns the CURVE_* index, or -1 if the curve isn't
+ * supported (callers should treat this as a bad certificate).
+ */
+static int
+parse_ec_curve(Elem *e)
+{
+	Elist *el;
+	Ints *oid;
+
+	if(!is_seq(e, &el) || el == nil || el->tl == nil)
+		return -1;
+	if(!is_oid(&el->tl->hd, &oid))
+		return -1;
+	return oid_lookup(oid, namedcurves_oid_tab);
+}
+
 static CertX509*
 decode_cert(Bytes* a)
 {
@@ -1763,6 +1823,7 @@
 	c->validity_end = nil;
 	c->subject = nil;
 	c->publickey_alg = -1;
+	c->curve = -1;
 	c->publickey = nil;
 	c->signature_alg = -1;
 	c->signature = nil;
@@ -1839,6 +1900,11 @@
 	c->publickey_alg = parse_alg(&elpubkey->hd);
 	if(c->publickey_alg < 0)
 		goto errret;
+	if(c->publickey_alg == ALG_ecPublicKey){
+		c->curve = parse_ec_curve(&elpubkey->hd);
+		if(c->curve < 0)
+			goto errret;
+	}
   	if(!is_bitstring(&elpubkey->tl->hd, &bits))
 		goto errret;
 	if(bits->unusedbits != 0)
@@ -2211,6 +2277,77 @@
 	pk = decode_rsapubkey(c->publickey);
 	freecert(c);
 	return pk;
+}
+
+/*
+ * Extract the EC public key from an X.509 certificate.  On success,
+ * *dom is initialised with the cert's named-curve parameters (caller
+ * must ecdomfree).  Returns nil if the cert isn't ECC-keyed or uses
+ * an unsupported curve.
+ */
+ECpub*
+X509toECpub(uchar *cert, int ncert, char *name, int nname, ECdomain *dom)
+{
+	char *e;
+	Bytes *b;
+	CertX509 *c;
+	ECpub *pub;
+
+	b = makebytes(cert, ncert);
+	c = decode_cert(b);
+	freebytes(b);
+	if(c == nil)
+		return nil;
+	if(name != nil && c->subject != nil){
+		e = strchr(c->subject, ',');
+		if(e != nil)
+			*e = 0;
+		strncpy(name, c->subject, nname);
+	}
+	pub = nil;
+	if(c->publickey_alg == ALG_ecPublicKey && c->curve >= 0 && c->curve < NUMCURVES){
+		ecdominit(dom, namedcurves[c->curve]);
+		pub = ecdecodepub(dom, c->publickey->data, c->publickey->len);
+		if(pub == nil)
+			ecdomfree(dom);
+	}
+	freecert(c);
+	return pub;
+}
+
+/*
+ * Verify an ECDSA signature.  sig is the ASN.1 SEQUENCE { r INTEGER, s INTEGER }
+ * as carried in TLS ServerKeyExchange or X.509 certificate signatures.
+ * edigest is the hash of the signed bytes (caller-computed).
+ * Returns nil on success, error string on failure.
+ */
+char*
+X509ecdsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, ECdomain *dom, ECpub *pub)
+{
+	Elem e;
+	Elist *el;
+	mpint *r, *s;
+	char *err;
+
+	r = s = nil;
+	err = "bad signature";
+	if(decode(sig, siglen, &e) != ASN_OK)
+		goto end;
+	if(!is_seq(&e, &el) || elistlen(el) != 2)
+		goto end;
+	r = asn1mpint(&el->hd);
+	if(r == nil)
+		goto end;
+	s = asn1mpint(&el->tl->hd);
+	if(s == nil)
+		goto end;
+	if(ecdsaverify(dom, pub, edigest, edigestlen, r, s))
+		err = nil;
+end:
+	freevalfields(&e.val);
+	mpfree(s);
+	mpfree(r);
+	return err;
 }
 
 int

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.