Hi, thanks for working on this and finding another acme implementor!
On Mon, Apr 20, 2020 at 06:51:17PM +0200, Bartosz Kuzma wrote: > Hello, > > I've tried to get a certificate from Buypass Go SSL provider using > acme-client(1) but it ends with the following error: > > acme-client: https://api.buypass.com/acme-v02/new-acct: bad HTTP: 400 > acme-client: transfer buffer: > [{"type":"urn:ietf:params:acme:error:malformed","d > etail":"Email is a required > contact","code":400,"message":"MALFORMED_BAD_REQUEST > ","details":"HTTP 400 Bad Request"}] (164 bytes) > > Buypass Go SSL requires that optional field contact in Account Objects > will contain email address. > > I've added new option "contact" to acme-client.conf to fullfil this > requirement. It is also required to change MARKER in certproc.c to fix line > endings handling in pem files. they are leaving out the last newline? > > -- > Kind regards, Bartosz Kuzma. > Index: usr.sbin/acme-client/acme-client.conf.5 > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/acme-client.conf.5,v > retrieving revision 1.22 > diff -u -p -r1.22 acme-client.conf.5 > --- usr.sbin/acme-client/acme-client.conf.5 10 Feb 2020 13:18:21 -0000 > 1.22 > +++ usr.sbin/acme-client/acme-client.conf.5 20 Apr 2020 16:24:46 -0000 > @@ -98,6 +98,10 @@ It defaults to > Specify the > .Ar url > under which the ACME API is reachable. > +.It Ic contact Ar contact > +Optional > +.Ar contact > +URLs that the authority can use to contact the client for issues related to > this account. I think we should call it emails. That's what people actually have. According to the RFC it's a list of contacts, but I'm fine with having only one. > .El > .Sh DOMAINS > The certificates to be obtained through ACME. > Index: usr.sbin/acme-client/certproc.c > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/certproc.c,v > retrieving revision 1.12 > diff -u -p -r1.12 certproc.c > --- usr.sbin/acme-client/certproc.c 7 Jun 2019 08:07:52 -0000 1.12 > +++ usr.sbin/acme-client/certproc.c 20 Apr 2020 16:24:46 -0000 > @@ -28,7 +28,7 @@ > > #include "extern.h" > > -#define MARKER "-----END CERTIFICATE-----\n" > +#define MARKER "-----END CERTIFICATE-----" > > int > certproc(int netsock, int filesock) > @@ -94,6 +94,12 @@ certproc(int netsock, int filesock) > } > > chaincp += strlen(MARKER); > + > + if ((chaincp = strchr(chaincp, '-')) == NULL) { > + warn("strchr"); > + goto out; > + } > + I don't quite understand what this is doing. > if ((chain = strdup(chaincp)) == NULL) { > warn("strdup"); > goto out; > Index: usr.sbin/acme-client/extern.h > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/extern.h,v > retrieving revision 1.17 > diff -u -p -r1.17 extern.h > --- usr.sbin/acme-client/extern.h 7 Feb 2020 14:34:15 -0000 1.17 > +++ usr.sbin/acme-client/extern.h 20 Apr 2020 16:24:46 -0000 > @@ -261,13 +261,13 @@ int json_parse_capaths(struct jsmnn *, > > char *json_fmt_newcert(const char *); > char *json_fmt_chkacc(void); > -char *json_fmt_newacc(void); > +char *json_fmt_newacc(const char *); > char *json_fmt_neworder(const char *const *, size_t); > char *json_fmt_protected_rsa(const char *, > const char *, const char *, const char *); > char *json_fmt_protected_ec(const char *, const char *, const char *, > const char *); > -char *json_fmt_protected_kid(const char*, const char *, const char *, > +char *json_fmt_protected_kid(const char *, const char *, const char > *, > const char *); > char *json_fmt_revokecert(const char *); > char *json_fmt_thumb_rsa(const char *, const char *); > Index: usr.sbin/acme-client/json.c > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/json.c,v > retrieving revision 1.16 > diff -u -p -r1.16 json.c > --- usr.sbin/acme-client/json.c 22 Jan 2020 22:25:22 -0000 1.16 > +++ usr.sbin/acme-client/json.c 20 Apr 2020 16:24:46 -0000 > @@ -616,19 +616,30 @@ json_fmt_chkacc(void) > * Format the "newAccount" resource request. > */ > char * > -json_fmt_newacc(void) > +json_fmt_newacc(const char *contact) > { > int c; > - char *p; > + char *p, *cnt; > + > + c = asprintf(&cnt, "\"contact\": [ \"%s\" ], ", contact); > + if (c == -1) { > + warn("asprintf"); > + goto err; > + } > bzzzrt, you can't do this. contact might be NULL, you already need to check it here, not just further down. In this particular case I think it's fine do something like this, makes the error handling easier, you don't need a goto etc. if (contact == NULL) { p = strdup("{" "\"termsOfServiceAgreed\": true" "}"); } else { c = asprintf(&p, "{" "\"contact\": [ \"mailto:%s\" ], " "\"termsOfServiceAgreed\": true" "}", contact); if (c == -1) p = NULL; } yes, we have two places now for the template string, but they are close enough together and I don't see them change that often (read: never). Also not the mailto, I think we should just put an email address in the config file and fill in the mailto ourselves. The rest reads good, haven't tried it yet though. Thanks, Florian > c = asprintf(&p, "{" > + "%s" > "\"termsOfServiceAgreed\": true" > - "}"); > + "}", contact == NULL ? "" : cnt); > + free(cnt); > if (c == -1) { > warn("asprintf"); > p = NULL; > } > return p; > + > +err: > + return NULL; > } > > /* > Index: usr.sbin/acme-client/netproc.c > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/netproc.c,v > retrieving revision 1.25 > diff -u -p -r1.25 netproc.c > --- usr.sbin/acme-client/netproc.c 11 Aug 2019 19:44:25 -0000 1.25 > +++ usr.sbin/acme-client/netproc.c 20 Apr 2020 16:24:46 -0000 > @@ -368,13 +368,13 @@ sreq(struct conn *c, const char *addr, i > * Returns non-zero on success. > */ > static int > -donewacc(struct conn *c, const struct capaths *p) > +donewacc(struct conn *c, const struct capaths *p, const char *contact) > { > int rc = 0; > char *req; > long lc; > > - if ((req = json_fmt_newacc()) == NULL) > + if ((req = json_fmt_newacc(contact)) == NULL) > warnx("json_fmt_newacc"); > else if ((lc = sreq(c, p->newaccount, 0, req, &c->kid)) < 0) > warnx("%s: bad comm", p->newaccount); > @@ -397,7 +397,7 @@ donewacc(struct conn *c, const struct ca > * Returns non-zero on success. > */ > static int > -dochkacc(struct conn *c, const struct capaths *p) > +dochkacc(struct conn *c, const struct capaths *p, const char *contact) > { > int rc = 0; > char *req; > @@ -412,7 +412,7 @@ dochkacc(struct conn *c, const struct ca > else if (c->buf.buf == NULL || c->buf.sz == 0) > warnx("%s: empty response", p->newaccount); > else if (lc == 400) > - rc = donewacc(c, p); > + rc = donewacc(c, p, contact); > else > rc = 1; > > @@ -742,7 +742,7 @@ netproc(int kfd, int afd, int Cfd, int c > c.newnonce = paths.newnonce; > > /* Check if our account already exists or create it. */ > - if (!dochkacc(&c, &paths)) > + if (!dochkacc(&c, &paths, authority->contact)) > goto out; > > /* > Index: usr.sbin/acme-client/parse.h > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/parse.h,v > retrieving revision 1.13 > diff -u -p -r1.13 parse.h > --- usr.sbin/acme-client/parse.h 17 Jun 2019 12:42:52 -0000 1.13 > +++ usr.sbin/acme-client/parse.h 20 Apr 2020 16:24:46 -0000 > @@ -38,6 +38,7 @@ struct authority_c { > char *api; > char *account; > enum keytype keytype; > + char *contact; > }; > > struct domain_c { > Index: usr.sbin/acme-client/parse.y > =================================================================== > RCS file: /cvs/src/usr.sbin/acme-client/parse.y,v > retrieving revision 1.39 > diff -u -p -r1.39 parse.y > --- usr.sbin/acme-client/parse.y 27 Dec 2019 16:56:40 -0000 1.39 > +++ usr.sbin/acme-client/parse.y 20 Apr 2020 16:24:47 -0000 > @@ -100,7 +100,7 @@ typedef struct { > > %} > > -%token AUTHORITY URL API ACCOUNT > +%token AUTHORITY URL API ACCOUNT CONTACT > %token DOMAIN ALTERNATIVE NAMES CERT FULL CHAIN KEY SIGN WITH > CHALLENGEDIR > %token YES NO > %token INCLUDE > @@ -230,6 +230,16 @@ authorityoptsl : API URL STRING { > auth->account = s; > auth->keytype = $4; > } > + | CONTACT STRING { > + char *s; > + if (auth->contact != NULL) { > + yyerror("duplicate contact"); > + YYERROR; > + } > + if ((s = strdup($2)) == NULL) > + err(EXIT_FAILURE, "strdup"); > + auth->contact = s; > + } > ; > > domain : DOMAIN STRING { > @@ -437,6 +447,7 @@ lookup(char *s) > {"certificate", CERT}, > {"chain", CHAIN}, > {"challengedir", CHALLENGEDIR}, > + {"contact", CONTACT}, > {"domain", DOMAIN}, > {"ecdsa", ECDSA}, > {"full", FULL}, > Index: etc/examples/acme-client.conf > =================================================================== > RCS file: /cvs/src/etc/examples/acme-client.conf,v > retrieving revision 1.2 > diff -u -p -r1.2 acme-client.conf > --- etc/examples/acme-client.conf 7 Jun 2019 08:08:30 -0000 1.2 > +++ etc/examples/acme-client.conf 20 Apr 2020 16:24:56 -0000 > @@ -11,6 +11,18 @@ authority letsencrypt-staging { > account key "/etc/acme/letsencrypt-staging-privkey.pem" > } > > +authority buypass { > + api url "https://api.buypass.com/acme/directory" > + account key "/etc/acme/buypass-privkey.pem" > + contact "mailto:j...@example.net" > +} > + > +authority buypass-test { > + api url "https://api.test4.buypass.no/acme/directory" > + account key "/etc/acme/buypass-test-privkey.pem" > + contact "mailto:j...@example.net" > +} > + > domain example.com { > alternative names { secure.example.com } > domain key "/etc/ssl/private/example.com.key" -- I'm not entirely sure you are real.