On Sat, Jun 15, 2019 at 06:20:49PM +0200, Florian Obser wrote:
> OK?
update to apply cleanly on -current
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 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.