summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/acme-client/acctproc.c197
-rw-r--r--usr.sbin/acme-client/acme-client.conf.513
-rw-r--r--usr.sbin/acme-client/extern.h9
-rw-r--r--usr.sbin/acme-client/json.c57
-rw-r--r--usr.sbin/acme-client/main.c5
-rw-r--r--usr.sbin/acme-client/parse.h9
-rw-r--r--usr.sbin/acme-client/parse.y8
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) {