shithub: riscv

Download patch

ref: bb2060028e616df560628cf6f1e6e5d196d50833
parent: 78cc69e9f24f9b3e256c29b73772eedafcb16792
author: ori@eigenstate.org <ori@eigenstate.org>
date: Thu Jun 23 20:08:34 EDT 2022

upas/dkim: dkim signing for upas

This change adds support for dkim signing to upas.

It has2 pieces:

1. Adding support for different asn1 formats to auth/rsa2asn1;
   we can now generate SubjectPublicKeyInfo RSA keys, which
   wrap the keys up with an algorithm identifier.
2. Adding a upas/dkim command which filters a message and signs
   it using dkim.

To configure dkim, you need to generate a (small-ish) rsa key;
large keys do not fit into DNS text records:

	# generate the private key and add it to factotum
	ramfs -p
	cd /tmp
	auth/rsagen -b 2048 -t 'service=dkim role=sign hash=sha256 domain=orib.dev owner=*' > dkim.key
	cat dkim.key > factotum.ctl

	# extract the public key, encode it, and strip out the junk
	pubkey=`{
		<dkim.key auth/rsa2asn1 -f spki | \
		auth/pemencode WHATEVER | \
		grep -v 'WHATEVER' | \
		ssam 'x/\n/d'
	}
	domain=example.org
	# then add it to /lib/ndb.local
	echo 'dom=dkim._domainkey.'$domain' soa=
		ip=144.202.1.203
		refresh=600 ttl=600
		ns=ns.orib.dev
		txt="k=rsa; v='$pubkey \
	>> /lib/ndb/local

Then, finally, insert it into your outgoing mail pipeline. One
thing to be careful of is that upas will do some outgoing 'From:'
rewriting, so you may need to make sure that either '$upasname'
is set, or 'upas/dkim' is inserted after the rewrite stage.
A good place is in /mail/lib/qmail, in place of upas/vf:

	% cat /mail/lib/qmail
	rfork s
	upas/dkim -d example.com | upas/qer /mail/queue mail $* || exit 'qer failed'
	upas/runq -n 10 /mail/queue /mail/lib/remotemail </dev/null >/dev/null >[2=1] &

diff: cannot open b/sys/src/cmd/upas/dkim//null: file does not exist: 'b/sys/src/cmd/upas/dkim//null'
--- a/sys/include/libsec.h
+++ b/sys/include/libsec.h
@@ -374,6 +374,7 @@
 int		pkcs1unpadbuf(uchar *buf, int len, mpint *modulus, int blocktype);
 int		asn1encodeRSApub(RSApub *pk, uchar *buf, int len);
 int		asn1encodeRSApriv(RSApriv *k, uchar *buf, int len);
+int		asn1encodeRSApubSPKI(RSApub *pk, uchar *buf, int len);
 int		asn1encodedigest(DigestState* (*fun)(uchar*, ulong, uchar*, DigestState*),
 			uchar *digest, uchar *buf, int len);
 
--- a/sys/man/1/filter
+++ b/sys/man/1/filter
@@ -24,6 +24,15 @@
 .I recipient
 .I fromfile
 .I mbox
+.B upas/dkim
+[
+.B -d
+.I domain
+]
+[
+.B -s
+.I selector
+]
 .PP
 .B upas/token
 .I key
@@ -183,6 +192,23 @@
 You should copy it into a directory that normally gets
 bound by your profile onto
 .BR /bin .
+.PP
+.I Dkim
+Takes a mail message as standard input, and signs
+a selection of headers and the body of the message.
+The
+.I -d
+flag specifies the domain,
+and the
+.I -s
+flag specifies the selector. If the selector is not
+specified, it defaults to
+.IR dkim .
+The keyspec searched for the signing key is:
+.IP
+.EX
+proto=rsa service=dkim role=sign hash=sha256 domain=$domain
+.EE
 .PP
 .I Vf
 (virus filter)
--- a/sys/man/8/rsa
+++ b/sys/man/8/rsa
@@ -28,9 +28,13 @@
 .PP
 .B rsa2asn1
 [
--a
+.B -a
 ]
 [
+.B -f
+.I fmt
+]
+[
 .I file
 ]
 .PP
@@ -200,6 +204,16 @@
 With the
 .I -a
 flag a private key is read and encoded in ANS.1/DER format.
+With the
+.I -f
+flag, the format of the ASN.1/DER encoded key is selected.
+The supported formats are 
+.I pkcs1
+and 
+.IR spki ,
+which refer to RFC3447 RSAPublicKey and RFC5280 SubjectPublicKeyInfo
+formatted RSA keys respectively.
+The default format is pkcs1.
 .PP
 .I Rsa2ssh
 reads a Plan 9 RSA public or private key and prints the public portion 
--- a/sys/src/cmd/auth/rsa2asn1.c
+++ b/sys/src/cmd/auth/rsa2asn1.c
@@ -6,11 +6,12 @@
 #include "rsa2any.h"
 
 int privatekey = 0;
+char *format = "pkcs1";
 
 void
 usage(void)
 {
-	fprint(2, "usage: auth/rsa2asn1 [-a] [file]\n");
+	fprint(2, "usage: auth/rsa2asn1 [-a] [-f fmt] [file]\n");
 	exits("usage");
 }
 
@@ -25,6 +26,9 @@
 	case 'a':
 		privatekey = 1;
 		break;
+	case 'f':
+		format = EARGF(usage());
+		break;
 	default:
 		usage();
 	}ARGEND
@@ -32,14 +36,25 @@
 	if(argc > 1)
 		usage();
 
+	n = -1;
 	if((k = getrsakey(argc, argv, privatekey, nil)) == nil)
 		sysfatal("%r");
 	if(privatekey){
-		if((n = asn1encodeRSApriv(k, buf, sizeof(buf))) < 0)
-			sysfatal("asn1encodeRSApriv: %r");
+		if(strcmp(format, "pkcs1") == 0)
+			n = asn1encodeRSApriv(k, buf, sizeof(buf));
+		else
+			sysfatal("unknown format %s", format);
+		if(n < 0)
+			sysfatal("encode: %r");
 	}else{
-		if((n = asn1encodeRSApub(&k->pub, buf, sizeof(buf))) < 0)
-			sysfatal("asn1encodeRSApub: %r");
+		if(strcmp(format, "pkcs1") == 0)
+			n = asn1encodeRSApub(&k->pub, buf, sizeof(buf));
+		else if(strcmp(format, "spki") == 0)
+			n = asn1encodeRSApubSPKI(&k->pub, buf, sizeof(buf));
+		else
+			sysfatal("unknown format %s", format);
+		if(n < 0)
+			sysfatal("encode: %r");
 	}
 	if(write(1, buf, n) != n)
 		sysfatal("write: %r");
--- /dev/null
+++ b/sys/src/cmd/upas/dkim/dkim.c
@@ -1,0 +1,210 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <libsec.h>
+#include <auth.h>
+#include <authsrv.h>
+#include <pool.h>
+
+char *signhdr[] = {
+	"from:",
+	"to:",
+	"subject:",
+	"date:",
+	"message-id:",
+	nil
+};
+
+char *keyspec;
+char *domain;
+char *selector = "dkim";
+
+int
+trim(char *p)
+{
+	char *e;
+
+	for(e = p; *e != 0; e++)
+		if(*e == '\r' || *e == '\n')
+			break;
+	*e = 0;
+	return e - p;
+}		
+
+int
+usehdr(char *ln, char **hs)
+{
+	char **p;
+
+	for(p = signhdr; *p; p++)
+		if(cistrncmp(ln, *p, strlen(*p)) == 0){
+			if((*hs = realloc(*hs, strlen(*hs) + strlen(*p) + 1)) == nil)
+				sysfatal("realloc: %r");
+			strcat(*hs, *p);
+			return 1;
+		}
+	return 0;
+}
+
+void
+append(char **m, int *nm, int *sz, char *ln, int n)
+{
+	while(*nm + n + 2 >= *sz){
+		*sz += *sz/2;
+		*m = realloc(*m, *sz);
+	}
+	memcpy(*m + *nm, ln, n);
+	memcpy(*m + *nm + n, "\r\n", 2);
+	*nm += n+2;
+}
+
+
+int
+sign(uchar *hash, int nhash, char **sig, int *nsig)
+{
+	AuthRpc *rpc;
+	int afd;
+
+	if((afd = open("/mnt/factotum/rpc", ORDWR|OCEXEC)) < 0)
+		return -1;
+	if((rpc = auth_allocrpc(afd)) == nil){
+		close(afd);
+		return -1;
+	}
+	if(auth_rpc(rpc, "start", keyspec, strlen(keyspec)) != ARok){
+		auth_freerpc(rpc);
+		close(afd);
+		return -1;
+	}
+
+	if(auth_rpc(rpc, "write", hash, nhash) != ARok)
+		sysfatal("sign: write hash: %r");
+	if(auth_rpc(rpc, "read", nil, 0) != ARok)
+		sysfatal("sign: read sig: %r");
+	if((*sig = malloc(rpc->narg)) == nil)
+		sysfatal("malloc: %r");
+	*nsig = rpc->narg;
+	memcpy(*sig, rpc->arg, *nsig);
+	auth_freerpc(rpc);
+	close(afd);
+	return 0;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-s sel] -d dom\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, n, nhdr, nmsg, nsig, ntail, hdrsz, msgsz, use;
+	uchar hdrhash[SHA2_256dlen], msghash[SHA2_256dlen];
+	char *hdr, *msg, *sig, *ln, *hdrset, *dhdr;
+	Biobuf *rd, *wr;
+	DigestState *sh, *sb;
+
+	ARGBEGIN{
+	case 'd':
+		domain = EARGF(usage());
+		break;
+	case 's':
+		selector = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	}ARGEND;
+
+	if(domain == nil)
+		usage();
+	fmtinstall('H', encodefmt);
+	fmtinstall('[', encodefmt);
+	keyspec = smprint("proto=rsa service=dkim role=sign hash=sha256 domain=%s", domain);
+
+	rd = Bfdopen(0, OREAD);
+	wr = Bfdopen(1, OWRITE);
+
+	nhdr = 0;
+	hdrsz = 32;
+	if((hdr = malloc(hdrsz)) == nil)
+		sysfatal("malloc: %r");
+	nmsg = 0;
+	msgsz = 32;
+	if((msg = malloc(msgsz)) == nil)
+		sysfatal("malloc: %r");
+
+	use = 0;
+	sh = nil;
+	hdrset = strdup("");
+	while((ln = Brdstr(rd, '\n', 1)) != nil){
+		n = trim(ln);
+		if(n == 0
+		|| (n == 1 && ln[0] == '\r' || ln[0] == '\n')
+		|| (n == 2 && strcmp(ln, "\r\n") == 0))
+			break;
+		/*
+		 * strip out existing DKIM signatures,
+		 * for the sake of mailing lists and such.
+		 */
+		if(cistrcmp(ln, "DKIM-Signature:") == 0)
+			continue;
+		if(ln[0] != ' ' && ln[0] != '\t')
+			use = usehdr(ln, &hdrset);
+		if(use){
+			sh = sha2_256((uchar*)ln, n, nil, sh);
+			sh = sha2_256((uchar*)"\r\n", 2, nil, sh);
+		}
+		append(&hdr, &nhdr, &hdrsz, ln, n);
+	}
+
+	sb = nil;
+	ntail = 0;
+	while((ln = Brdstr(rd, '\n', 0)) != nil){
+		n = trim(ln);
+		if(n == 0){
+			ntail++;
+			continue;
+		}
+		for(i = 0; i < ntail; i++){
+			sb = sha2_256((uchar*)"\r\n", 2, nil, sb);
+			append(&msg, &nmsg, &msgsz, "", 0);
+			ntail = 0;
+		}
+		sb = sha2_256((uchar*)ln, n, nil, sb);
+		sb = sha2_256((uchar*)"\r\n", 2, nil, sb);
+		append(&msg, &nmsg, &msgsz, ln, n);
+	}
+	if(nmsg == 0 || ntail > 1)
+		sb = sha2_256((uchar*)"\r\n", 2, nil, sb);
+	Bterm(rd);
+
+	sha2_256(nil, 0, msghash, sb);
+	dhdr = smprint(
+		"DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=%s;\r\n"
+		" h=%s; s=%s;\r\n"
+		" bh=%.*[; \r\n"
+		" b=",
+		domain, hdrset, selector,
+		(int)sizeof(msghash), msghash);
+	if(dhdr == nil)
+		sysfatal("smprint: %r");
+	sh = sha2_256((uchar*)dhdr, strlen(dhdr), nil, sh);
+	sha2_256(nil, 0, hdrhash, sh);
+	if(sign(hdrhash, sizeof(hdrhash), &sig, &nsig) == -1)
+		sysfatal("sign: %r");
+
+	Bwrite(wr, dhdr, strlen(dhdr));
+	Bprint(wr, "%.*[\r\n", nsig, sig);
+	Bwrite(wr, hdr, nhdr);
+	Bprint(wr, "\n");
+	Bwrite(wr, msg, nmsg);
+	Bterm(wr);
+
+	free(hdr);
+	free(msg);
+	free(sig);
+	exits(nil);	
+}
--- /dev/null
+++ b/sys/src/cmd/upas/dkim/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+TARG=dkim
+OFILES=dkim.$O
+
+</sys/src/cmd/mkone
+<../mkupas
--- a/sys/src/cmd/upas/mkfile
+++ b/sys/src/cmd/upas/mkfile
@@ -6,6 +6,7 @@
 	alias\
 	bayes\
 	binscripts\
+	dkim\
 	filterkit\
 	fs\
 	imap4d\
--- a/sys/src/libsec/port/x509.c
+++ b/sys/src/libsec/port/x509.c
@@ -788,6 +788,7 @@
 
 	p = &uc;
 	err = enc(&p, e, 1);
+	*pbytes = nil;
 	if(err == ASN_OK) {
 		ans = newbytes(p-&uc);
 		p = ans->data;
@@ -2900,6 +2901,32 @@
 	}
 	memmove(buf, b->data, len = b->len);
 	freebytes(b);
+	return len;
+}
+
+int
+asn1encodeRSApubSPKI(RSApub *pk, uchar *buf, int len)
+{
+	Bytes *b, *k;
+	Elem e;
+
+	k = encode_rsapubkey(pk);
+	if(k == nil)
+		return -1;
+	e = mkseq(
+		mkel(mkalg(ALG_rsaEncryption),
+		mkel(mkbits(k->data, k->len),
+		nil)));
+	encode(e, &b);
+	freebytes(k);
+	if(b == nil)
+		return -1;
+	if(b->len > len){
+		freebytes(b);
+		werrstr("buffer too small");
+		return -1;
+	}
+	memmove(buf, b->data, len = b->len);
 	return len;
 }