OK? 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) + 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); + 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"); + 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); + 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; + 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) { + warnx("EVP_PKEY_get1_EC_KEY"); + goto out; + } + degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec)); + bn_len = (degree + 7) / 8; + EC_KEY_free(ec); + + 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 1a2c1749e49..a49415e1610 100644 --- main.c +++ main.c @@ -38,7 +38,6 @@ main(int argc, char *argv[]) const char **alts = NULL; char *certdir = NULL, *certfile = NULL; char *chainfile = NULL, *fullchainfile = NULL; - char *acctkey = NULL; char *chngdir = NULL, *auth = NULL; char *conffile = CONF_FILE; char *tmps, *tmpsd; @@ -147,8 +146,6 @@ main(int argc, char *argv[]) errx(EXIT_FAILURE, "authority %s not found", auth); } - acctkey = authority->account; - if ((chngdir = domain->challengedir) == NULL) if ((chngdir = strdup(WWW_DIR)) == NULL) err(EXIT_FAILURE, "strdup"); @@ -273,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], acctkey); + 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.