shithub: pki

ref: e18ea66b577b3678c8897e26bc29dcbf027e2fcb
dir: /libsec.diff/

View raw version
diff e1201b57f3e37d06589578280251d5b54e490317 uncommitted
--- a/sys/include/libsec.h
+++ b/sys/include/libsec.h
@@ -1,7 +1,6 @@
 #pragma	lib	"libsec.a"
 #pragma	src	"/sys/src/libsec"
 
-
 #ifndef _MPINT
 typedef struct mpint mpint;
 #endif
@@ -325,6 +324,7 @@
 typedef struct RSApub RSApub;
 typedef struct RSApriv RSApriv;
 typedef struct PEMChain PEMChain;
+typedef struct CertX509 CertX509;
 
 /* public/encryption key */
 struct RSApub
@@ -354,6 +354,24 @@
 	int	pemlen;
 };
 
+struct CertX509 {
+	int	serial;
+	char*	issuer;
+	vlong	validity_start;
+	vlong	validity_end;
+	char*	subject;
+	char**	altsubject;
+	int	naltsubject;
+	int	publickey_alg;
+	void*	publickey;
+	int	signature_alg;
+	void*	signature;
+	int	curve;
+	void*	ext;
+	uchar	digest[SHA2_512dlen];
+	int	digestlen;
+};
+
 RSApriv*	rsagen(int nlen, int elen, int rounds);
 RSApriv*	rsafill(mpint *n, mpint *e, mpint *d, mpint *p, mpint *q);
 mpint*		rsaencrypt(RSApub *k, mpint *in, mpint *out);
@@ -363,6 +381,9 @@
 RSApriv*	rsaprivalloc(void);
 void		rsaprivfree(RSApriv*);
 RSApub*		rsaprivtopub(RSApriv*);
+char*		X509verify(CertX509*, CertX509*);
+CertX509*	X509decode(uchar*, int);
+void		X509free(CertX509*);
 RSApub*		X509toRSApub(uchar*, int, char*, int);
 RSApub*		X509reqtoRSApub(uchar*, int, char*, int);
 RSApub*		asn1toRSApub(uchar*, int);
@@ -375,7 +396,7 @@
 char*		X509rsaverify(uchar *cert, int ncert, RSApub *pk);
 char*		X509rsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, RSApub *pk);
 
-void		X509dump(uchar *cert, int ncert);
+void		X509dump(int fd, uchar *cert, int ncert);
 
 mpint*		pkcs1padbuf(uchar *buf, int len, mpint *modulus, int blocktype);
 int		pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype);
@@ -495,7 +516,51 @@
 	char	*pskID;
 } TLSconn;
 
+typedef struct TLSClientConf {
+	uchar	*cert;
+	int	ncert;
+	int	noverify;
+	char	*sessiontype;
+	int	(*trace)(char*fmt, ...);
+	struct {
+		uchar	*key;
+		int	nkey;
+		char	*id;
+	} psk;
+	struct {
+		int	nkey;
+		char	*constant;
+	} ttls;
+} TLSClientConf;
+
+typedef struct TLSServerConf {
+	PEMChain	*certs;
+	char		*sessiontype;
+	int		(*trace)(char*fmt, ...);
+	struct {
+		uchar	*key;
+		int	nkey;
+		char	*id;
+	} psk;
+	struct {
+		int	nkey;
+		char	*constant;
+	} ttls;
+} TLSServerConf;
+
+typedef struct TLSParams {
+	char		dir[40];	/* connection directory */
+	PEMChain	*certs;
+	struct {
+		uchar	*key;
+		int	nkey;
+	} ttls;
+} TLSParams;
+
 /* tlshand.c */
+int tlsclient(int fd, char *srv, TLSClientConf *cfg, TLSParams **pparam);
+int tlsserver(int fd, TLSServerConf *cfg, TLSParams **pparam);
+void tlsparamfree(TLSParams *p);
 int tlsClient(int fd, TLSconn *c);
 int tlsServer(int fd, TLSconn *c);
 
--- a/sys/src/libsec/port/curve25519_dh.c
+++ b/sys/src/libsec/port/curve25519_dh.c
@@ -1,39 +1,816 @@
 #include "os.h"
 #include <mp.h>
 #include <libsec.h>
+// Derived From Monocypher version 4.0.2
+//
+// This file is dual-licensed.  Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence.  The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain.  The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2017-2020, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+//    notice, this list of conditions and the following disclaimer in the
+//    documentation and/or other materials provided with the
+//    distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2017-2020 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide.  This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software.  If not, see
+// <https://creativecommons.org/publicdomain/zero/1.0/>
 
-static uchar nine[32] = {9};
-static uchar zero[32] = {0};
+/////////////////
+/// Utilities ///
+/////////////////
+#define FOR_T(type, i, start, end) for (type i = (start); i < (end); i++)
+#define FOR(i, start, end)         FOR_T(usize, i, start, end)
+#define COPY(dst, src, size)       FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_]
+#define ZERO(buf, size)            FOR(_i_, 0, size) (buf)[_i_] = 0
+#define WIPE_CTX(ctx)              crypto_wipe(ctx   , sizeof(*(ctx)))
+#define WIPE_BUFFER(buffer)        crypto_wipe(buffer, sizeof(buffer))
+#define MIN(a, b)                  ((a) <= (b) ? (a) : (b))
+#define MAX(a, b)                  ((a) >= (b) ? (a) : (b))
 
-int
-x25519(uchar out[32], uchar s[32], uchar u[32])
+typedef s8int	i8;
+typedef u8int	u8;
+typedef s16int	i16;
+typedef u32int	u32;
+typedef s32int	i32;
+typedef s64int	i64;
+typedef u64int	u64;
+#ifdef __U_H
+typedef	size_t	usize;
+#endif
+
+static const u8 zero[128] = {0};
+
+// returns the smallest positive integer y such that
+// (x + y) % pow_2  == 0
+// Basically, y is the "gap" missing to align x.
+// Only works when pow_2 is a power of 2.
+// Note: we use ~x+1 instead of -x to avoid compiler warnings
+static usize gap(usize x, usize pow_2)
 {
-	uchar sf, sl, ul;
+	return (~x + 1) & (pow_2 - 1);
+}
 
-	sf = s[0];
-	sl = s[31];
-	ul = u[31];
+static u32 load24_le(const u8 s[3])
+{
+	return
+		((u32)s[0] <<  0) |
+		((u32)s[1] <<  8) |
+		((u32)s[2] << 16);
+}
 
-	/* clamp */
-	s[0] &= ~7;			/* clear bit 0,1,2 */
-	s[31] = 0x40 | (s[31] & 0x7f);	/* set bit 254, clear bit 255 */
+static u32 load32_le(const u8 s[4])
+{
+	return
+		((u32)s[0] <<  0) |
+		((u32)s[1] <<  8) |
+		((u32)s[2] << 16) |
+		((u32)s[3] << 24);
+}
 
-	/*
-		Implementations MUST accept non-canonical values and process them as
-   		if they had been reduced modulo the field prime.  The non-canonical
-   		values are 2^255 - 19 through 2^255 - 1 for X25519
-	*/
-	u[31] &= 0x7f;
-	
-	curve25519(out, s, u);
+static u64 load64_le(const u8 s[8])
+{
+	return load32_le(s) | ((u64)load32_le(s+4) << 32);
+}
 
-	s[0] = sf;
-	s[31] = sl;
-	u[31] = ul;
+static void store32_le(u8 out[4], u32 in)
+{
+	out[0] =  in        & 0xff;
+	out[1] = (in >>  8) & 0xff;
+	out[2] = (in >> 16) & 0xff;
+	out[3] = (in >> 24) & 0xff;
+}
 
-	return tsmemcmp(out, zero, 32) != 0;
+static void store64_le(u8 out[8], u64 in)
+{
+	store32_le(out    , (u32)in );
+	store32_le(out + 4, in >> 32);
 }
 
+static void load32_le_buf (u32 *dst, const u8 *src, usize size) {
+	FOR(i, 0, size) { dst[i] = load32_le(src + i*4); }
+}
+static void load64_le_buf (u64 *dst, const u8 *src, usize size) {
+	FOR(i, 0, size) { dst[i] = load64_le(src + i*8); }
+}
+static void store32_le_buf(u8 *dst, const u32 *src, usize size) {
+	FOR(i, 0, size) { store32_le(dst + i*4, src[i]); }
+}
+static void store64_le_buf(u8 *dst, const u64 *src, usize size) {
+	FOR(i, 0, size) { store64_le(dst + i*8, src[i]); }
+}
+
+static u64 rotr64(u64 x, u64 n) { return (x >> n) ^ (x << (64 - n)); }
+static u32 rotl32(u32 x, u32 n) { return (x << n) ^ (x >> (32 - n)); }
+
+static int neq0(u64 diff)
+{
+	// constant time comparison to zero
+	// return diff != 0 ? -1 : 0
+	u64 half = (diff >> 32) | ((u32)diff);
+	return (1 & ((half - 1) >> 32)) - 1;
+}
+
+static u64 x16(const u8 a[16], const u8 b[16])
+{
+	return (load64_le(a + 0) ^ load64_le(b + 0))
+		|  (load64_le(a + 8) ^ load64_le(b + 8));
+}
+static u64 x32(const u8 a[32],const u8 b[32]){return x16(a,b)| x16(a+16, b+16);}
+static u64 x64(const u8 a[64],const u8 b[64]){return x32(a,b)| x32(a+32, b+32);}
+int crypto_verify16(const u8 a[16], const u8 b[16]){ return neq0(x16(a, b)); }
+int crypto_verify32(const u8 a[32], const u8 b[32]){ return neq0(x32(a, b)); }
+int crypto_verify64(const u8 a[64], const u8 b[64]){ return neq0(x64(a, b)); }
+
+void crypto_wipe(void *secret, usize size)
+{
+	volatile u8 *v_secret = (u8*)secret;
+	ZERO(v_secret, size);
+}
+
+////////////////////////////////////
+/// Arithmetic modulo 2^255 - 19 ///
+////////////////////////////////////
+//  Originally taken from SUPERCOP's ref10 implementation.
+//  A bit bigger than TweetNaCl, over 4 times faster.
+
+// field element
+typedef i32 fe[10];
+
+// field constants
+//
+// fe_one      : 1
+// sqrtm1      : sqrt(-1)
+// d           :     -121665 / 121666
+// D2          : 2 * -121665 / 121666
+// lop_x, lop_y: low order point in Edwards coordinates
+// ufactor     : -sqrt(-1) * 2
+// A2          : 486662^2  (A squared)
+static const fe fe_one  = {1};
+static const fe sqrtm1  = {
+	-32595792, -7943725, 9377950, 3500415, 12389472,
+	-272473, -25146209, -2005654, 326686, 11406482,
+};
+static const fe d       = {
+	-10913610, 13857413, -15372611, 6949391, 114729,
+	-8787816, -6275908, -3247719, -18696448, -12055116,
+};
+static const fe D2      = {
+	-21827239, -5839606, -30745221, 13898782, 229458,
+	15978800, -12551817, -6495438, 29715968, 9444199,
+};
+static const fe lop_x   = {
+	21352778, 5345713, 4660180, -8347857, 24143090,
+	14568123, 30185756, -12247770, -33528939, 8345319,
+};
+static const fe lop_y   = {
+	-6952922, -1265500, 6862341, -7057498, -4037696,
+	-5447722, 31680899, -15325402, -19365852, 1569102,
+};
+static const fe ufactor = {
+	-1917299, 15887451, -18755900, -7000830, -24778944,
+	544946, -16816446, 4011309, -653372, 10741468,
+};
+static const fe A2      = {
+	12721188, 3529, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void fe_0(fe h) {           ZERO(h  , 10); }
+static void fe_1(fe h) { h[0] = 1; ZERO(h+1,  9); }
+
+static void fe_copy(fe h,const fe f           ){FOR(i,0,10) h[i] =  f[i];      }
+static void fe_neg (fe h,const fe f           ){FOR(i,0,10) h[i] = -f[i];      }
+static void fe_add (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] + g[i];}
+static void fe_sub (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] - g[i];}
+
+static void fe_cswap(fe f, fe g, int b)
+{
+	i32 mask = -b; // -1 = 0xffffffff
+	FOR (i, 0, 10) {
+		i32 x = (f[i] ^ g[i]) & mask;
+		f[i] = f[i] ^ x;
+		g[i] = g[i] ^ x;
+	}
+}
+
+static void fe_ccopy(fe f, const fe g, int b)
+{
+	i32 mask = -b; // -1 = 0xffffffff
+	FOR (i, 0, 10) {
+		i32 x = (f[i] ^ g[i]) & mask;
+		f[i] = f[i] ^ x;
+	}
+}
+
+
+// Signed carry propagation
+// ------------------------
+//
+// Let t be a number.  It can be uniquely decomposed thus:
+//
+//    t = h*2^26 + l
+//    such that -2^25 <= l < 2^25
+//
+// Let c = (t + 2^25) / 2^26            (rounded down)
+//     c = (h*2^26 + l + 2^25) / 2^26   (rounded down)
+//     c =  h   +   (l + 2^25) / 2^26   (rounded down)
+//     c =  h                           (exactly)
+// Because 0 <= l + 2^25 < 2^26
+//
+// Let u = t          - c*2^26
+//     u = h*2^26 + l - h*2^26
+//     u = l
+// Therefore, -2^25 <= u < 2^25
+//
+// Additionally, if |t| < x, then |h| < x/2^26 (rounded down)
+//
+// Notations:
+// - In C, 1<<25 means 2^25.
+// - In C, x>>25 means floor(x / (2^25)).
+// - All of the above applies with 25 & 24 as well as 26 & 25.
+//
+//
+// Note on negative right shifts
+// -----------------------------
+//
+// In C, x >> n, where x is a negative integer, is implementation
+// defined.  In practice, all platforms do arithmetic shift, which is
+// equivalent to division by 2^26, rounded down.  Some compilers, like
+// GCC, even guarantee it.
+//
+// If we ever stumble upon a platform that does not propagate the sign
+// bit (we won't), visible failures will show at the slightest test, and
+// the signed shifts can be replaced by the following:
+//
+//     typedef struct { i64 x:39; } s25;
+//     typedef struct { i64 x:38; } s26;
+//     i64 shift25(i64 x) { s25 s; s.x = ((u64)x)>>25; return s.x; }
+//     i64 shift26(i64 x) { s26 s; s.x = ((u64)x)>>26; return s.x; }
+//
+// Current compilers cannot optimise this, causing a 30% drop in
+// performance.  Fairly expensive for something that never happens.
+//
+//
+// Precondition
+// ------------
+//
+// |t0|       < 2^63
+// |t1|..|t9| < 2^62
+//
+// Algorithm
+// ---------
+// c   = t0 + 2^25 / 2^26   -- |c|  <= 2^36
+// t0 -= c * 2^26           -- |t0| <= 2^25
+// t1 += c                  -- |t1| <= 2^63
+//
+// c   = t4 + 2^25 / 2^26   -- |c|  <= 2^36
+// t4 -= c * 2^26           -- |t4| <= 2^25
+// t5 += c                  -- |t5| <= 2^63
+//
+// c   = t1 + 2^24 / 2^25   -- |c|  <= 2^38
+// t1 -= c * 2^25           -- |t1| <= 2^24
+// t2 += c                  -- |t2| <= 2^63
+//
+// c   = t5 + 2^24 / 2^25   -- |c|  <= 2^38
+// t5 -= c * 2^25           -- |t5| <= 2^24
+// t6 += c                  -- |t6| <= 2^63
+//
+// c   = t2 + 2^25 / 2^26   -- |c|  <= 2^37
+// t2 -= c * 2^26           -- |t2| <= 2^25        < 1.1 * 2^25  (final t2)
+// t3 += c                  -- |t3| <= 2^63
+//
+// c   = t6 + 2^25 / 2^26   -- |c|  <= 2^37
+// t6 -= c * 2^26           -- |t6| <= 2^25        < 1.1 * 2^25  (final t6)
+// t7 += c                  -- |t7| <= 2^63
+//
+// c   = t3 + 2^24 / 2^25   -- |c|  <= 2^38
+// t3 -= c * 2^25           -- |t3| <= 2^24        < 1.1 * 2^24  (final t3)
+// t4 += c                  -- |t4| <= 2^25 + 2^38 < 2^39
+//
+// c   = t7 + 2^24 / 2^25   -- |c|  <= 2^38
+// t7 -= c * 2^25           -- |t7| <= 2^24        < 1.1 * 2^24  (final t7)
+// t8 += c                  -- |t8| <= 2^63
+//
+// c   = t4 + 2^25 / 2^26   -- |c|  <= 2^13
+// t4 -= c * 2^26           -- |t4| <= 2^25        < 1.1 * 2^25  (final t4)
+// t5 += c                  -- |t5| <= 2^24 + 2^13 < 1.1 * 2^24  (final t5)
+//
+// c   = t8 + 2^25 / 2^26   -- |c|  <= 2^37
+// t8 -= c * 2^26           -- |t8| <= 2^25        < 1.1 * 2^25  (final t8)
+// t9 += c                  -- |t9| <= 2^63
+//
+// c   = t9 + 2^24 / 2^25   -- |c|  <= 2^38
+// t9 -= c * 2^25           -- |t9| <= 2^24        < 1.1 * 2^24  (final t9)
+// t0 += c * 19             -- |t0| <= 2^25 + 2^38*19 < 2^44
+//
+// c   = t0 + 2^25 / 2^26   -- |c|  <= 2^18
+// t0 -= c * 2^26           -- |t0| <= 2^25        < 1.1 * 2^25  (final t0)
+// t1 += c                  -- |t1| <= 2^24 + 2^18 < 1.1 * 2^24  (final t1)
+//
+// Postcondition
+// -------------
+//   |t0|, |t2|, |t4|, |t6|, |t8|  <  1.1 * 2^25
+//   |t1|, |t3|, |t5|, |t7|, |t9|  <  1.1 * 2^24
+#define FE_CARRY	\
+	i64 c; \
+	c = (t0 + ((i64)1<<25)) >> 26;  t0 -= c * ((i64)1 << 26);  t1 += c; \
+	c = (t4 + ((i64)1<<25)) >> 26;  t4 -= c * ((i64)1 << 26);  t5 += c; \
+	c = (t1 + ((i64)1<<24)) >> 25;  t1 -= c * ((i64)1 << 25);  t2 += c; \
+	c = (t5 + ((i64)1<<24)) >> 25;  t5 -= c * ((i64)1 << 25);  t6 += c; \
+	c = (t2 + ((i64)1<<25)) >> 26;  t2 -= c * ((i64)1 << 26);  t3 += c; \
+	c = (t6 + ((i64)1<<25)) >> 26;  t6 -= c * ((i64)1 << 26);  t7 += c; \
+	c = (t3 + ((i64)1<<24)) >> 25;  t3 -= c * ((i64)1 << 25);  t4 += c; \
+	c = (t7 + ((i64)1<<24)) >> 25;  t7 -= c * ((i64)1 << 25);  t8 += c; \
+	c = (t4 + ((i64)1<<25)) >> 26;  t4 -= c * ((i64)1 << 26);  t5 += c; \
+	c = (t8 + ((i64)1<<25)) >> 26;  t8 -= c * ((i64)1 << 26);  t9 += c; \
+	c = (t9 + ((i64)1<<24)) >> 25;  t9 -= c * ((i64)1 << 25);  t0 += c * 19; \
+	c = (t0 + ((i64)1<<25)) >> 26;  t0 -= c * ((i64)1 << 26);  t1 += c; \
+	h[0]=(i32)t0;  h[1]=(i32)t1;  h[2]=(i32)t2;  h[3]=(i32)t3;  h[4]=(i32)t4; \
+	h[5]=(i32)t5;  h[6]=(i32)t6;  h[7]=(i32)t7;  h[8]=(i32)t8;  h[9]=(i32)t9
+
+// Decodes a field element from a byte buffer.
+// mask specifies how many bits we ignore.
+// Traditionally we ignore 1. It's useful for EdDSA,
+// which uses that bit to denote the sign of x.
+// Elligator however uses positive representatives,
+// which means ignoring 2 bits instead.
+static void fe_frombytes_mask(fe h, const u8 s[32], unsigned nb_mask)
+{
+	u32 mask = 0xffffff >> nb_mask;
+	i64 t0 =  load32_le(s);                    // t0 < 2^32
+	i64 t1 =  load24_le(s +  4) << 6;          // t1 < 2^30
+	i64 t2 =  load24_le(s +  7) << 5;          // t2 < 2^29
+	i64 t3 =  load24_le(s + 10) << 3;          // t3 < 2^27
+	i64 t4 =  load24_le(s + 13) << 2;          // t4 < 2^26
+	i64 t5 =  load32_le(s + 16);               // t5 < 2^32
+	i64 t6 =  load24_le(s + 20) << 7;          // t6 < 2^31
+	i64 t7 =  load24_le(s + 23) << 5;          // t7 < 2^29
+	i64 t8 =  load24_le(s + 26) << 4;          // t8 < 2^28
+	i64 t9 = (load24_le(s + 29) & mask) << 2;  // t9 < 2^25
+	FE_CARRY;                                  // Carry precondition OK
+}
+
+static void fe_frombytes(fe h, const u8 s[32])
+{
+	fe_frombytes_mask(h, s, 1);
+}
+
+
+// Precondition
+//   |h[0]|, |h[2]|, |h[4]|, |h[6]|, |h[8]|  <  1.1 * 2^25
+//   |h[1]|, |h[3]|, |h[5]|, |h[7]|, |h[9]|  <  1.1 * 2^24
+//
+// Therefore, |h| < 2^255-19
+// There are two possibilities:
+//
+// - If h is positive, all we need to do is reduce its individual
+//   limbs down to their tight positive range.
+// - If h is negative, we also need to add 2^255-19 to it.
+//   Or just remove 19 and chop off any excess bit.
+static void fe_tobytes(u8 s[32], const fe h)
+{
+	i32 t[10];
+	COPY(t, h, 10);
+	i32 q = (19 * t[9] + (((i32) 1) << 24)) >> 25;
+	//                 |t9|                    < 1.1 * 2^24
+	//  -1.1 * 2^24  <  t9                     < 1.1 * 2^24
+	//  -21  * 2^24  <  19 * t9                < 21  * 2^24
+	//  -2^29        <  19 * t9 + 2^24         < 2^29
+	//  -2^29 / 2^25 < (19 * t9 + 2^24) / 2^25 < 2^29 / 2^25
+	//  -16          < (19 * t9 + 2^24) / 2^25 < 16
+	FOR (i, 0, 5) {
+		q += t[2*i  ]; q >>= 26; // q = 0 or -1
+		q += t[2*i+1]; q >>= 25; // q = 0 or -1
+	}
+	// q =  0 iff h >= 0
+	// q = -1 iff h <  0
+	// Adding q * 19 to h reduces h to its proper range.
+	q *= 19;  // Shift carry back to the beginning
+	FOR (i, 0, 5) {
+		t[i*2  ] += q;  q = t[i*2  ] >> 26;  t[i*2  ] -= q * ((i32)1 << 26);
+		t[i*2+1] += q;  q = t[i*2+1] >> 25;  t[i*2+1] -= q * ((i32)1 << 25);
+	}
+	// h is now fully reduced, and q represents the excess bit.
+
+	store32_le(s +  0, ((u32)t[0] >>  0) | ((u32)t[1] << 26));
+	store32_le(s +  4, ((u32)t[1] >>  6) | ((u32)t[2] << 19));
+	store32_le(s +  8, ((u32)t[2] >> 13) | ((u32)t[3] << 13));
+	store32_le(s + 12, ((u32)t[3] >> 19) | ((u32)t[4] <<  6));
+	store32_le(s + 16, ((u32)t[5] >>  0) | ((u32)t[6] << 25));
+	store32_le(s + 20, ((u32)t[6] >>  7) | ((u32)t[7] << 19));
+	store32_le(s + 24, ((u32)t[7] >> 13) | ((u32)t[8] << 12));
+	store32_le(s + 28, ((u32)t[8] >> 20) | ((u32)t[9] <<  6));
+
+	WIPE_BUFFER(t);
+}
+
+// Precondition
+// -------------
+//   |f0|, |f2|, |f4|, |f6|, |f8|  <  1.65 * 2^26
+//   |f1|, |f3|, |f5|, |f7|, |f9|  <  1.65 * 2^25
+//
+//   |g0|, |g2|, |g4|, |g6|, |g8|  <  1.65 * 2^26
+//   |g1|, |g3|, |g5|, |g7|, |g9|  <  1.65 * 2^25
+static void fe_mul_small(fe h, const fe f, i32 g)
+{
+	i64 t0 = f[0] * (i64) g;  i64 t1 = f[1] * (i64) g;
+	i64 t2 = f[2] * (i64) g;  i64 t3 = f[3] * (i64) g;
+	i64 t4 = f[4] * (i64) g;  i64 t5 = f[5] * (i64) g;
+	i64 t6 = f[6] * (i64) g;  i64 t7 = f[7] * (i64) g;
+	i64 t8 = f[8] * (i64) g;  i64 t9 = f[9] * (i64) g;
+	// |t0|, |t2|, |t4|, |t6|, |t8|  <  1.65 * 2^26 * 2^31  < 2^58
+	// |t1|, |t3|, |t5|, |t7|, |t9|  <  1.65 * 2^25 * 2^31  < 2^57
+
+	FE_CARRY; // Carry precondition OK
+}
+
+// Precondition
+// -------------
+//   |f0|, |f2|, |f4|, |f6|, |f8|  <  1.65 * 2^26
+//   |f1|, |f3|, |f5|, |f7|, |f9|  <  1.65 * 2^25
+//
+//   |g0|, |g2|, |g4|, |g6|, |g8|  <  1.65 * 2^26
+//   |g1|, |g3|, |g5|, |g7|, |g9|  <  1.65 * 2^25
+static void fe_mul(fe h, const fe f, const fe g)
+{
+	// Everything is unrolled and put in temporary variables.
+	// We could roll the loop, but that would make curve25519 twice as slow.
+	i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4];
+	i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9];
+	i32 g0 = g[0]; i32 g1 = g[1]; i32 g2 = g[2]; i32 g3 = g[3]; i32 g4 = g[4];
+	i32 g5 = g[5]; i32 g6 = g[6]; i32 g7 = g[7]; i32 g8 = g[8]; i32 g9 = g[9];
+	i32 F1 = f1*2; i32 F3 = f3*2; i32 F5 = f5*2; i32 F7 = f7*2; i32 F9 = f9*2;
+	i32 G1 = g1*19;  i32 G2 = g2*19;  i32 G3 = g3*19;
+	i32 G4 = g4*19;  i32 G5 = g5*19;  i32 G6 = g6*19;
+	i32 G7 = g7*19;  i32 G8 = g8*19;  i32 G9 = g9*19;
+	// |F1|, |F3|, |F5|, |F7|, |F9|  <  1.65 * 2^26
+	// |G0|, |G2|, |G4|, |G6|, |G8|  <  2^31
+	// |G1|, |G3|, |G5|, |G7|, |G9|  <  2^30
+
+	i64 t0 = f0*(i64)g0 + F1*(i64)G9 + f2*(i64)G8 + F3*(i64)G7 + f4*(i64)G6
+	       + F5*(i64)G5 + f6*(i64)G4 + F7*(i64)G3 + f8*(i64)G2 + F9*(i64)G1;
+	i64 t1 = f0*(i64)g1 + f1*(i64)g0 + f2*(i64)G9 + f3*(i64)G8 + f4*(i64)G7
+	       + f5*(i64)G6 + f6*(i64)G5 + f7*(i64)G4 + f8*(i64)G3 + f9*(i64)G2;
+	i64 t2 = f0*(i64)g2 + F1*(i64)g1 + f2*(i64)g0 + F3*(i64)G9 + f4*(i64)G8
+	       + F5*(i64)G7 + f6*(i64)G6 + F7*(i64)G5 + f8*(i64)G4 + F9*(i64)G3;
+	i64 t3 = f0*(i64)g3 + f1*(i64)g2 + f2*(i64)g1 + f3*(i64)g0 + f4*(i64)G9
+	       + f5*(i64)G8 + f6*(i64)G7 + f7*(i64)G6 + f8*(i64)G5 + f9*(i64)G4;
+	i64 t4 = f0*(i64)g4 + F1*(i64)g3 + f2*(i64)g2 + F3*(i64)g1 + f4*(i64)g0
+	       + F5*(i64)G9 + f6*(i64)G8 + F7*(i64)G7 + f8*(i64)G6 + F9*(i64)G5;
+	i64 t5 = f0*(i64)g5 + f1*(i64)g4 + f2*(i64)g3 + f3*(i64)g2 + f4*(i64)g1
+	       + f5*(i64)g0 + f6*(i64)G9 + f7*(i64)G8 + f8*(i64)G7 + f9*(i64)G6;
+	i64 t6 = f0*(i64)g6 + F1*(i64)g5 + f2*(i64)g4 + F3*(i64)g3 + f4*(i64)g2
+	       + F5*(i64)g1 + f6*(i64)g0 + F7*(i64)G9 + f8*(i64)G8 + F9*(i64)G7;
+	i64 t7 = f0*(i64)g7 + f1*(i64)g6 + f2*(i64)g5 + f3*(i64)g4 + f4*(i64)g3
+	       + f5*(i64)g2 + f6*(i64)g1 + f7*(i64)g0 + f8*(i64)G9 + f9*(i64)G8;
+	i64 t8 = f0*(i64)g8 + F1*(i64)g7 + f2*(i64)g6 + F3*(i64)g5 + f4*(i64)g4
+	       + F5*(i64)g3 + f6*(i64)g2 + F7*(i64)g1 + f8*(i64)g0 + F9*(i64)G9;
+	i64 t9 = f0*(i64)g9 + f1*(i64)g8 + f2*(i64)g7 + f3*(i64)g6 + f4*(i64)g5
+	       + f5*(i64)g4 + f6*(i64)g3 + f7*(i64)g2 + f8*(i64)g1 + f9*(i64)g0;
+	// t0 < 0.67 * 2^61
+	// t1 < 0.41 * 2^61
+	// t2 < 0.52 * 2^61
+	// t3 < 0.32 * 2^61
+	// t4 < 0.38 * 2^61
+	// t5 < 0.22 * 2^61
+	// t6 < 0.23 * 2^61
+	// t7 < 0.13 * 2^61
+	// t8 < 0.09 * 2^61
+	// t9 < 0.03 * 2^61
+
+	FE_CARRY; // Everything below 2^62, Carry precondition OK
+}
+
+// Precondition
+// -------------
+//   |f0|, |f2|, |f4|, |f6|, |f8|  <  1.65 * 2^26
+//   |f1|, |f3|, |f5|, |f7|, |f9|  <  1.65 * 2^25
+//
+// Note: we could use fe_mul() for this, but this is significantly faster
+static void fe_sq(fe h, const fe f)
+{
+	i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4];
+	i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9];
+	i32 f0_2  = f0*2;   i32 f1_2  = f1*2;   i32 f2_2  = f2*2;   i32 f3_2 = f3*2;
+	i32 f4_2  = f4*2;   i32 f5_2  = f5*2;   i32 f6_2  = f6*2;   i32 f7_2 = f7*2;
+	i32 f5_38 = f5*38;  i32 f6_19 = f6*19;  i32 f7_38 = f7*38;
+	i32 f8_19 = f8*19;  i32 f9_38 = f9*38;
+	// |f0_2| , |f2_2| , |f4_2| , |f6_2| , |f8_2|  <  1.65 * 2^27
+	// |f1_2| , |f3_2| , |f5_2| , |f7_2| , |f9_2|  <  1.65 * 2^26
+	// |f5_38|, |f6_19|, |f7_38|, |f8_19|, |f9_38| <  2^31
+
+	i64 t0 = f0  *(i64)f0    + f1_2*(i64)f9_38 + f2_2*(i64)f8_19
+	       + f3_2*(i64)f7_38 + f4_2*(i64)f6_19 + f5  *(i64)f5_38;
+	i64 t1 = f0_2*(i64)f1    + f2  *(i64)f9_38 + f3_2*(i64)f8_19
+	       + f4  *(i64)f7_38 + f5_2*(i64)f6_19;
+	i64 t2 = f0_2*(i64)f2    + f1_2*(i64)f1    + f3_2*(i64)f9_38
+	       + f4_2*(i64)f8_19 + f5_2*(i64)f7_38 + f6  *(i64)f6_19;
+	i64 t3 = f0_2*(i64)f3    + f1_2*(i64)f2    + f4  *(i64)f9_38
+	       + f5_2*(i64)f8_19 + f6  *(i64)f7_38;
+	i64 t4 = f0_2*(i64)f4    + f1_2*(i64)f3_2  + f2  *(i64)f2
+	       + f5_2*(i64)f9_38 + f6_2*(i64)f8_19 + f7  *(i64)f7_38;
+	i64 t5 = f0_2*(i64)f5    + f1_2*(i64)f4    + f2_2*(i64)f3
+	       + f6  *(i64)f9_38 + f7_2*(i64)f8_19;
+	i64 t6 = f0_2*(i64)f6    + f1_2*(i64)f5_2  + f2_2*(i64)f4
+	       + f3_2*(i64)f3    + f7_2*(i64)f9_38 + f8  *(i64)f8_19;
+	i64 t7 = f0_2*(i64)f7    + f1_2*(i64)f6    + f2_2*(i64)f5
+	       + f3_2*(i64)f4    + f8  *(i64)f9_38;
+	i64 t8 = f0_2*(i64)f8    + f1_2*(i64)f7_2  + f2_2*(i64)f6
+	       + f3_2*(i64)f5_2  + f4  *(i64)f4    + f9  *(i64)f9_38;
+	i64 t9 = f0_2*(i64)f9    + f1_2*(i64)f8    + f2_2*(i64)f7
+	       + f3_2*(i64)f6    + f4  *(i64)f5_2;
+	// t0 < 0.67 * 2^61
+	// t1 < 0.41 * 2^61
+	// t2 < 0.52 * 2^61
+	// t3 < 0.32 * 2^61
+	// t4 < 0.38 * 2^61
+	// t5 < 0.22 * 2^61
+	// t6 < 0.23 * 2^61
+	// t7 < 0.13 * 2^61
+	// t8 < 0.09 * 2^61
+	// t9 < 0.03 * 2^61
+
+	FE_CARRY;
+}
+
+//  Parity check.  Returns 0 if even, 1 if odd
+static int fe_isodd(const fe f)
+{
+	u8 s[32];
+	fe_tobytes(s, f);
+	u8 isodd = s[0] & 1;
+	WIPE_BUFFER(s);
+	return isodd;
+}
+
+// Returns 1 if equal, 0 if not equal
+static int fe_isequal(const fe f, const fe g)
+{
+	u8 fs[32];
+	u8 gs[32];
+	fe_tobytes(fs, f);
+	fe_tobytes(gs, g);
+	int isdifferent = crypto_verify32(fs, gs);
+	WIPE_BUFFER(fs);
+	WIPE_BUFFER(gs);
+	return 1 + isdifferent;
+}
+
+// Inverse square root.
+// Returns true if x is a square, false otherwise.
+// After the call:
+//   isr = sqrt(1/x)        if x is a non-zero square.
+//   isr = sqrt(sqrt(-1)/x) if x is not a square.
+//   isr = 0                if x is zero.
+// We do not guarantee the sign of the square root.
+//
+// Notes:
+// Let quartic = x^((p-1)/4)
+//
+// x^((p-1)/2) = chi(x)
+// quartic^2   = chi(x)
+// quartic     = sqrt(chi(x))
+// quartic     = 1 or -1 or sqrt(-1) or -sqrt(-1)
+//
+// Note that x is a square if quartic is 1 or -1
+// There are 4 cases to consider:
+//
+// if   quartic         = 1  (x is a square)
+// then x^((p-1)/4)     = 1
+//      x^((p-5)/4) * x = 1
+//      x^((p-5)/4)     = 1/x
+//      x^((p-5)/8)     = sqrt(1/x) or -sqrt(1/x)
+//
+// if   quartic                = -1  (x is a square)
+// then x^((p-1)/4)            = -1
+//      x^((p-5)/4) * x        = -1
+//      x^((p-5)/4)            = -1/x
+//      x^((p-5)/8)            = sqrt(-1)   / sqrt(x)
+//      x^((p-5)/8) * sqrt(-1) = sqrt(-1)^2 / sqrt(x)
+//      x^((p-5)/8) * sqrt(-1) = -1/sqrt(x)
+//      x^((p-5)/8) * sqrt(-1) = -sqrt(1/x) or sqrt(1/x)
+//
+// if   quartic         = sqrt(-1)  (x is not a square)
+// then x^((p-1)/4)     = sqrt(-1)
+//      x^((p-5)/4) * x = sqrt(-1)b
+//      x^((p-5)/4)     = sqrt(-1)/x
+//      x^((p-5)/8)     = sqrt(sqrt(-1)/x) or -sqrt(sqrt(-1)/x)
+//
+// Note that the product of two non-squares is always a square:
+//   For any non-squares a and b, chi(a) = -1 and chi(b) = -1.
+//   Since chi(x) = x^((p-1)/2), chi(a)*chi(b) = chi(a*b) = 1.
+//   Therefore a*b is a square.
+//
+//   Since sqrt(-1) and x are both non-squares, their product is a
+//   square, and we can compute their square root.
+//
+// if   quartic                = -sqrt(-1)  (x is not a square)
+// then x^((p-1)/4)            = -sqrt(-1)
+//      x^((p-5)/4) * x        = -sqrt(-1)
+//      x^((p-5)/4)            = -sqrt(-1)/x
+//      x^((p-5)/8)            = sqrt(-sqrt(-1)/x)
+//      x^((p-5)/8)            = sqrt( sqrt(-1)/x) * sqrt(-1)
+//      x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * sqrt(-1)^2
+//      x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * -1
+//      x^((p-5)/8) * sqrt(-1) = -sqrt(sqrt(-1)/x) or sqrt(sqrt(-1)/x)
+static int invsqrt(fe isr, const fe x)
+{
+	fe t0, t1, t2;
+
+	// t0 = x^((p-5)/8)
+	// Can be achieved with a simple double & add ladder,
+	// but it would be slower.
+	fe_sq(t0, x);
+	fe_sq(t1,t0);                     fe_sq(t1, t1);    fe_mul(t1, x, t1);
+	fe_mul(t0, t0, t1);
+	fe_sq(t0, t0);                                      fe_mul(t0, t1, t0);
+	fe_sq(t1, t0);  FOR (i, 1,   5) { fe_sq(t1, t1); }  fe_mul(t0, t1, t0);
+	fe_sq(t1, t0);  FOR (i, 1,  10) { fe_sq(t1, t1); }  fe_mul(t1, t1, t0);
+	fe_sq(t2, t1);  FOR (i, 1,  20) { fe_sq(t2, t2); }  fe_mul(t1, t2, t1);
+	fe_sq(t1, t1);  FOR (i, 1,  10) { fe_sq(t1, t1); }  fe_mul(t0, t1, t0);
+	fe_sq(t1, t0);  FOR (i, 1,  50) { fe_sq(t1, t1); }  fe_mul(t1, t1, t0);
+	fe_sq(t2, t1);  FOR (i, 1, 100) { fe_sq(t2, t2); }  fe_mul(t1, t2, t1);
+	fe_sq(t1, t1);  FOR (i, 1,  50) { fe_sq(t1, t1); }  fe_mul(t0, t1, t0);
+	fe_sq(t0, t0);  FOR (i, 1,   2) { fe_sq(t0, t0); }  fe_mul(t0, t0, x);
+
+	// quartic = x^((p-1)/4)
+	i32 *quartic = t1;
+	fe_sq (quartic, t0);
+	fe_mul(quartic, quartic, x);
+
+	i32 *check = t2;
+	fe_0  (check);          int z0 = fe_isequal(x      , check);
+	fe_1  (check);          int p1 = fe_isequal(quartic, check);
+	fe_neg(check, check );  int m1 = fe_isequal(quartic, check);
+	fe_neg(check, sqrtm1);  int ms = fe_isequal(quartic, check);
+
+	// if quartic == -1 or sqrt(-1)
+	// then  isr = x^((p-1)/4) * sqrt(-1)
+	// else  isr = x^((p-1)/4)
+	fe_mul(isr, t0, sqrtm1);
+	fe_ccopy(isr, t0, 1 - (m1 | ms));
+
+	WIPE_BUFFER(t0);
+	WIPE_BUFFER(t1);
+	WIPE_BUFFER(t2);
+	return p1 | m1 | z0;
+}
+
+// Inverse in terms of inverse square root.
+// Requires two additional squarings to get rid of the sign.
+//
+//   1/x = x * (+invsqrt(x^2))^2
+//       = x * (-invsqrt(x^2))^2
+//
+// A fully optimised exponentiation by p-1 would save 6 field
+// multiplications, but it would require more code.
+static void fe_invert(fe out, const fe x)
+{
+	fe tmp;
+	fe_sq(tmp, x);
+	invsqrt(tmp, tmp);
+	fe_sq(tmp, tmp);
+	fe_mul(out, tmp, x);
+	WIPE_BUFFER(tmp);
+}
+
+// trim a scalar for scalar multiplication
+void crypto_eddsa_trim_scalar(u8 out[32], const u8 in[32])
+{
+	COPY(out, in, 32);
+	out[ 0] &= 248;
+	out[31] &= 127;
+	out[31] |= 64;
+}
+
+// get bit from scalar at position i
+static int scalar_bit(const u8 s[32], int i)
+{
+	if (i < 0) { return 0; } // handle -1 for sliding windows
+	return (s[i>>3] >> (i&7)) & 1;
+}
+
+///////////////
+/// X-25519 /// Taken from SUPERCOP's ref10 implementation.
+///////////////
+static void scalarmult(u8 q[32], const u8 scalar[32], const u8 p[32],
+                       int nb_bits)
+{
+	// computes the scalar product
+	fe x1;
+	fe_frombytes(x1, p);
+
+	// computes the actual scalar product (the result is in x2 and z2)
+	fe x2, z2, x3, z3, t0, t1;
+	// Montgomery ladder
+	// In projective coordinates, to avoid divisions: x = X / Z
+	// We don't care about the y coordinate, it's only 1 bit of information
+	fe_1(x2);        fe_0(z2); // "zero" point
+	fe_copy(x3, x1); fe_1(z3); // "one"  point
+	int swap = 0;
+	for (int pos = nb_bits-1; pos >= 0; --pos) {
+		// constant time conditional swap before ladder step
+		int b = scalar_bit(scalar, pos);
+		swap ^= b; // xor trick avoids swapping at the end of the loop
+		fe_cswap(x2, x3, swap);
+		fe_cswap(z2, z3, swap);
+		swap = b;  // anticipates one last swap after the loop
+
+		// Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3)
+		// with differential addition
+		fe_sub(t0, x3, z3);
+		fe_sub(t1, x2, z2);
+		fe_add(x2, x2, z2);
+		fe_add(z2, x3, z3);
+		fe_mul(z3, t0, x2);
+		fe_mul(z2, z2, t1);
+		fe_sq (t0, t1    );
+		fe_sq (t1, x2    );
+		fe_add(x3, z3, z2);
+		fe_sub(z2, z3, z2);
+		fe_mul(x2, t1, t0);
+		fe_sub(t1, t1, t0);
+		fe_sq (z2, z2    );
+		fe_mul_small(z3, t1, 121666);
+		fe_sq (x3, x3    );
+		fe_add(t0, t0, z3);
+		fe_mul(z3, x1, z2);
+		fe_mul(z2, t1, t0);
+	}
+	// last swap is necessary to compensate for the xor trick
+	// Note: after this swap, P3 == P2 + P1.
+	fe_cswap(x2, x3, swap);
+	fe_cswap(z2, z3, swap);
+
+	// normalises the coordinates: x == X / Z
+	fe_invert(z2, z2);
+	fe_mul(x2, x2, z2);
+	fe_tobytes(q, x2);
+
+	WIPE_BUFFER(x1);
+	WIPE_BUFFER(x2);  WIPE_BUFFER(z2);  WIPE_BUFFER(t0);
+	WIPE_BUFFER(x3);  WIPE_BUFFER(z3);  WIPE_BUFFER(t1);
+}
+
+static void crypto_x25519(u8       raw_shared_secret[32],
+                   const u8 your_secret_key  [32],
+                   const u8 their_public_key [32])
+{
+	// restrict the possible scalar values
+	u8 e[32];
+	crypto_eddsa_trim_scalar(e, your_secret_key);
+	scalarmult(raw_shared_secret, e, their_public_key, 255);
+	WIPE_BUFFER(e);
+}
+
+static const uchar xnine[32] = {9};
+static const uchar xzero[32] = {0};
+
+int
+x25519(uchar out[32], uchar s[32], uchar u[32])
+{
+	crypto_x25519(out, s, u);
+	return tsmemcmp(out, xzero, 32) != 0;
+}
+
 void
 curve25519_dh_new(uchar x[32], uchar y[32])
 {
@@ -44,7 +821,7 @@
 	/* don't check for zero: the scalar is never
 		zero because of clamping, and the basepoint is not the identity
 		in the prime-order subgroup(s). */
-	x25519(y, x, nine);
+	crypto_x25519(y, x, xnine);
 
 	/* bit 255 is always 0, so make it random */
 	y[31] |= b & 0x80;
--- a/sys/src/libsec/port/tlshand.c
+++ b/sys/src/libsec/port/tlshand.c
@@ -91,7 +91,8 @@
 	int erred;		// set when tlsError called
 	int (*trace)(char*fmt, ...); // for debugging
 	int version;	// protocol we are speaking
-	Bytes *cert;	// server certificate; only last - no chain
+	Bytes **certs;	// server certificate; only last - no chain
+	int ncerts;
 
 	int cipher;
 	int nsecret;	// amount of secret data to init keys
@@ -406,6 +407,8 @@
 static Bytes*	pkcs1_decrypt(TlsSec *sec, Bytes *data);
 static Bytes*	pkcs1_sign(TlsSec *sec, uchar *digest, int digestlen, int sigalg);
 
+static int	validCert(char *rsrc, PEMChain *pc);
+
 static void* emalloc(int);
 static void* erealloc(void*, int);
 static void put32(uchar *p, u32int);
@@ -424,6 +427,78 @@
 
 //================= client/server ========================
 
+int
+tlsserver(int fd, TLSServerConf *cfg, TLSParams **pparam)
+{
+	char buf[8];
+	char dname[32];
+	uchar seed[2*RandomSize];
+	int n, data, ctl, hand;
+	TlsConnection *tls;
+	TLSParams *param;
+
+	param = emalloc(sizeof(TLSParams));
+	ctl = open("/net/tls/clone", ORDWR|OCEXEC);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	snprint(param->dir, sizeof(param->dir), "/net/tls/%s", buf);
+	snprint(dname, sizeof(dname), "/net/tls/%s/hand", buf);
+	hand = open(dname, ORDWR|OCEXEC);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	data = -1;
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	tls = tlsServer2(ctl, hand,
+		cfg->certs->pem, cfg->certs->pemlen,
+		cfg->psk.id, cfg->psk.key, cfg->psk.nkey,
+		cfg->trace, cfg->certs->next);
+	if(tls != nil){
+		snprint(dname, sizeof(dname), "/net/tls/%s/data", buf);
+		data = open(dname, ORDWR);
+	}
+	close(hand);
+	close(ctl);
+	if(data < 0){
+		tlsparamfree(param);
+		tlsConnectionFree(tls);
+		return -1;
+	}
+	param->certs = nil;  // client certificates are not yet implemented
+	if(cfg->sessiontype != nil){
+		if(strcmp(cfg->sessiontype, "ttls") != 0 || cfg->ttls.nkey == 0){
+			werrstr("invalid tls session: %s", cfg->sessiontype);
+			close(data);
+			tlsparamfree(param);
+			tlsConnectionFree(tls);
+			return -1;
+		}
+		param->ttls.key = emalloc(param->ttls.nkey);
+		memmove(seed, tls->sec->crandom, RandomSize);
+		memmove(seed+RandomSize, tls->sec->srandom, RandomSize);
+		tls->sec->prf(
+			param->ttls.key, param->ttls.nkey,
+			tls->sec->sec, MasterSecretSize,
+			cfg->ttls.constant, 
+			seed, sizeof(seed));
+	}
+	tlsConnectionFree(tls);
+	close(fd);
+	if(pparam == nil)
+		tlsparamfree(param);
+	else
+		*pparam = param;
+	return data;
+
+}
+
 //	push TLS onto fd, returning new (application) file descriptor
 //		or -1 if error.
 int
@@ -491,7 +566,7 @@
 }
 
 static uchar*
-tlsClientExtensions(TLSconn *conn, int *plen)
+tlsClientExtensions(char *srvname, int *plen)
 {
 	uchar *b, *p;
 	int i, n, m;
@@ -499,7 +574,7 @@
 	p = b = nil;
 
 	// RFC6066 - Server Name Identification
-	if(conn->serverName != nil && (n = strlen(conn->serverName)) > 0){
+	if(srvname != nil && (n = strlen(srvname)) > 0){
 		m = p - b;
 		b = erealloc(b, m + 2+2+2+1+2+n);
 		p = b + m;
@@ -509,7 +584,7 @@
 		put16(p, 1+2+n), p += 2;	/* Server Name list length */
 		*p++ = 0;			/* Server Name Type: host_name */
 		put16(p, n), p += 2;		/* Server Name length */
-		memmove(p, conn->serverName, n);
+		memmove(p, srvname, n);
 		p += n;
 	}
 
@@ -557,6 +632,118 @@
 	return b;
 }
 
+int
+tlsclient(int fd, char *srvname, TLSClientConf *cfg, TLSParams **pparam)
+{
+	char buf[8];
+	char dname[32];
+	uchar seed[2*RandomSize];
+	int i, n, data, ctl, hand;
+	TlsConnection *tls;
+	TLSParams *param;
+	PEMChain *pc, **ppc;
+	uchar *ext;
+
+	param = emalloc(sizeof(TLSParams));
+	ctl = open("/net/tls/clone", ORDWR|OCEXEC);
+	if(ctl < 0)
+		return -1;
+	n = read(ctl, buf, sizeof(buf)-1);
+	if(n < 0){
+		close(ctl);
+		return -1;
+	}
+	buf[n] = 0;
+	snprint(param->dir, sizeof(param->dir), "/net/tls/%s", buf);
+	snprint(dname, sizeof(dname), "/net/tls/%s/hand", buf);
+	hand = open(dname, ORDWR|OCEXEC);
+	if(hand < 0){
+		close(ctl);
+		return -1;
+	}
+	snprint(dname, sizeof(dname), "/net/tls/%s/data", buf);
+	data = open(dname, ORDWR);
+	if(data < 0){
+		close(hand);
+		close(ctl);
+		return -1;
+	}
+	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
+	ext = tlsClientExtensions(srvname, &n);
+	tls = tlsClient2(ctl, hand,
+		cfg->cert, cfg->ncert, 
+		cfg->psk.id, cfg->psk.key, cfg->psk.nkey,
+		ext, n, cfg->trace);
+	free(ext);
+	close(hand);
+	close(ctl);
+	if(tls == nil){
+		tlsparamfree(param);
+		close(data);
+		return -1;
+	}
+fprint(2, "certs: %#p, ncerts: %d\n", tls->certs, tls->ncerts);
+	if(tls->certs != nil){
+		ppc = &param->certs;
+		for(i = 0; i < tls->ncerts; i++){
+fprint(2, "add cert [%d]\n", i);
+			pc = emalloc(sizeof(PEMChain) + tls->certs[i]->len);
+			pc->next = nil;
+			pc->pem = (uchar*)(pc+1);
+			pc->pemlen = tls->certs[i]->len;
+			memcpy(pc->pem, tls->certs[i]->data, pc->pemlen);
+			*ppc = pc;
+			ppc = &pc->next;
+		}
+	} else {
+		param->certs = nil;
+	}
+	param->ttls.nkey = cfg->ttls.nkey;
+	if(cfg->sessiontype != nil){
+		if(strcmp(cfg->sessiontype, "ttls") != 0 || cfg->ttls.nkey == 0){
+			werrstr("invalid tls session: %s", cfg->sessiontype);
+			tlsparamfree(param);
+			close(data);
+			return -1;
+		}
+		param->ttls.key = emalloc(param->ttls.nkey);
+		memmove(seed, tls->sec->crandom, RandomSize);
+		memmove(seed+RandomSize, tls->sec->srandom, RandomSize);
+		tls->sec->prf(
+			param->ttls.key, param->ttls.nkey,
+			tls->sec->sec, MasterSecretSize,
+			cfg->ttls.constant, 
+			seed, sizeof(seed));
+	}
+	tlsConnectionFree(tls);
+	close(fd);
+	if(cfg->noverify == 0 
+	&& cfg->psk.key == nil
+	&& validCert("orib.dev", param->certs) == 0){
+		tlsparamfree(param);
+		close(data);
+		return -1;
+	}
+	if(pparam == nil)
+		tlsparamfree(param);
+	else
+		*pparam = param;
+	return data;
+}
+
+void
+tlsparamfree(TLSParams *param)
+{
+	PEMChain *pc, *pn;
+
+	for(pc = param->certs; pc != nil; pc = pn){
+		pn = pc->next;
+		free(pc);
+	}
+	free(param->ttls.key);
+	free(param);
+}
+
 //	push TLS onto fd, returning new (application) file descriptor
 //		or -1 if error.
 int
@@ -595,7 +782,7 @@
 		return -1;
 	}
 	fprint(ctl, "fd %d 0x%x", fd, ProtocolVersion);
-	ext = tlsClientExtensions(conn, &n);
+	ext = tlsClientExtensions(conn->serverName, &n);
 	tls = tlsClient2(ctl, hand,
 		conn->cert, conn->certlen, 
 		conn->pskID, conn->psk, conn->psklen,
@@ -608,10 +795,10 @@
 		return -1;
 	}
 	free(conn->cert);
-	if(tls->cert != nil){
-		conn->certlen = tls->cert->len;
+	if(tls->certs != nil){
+		conn->certlen = tls->certs[0]->len;
 		conn->cert = emalloc(conn->certlen);
-		memcpy(conn->cert, tls->cert->data, conn->certlen);
+		memcpy(conn->cert, tls->certs[0]->data, conn->certlen);
 	} else {
 		conn->certlen = 0;
 		conn->cert = nil;
@@ -1042,7 +1229,7 @@
 	uchar *ext, int extlen,
 	int (*trace)(char*fmt, ...))
 {
-	int creq, dhx, cipher;
+	int i, creq, dhx, cipher;
 	TlsConnection *c;
 	Bytes *epm;
 	Msg m;
@@ -1057,7 +1244,7 @@
 	c->ctl = ctl;
 	c->hand = hand;
 	c->trace = trace;
-	c->cert = nil;
+	c->certs = nil;
 	c->sendp = c->buf;
 
 	c->version = ProtocolVersion;
@@ -1118,11 +1305,15 @@
 	if(!msgRecv(c, &m))
 		goto Err;
 	if(m.tag == HCertificate){
+fprint(2, "here! %d\n", m.u.certificate.ncert);
 		if(m.u.certificate.ncert < 1) {
 			tlsError(c, EIllegalParameter, "runt certificate");
 			goto Err;
 		}
-		c->cert = makebytes(m.u.certificate.certs[0]->data, m.u.certificate.certs[0]->len);
+		c->ncerts = m.u.certificate.ncert;
+		c->certs = emalloc(c->ncerts * sizeof(Bytes*));
+		for(i = 0; i < c->ncerts; i++)
+			c->certs[i] = makebytes(m.u.certificate.certs[i]->data, m.u.certificate.certs[i]->len);
 		if(!msgRecv(c, &m))
 			goto Err;
 	} else if(psklen == 0) {
@@ -1133,7 +1324,7 @@
 		if(dhx){
 			char *err = verifyDHparams(c->sec,
 				m.u.serverKeyExchange.dh_parameters,
-				c->cert,
+				c->certs[0],
 				m.u.serverKeyExchange.dh_signature,
 				c->version<TLS12Version ? 0x01 : m.u.serverKeyExchange.sigalg);
 			if(err != nil){
@@ -1179,8 +1370,8 @@
 	msgClear(&m);
 
 	if(!dhx){
-		if(c->cert != nil){
-			epm = tlsSecRSAc(c->sec, c->cert->data, c->cert->len);
+		if(c->certs != nil){
+			epm = tlsSecRSAc(c->sec, c->certs[0]->data, c->certs[0]->len);
 			if(epm == nil){
 				tlsError(c, EBadCertificate, "bad certificate: %r");
 				goto Err;
@@ -2100,11 +2291,12 @@
 static void
 tlsConnectionFree(TlsConnection *c)
 {
+	int i;
+
 	if(c == nil)
 		return;
 
 	dh_finish(&c->sec->dh, nil);
-
 	mpfree(c->sec->ec.Q.x);
 	mpfree(c->sec->ec.Q.y);
 	mpfree(c->sec->ec.Q.d);
@@ -2112,12 +2304,58 @@
 
 	factotum_rsa_close(c->sec->rpc);
 	rsapubfree(c->sec->rsapub);
-	freebytes(c->cert);
+	for(i = 0; i < c->ncerts; i++)
+		freebytes(c->certs[i]);
+	free(c->certs);
 
 	memset(c, 0, sizeof(*c));
 	free(c);
 }
 
+static int
+validCert(char *host, PEMChain *pc)
+{
+	char *q, *t, buf[64];
+	PEMChain *e;
+	int n, fd, r;
+
+print("host: %s\n", host);
+	if(host == nil)
+		return 0;
+	if((fd = open("/mnt/pki/new", ORDWR)) == -1)
+		return 0;
+	q = quotestrdup(host);
+	fprint(fd, "verify host %s", q);
+	free(q);
+
+	t = "cert";
+	for(e = pc; e != nil; e = e->next){
+fprint(2, "%s der %d\n", t, e->pemlen);
+		if((r = fprint(fd, "%s der %d\n", t, e->pemlen)) == -1){
+fprint(2, "nope0[%d]: %r", r);
+			close(fd);
+			return 0;
+		}
+		if((write(fd, e->pem, e->pemlen)) != e->pemlen){
+fprint(2, "nope1: %r");
+			close(fd);
+			return 0;
+		}
+		t = "icert";
+	}
+	fprint(fd, "done\n");
+	if((n = pread(fd, buf, sizeof(buf)-1, 0)) == -1){
+		close(fd);
+		return 0;
+	}
+	buf[n] = 0;
+	close(fd);
+	if(strcmp(buf, "accept") != 0){
+		werrstr("cert validation failed: %s", buf);
+		return 0;
+	}
+	return 1;
+}
 
 //================= cipher choices ========================
 
--- a/sys/src/libsec/port/x509.c
+++ b/sys/src/libsec/port/x509.c
@@ -128,7 +128,7 @@
 static int	is_octetstring(Elem* pe, Bytes** poctets);
 static int	is_oid(Elem* pe, Ints** poid);
 static int	is_string(Elem* pe, char** pstring);
-static int	is_time(Elem* pe, char** ptime);
+static int	parse_time(Elem* pe, vlong* ptime);
 static int	decode(uchar* a, int alen, Elem* pelem);
 static int	encode(Elem e, Bytes** pbytes);
 static int	oid_lookup(Ints* o, Ints** tab);
@@ -1281,15 +1281,24 @@
 }
 
 static int
-is_time(Elem* pe, char** ptime)
+parse_time(Elem* pe, vlong* ptime)
 {
-	if(pe->tag.class == Universal
-	   && (pe->tag.num == UTCTime || pe->tag.num == GeneralizedTime)
-	   && pe->val.tag == VString) {
-		*ptime = pe->val.u.stringval;
-		return 1;
+	char *e, *fmt;
+	Tm t;
+
+	if(pe->tag.class != Universal)
+		return 0;
+	if(pe->val.tag != VString)
+		return 0;
+	switch(pe->tag.num){
+	case UTCTime:		fmt = "YYMMDDhhmmss?Z";	break;
+	case GeneralizedTime:	fmt = "YYYYMMDDhhmmss?Z";	break;
+	default:		return 0;		break;
 	}
-	return 0;
+	if(tmparse(&t, fmt, pe->val.u.stringval, nil, &e) == nil)
+		return 0;
+	*ptime = tmnorm(&t);
+	return 1;
 }
 
 
@@ -1585,20 +1594,6 @@
  *           revocationDate UTCTime}
  */
 
-typedef struct CertX509 {
-	int	serial;
-	char*	issuer;
-	char*	validity_start;
-	char*	validity_end;
-	char*	subject;
-	int	publickey_alg;
-	Bits*	publickey;
-	int	signature_alg;
-	Bits*	signature;
-	int	curve;
-	Bytes*	ext;
-} CertX509;
-
 /* Algorithm object-ids */
 enum {
 	ALG_rsaEncryption,
@@ -1734,14 +1729,12 @@
 
 static void appendaltnames(char *name, int nname, Bytes *ext, int req);
 
-static void
-freecert(CertX509* c)
+void
+X509free(CertX509* c)
 {
 	if(c == nil)
 		return;
 	free(c->issuer);
-	free(c->validity_start);
-	free(c->validity_end);
 	free(c->subject);
 	freebits(c->publickey);
 	freebits(c->signature);
@@ -1795,7 +1788,7 @@
 		el = el->tl;
 	}
 	if(i > 0) {
-		ans = (char*)emalloc(plen);
+		ans = emalloc(plen);
 		*ans = '\0';
 		while(--i >= 0) {
 			s = parts[i];
@@ -1837,152 +1830,6 @@
 	return oid_lookup(oid, namedcurves_oid_tab);
 }
 
-static CertX509*
-decode_cert(uchar *buf, int len)
-{
-	int ok = 0;
-	int n;
-	Elem  ecert;
-	Elem* ecertinfo;
-	Elem* esigalg;
-	Elem* esig;
-	Elem* eserial;
-	Elem* eissuer;
-	Elem* evalidity;
-	Elem* esubj;
-	Elem* epubkey;
-	Elist* el;
-	Elist* elcert = nil;
-	Elist* elcertinfo = nil;
-	Elist* elvalidity = nil;
-	Elist* elpubkey = nil;
-	Bits* bits = nil;
-	Bytes* b;
-	Elem* e;
-	CertX509* c = nil;
-
-	if(decode(buf, len, &ecert) != ASN_OK)
-		goto errret;
-
-	c = (CertX509*)emalloc(sizeof(CertX509));
-	c->serial = -1;
-	c->issuer = nil;
-	c->validity_start = nil;
-	c->validity_end = nil;
-	c->subject = nil;
-	c->publickey_alg = -1;
-	c->publickey = nil;
-	c->signature_alg = -1;
-	c->signature = nil;
-	c->ext = nil;
-
-	/* Certificate */
- 	if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3)
-		goto errret;
- 	ecertinfo = &elcert->hd;
- 	el = elcert->tl;
- 	esigalg = &el->hd;
-	c->signature_alg = parse_alg(esigalg);
- 	el = el->tl;
- 	esig = &el->hd;
-
-	/* Certificate Info */
-	if(!is_seq(ecertinfo, &elcertinfo))
-		goto errret;
-	n = elistlen(elcertinfo);
-  	if(n < 6)
-		goto errret;
-	eserial =&elcertinfo->hd;
- 	el = elcertinfo->tl;
- 	/* check for optional version, marked by explicit context tag 0 */
-	if(eserial->tag.class == Context && eserial->tag.num == 0) {
- 		eserial = &el->hd;
- 		if(n < 7)
- 			goto errret;
- 		el = el->tl;
- 	}
-
-	if(parse_alg(&el->hd) != c->signature_alg)
-		goto errret;
- 	el = el->tl;
- 	eissuer = &el->hd;
- 	el = el->tl;
- 	evalidity = &el->hd;
- 	el = el->tl;
- 	esubj = &el->hd;
- 	el = el->tl;
- 	epubkey = &el->hd;
-	if(el->tl != nil
-	&& el->tl->hd.tag.class == Context
-	&& el->tl->hd.tag.num == 3
-	&& el->tl->hd.val.tag == VOctets){
-		c->ext = el->tl->hd.val.u.octetsval;
-		el->tl->hd.val.u.octetsval = nil;	/* transfer ownership */
-	}
-	if(!is_int(eserial, &c->serial)) {
-		if(!is_bigint(eserial, &b))
-			goto errret;
-		c->serial = -1;	/* else we have to change cert struct */
-  	}
-	c->issuer = parse_name(eissuer);
-	if(c->issuer == nil)
-		goto errret;
-	/* Validity */
-  	if(!is_seq(evalidity, &elvalidity))
-		goto errret;
-	if(elistlen(elvalidity) != 2)
-		goto errret;
-	e = &elvalidity->hd;
-	if(!is_time(e, &c->validity_start))
-		goto errret;
-	e->val.u.stringval = nil;	/* string ownership transfer */
-	e = &elvalidity->tl->hd;
- 	if(!is_time(e, &c->validity_end))
-		goto errret;
-	e->val.u.stringval = nil;	/* string ownership transfer */
-
-	/* resume CertificateInfo */
- 	c->subject = parse_name(esubj);
-
-	/* SubjectPublicKeyInfo */
-	if(!is_seq(epubkey, &elpubkey))
-		goto errret;
-	if(elistlen(elpubkey) != 2)
-		goto errret;
-
-	c->publickey_alg = parse_alg(&elpubkey->hd);
-	if(c->publickey_alg < 0)
-		goto errret;
-	c->curve = -1;
-	if(c->publickey_alg == ALG_ecPublicKey){
-		c->curve = parse_curve(&elpubkey->hd);
-		if(c->curve < 0)
-			goto errret;
-	}
-	elpubkey = elpubkey->tl;
-	if(!is_bitstring(&elpubkey->hd, &bits))
-		goto errret;
-	elpubkey->hd.val.u.bitstringval = nil;	/* transfer ownership */
-	c->publickey = bits;
-
-	/*resume Certificate */
-	if(c->signature_alg < 0)
-		goto errret;
-	if(!is_bitstring(esig, &bits))
-		goto errret;
-	esig->val.u.bitstringval = nil;	/* transfer ownership */
-	c->signature = bits;
-	ok = 1;
-
-errret:
-	freevalfields(&ecert.val);	/* recurses through lists, too */
-	if(!ok){
-		freecert(c);
-		c = nil;
-	}
-	return c;
-}
-
 /*
  *	RSAPublickKey ::= SEQUENCE {
  *		modulus INTEGER,
@@ -2189,6 +2036,152 @@
 
 static char Ebadsig[] = "bad signature";
 
+CertX509*
+X509decode(uchar *buf, int len)
+{
+	int ok = 0;
+	int n;
+	Elem  ecert;
+	Elem* ecertinfo;
+	Elem* esigalg;
+	Elem* esig;
+	Elem* eserial;
+	Elem* eissuer;
+	Elem* evalidity;
+	Elem* esubj;
+	Elem* epubkey;
+	Elist* el;
+	Elist* elcert = nil;
+	Elist* elcertinfo = nil;
+	Elist* elvalidity = nil;
+	Elist* elpubkey = nil;
+	Bits* bits = nil;
+	Bytes* b;
+	Elem* e;
+	CertX509* c = nil;
+
+	if(decode(buf, len, &ecert) != ASN_OK)
+		goto errret;
+
+	c = (CertX509*)emalloc(sizeof(CertX509));
+	c->serial = -1;
+	c->issuer = nil;
+	c->validity_start = -1;
+	c->validity_end = -1;
+	c->subject = nil;
+	c->publickey_alg = -1;
+	c->publickey = nil;
+	c->signature_alg = -1;
+	c->signature = nil;
+	c->ext = nil;
+
+	/* Certificate */
+ 	if(!is_seq(&ecert, &elcert) || elistlen(elcert) !=3)
+		goto errret;
+ 	ecertinfo = &elcert->hd;
+ 	el = elcert->tl;
+ 	esigalg = &el->hd;
+	c->signature_alg = parse_alg(esigalg);
+ 	el = el->tl;
+ 	esig = &el->hd;
+
+	/* Certificate Info */
+	if(!is_seq(ecertinfo, &elcertinfo))
+		goto errret;
+	n = elistlen(elcertinfo);
+  	if(n < 6)
+		goto errret;
+	eserial =&elcertinfo->hd;
+ 	el = elcertinfo->tl;
+ 	/* check for optional version, marked by explicit context tag 0 */
+	if(eserial->tag.class == Context && eserial->tag.num == 0) {
+ 		eserial = &el->hd;
+ 		if(n < 7)
+ 			goto errret;
+ 		el = el->tl;
+ 	}
+
+	if(parse_alg(&el->hd) != c->signature_alg)
+		goto errret;
+ 	el = el->tl;
+ 	eissuer = &el->hd;
+ 	el = el->tl;
+ 	evalidity = &el->hd;
+ 	el = el->tl;
+ 	esubj = &el->hd;
+ 	el = el->tl;
+ 	epubkey = &el->hd;
+	if(el->tl != nil
+	&& el->tl->hd.tag.class == Context
+	&& el->tl->hd.tag.num == 3
+	&& el->tl->hd.val.tag == VOctets){
+		c->ext = el->tl->hd.val.u.octetsval;
+		el->tl->hd.val.u.octetsval = nil;	/* transfer ownership */
+	}
+	if(!is_int(eserial, &c->serial)) {
+		if(!is_bigint(eserial, &b))
+			goto errret;
+		c->serial = -1;	/* else we have to change cert struct */
+  	}
+	c->issuer = parse_name(eissuer);
+	if(c->issuer == nil)
+		goto errret;
+	/* Validity */
+  	if(!is_seq(evalidity, &elvalidity))
+		goto errret;
+	if(elistlen(elvalidity) != 2)
+		goto errret;
+	e = &elvalidity->hd;
+	if(!parse_time(e, &c->validity_start))
+		goto errret;
+	e->val.u.stringval = nil;	/* string ownership transfer */
+	e = &elvalidity->tl->hd;
+ 	if(!parse_time(e, &c->validity_end))
+		goto errret;
+	e->val.u.stringval = nil;	/* string ownership transfer */
+
+	/* resume CertificateInfo */
+ 	c->subject = parse_name(esubj);
+
+	/* SubjectPublicKeyInfo */
+	if(!is_seq(epubkey, &elpubkey))
+		goto errret;
+	if(elistlen(elpubkey) != 2)
+		goto errret;
+
+	c->publickey_alg = parse_alg(&elpubkey->hd);
+	if(c->publickey_alg < 0)
+		goto errret;
+	c->curve = -1;
+	if(c->publickey_alg == ALG_ecPublicKey){
+		c->curve = parse_curve(&elpubkey->hd);
+		if(c->curve < 0)
+			goto errret;
+	}
+	elpubkey = elpubkey->tl;
+	if(!is_bitstring(&elpubkey->hd, &bits))
+		goto errret;
+	elpubkey->hd.val.u.bitstringval = nil;	/* transfer ownership */
+	c->publickey = bits;
+
+	/*resume Certificate */
+	if(c->signature_alg < 0)
+		goto errret;
+	if(!is_bitstring(esig, &bits))
+		goto errret;
+	esig->val.u.bitstringval = nil;	/* transfer ownership */
+	c->signature = bits;
+	ok = 1;
+
+errret:
+	freevalfields(&ecert.val);	/* recurses through lists, too */
+	if(!ok){
+		X509free(c);
+		c = nil;
+	}
+	return c;
+}
+
 char*
 X509rsaverifydigest(uchar *sig, int siglen, uchar *edigest, int edigestlen, RSApub *pk)
 {
@@ -2290,8 +2283,9 @@
 {
 	CertX509 *c;
 	ECpub *pub;
+	Bits *pk;
 
-	c = decode_cert(cert, ncert);
+	c = X509decode(cert, ncert);
 	if(c == nil)
 		return nil;
 	copysubject(name, nname, c->subject);
@@ -2299,11 +2293,12 @@
 	pub = nil;
 	if(c->publickey_alg == ALG_ecPublicKey){
 		ecdominit(dom, namedcurves[c->curve]);
-		pub = ecdecodepub(dom, c->publickey->data, c->publickey->len);
+		pk = c->publickey;
+		pub = ecdecodepub(dom, pk->data, pk->len);
 		if(pub == nil)
 			ecdomfree(dom);
 	}
-	freecert(c);
+	X509free(c);
 	return pub;
 }
 
@@ -2311,20 +2306,22 @@
 X509ecdsaverify(uchar *cert, int ncert, ECdomain *dom, ECpub *pk)
 {
 	char *e;
+	Bits *sig;
 	CertX509 *c;
 	int digestlen;
 	uchar digest[MAXdlen];
 
-	c = decode_cert(cert, ncert);
+	c = X509decode(cert, ncert);
 	if(c == nil)
 		return "cannot decode cert";
 	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
 	if(digestlen <= 0){
-		freecert(c);
+		X509free(c);
 		return "cannot decode certinfo";
 	}
-	e = X509ecdsaverifydigest(c->signature->data, c->signature->len, digest, digestlen, dom, pk);
-	freecert(c);
+	sig = c->signature;
+	e = X509ecdsaverifydigest(sig->data, sig->len, digest, digestlen, dom, pk);
+	X509free(c);
 	return e;
 }
 
@@ -2333,40 +2330,71 @@
 {
 	CertX509 *c;
 	RSApub *pub;
+	Bits *pk;
 
-	c = decode_cert(cert, ncert);
+	c = X509decode(cert, ncert);
 	if(c == nil)
 		return nil;
 	copysubject(name, nname, c->subject);
 	appendaltnames(name, nname, c->ext, 0);
 	pub = nil;
+	pk = c->publickey;
 	if(c->publickey_alg == ALG_rsaEncryption)
-		pub = asn1toRSApub(c->publickey->data, c->publickey->len);
-	freecert(c);
+		pub = asn1toRSApub(pk->data, pk->len);
+	X509free(c);
 	return pub;
 }
 
-char*
+char*	
 X509rsaverify(uchar *cert, int ncert, RSApub *pk)
 {
 	char *e;
+	Bits *sig;
 	CertX509 *c;
 	int digestlen;
 	uchar digest[MAXdlen];
 
-	c = decode_cert(cert, ncert);
+	c = X509decode(cert, ncert);
 	if(c == nil)
 		return "cannot decode cert";
 	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
 	if(digestlen <= 0){
-		freecert(c);
+		X509free(c);
 		return "cannot decode certinfo";
 	}
-	e = X509rsaverifydigest(c->signature->data, c->signature->len, digest, digestlen, pk);
-	freecert(c);
+	sig = c->signature;
+	e = X509rsaverifydigest(sig->data, sig->len, digest, digestlen, pk);
+	X509free(c);
 	return e;
 }
 
+char*
+X509verify(CertX509 *crt, CertX509 *vrf)
+{
+	RSApub *rsapub;
+	ECpub *ecpub;
+	ECdomain ecdom;
+	Bits *pk, *sig;
+	char *e;
+
+	e = "unknown algorithm";
+	pk = vrf->publickey;
+	sig = crt->signature;
+	switch(vrf->publickey_alg){
+	case ALG_rsaEncryption:
+		rsapub = asn1toRSApub(pk->data, pk->len);
+		e = X509rsaverifydigest(sig->data, sig->len, crt->digest, crt->digestlen, rsapub);
+		break;
+	case ALG_ecPublicKey:
+		ecdominit(&ecdom, namedcurves[vrf->curve]);
+		ecpub = ecdecodepub(&ecdom, pk->data, pk->len);
+		e = X509ecdsaverifydigest(sig->data, sig->len, crt->digest, crt->digestlen, &ecdom, ecpub);
+		ecdomfree(&ecdom);
+		break;
+	}
+	return e;
+}
+
 /* ------- Elem constructors ---------- */
 static Elem
 Null(void)
@@ -3125,14 +3153,16 @@
 X509digestSPKI(uchar *cert, int ncert, DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*), uchar *digest)
 {
 	CertX509 *c;
+	Bits *pk;
 
-	c = decode_cert(cert, ncert);
+	c = X509decode(cert, ncert);
 	if(c == nil){
 		werrstr("cannot decode cert");
 		return -1;
 	}
-	digestSPKI(c->publickey_alg, c->publickey->data, c->publickey->len, fun, digest);
-	freecert(c);
+	pk = c->publickey;
+	digestSPKI(c->publickey_alg, pk->data, pk->len, fun, digest);
+	X509free(c);
 	return 0;
 }
 
@@ -3241,60 +3271,55 @@
 }
 
 void
-X509dump(uchar *cert, int ncert)
+X509dump(int fd, uchar *cert, int ncert)
 {
 	char *e;
+	Bits *pk, *sig;
 	CertX509 *c;
 	RSApub *rsapub;
 	ECpub *ecpub;
 	ECdomain ecdom;
-	int digestlen;
 	uchar digest[MAXdlen];
 
-	print("begin X509dump\n");
-	c = decode_cert(cert, ncert);
+	fprint(fd, "begin X509dump\n");
+	c = X509decode(cert, ncert);
 	if(c == nil){
-		print("cannot decode cert\n");
+		fprint(fd, "cannot decode cert\n");
 		return;
 	}
 
-	digestlen = digest_certinfo(cert, ncert, digestalg[c->signature_alg], digest);
-	if(digestlen <= 0){
-		freecert(c);
-		print("cannot decode certinfo\n");
-		return;
-	}
+	pk = c->publickey;
+	sig = c->signature;
+	fprint(fd, "serial %d\n", c->serial);
+	fprint(fd, "issuer %s\n", c->issuer);
+	fprint(fd, "validity %lld %lld\n", c->validity_start, c->validity_end);
+	fprint(fd, "subject %s\n", c->subject);
+	fprint(fd, "sigalg=%d digest=%.*H\n", c->signature_alg, c->digestlen, c->digest);
+	fprint(fd, "publickey_alg=%d pubkey[%d] %.*H\n", c->publickey_alg, pk->len,
+		pk->len, pk->data);
 
-	print("serial %d\n", c->serial);
-	print("issuer %s\n", c->issuer);
-	print("validity %s %s\n", c->validity_start, c->validity_end);
-	print("subject %s\n", c->subject);
-	print("sigalg=%d digest=%.*H\n", c->signature_alg, digestlen, digest);
-	print("publickey_alg=%d pubkey[%d] %.*H\n", c->publickey_alg, c->publickey->len,
-		c->publickey->len, c->publickey->data);
-
 	switch(c->publickey_alg){
 	case ALG_rsaEncryption:
-		rsapub = asn1toRSApub(c->publickey->data, c->publickey->len);
+		rsapub = asn1toRSApub(pk->data, pk->len);
 		if(rsapub != nil){
-			print("rsa pubkey e=%B n(%d)=%B\n", rsapub->ek, mpsignif(rsapub->n), rsapub->n);
-			e = X509rsaverifydigest(c->signature->data, c->signature->len,
-				digest, digestlen, rsapub);
+			fprint(fd, "rsa pubkey e=%B n(%d)=%B\n", rsapub->ek, mpsignif(rsapub->n), rsapub->n);
+			e = X509rsaverifydigest(sig->data, sig->len,
+				c->digest, c->digestlen, rsapub);
 			if(e==nil)
 				e = "nil (meaning ok)";
-			print("self-signed X509rsaverifydigest returns: %s\n", e);
+			fprint(fd, "self-signed X509rsaverifydigest returns: %s\n", e);
 			rsapubfree(rsapub);
 		}
 		break;
 	case ALG_ecPublicKey:
 		ecdominit(&ecdom, namedcurves[c->curve]);
-		ecpub = ecdecodepub(&ecdom, c->publickey->data, c->publickey->len);
+		ecpub = ecdecodepub(&ecdom, pk->data, pk->len);
 		if(ecpub != nil){
-			e = X509ecdsaverifydigest(c->signature->data, c->signature->len,
-				digest, digestlen, &ecdom, ecpub);
+			e = X509ecdsaverifydigest(sig->data, sig->len,
+				c->digest, c->digestlen, &ecdom, ecpub);
 			if(e==nil)
 				e = "nil (meaning ok)";
-			print("self-signed X509ecdsaverifydigest returns: %s\n", e);
+			fprint(fd, "self-signed X509ecdsaverifydigest returns: %s\n", e);
 			ecpubfree(ecpub);
 		}
 		ecdomfree(&ecdom);
@@ -3301,15 +3326,15 @@
 		break;
 	}
 
-	digestSPKI(c->publickey_alg, c->publickey->data, c->publickey->len, sha2_256, digest);
-	print("publickey_thumbprint sha256=%.*[\n", SHA2_256dlen, digest);
+	digestSPKI(c->publickey_alg, pk->data, pk->len, sha2_256, c->digest);
+	fprint(fd, "publickey_thumbprint sha256=%.*[\n", SHA2_256dlen, c->digest);
 
 	sha2_256(cert, ncert, digest, nil);
-	print("cert_thumbprint sha256=%.*[\n", SHA2_256dlen, digest);
+	fprint(fd, "cert_thumbprint sha256=%.*[\n", SHA2_256dlen, digest);
 
 	sha1(cert, ncert, digest, nil);
-	print("cert_thumbprint sha1=%.*H\n", SHA1dlen, digest);
+	fprint(fd, "cert_thumbprint sha1=%.*H\n", SHA1dlen, digest);
 
-	freecert(c);
-	print("end X509dump\n");
+	X509free(c);
+	fprint(fd, "end X509dump\n");
 }