diff options
author | Florian Obser <florian@cvs.openbsd.org> | 2019-06-17 12:42:53 +0000 |
---|---|---|
committer | Florian Obser <florian@cvs.openbsd.org> | 2019-06-17 12:42:53 +0000 |
commit | cec2ffb0fc79314b60bbb20674a5e6ffebca13d1 (patch) | |
tree | a5425fc6a97783da2db1381552a6681ddc7750bf /usr.sbin/acme-client | |
parent | 24659d9c81603df90ca93b30041b38fa87b3d3eb (diff) |
Implement elliptic curve account keys.
OK benno
Input & OK tb
Diffstat (limited to 'usr.sbin/acme-client')
-rw-r--r-- | usr.sbin/acme-client/acctproc.c | 197 | ||||
-rw-r--r-- | usr.sbin/acme-client/acme-client.conf.5 | 13 | ||||
-rw-r--r-- | usr.sbin/acme-client/extern.h | 9 | ||||
-rw-r--r-- | usr.sbin/acme-client/json.c | 57 | ||||
-rw-r--r-- | usr.sbin/acme-client/main.c | 5 | ||||
-rw-r--r-- | usr.sbin/acme-client/parse.h | 9 | ||||
-rw-r--r-- | usr.sbin/acme-client/parse.y | 8 |
7 files changed, 259 insertions, 39 deletions
diff --git a/usr.sbin/acme-client/acctproc.c b/usr.sbin/acme-client/acctproc.c index a904dea8925..7775e80aece 100644 --- a/usr.sbin/acme-client/acctproc.c +++ b/usr.sbin/acme-client/acctproc.c @@ -1,4 +1,4 @@ -/* $Id: acctproc.c,v 1.17 2019/06/17 08:59:33 florian Exp $ */ +/* $Id: acctproc.c,v 1.18 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -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> @@ -91,6 +92,41 @@ op_thumb_rsa(EVP_PKEY *pkey) } /* + * 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_get0_EC_KEY(pkey)) == NULL) + warnx("EVP_PKEY_get0_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"); + + BN_free(X); + BN_free(Y); + free(x); + free(y); + return json; +} + +/* * The thumbprint operation is used for the challenge sequence. */ static int @@ -109,6 +145,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 +223,41 @@ 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_get0_EC_KEY(pkey)) == NULL) + warnx("EVP_PKEY_get0_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; + + 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 +265,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 +299,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 +325,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 +364,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 +373,57 @@ 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_get0_EC_KEY(pkey)) == NULL) { + warnx("EVP_PKEY_get0_EC_KEY"); + goto out; + } + degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec)); + bn_len = (degree + 7) / 8; + + 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 +455,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 +497,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 a/usr.sbin/acme-client/acme-client.conf.5 b/usr.sbin/acme-client/acme-client.conf.5 index 3df0ca38915..890a2c2517c 100644 --- a/usr.sbin/acme-client/acme-client.conf.5 +++ b/usr.sbin/acme-client/acme-client.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: acme-client.conf.5,v 1.19 2019/06/12 11:36:32 jmc Exp $ +.\" $OpenBSD: acme-client.conf.5,v 1.20 2019/06/17 12:42:52 florian Exp $ .\" .\" Copyright (c) 2005 Esben Norby <norby@openbsd.org> .\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org> @@ -17,7 +17,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: June 12 2019 $ +.Dd $Mdocdate: June 17 2019 $ .Dt ACME-CLIENT.CONF 5 .Os .Sh NAME @@ -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 a/usr.sbin/acme-client/extern.h b/usr.sbin/acme-client/extern.h index b2d2e47f1d7..e6b7af0d05b 100644 --- a/usr.sbin/acme-client/extern.h +++ b/usr.sbin/acme-client/extern.h @@ -1,4 +1,4 @@ -/* $Id: extern.h,v 1.15 2019/06/16 19:49:13 florian Exp $ */ +/* $Id: extern.h,v 1.16 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -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 a/usr.sbin/acme-client/json.c b/usr.sbin/acme-client/json.c index bee5c83c724..471a5cea8de 100644 --- a/usr.sbin/acme-client/json.c +++ b/usr.sbin/acme-client/json.c @@ -1,4 +1,4 @@ -/* $Id: json.c,v 1.12 2019/06/07 08:07:52 florian Exp $ */ +/* $Id: json.c,v 1.13 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -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 a/usr.sbin/acme-client/main.c b/usr.sbin/acme-client/main.c index a409e84fc9a..7cbeeb7de03 100644 --- a/usr.sbin/acme-client/main.c +++ b/usr.sbin/acme-client/main.c @@ -1,4 +1,4 @@ -/* $Id: main.c,v 1.51 2019/06/16 19:49:13 florian Exp $ */ +/* $Id: main.c,v 1.52 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> * @@ -262,7 +262,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 a/usr.sbin/acme-client/parse.h b/usr.sbin/acme-client/parse.h index 95229a42441..382aa684f9e 100644 --- a/usr.sbin/acme-client/parse.h +++ b/usr.sbin/acme-client/parse.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.h,v 1.12 2019/06/14 19:55:08 florian Exp $ */ +/* $OpenBSD: parse.h,v 1.13 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org> * @@ -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 a/usr.sbin/acme-client/parse.y b/usr.sbin/acme-client/parse.y index 0fdab0d7435..20818328d92 100644 --- a/usr.sbin/acme-client/parse.y +++ b/usr.sbin/acme-client/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.37 2019/06/15 12:51:19 florian Exp $ */ +/* $OpenBSD: parse.y,v 1.38 2019/06/17 12:42:52 florian Exp $ */ /* * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> @@ -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) { |