On Sun, Jun 16, 2019 at 09:25:45AM +0200, Florian Obser wrote:
> On Sat, Jun 15, 2019 at 06:20:49PM +0200, Florian Obser wrote:
> > OK?
>
> update to apply cleanly on -current
The acctproc.c change looks ok with some minor comments inline.
I haven't tried to verify the JSON stuff.
>
> diff --git acctproc.c acctproc.c
> index 52309b765d5..09ac8c83372 100644
> --- acctproc.c
> +++ acctproc.c
> @@ -24,6 +24,7 @@
> #include <unistd.h>
>
> #include <openssl/pem.h>
> +#include <openssl/evp.h>
> #include <openssl/rsa.h>
> #include <openssl/rand.h>
> #include <openssl/err.h>
> @@ -90,6 +91,42 @@ op_thumb_rsa(EVP_PKEY *pkey)
> return json;
> }
>
> +/*
> + * Extract the relevant EC components from the key and create the JSON
> + * thumbprint from them.
> + */
> +static char *
> +op_thumb_ec(EVP_PKEY *pkey)
> +{
> + BIGNUM *X = NULL, *Y = NULL;
> + EC_KEY *ec = NULL;
> + char *x = NULL, *y = NULL;
> + char *json = NULL;
> +
> + if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
I would suggest using get0 as in the RSA case ...
> + warnx("EVP_PKEY_get1_EC_KEY");
> + else if ((X = BN_new()) == NULL)
> + warnx("BN_new");
> + else if ((Y = BN_new()) == NULL)
> + warnx("BN_new");
> + else if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec),
> + EC_KEY_get0_public_key(ec), X, Y, NULL))
> + warnx("EC_POINT_get_affine_coordinates_GFp");
> + else if ((x = bn2string(X)) == NULL)
> + warnx("bn2string");
> + else if ((y = bn2string(Y)) == NULL)
> + warnx("bn2string");
> + else if ((json = json_fmt_thumb_ec(x, y)) == NULL)
> + warnx("json_fmt_thumb_rsa");
> +
> + EC_KEY_free(ec);
...and drop this EC_KEY_free()
> + BN_free(X);
> + BN_free(Y);
> + free(x);
> + free(y);
> + return json;
> +}
> +
> /*
> * The thumbprint operation is used for the challenge sequence.
> */
> @@ -109,6 +146,10 @@ op_thumbprint(int fd, EVP_PKEY *pkey)
> if ((thumb = op_thumb_rsa(pkey)) != NULL)
> break;
> goto out;
> + case EVP_PKEY_EC:
> + if ((thumb = op_thumb_ec(pkey)) != NULL)
> + break;
> + goto out;
> default:
> warnx("EVP_PKEY_type: unknown key type");
> goto out;
> @@ -183,6 +224,42 @@ op_sign_rsa(char **prot, EVP_PKEY *pkey, const char
> *nonce, const char *url)
> return rc;
> }
>
> +static int
> +op_sign_ec(char **prot, EVP_PKEY *pkey, const char *nonce, const char *url)
> +{
> + BIGNUM *X = NULL, *Y = NULL;
> + EC_KEY *ec = NULL;
> + char *x = NULL, *y = NULL;
> + int rc = 0;
> +
> + *prot = NULL;
> +
> + if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL)
> + warnx("EVP_PKEY_get1_EC_KEY");
same here: get0...
> + else if ((X = BN_new()) == NULL)
> + warnx("BN_new");
> + else if ((Y = BN_new()) == NULL)
> + warnx("BN_new");
> + else if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec),
> + EC_KEY_get0_public_key(ec), X, Y, NULL))
> + warnx("EC_POINT_get_affine_coordinates_GFp");
> + else if ((x = bn2string(X)) == NULL)
> + warnx("bn2string");
> + else if ((y = bn2string(Y)) == NULL)
> + warnx("bn2string");
> + else if ((*prot = json_fmt_protected_ec(x, y, nonce, url)) == NULL)
> + warnx("json_fmt_protected_ec");
> + else
> + rc = 1;
> +
> + EC_KEY_free(ec);
... and omit EC_KEY_free()
> + BN_free(X);
> + BN_free(Y);
> + free(x);
> + free(y);
> + return rc;
> +}
> +
> /*
> * Operation to sign a message with the account key.
> * This requires the sender ("fd") to provide the payload and a nonce.
> @@ -190,14 +267,19 @@ op_sign_rsa(char **prot, EVP_PKEY *pkey, const char
> *nonce, const char *url)
> static int
> op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
> {
> - char *nonce = NULL, *pay = NULL, *pay64 = NULL;
> - char *prot = NULL, *prot64 = NULL;
> - char *sign = NULL, *dig64 = NULL, *fin = NULL;
> - char *url = NULL, *kid = NULL;
> - unsigned char *dig = NULL;
> - EVP_MD_CTX *ctx = NULL;
> - int cc, rc = 0;
> - unsigned int digsz;
> + EVP_MD_CTX *ctx = NULL;
> + const EVP_MD *evp_md = NULL;
> + EC_KEY *ec;
> + ECDSA_SIG *ec_sig = NULL;
> + const BIGNUM *ec_sig_r = NULL, *ec_sig_s = NULL;
> + int cc, rc = 0;
> + unsigned int digsz, bufsz, degree, bn_len, r_len, s_len;
> + char *nonce = NULL, *pay = NULL, *pay64 = NULL;
> + char *prot = NULL, *prot64 = NULL;
> + char *sign = NULL, *dig64 = NULL, *fin = NULL;
> + char *url = NULL, *kid = NULL, *alg = NULL;
> + unsigned char *dig = NULL, *buf = NULL;
To simplify slightly, you could use
unsigned char dig[EVP_MAX_MD_SIZE];
instead of allocating dig dynamically (EVP_MAX_MD_SIZE is 64).
> + const unsigned char *digp;
>
> /* Read our payload and nonce from the requestor. */
>
> @@ -219,8 +301,23 @@ op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
> goto out;
> }
>
> + switch (EVP_PKEY_type(pkey->type)) {
> + case EVP_PKEY_RSA:
> + alg = "RS256";
> + evp_md = EVP_sha256();
> + break;
> + case EVP_PKEY_EC:
> + alg = "ES384";
> + evp_md = EVP_sha384();
> + break;
> + default:
> + warnx("unknown account key type");
> + goto out;
> + }
> +
> if (op == ACCT_KID_SIGN) {
> - if ((prot = json_fmt_protected_kid(kid, nonce, url)) == NULL) {
> + if ((prot = json_fmt_protected_kid(alg, kid, nonce, url)) ==
> + NULL) {
> warnx("json_fmt_protected_kid");
> goto out;
> }
> @@ -230,6 +327,10 @@ op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
> if (!op_sign_rsa(&prot, pkey, nonce, url))
> goto out;
> break;
> + case EVP_PKEY_EC:
> + if (!op_sign_ec(&prot, pkey, nonce, url))
> + goto out;
> + break;
> default:
> warnx("EVP_PKEY_type");
> goto out;
> @@ -265,7 +366,7 @@ op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
> if ((ctx = EVP_MD_CTX_create()) == NULL) {
> warnx("EVP_MD_CTX_create");
> goto out;
> - } else if (!EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
> + } else if (!EVP_SignInit_ex(ctx, evp_md, NULL)) {
> warnx("EVP_SignInit_ex");
> goto out;
> } else if (!EVP_SignUpdate(ctx, sign, strlen(sign))) {
> @@ -274,8 +375,58 @@ op_sign(int fd, EVP_PKEY *pkey, enum acctop op)
> } else if (!EVP_SignFinal(ctx, dig, &digsz, pkey)) {
> warnx("EVP_SignFinal");
> goto out;
> - } else if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
> - warnx("base64buf_url");
> + }
> +
> + switch (EVP_PKEY_type(pkey->type)) {
> + case EVP_PKEY_RSA:
> + if ((dig64 = base64buf_url((char *)dig, digsz)) == NULL) {
> + warnx("base64buf_url");
> + goto out;
> + }
> + break;
> + case EVP_PKEY_EC:
> + if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
use get0
> + warnx("EVP_PKEY_get1_EC_KEY");
> + goto out;
> + }
> + degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec));
> + bn_len = (degree + 7) / 8;
This calculation is fine if you use curves over a prime field GFp (as
you currently do), but it will be incorrect if you should ever want to
use a curve over a binary field GF2m.
> + EC_KEY_free(ec);
if you switched to get0 above, don't use EC_KEY_free().
> +
> + digp = dig; /* d2i_ECDSA_SIG advances digp */
> + if ((ec_sig = d2i_ECDSA_SIG(NULL, &digp, digsz)) == NULL) {
> + warnx("d2i_ECDSA_SIG");
> + goto out;
> + }
> +
> + ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);
> +
> + r_len = BN_num_bytes(ec_sig_r);
> + s_len = BN_num_bytes(ec_sig_s);
> +
> + if((r_len > bn_len) || (s_len > bn_len)) {
> + warnx("ECDSA_SIG_get0");
> + goto out;
> + }
> +
> + bufsz = 2 * bn_len;
> + if ((buf = calloc(1, bufsz)) == NULL) {
> + warnx("calloc");
> + goto out;
> + }
> +
> + /* put r and s in with leading zeros if any */
> + BN_bn2bin(ec_sig_r, buf + bn_len - r_len);
> + BN_bn2bin(ec_sig_s, buf + bufsz - s_len);
> +
> + if ((dig64 = base64buf_url((char *)buf, bufsz)) == NULL) {
> + warnx("base64buf_url");
> + goto out;
> + }
> +
> + break;
> + default:
> + warnx("EVP_PKEY_type");
> goto out;
> }
>
> @@ -307,11 +458,12 @@ out:
> free(dig);
> free(dig64);
> free(fin);
> + free(buf);
> return rc;
> }
>
> int
> -acctproc(int netsock, const char *acctkey)
> +acctproc(int netsock, const char *acctkey, enum keytype keytype)
> {
> FILE *f = NULL;
> EVP_PKEY *pkey = NULL;
> @@ -348,15 +500,23 @@ acctproc(int netsock, const char *acctkey)
> }
>
> if (newacct) {
> - if ((pkey = rsa_key_create(f, acctkey)) == NULL)
> - goto out;
> - dodbg("%s: generated RSA account key", acctkey);
> + switch (keytype) {
> + case KT_ECDSA:
> + if ((pkey = ec_key_create(f, acctkey)) == NULL)
> + goto out;
> + dodbg("%s: generated ECDSA account key", acctkey);
> + break;
> + case KT_RSA:
> + if ((pkey = rsa_key_create(f, acctkey)) == NULL)
> + goto out;
> + dodbg("%s: generated RSA account key", acctkey);
> + break;
> + }
> } else {
> if ((pkey = key_load(f, acctkey)) == NULL)
> goto out;
> - if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA)
> - goto out;
> - doddbg("%s: loaded RSA account key", acctkey);
> + /* XXX check if account key type equals configured key type */
> + doddbg("%s: loaded account key", acctkey);
> }
>
> fclose(f);
> diff --git acme-client.conf.5 acme-client.conf.5
> index 3df0ca38915..5852d8da839 100644
> --- acme-client.conf.5
> +++ acme-client.conf.5
> @@ -83,10 +83,17 @@ is a string used to reference this certificate authority.
> .Pp
> It is followed by a block of options enclosed in curly brackets:
> .Bl -tag -width Ds
> -.It Ic account key Ar file
> +.It Ic account key Ar file Op Ar keytype
> Specify a
> .Ar file
> used to identify the user of this certificate authority.
> +.Ar keytype
> +can be
> +.Cm rsa
> +or
> +.Cm ecdsa .
> +It defaults to
> +.Cm rsa .
> .It Ic api url Ar url
> Specify the
> .Ar url
> diff --git extern.h extern.h
> index d533466fbe6..7fcbf77d88f 100644
> --- extern.h
> +++ extern.h
> @@ -199,7 +199,7 @@ __BEGIN_DECLS
> * Start with our components.
> * These are all isolated and talk to each other using sockets.
> */
> -int acctproc(int, const char *);
> +int acctproc(int, const char *, enum keytype);
> int certproc(int, int);
> int chngproc(int, const char *);
> int dnsproc(int);
> @@ -265,10 +265,13 @@ char *json_fmt_newacc(void);
> 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_kid(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 *,
> const char *);
> char *json_fmt_revokecert(const char *);
> char *json_fmt_thumb_rsa(const char *, const char *);
> +char *json_fmt_thumb_ec(const char *, const char *);
> char *json_fmt_signed(const char *, const char *, const char *);
>
> /*
> diff --git json.c json.c
> index bee5c83c724..baae3c3a4a9 100644
> --- json.c
> +++ json.c
> @@ -733,18 +733,43 @@ json_fmt_protected_rsa(const char *exp, const char
> *mod, const char *nce,
> * Protected component of json_fmt_signed().
> */
> char *
> -json_fmt_protected_kid(const char *kid, const char *nce, const char *url)
> +json_fmt_protected_ec(const char *x, const char *y, const char *nce,
> + const char *url)
> {
> int c;
> char *p;
>
> c = asprintf(&p, "{"
> - "\"alg\": \"RS256\", "
> + "\"alg\": \"ES384\", "
> + "\"jwk\": "
> + "{\"crv\": \"P-384\", \"kty\": \"EC\", \"x\": \"%s\", "
> + "\"y\": \"%s\"}, \"nonce\": \"%s\", \"url\": \"%s\""
> + "}",
> + x, y, nce, url);
> + if (c == -1) {
> + warn("asprintf");
> + p = NULL;
> + }
> + return p;
> +}
> +
> +/*
> + * Protected component of json_fmt_signed().
> + */
> +char *
> +json_fmt_protected_kid(const char *alg, const char *kid, const char *nce,
> + const char *url)
> +{
> + int c;
> + char *p;
> +
> + c = asprintf(&p, "{"
> + "\"alg\": \"%s\", "
> "\"kid\": \"%s\", "
> "\"nonce\": \"%s\", "
> "\"url\": \"%s\""
> "}",
> - kid, nce, url);
> + alg, kid, nce, url);
> if (c == -1) {
> warn("asprintf");
> p = NULL;
> @@ -796,3 +821,27 @@ json_fmt_thumb_rsa(const char *exp, const char *mod)
> }
> return p;
> }
> +
> +/*
> + * Produce thumbprint input.
> + * This isn't technically a JSON string--it's the input we'll use for
> + * hashing and digesting.
> + * However, it's in the form of a JSON string, so do it here.
> + */
> +char *
> +json_fmt_thumb_ec(const char *x, const char *y)
> +{
> + int c;
> + char *p;
> +
> + /*NOTE: WHITESPACE IS IMPORTANT. */
> +
> + c = asprintf(&p, "{\"crv\":\"P-384\",\"kty\":\"EC\",\"x\":\"%s\","
> + "\"y\":\"%s\"}",
> + x, y);
> + if (c == -1) {
> + warn("asprintf");
> + p = NULL;
> + }
> + return p;
> +}
> diff --git main.c main.c
> index de9b5e25e25..a49415e1610 100644
> --- main.c
> +++ main.c
> @@ -270,7 +270,8 @@ main(int argc, char *argv[])
> close(chng_fds[0]);
> close(file_fds[0]);
> close(file_fds[1]);
> - c = acctproc(acct_fds[0], authority->account);
> + c = acctproc(acct_fds[0], authority->account,
> + authority->keytype);
> exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
> }
>
> diff --git parse.h parse.h
> index 95229a42441..d7bd3b2a7b4 100644
> --- parse.h
> +++ parse.h
> @@ -34,9 +34,10 @@ enum keytype {
>
> struct authority_c {
> TAILQ_ENTRY(authority_c) entry;
> - char *name;
> - char *api;
> - char *account;
> + char *name;
> + char *api;
> + char *account;
> + enum keytype keytype;
> };
>
> struct domain_c {
> diff --git parse.y parse.y
> index 0fdab0d7435..2b6bdb3135a 100644
> --- parse.y
> +++ parse.y
> @@ -219,7 +219,7 @@ authorityoptsl : API URL STRING {
> err(EXIT_FAILURE, "strdup");
> auth->api = s;
> }
> - | ACCOUNT KEY STRING {
> + | ACCOUNT KEY STRING keytype{
> char *s;
> if (auth->account != NULL) {
> yyerror("duplicate account");
> @@ -228,6 +228,7 @@ authorityoptsl : API URL STRING {
> if ((s = strdup($3)) == NULL)
> err(EXIT_FAILURE, "strdup");
> auth->account = s;
> + auth->keytype = $4;
> }
> ;
>
> @@ -1020,7 +1021,8 @@ print_config(struct acme_conf *xconf)
> if (a->api != NULL)
> printf("\tapi url \"%s\"\n", a->api);
> if (a->account != NULL)
> - printf("\taccount key \"%s\"\n", a->account);
> + printf("\taccount key \"%s\" %s\n", a->account,
> + kt2txt(a->keytype));
> printf("}\n\n");
> }
> TAILQ_FOREACH(d, &xconf->domain_list, entry) {
>
>
> --
> I'm not entirely sure you are real.
>