diff options
author | ori@eigenstate.org <ori@eigenstate.org> | 2022-06-24 00:08:34 +0000 |
---|---|---|
committer | Ori Bernstein <ori@eigenstate.org> | 2022-06-24 00:08:34 +0000 |
commit | bb2060028e616df560628cf6f1e6e5d196d50833 (patch) | |
tree | c1e7a47adaacca71ffccfaa728cf4caed034bc9a /sys/src | |
parent | 78cc69e9f24f9b3e256c29b73772eedafcb16792 (diff) |
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] &
Diffstat (limited to 'sys/src')
-rw-r--r-- | sys/src/cmd/auth/rsa2asn1.c | 25 | ||||
-rw-r--r-- | sys/src/cmd/upas/dkim/dkim.c | 210 | ||||
-rw-r--r-- | sys/src/cmd/upas/dkim/mkfile | 7 | ||||
-rw-r--r-- | sys/src/cmd/upas/mkfile | 1 | ||||
-rw-r--r-- | sys/src/libsec/port/x509.c | 27 |
5 files changed, 265 insertions, 5 deletions
diff --git a/sys/src/cmd/auth/rsa2asn1.c b/sys/src/cmd/auth/rsa2asn1.c index cf7bfc77d..8d8769955 100644 --- 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 @@ main(int argc, char **argv) case 'a': privatekey = 1; break; + case 'f': + format = EARGF(usage()); + break; default: usage(); }ARGEND @@ -32,14 +36,25 @@ main(int argc, char **argv) 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"); diff --git a/sys/src/cmd/upas/dkim/dkim.c b/sys/src/cmd/upas/dkim/dkim.c new file mode 100644 index 000000000..4b039e6b8 --- /dev/null +++ b/sys/src/cmd/upas/dkim/dkim.c @@ -0,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); +} diff --git a/sys/src/cmd/upas/dkim/mkfile b/sys/src/cmd/upas/dkim/mkfile new file mode 100644 index 000000000..89948e0fb --- /dev/null +++ b/sys/src/cmd/upas/dkim/mkfile @@ -0,0 +1,7 @@ +</$objtype/mkfile + +TARG=dkim +OFILES=dkim.$O + +</sys/src/cmd/mkone +<../mkupas diff --git a/sys/src/cmd/upas/mkfile b/sys/src/cmd/upas/mkfile index 2edc24a38..3933b137b 100644 --- a/sys/src/cmd/upas/mkfile +++ b/sys/src/cmd/upas/mkfile @@ -6,6 +6,7 @@ PROGS=\ alias\ bayes\ binscripts\ + dkim\ filterkit\ fs\ imap4d\ diff --git a/sys/src/libsec/port/x509.c b/sys/src/libsec/port/x509.c index 4fc6ca045..63eb1e6f1 100644 --- a/sys/src/libsec/port/x509.c +++ b/sys/src/libsec/port/x509.c @@ -788,6 +788,7 @@ encode(Elem e, Bytes** pbytes) p = &uc; err = enc(&p, e, 1); + *pbytes = nil; if(err == ASN_OK) { ans = newbytes(p-&uc); p = ans->data; @@ -2903,6 +2904,32 @@ asn1encodeRSApriv(RSApriv *k, uchar *buf, int len) 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; +} + uchar* X509rsagen(RSApriv *priv, char *subj, ulong valid[2], int *certlen) { |