diff --git a/sys/man/8/acmed b/sys/man/8/acmed index a92c5e142..7361b767f 100644 --- a/sys/man/8/acmed +++ b/sys/man/8/acmed @@ -1,8 +1,8 @@ .TH ACMED 8 .SH NAME -ip/acmed \- acme certificate client +auth/acmed \- acme certificate client .SH SYNOPSIS -.B ip/acmed +.B auth/acmed [ .B -a .I acctkey @@ -178,7 +178,7 @@ to be mounted as the ACME protocol uses HTTP to talk to the provider. .IP .EX -ip/acmed me@example.com /sys/lib/tls/acmed/mydomain.com.csr \\ +auth/acmed me@example.com /sys/lib/tls/acmed/mydomain.com.csr \\ > /sys/lib/tls/acmed/mydomain.com.crt .EE .PP @@ -217,14 +217,14 @@ can be invoked to fetch the certificate using the DNS challenge method: .IP .EX -ip/acmed -t dns me@example.com mydomain.com.csr \\ +auth/acmed -t dns me@example.com mydomain.com.csr \\ > /sys/lib/tls/acmed/mydomain.com.crt .EE .SH FILES .BI /sys/lib/tls/acmed/ * .pub Account public keys. .SH SOURCE -.B /sys/src/cmd/ip/acmed.c +.B /sys/src/cmd/auth/acmed.c .SH SEE ALSO .IR factotum (4), .IR ndb (6), diff --git a/sys/src/cmd/auth/acmed.c b/sys/src/cmd/auth/acmed.c new file mode 100644 index 000000000..c97429b3a --- /dev/null +++ b/sys/src/cmd/auth/acmed.c @@ -0,0 +1,905 @@ +#include +#include +#include +#include +#include +#include +#include + +typedef struct Hdr Hdr; + +#pragma varargck type "E" char* + +struct Hdr { + char *name; + char *val; + int nval; +}; + +#define Keyspec "proto=rsa service=acme role=sign hash=sha256 acct=%s" +#define Useragent "useragent aclient-plan9" +#define Contenttype "contenttype application/jose+json" +#define between(x,min,max) (((min-1-x) & (x-max-1))>>8) +int debug; +int (*challengefn)(char*, char*, char*, int*); +char *keyspec; +char *provider = "https://acme-v02.api.letsencrypt.org/directory"; /* default endpoint */ +char *challengecmd; +char *challengeout; +char *keyid; +char *epnewnonce; +char *epnewacct; +char *epneworder; +char *eprevokecert; +char *epkeychange; +char *jwsthumb; +JSON *jwskey; + +#define dprint(...) if(debug)fprint(2, __VA_ARGS__); + +char* +evsmprint(char *fmt, va_list ap) +{ + char *r; + + if((r = vsmprint(fmt, ap)) == nil) + abort(); + return r; +} + +char* +esmprint(char *fmt, ...) +{ + va_list ap; + char *r; + + va_start(ap, fmt); + r = evsmprint(fmt, ap); + va_end(ap); + return r; +} + +int +encurl64chr(int o) +{ + int c; + + c = between(o, 0, 25) & ('A'+o); + c |= between(o, 26, 51) & ('a'+(o-26)); + c |= between(o, 52, 61) & ('0'+(o-52)); + c |= between(o, 62, 62) & ('-'); + c |= between(o, 63, 63) & ('_'); + return c; +} +char* +encurl64(void *in, int n) +{ + int lim; + char *out, *p; + + lim = 4*n/3 + 5; + if((out = malloc(lim)) == nil) + abort(); + enc64x(out, lim, in, n, encurl64chr); + if((p = strchr(out, '=')) != nil) + *p = 0; + return out; +} + +char* +signRS256(char *hdr, char *prot) +{ + uchar hash[SHA2_256dlen]; + DigestState *s; + AuthRpc *rpc; + int afd; + char *r; + + if((afd = open("/mnt/factotum/rpc", ORDWR|OCEXEC)) < 0) + return nil; + if((rpc = auth_allocrpc(afd)) == nil){ + close(afd); + return nil; + } + if(auth_rpc(rpc, "start", keyspec, strlen(keyspec)) != ARok){ + auth_freerpc(rpc); + close(afd); + return nil; + } + + s = sha2_256((uchar*)hdr, strlen(hdr), nil, nil); + s = sha2_256((uchar*)".", strlen("."), nil, s); + sha2_256((uchar*)prot, strlen(prot), hash, s); + + if(auth_rpc(rpc, "write", hash, sizeof(hash)) != ARok) + sysfatal("sign: write hash: %r"); + if(auth_rpc(rpc, "read", nil, 0) != ARok) + sysfatal("sign: read sig: %r"); + r = encurl64(rpc->arg, rpc->narg); + auth_freerpc(rpc); + close(afd); + return r; +} + +/* + * Reads all available data from an fd. + * guarantees returned value is terminated. + */ +static void* +slurp(int fd, int *n) +{ + char *b; + int r, sz; + + *n = 0; + sz = 32; + if((b = malloc(sz)) == nil) + abort(); + while(1){ + if(*n + 1 == sz){ + sz *= 2; + if((b = realloc(b, sz)) == nil) + abort(); + } + r = read(fd, b + *n, sz - *n - 1); + if(r == 0) + break; + if(r == -1){ + free(b); + return nil; + } + *n += r; + } + b[*n] = 0; + return b; +} + +static int +webopen(char *url, char *dir, int ndir) +{ + char buf[16]; + int n, cfd, conn; + + if((cfd = open("/mnt/web/clone", ORDWR|OCEXEC)) == -1) + return -1; + if((n = read(cfd, buf, sizeof(buf)-1)) == -1) + goto Error; + buf[n] = 0; + conn = atoi(buf); + + if(fprint(cfd, "url %s", url) == -1) + goto Error; + snprint(dir, ndir, "/mnt/web/%d", conn); + return cfd; +Error: + close(cfd); + return -1; +} + +static char* +get(char *url, int *n) +{ + char *r, dir[64], path[80]; + int cfd, dfd; + + r = nil; + dfd = -1; + if((cfd = webopen(url, dir, sizeof(dir))) == -1) + goto Error; + snprint(path, sizeof(path), "%s/%s", dir, "body"); + if((dfd = open(path, OREAD|OCEXEC)) == -1) + goto Error; + r = slurp(dfd, n); +Error: + if(dfd != -1) close(dfd); + if(cfd != -1) close(cfd); + return r; +} + +static char* +post(char *url, char *buf, int nbuf, int *nret, Hdr *h) +{ + char *r, dir[64], path[80]; + int cfd, dfd, hfd, ok; + + r = nil; + ok = 0; + dfd = -1; + hfd = -1; + if((cfd = webopen(url, dir, sizeof(dir))) == -1) + goto Error; + if(write(cfd, Contenttype, strlen(Contenttype)) == -1) + goto Error; + snprint(path, sizeof(path), "%s/%s", dir, "postbody"); + if((dfd = open(path, OWRITE|OCEXEC)) == -1) + goto Error; + if(write(dfd, buf, nbuf) != nbuf) + goto Error; + close(dfd); + snprint(path, sizeof(path), "%s/%s", dir, "body"); + if((dfd = open(path, OREAD|OCEXEC)) == -1) + goto Error; + if(h != nil){ + snprint(path, sizeof(path), "%s/%s", dir, h->name); + if((hfd = open(path, OREAD|OCEXEC)) == -1) + goto Error; + if((h->val = slurp(hfd, &h->nval)) == nil) + goto Error; + } + if((r = slurp(dfd, nret)) == nil) + goto Error; + ok = 1; +Error: + if(hfd != -1) close(hfd); + if(dfd != -1) close(dfd); + if(cfd != -1) close(cfd); + if(!ok && h != nil){ + free(h->val); + h->val = nil; + h->nval = 0; + } + return r; +} + +static int +endpoints(void) +{ + JSON *j; + JSONEl *e; + char *s; + int n; + + if((s = get(provider, &n)) == nil) + sysfatal("get %s: %r", provider); + if((j = jsonparse(s)) == nil) + sysfatal("parse endpoints: %r"); + if(j->t != JSONObject) + sysfatal("expected object"); + for(e = j->first; e != nil; e = e->next){ + if(e->val->t != JSONString) + continue; + if(strcmp(e->name, "keyChange") == 0) + epkeychange = strdup(e->val->s); + else if(strcmp(e->name, "newAccount") == 0) + epnewacct = strdup(e->val->s); + else if(strcmp(e->name, "newNonce") == 0) + epnewnonce = strdup(e->val->s); + else if(strcmp(e->name, "newOrder") == 0) + epneworder = strdup(e->val->s); + else if(strcmp(e->name, "revokeCert") == 0) + eprevokecert = strdup(e->val->s); + } + jsonfree(j); + free(s); + if(epnewnonce==nil|| epnewacct==nil || epneworder==nil + || eprevokecert==nil || epkeychange==nil){ + sysfatal("missing directory entries"); + return -1; + } + return 0; +} + +static char* +getnonce(void) +{ + char *r, dir[64], path[80]; + int n, cfd, dfd, hfd; + + r = nil; + dfd = -1; + hfd = -1; + if((cfd = webopen(epnewnonce, dir, sizeof(dir))) == -1) + goto Error; + fprint(cfd, "request HEAD"); + + snprint(path, sizeof(path), "%s/%s", dir, "body"); + if((dfd = open(path, OREAD|OCEXEC)) == -1) + goto Error; + snprint(path, sizeof(path), "%s/%s", dir, "replaynonce"); + if((hfd = open(path, OREAD|OCEXEC)) == -1) + goto Error; + r = slurp(hfd, &n); +Error: + if(hfd != -1) + close(hfd); + if(dfd != -1) + close(dfd); + close(cfd); + return r; +} + +char* +jwsenc(char *hdr, char *msg, int *nbuf) +{ + char *h, *m, *s, *r; + + h = encurl64(hdr, strlen(hdr)); + m = encurl64(msg, strlen(msg)); + s = signRS256(h, m); + if(s == nil) + return nil; + + r = esmprint( + "{\n" + "\"protected\": \"%s\",\n" + "\"payload\": \"%s\",\n" + "\"signature\": \"%s\"\n" + "}\n", + h, m, s); + *nbuf = strlen(r); + free(h); + free(m); + free(s); + + return r; +} + +char* +jwsheader(char *url) +{ + char *nonce; + + if((nonce = getnonce()) == nil) + sysfatal("get nonce: %r"); + return esmprint( + "{" + "\"alg\": \"RS256\"," + "\"nonce\": \"%E\"," + "\"kid\": \"%E\"," + "\"url\": \"%E\"" + "}", + nonce, keyid, url); +} + +char* +jwsrequest(char *url, int *nresp, Hdr *h, char *fmt, ...) +{ + char *hdr, *msg, *req, *resp; + int nreq; + va_list ap; + + va_start(ap, fmt); + hdr = jwsheader(url); + msg = evsmprint(fmt, ap); + req = jwsenc(hdr, msg, &nreq); + dprint("req=\"%s\"\n", req); + resp = post(url, req, nreq, nresp, h); + free(hdr); + free(req); + free(msg); + va_end(ap); + dprint("resp=%s\n", resp); + return resp; +} + +static void +mkaccount(char *addr) +{ + char *nonce, *hdr, *msg, *req, *resp; + int nreq, nresp; + Hdr loc = { "location" }; + + if((nonce = getnonce()) == nil) + sysfatal("get nonce: %r"); + hdr = esmprint( + "{" + "\"alg\": \"RS256\"," + "\"jwk\": %J," + "\"nonce\": \"%E\"," + "\"url\": \"%E\"" + "}", + jwskey, nonce, epnewacct); + msg = esmprint( + "{" + "\"termsOfServiceAgreed\": true," + "\"contact\": [\"mailto:%E\"]" + "}", + addr); + free(nonce); + if((req = jwsenc(hdr, msg, &nreq)) == nil) + sysfatal("failed to sign: %r"); + dprint("req=\"%s\"\n", req); + + if((resp = post(epnewacct, req, nreq, &nresp, &loc)) == nil) + sysfatal("failed req: %r"); + dprint("resp=%s, loc=%s\n", resp, loc.val); + keyid = loc.val; +} + +static JSON* +submitorder(char **dom, int ndom, Hdr *hdr) +{ + char *req, *resp, *sep, rbuf[8192]; + int nresp, i; + JSON *r; + + sep = ""; + req = seprint(rbuf, rbuf+sizeof(rbuf), + "{" + " \"identifiers\": ["); + for(i = 0; i < ndom; i++){ + req = seprint(req, rbuf+sizeof(rbuf), + "%s{" + " \"type\": \"dns\"," + " \"value\": \"%E\"" + "}", + sep, dom[i]); + sep = ","; + } + req = seprint(req, rbuf+sizeof(rbuf), + " ]," + " \"wildcard\": false" + "}"); + if(req - rbuf < 2) + sysfatal("truncated order"); + resp = jwsrequest(epneworder, &nresp, hdr, "%s", rbuf); + if(resp == nil) + sysfatal("submit order: %r"); + if((r = jsonparse(resp)) == nil) + sysfatal("parse order: %r"); + free(resp); + return r; +} + +static void +hashauthbuf(char *buf, int nbuf) +{ + uchar hash[SHA2_256dlen]; + char *enc; + + sha2_256((uchar*)buf, strlen(buf), hash, nil); + if((enc = encurl64(hash, sizeof(hash))) == nil) + sysfatal("hashbuf: %r"); + if(snprint(buf, nbuf, "%s", enc) != strlen(enc)) + sysfatal("hashbuf: buffer too small, truncated"); + free(enc); +} + +static int +runchallenge(char *ty, char *dom, char *tok, int *matched) +{ + char auth[1024]; + Waitmsg *w; + int pid; + + snprint(auth, sizeof(auth), "%s.%s", tok, jwsthumb); + if(strcmp(ty, "dns-01") == 0) + hashauthbuf(auth, sizeof(auth)); + + pid = fork(); + switch(pid){ + case -1: + return -1; + case 0: + dup(1, 2); + execl(challengecmd, challengecmd, ty, dom, tok, auth, nil); + sysfatal("%s: %r", challengecmd); + } + + while((w = wait()) != nil){ + if(w->pid != pid){ + free(w); + continue; + } + if(w->msg[0] == '\0'){ + free(w); + *matched = 1; + return 0; + } + werrstr("%s", w->msg); + free(w); + return -1; + } + return -1; +} + +static int +httpchallenge(char *ty, char *, char *tok, int *matched) +{ + char path[1024]; + int fd, r; + + if(strcmp(ty, "http-01") != 0) + return -1; + *matched = 1; + + snprint(path, sizeof(path), "%s/%s", challengeout, tok); + if((fd = create(path, OWRITE|OCEXEC, 0666)) == -1) + return -1; + r = fprint(fd, "%s.%s\n", tok, jwsthumb); + close(fd); + return r; +} + +static int +dnschallenge(char *ty, char *dom, char *tok, int *matched) +{ + char auth[1024]; + int fd; + + if(strcmp(ty, "dns-01") != 0) + return -1; + *matched = 1; + + snprint(auth, sizeof(auth), "%s.%s", tok, jwsthumb); + hashauthbuf(auth, sizeof(auth)); + + if((fd = create(challengeout, OWRITE|OCEXEC, 0666)) == -1){ + werrstr("could not create challenge: %r"); + return -1; + } + if(fprint(fd,"dom=_acme-challenge.%s soa=\n\ttxt=\"%s\"\n", dom, auth) == -1){ + werrstr("could not write challenge: %r"); + close(fd); + return -1; + } + close(fd); + + if((fd = open("/net/dns", OWRITE|OCEXEC)) == -1){ + werrstr("could not open dns ctl: %r"); + return -1; + } + if(fprint(fd, "refresh") == -1){ + werrstr("could not write dns refresh: %r"); + close(fd); + return -1; + } + close(fd); + + return 0; +} + +static int +challenge(JSON *j, char *authurl, JSON *id, char *dom[], int ndom, int *matched) +{ + JSON *dn, *ty, *url, *tok, *poll, *state; + char *resp; + int i, nresp; + + if((dn = jsonbyname(id, "value")) == nil) + return -1; + if(dn->t != JSONString) + return -1; + + /* make sure the identifier matches the csr */ + for(i = 0; i < ndom; i++){ + if(cistrcmp(dom[i], dn->s) == 0) + break; + } + if(i >= ndom){ + werrstr("unknown challenge identifier '%s'", dn->s); + return -1; + } + + if((ty = jsonbyname(j, "type")) == nil) + return -1; + if((url = jsonbyname(j, "url")) == nil) + return -1; + if((tok = jsonbyname(j, "token")) == nil) + return -1; + + if(ty->t != JSONString || url->t != JSONString || tok->t != JSONString) + return -1; + + dprint("trying challenge %s\n", ty->s); + if(challengefn(ty->s, dn->s, tok->s, matched) == -1){ + dprint("challengefn failed: %r\n"); + return -1; + } + + if((resp = jwsrequest(url->s, &nresp, nil, "{}")) == nil) + sysfatal("challenge: post %s: %r", url->s); + free(resp); + + for(i = 0; i < 60; i++){ + sleep(1000); + if((resp = jwsrequest(authurl, &nresp, nil, "")) == nil) + sysfatal("challenge: post %s: %r", url->s); + if((poll = jsonparse(resp)) == nil){ + free(resp); + return -1; + } + if((state = jsonbyname(poll, "status")) != nil && state->t == JSONString){ + if(strcmp(state->s, "valid") == 0){ + jsonfree(poll); + return 0; + } + else if(strcmp(state->s, "pending") != 0){ + fprint(2, "error: %J", poll); + werrstr("status '%s'", state->s); + jsonfree(poll); + return -1; + } + } + jsonfree(poll); + } + werrstr("timeout"); + return -1; +} + +static int +dochallenges(char *dom[], int ndom, JSON *order) +{ + JSON *chals, *j, *cl, *id; + JSONEl *ae, *ce; + int nresp, matched; + char *resp; + + if((j = jsonbyname(order, "authorizations")) == nil){ + werrstr("parse response: missing authorizations"); + return -1; + } + if(j->t != JSONArray){ + werrstr("parse response: authorizations must be array"); + return -1; + } + for(ae = j->first; ae != nil; ae = ae->next){ + if(ae->val->t != JSONString){ + werrstr("challenge: auth must be url"); + return -1; + } + if((resp = jwsrequest(ae->val->s, &nresp, nil, "")) == nil){ + werrstr("challenge: request %s: %r", ae->val->s); + return -1; + } + if((chals = jsonparse(resp)) == nil){ + werrstr("invalid challenge: %r"); + return -1; + } + if((id = jsonbyname(chals, "identifier")) == nil){ + werrstr("missing identifier"); + jsonfree(chals); + return -1; + } + if((cl = jsonbyname(chals, "challenges")) == nil){ + werrstr("missing challenge"); + jsonfree(chals); + return -1; + } + matched = 0; + for(ce = cl->first; ce != nil; ce = ce->next){ + if(challenge(ce->val, ae->val->s, id, dom, ndom, &matched) == 0) + break; + if(matched) + werrstr("could not complete challenge: %r"); + } + if(!matched) + sysfatal("no matching auth type"); + jsonfree(chals); + free(resp); + } + return 0; +} + +static int +submitcsr(JSON *order, char *b64csr) +{ + char *resp; + int nresp; + JSON *j; + + if((j = jsonbyname(order, "finalize")) == nil) + sysfatal("parse response: missing authorizations"); + if(j->t != JSONString) + werrstr("parse response: finalizer must be string"); + if((resp = jwsrequest(j->s, &nresp, nil, "{\"csr\":\"%E\"}", b64csr)) == nil) + sysfatal("submit csr: %r"); + free(resp); + return 0; +} + +static int +fetchcert(char *url) +{ + JSON *cert, *poll, *state; + int i, r, nresp; + char *resp; + + poll = nil; + for(i = 0; i < 60; i++){ + sleep(1000); + if((resp = jwsrequest(url, &nresp, nil, "")) == nil) + return -1; + if((poll = jsonparse(resp)) == nil){ + free(resp); + return -1; + } + free(resp); + if((state = jsonbyname(poll, "status")) != nil && state->t == JSONString){ + if(strcmp(state->s, "valid") == 0) + break; + else if(strcmp(state->s, "pending") != 0 && strcmp(state->s, "processing") != 0){ + fprint(2, "error: %J", poll); + werrstr("invalid request: %s", state->s); + jsonfree(poll); + return -1; + + } + } + jsonfree(poll); + } + if(poll == nil){ + werrstr("timed out"); + return -1; + } + if((cert = jsonbyname(poll, "certificate")) == nil || cert->t != JSONString){ + werrstr("missing cert url in response"); + jsonfree(poll); + return -1; + } + if((resp = jwsrequest(cert->s, &nresp, nil, "")) == nil){ + jsonfree(poll); + return -1; + } + jsonfree(poll); + r = write(1, resp, nresp); + free(resp); + if(r != nresp) + return -1; + return 0; +} + +static void +getcert(char *csrpath) +{ + char *csr, *dom[64], name[2048]; + uchar *der; + int nder, ndom, fd; + RSApub *rsa; + Hdr loc = { "location" }; + JSON *o; + + if((fd = open(csrpath, OREAD|OCEXEC)) == -1) + sysfatal("open %s: %r", csrpath); + if((der = slurp(fd, &nder)) == nil) + sysfatal("read %s: %r", csrpath); + if((rsa = X509reqtoRSApub(der, nder, name, sizeof(name))) == nil) + sysfatal("decode csr: %r"); + if((csr = encurl64(der, nder)) == nil) + sysfatal("encode %s: %r", csrpath); + if((ndom = getfields(name, dom, nelem(dom), 1, ", ")) == nelem(dom)) + sysfatal("too man domains"); + rsapubfree(rsa); + close(fd); + free(der); + + if((o = submitorder(dom, ndom, &loc)) == nil) + sysfatal("order: %r"); + if(dochallenges(dom, ndom, o) == -1) + sysfatal("challenge: %r"); + if(submitcsr(o, csr) == -1) + sysfatal("signing cert: %r"); + if(fetchcert(loc.val) == -1) + sysfatal("saving cert: %r"); + free(csr); +} + +static int +Econv(Fmt *f) +{ + char *s; + Rune r; + int w; + + w = 0; + s = va_arg(f->args, char*); + while(*s){ + s += chartorune(&r, s); + if(r == '\\' || r == '\"') + w += fmtrune(f, '\\'); + w += fmtrune(f, r); + } + return w; +} + +static int +loadkey(char *path) +{ + uchar h[SHA2_256dlen]; + char key[8192]; + JSON *j, *e, *kty, *n; + DigestState *ds; + int fd, nr; + + if((fd = open(path, OREAD|OCEXEC)) == -1) + return -1; + nr = readn(fd, key, sizeof(key)); + close(fd); + if(nr == -1) + return -1; + key[nr] = 0; + + if((j = jsonparse(key)) == nil) + return -1; + if((e = jsonbyname(j, "e")) == nil || e->t != JSONString) + return -1; + if((kty = jsonbyname(j, "kty")) == nil || kty->t != JSONString) + return -1; + if((n = jsonbyname(j, "n")) == nil || n->t != JSONString) + return -1; + + ds = sha2_256((uchar*)"{\"e\":\"", 6, nil, nil); + ds = sha2_256((uchar*)e->s, strlen(e->s), nil, ds); + ds = sha2_256((uchar*)"\",\"kty\":\"", 9, nil, ds); + ds = sha2_256((uchar*)kty->s, strlen(kty->s), nil, ds); + ds = sha2_256((uchar*)"\",\"n\":\"", 7, nil, ds); + ds = sha2_256((uchar*)n->s, strlen(n->s), nil, ds); + sha2_256((uchar*)"\"}", 2, h, ds); + jwskey = j; + jwsthumb = encurl64(h, sizeof(h)); + return 0; +} + +static void +usage(void) +{ + fprint(2, "usage: %s [-a acctkey] [-e cmd | -o chalout -t type] [-p provider] acct csr\n", argv0); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *acctkey, *ct, *co; + + JSONfmtinstall(); + fmtinstall('E', Econv); + + ct = nil; + co = nil; + acctkey = nil; + ARGBEGIN{ + case 'd': + debug++; + break; + case 'a': + acctkey = EARGF(usage()); + break; + case 'e': + challengecmd = EARGF(usage()); + break; + case 'o': + co = EARGF(usage()); + break; + case 't': + ct = EARGF(usage()); + break; + case 'p': + provider = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND; + + if(challengecmd != nil){ + if(ct != nil || co != nil) + usage(); + challengeout = "/dev/null"; + challengefn = runchallenge; + }else if(ct == nil || strcmp(ct, "http") == 0){ + challengeout = (co != nil) ? co : "/usr/web/.well-known/acme-challenge"; + challengefn = httpchallenge; + }else if(strcmp(ct, "dns") == 0){ + challengeout = (co != nil) ? co : "/lib/ndb/dnschallenge"; + challengefn = dnschallenge; + }else { + sysfatal("unknown challenge type '%s'", ct); + } + + if(argc != 2) + usage(); + + if(acctkey == nil) + acctkey = esmprint("/sys/lib/tls/acmed/%s.pub", argv[0]); + if((keyspec = smprint(Keyspec, argv[0])) == nil) + sysfatal("smprint: %r"); + if(loadkey(acctkey) == -1) + sysfatal("load key: %r"); + + if(endpoints() == -1) + sysfatal("endpoints: %r"); + mkaccount(argv[0]); + getcert(argv[1]); + exits(nil); +} diff --git a/sys/src/cmd/auth/mkfile b/sys/src/cmd/auth/mkfile index a3a9bcd68..f8f6ec829 100644 --- a/sys/src/cmd/auth/mkfile +++ b/sys/src/cmd/auth/mkfile @@ -3,6 +3,7 @@ # programs # TARG=\ + acmed\ as\ asaudit\ asn1dump\