Skip to content

Commit

Permalink
Add support for certificate profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
ndilieto committed Jan 19, 2025
1 parent 0fc608d commit 4cd4f10
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 37 deletions.
39 changes: 23 additions & 16 deletions crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -2134,7 +2134,8 @@ bool is_ip(const char *s, unsigned char *ip, size_t *ip_len)
return ret;
}

char *csr_gen(char * const *names, bool status_req, privkey_t key)
char *csr_gen(char * const *names, bool status_req, bool no_key_usage,
privkey_t key)
{
char *req = NULL;
unsigned char *csrdata = NULL;
Expand Down Expand Up @@ -2259,11 +2260,13 @@ char *csr_gen(char * const *names, bool status_req, privkey_t key)
names++;
}

r = gnutls_x509_crq_set_key_usage(crq, key_usage);
if (r != GNUTLS_E_SUCCESS) {
warnx("csr_gen: gnutls_x509_crq_set_key_usage: %s",
gnutls_strerror(r));
goto out;
if (!no_key_usage) {
r = gnutls_x509_crq_set_key_usage(crq, key_usage);
if (r != GNUTLS_E_SUCCESS) {
warnx("csr_gen: gnutls_x509_crq_set_key_usage: %s",
gnutls_strerror(r));
goto out;
}
}

if (status_req) {
Expand Down Expand Up @@ -2397,12 +2400,14 @@ char *csr_gen(char * const *names, bool status_req, privkey_t key)
goto out;
}
sk_X509_EXTENSION_push(exts, ext);
ext = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, key_usage);
if (!ext) {
openssl_error("csr_gen");
goto out;
if (!no_key_usage) {
ext = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, key_usage);
if (!ext) {
openssl_error("csr_gen");
goto out;
}
sk_X509_EXTENSION_push(exts, ext);
}
sk_X509_EXTENSION_push(exts, ext);
if (status_req) {
#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050000fL
warnx("csr_gen: -m, --must-staple is not supported by LibreSSL "
Expand Down Expand Up @@ -2452,11 +2457,13 @@ char *csr_gen(char * const *names, bool status_req, privkey_t key)
goto out;
}

r = mbedtls_x509write_csr_set_key_usage(&csr, key_usage);
if (r) {
warnx("csr_gen: mbedtls_x509write_csr_set_key_usage failed: %s",
_mbedtls_strerror(r));
goto out;
if (!no_key_usage) {
r = mbedtls_x509write_csr_set_key_usage(&csr, key_usage);
if (r) {
warnx("csr_gen: mbedtls_x509write_csr_set_key_usage failed: %s",
_mbedtls_strerror(r));
goto out;
}
}

r = mbedtls_x509write_csr_set_subject_name(&csr, cn);
Expand Down
2 changes: 1 addition & 1 deletion crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ char *jws_encode_hmac(const char *, const char *, size_t, const char *);
keytype_t key_type(privkey_t);
privkey_t key_load(keytype_t, int bits, const char *, ...);
bool is_ip(const char *, unsigned char *, size_t *);
char *csr_gen(char * const *, bool, privkey_t);
char *csr_gen(char * const *, bool, bool, privkey_t);
char *csr_load(const char *, char ***);
char *cert_der_base64url(const char *);
bool cert_valid(const char *, char * const *, const char *, int, bool);
Expand Down
20 changes: 15 additions & 5 deletions docs/uacme.html
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,9 @@ <h2 id="_synopsis">SYNOPSIS</h2>
[<strong>-c</strong>|<strong>--confdir</strong> <em>DIR</em>] [<strong>-d</strong>|<strong>--days</strong> <em>DAYS</em>] [<strong>-e</strong>|<strong>--eab</strong> KEYID:KEY]
[<strong>-f</strong>|<strong>--force</strong>] [<strong>-h</strong>|<strong>--hook</strong> <em>PROGRAM</em>] [<strong>-i</strong>|<strong>--no-ari</strong>]
[<strong>-l</strong>|<strong>--alternate</strong> <em>N</em> | <em>FP</em>] [<strong>-m</strong>|<strong>--must-staple</strong>]
[<strong>-n</strong>|<strong>--never-create</strong>] [<strong>-o</strong>|<strong>--no-ocsp</strong>] [<strong>-r</strong>|<strong>--reason</strong> CODE]
[<strong>-s</strong>|<strong>--staging</strong>] [<strong>-t</strong>|<strong>--type</strong> <strong>RSA</strong>|<strong>EC</strong>] [<strong>-v</strong>|<strong>--verbose</strong> &#8230;]
[<strong>-V</strong>|<strong>--version</strong>] [<strong>-y</strong>|<strong>--yes</strong>] [<strong>-?</strong>|<strong>--help</strong>]
[<strong>-n</strong>|<strong>--never-create</strong>] [<strong>-o</strong>|<strong>--no-ocsp</strong>] [<strong>-p</strong>|<strong>--profile</strong> PROFILE]
[<strong>-r</strong>|<strong>--reason</strong> CODE] [<strong>-s</strong>|<strong>--staging</strong>] [<strong>-t</strong>|<strong>--type</strong> <strong>RSA</strong>|<strong>EC</strong>]
[<strong>-v</strong>|<strong>--verbose</strong> &#8230;] [<strong>-V</strong>|<strong>--version</strong>] [<strong>-y</strong>|<strong>--yes</strong>] [<strong>-?</strong>|<strong>--help</strong>]
<strong>new</strong> [<em>EMAIL</em>] | <strong>update</strong> [<em>EMAIL</em>] | <strong>deactivate</strong> | <strong>newkey</strong> |
<strong>issue</strong> <em>IDENTIFIER</em> [<em>ALTNAME</em> &#8230;]] | <strong>issue</strong> <em>CSRFILE</em> |
<strong>revoke</strong> <em>CERTFILE</em> [<em>CERTKEYFILE</em>]</p></div>
Expand Down Expand Up @@ -1032,6 +1032,16 @@ <h2 id="_options">OPTIONS</h2>
</p>
</dd>
<dt class="hdlist1">
<strong>-p, --profile</strong> <em>PROFILE</em>
</dt>
<dd>
<p>
If the server supports it, use <em>PROFILE</em>, a collection of attributes
about the certificate that will be issued, such as what extensions it
will contain, how long it will be valid for, and more.
</p>
</dd>
<dt class="hdlist1">
<strong>-r, --reason</strong> <em>CODE</em>
</dt>
<dd>
Expand Down Expand Up @@ -1472,9 +1482,9 @@ <h2 id="_copyright">COPYRIGHT</h2>
<div id="footnotes"><hr></div>
<div id="footer">
<div id="footer-text">
Version 1.7.6<br>
Version 1.7.6-dev<br>
Last updated
2024-12-29 17:25:16 CET
2025-01-19 12:57:02 CET
</div>
</div>
</body>
Expand Down
14 changes: 10 additions & 4 deletions uacme.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
.\" Title: uacme
.\" Author: [see the "AUTHOR" section]
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
.\" Date: 12/29/2024
.\" Date: 01/19/2025
.\" Manual: User Commands
.\" Source: uacme 1.7.6
.\" Source: uacme 1.7.6-dev
.\" Language: English
.\"
.TH "UACME" "1" "12/29/2024" "uacme 1\&.7\&.6" "User Commands"
.TH "UACME" "1" "01/19/2025" "uacme 1\&.7\&.6\-dev" "User Commands"
.\" -----------------------------------------------------------------
.\" * Define some portability stuff
.\" -----------------------------------------------------------------
Expand All @@ -31,7 +31,7 @@
uacme \- ACMEv2 client written in plain C with minimal dependencies
.SH "SYNOPSIS"
.sp
\fBuacme\fR [\fB\-a\fR|\fB\-\-acme\-url\fR \fIURL\fR] [\fB\-b\fR|\fB\-\-bits\fR \fIBITS\fR] [\fB\-c\fR|\fB\-\-confdir\fR \fIDIR\fR] [\fB\-d\fR|\fB\-\-days\fR \fIDAYS\fR] [\fB\-e\fR|\fB\-\-eab\fR KEYID:KEY] [\fB\-f\fR|\fB\-\-force\fR] [\fB\-h\fR|\fB\-\-hook\fR \fIPROGRAM\fR] [\fB\-i\fR|\fB\-\-no\-ari\fR] [\fB\-l\fR|\fB\-\-alternate\fR \fIN\fR | \fIFP\fR] [\fB\-m\fR|\fB\-\-must\-staple\fR] [\fB\-n\fR|\fB\-\-never\-create\fR] [\fB\-o\fR|\fB\-\-no\-ocsp\fR] [\fB\-r\fR|\fB\-\-reason\fR CODE] [\fB\-s\fR|\fB\-\-staging\fR] [\fB\-t\fR|\fB\-\-type\fR \fBRSA\fR|\fBEC\fR] [\fB\-v\fR|\fB\-\-verbose\fR \&...] [\fB\-V\fR|\fB\-\-version\fR] [\fB\-y\fR|\fB\-\-yes\fR] [\fB\-?\fR|\fB\-\-help\fR] \fBnew\fR [\fIEMAIL\fR] | \fBupdate\fR [\fIEMAIL\fR] | \fBdeactivate\fR | \fBnewkey\fR | \fBissue\fR \fIIDENTIFIER\fR [\fIALTNAME\fR \&...]] | \fBissue\fR \fICSRFILE\fR | \fBrevoke\fR \fICERTFILE\fR [\fICERTKEYFILE\fR]
\fBuacme\fR [\fB\-a\fR|\fB\-\-acme\-url\fR \fIURL\fR] [\fB\-b\fR|\fB\-\-bits\fR \fIBITS\fR] [\fB\-c\fR|\fB\-\-confdir\fR \fIDIR\fR] [\fB\-d\fR|\fB\-\-days\fR \fIDAYS\fR] [\fB\-e\fR|\fB\-\-eab\fR KEYID:KEY] [\fB\-f\fR|\fB\-\-force\fR] [\fB\-h\fR|\fB\-\-hook\fR \fIPROGRAM\fR] [\fB\-i\fR|\fB\-\-no\-ari\fR] [\fB\-l\fR|\fB\-\-alternate\fR \fIN\fR | \fIFP\fR] [\fB\-m\fR|\fB\-\-must\-staple\fR] [\fB\-n\fR|\fB\-\-never\-create\fR] [\fB\-o\fR|\fB\-\-no\-ocsp\fR] [\fB\-p\fR|\fB\-\-profile\fR PROFILE] [\fB\-r\fR|\fB\-\-reason\fR CODE] [\fB\-s\fR|\fB\-\-staging\fR] [\fB\-t\fR|\fB\-\-type\fR \fBRSA\fR|\fBEC\fR] [\fB\-v\fR|\fB\-\-verbose\fR \&...] [\fB\-V\fR|\fB\-\-version\fR] [\fB\-y\fR|\fB\-\-yes\fR] [\fB\-?\fR|\fB\-\-help\fR] \fBnew\fR [\fIEMAIL\fR] | \fBupdate\fR [\fIEMAIL\fR] | \fBdeactivate\fR | \fBnewkey\fR | \fBissue\fR \fIIDENTIFIER\fR [\fIALTNAME\fR \&...]] | \fBissue\fR \fICSRFILE\fR | \fBrevoke\fR \fICERTFILE\fR [\fICERTKEYFILE\fR]
.SH "DESCRIPTION"
.sp
\fBuacme\fR is a client for the ACMEv2 protocol described in RFC8555, written in plain C with minimal dependencies (libcurl and one of GnuTLS, OpenSSL or mbedTLS)\&. The ACMEv2 protocol allows a Certificate Authority (https://letsencrypt\&.org is a popular one) and an applicant to automate the process of verification and certificate issuance\&. The protocol also provides facilities for other certificate management functions, such as certificate revocation\&. For more information see https://tools\&.ietf\&.org/html/rfc8555
Expand Down Expand Up @@ -238,6 +238,12 @@ forces reissuance regardless of the expiration date\&. See also
\fB\-d, \-\-days\fR\&.
.RE
.PP
\fB\-p, \-\-profile\fR \fIPROFILE\fR
.RS 4
If the server supports it, use
\fIPROFILE\fR, a collection of attributes about the certificate that will be issued, such as what extensions it will contain, how long it will be valid for, and more\&.
.RE
.PP
\fB\-r, \-\-reason\fR \fICODE\fR
.RS 4
Use
Expand Down
11 changes: 8 additions & 3 deletions uacme.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ SYNOPSIS
[*-c*|*--confdir* 'DIR'] [*-d*|*--days* 'DAYS'] [*-e*|*--eab* KEYID:KEY]
[*-f*|*--force*] [*-h*|*--hook* 'PROGRAM'] [*-i*|*--no-ari*]
[*-l*|*--alternate* 'N' | 'FP'] [*-m*|*--must-staple*]
[*-n*|*--never-create*] [*-o*|*--no-ocsp*] [*-r*|*--reason* CODE]
[*-s*|*--staging*] [*-t*|*--type* *RSA*|*EC*] [*-v*|*--verbose* ...]
[*-V*|*--version*] [*-y*|*--yes*] [*-?*|*--help*]
[*-n*|*--never-create*] [*-o*|*--no-ocsp*] [*-p*|*--profile* PROFILE]
[*-r*|*--reason* CODE] [*-s*|*--staging*] [*-t*|*--type* *RSA*|*EC*]
[*-v*|*--verbose* ...] [*-V*|*--version*] [*-y*|*--yes*] [*-?*|*--help*]
*new* ['EMAIL'] | *update* ['EMAIL'] | *deactivate* | *newkey* |
*issue* 'IDENTIFIER' ['ALTNAME' ...]] | *issue* 'CSRFILE' |
*revoke* 'CERTFILE' ['CERTKEYFILE']
Expand Down Expand Up @@ -137,6 +137,11 @@ OPTIONS
reported as revoked *uacme* forces reissuance regardless of the
expiration date. See also *-d, --days*.

*-p, --profile* 'PROFILE'::
If the server supports it, use 'PROFILE', a collection of attributes
about the certificate that will be issued, such as what extensions it
will contain, how long it will be valid for, and more.

*-r, --reason* 'CODE'::
Use 'CODE' (default 0) as reason code in revocation requests. A list
of values is at <https://tools.ietf.org/html/rfc5280#section-5.3.1>.
Expand Down
39 changes: 31 additions & 8 deletions uacme.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ typedef struct acme {
const char *directory;
const char *hook;
const char *email;
const char *profile;
char *keyprefix;
char *certprefix;
} acme_t;
Expand Down Expand Up @@ -417,7 +418,7 @@ char *identifiers(char * const *names)
{
char *ids = NULL;
char *tmp = NULL;
if (asprintf(&tmp, "{\"identifiers\":[") < 0) {
if (asprintf(&tmp, "\"identifiers\":[") < 0) {
warnx("identifiers: asprintf failed");
return NULL;
}
Expand All @@ -434,7 +435,7 @@ char *identifiers(char * const *names)
names++;
}
tmp[strlen(tmp)-1] = 0;
if (asprintf(&ids, "%s]}", tmp) < 0) {
if (asprintf(&ids, "%s]", tmp) < 0) {
warnx("identifiers: asprintf failed");
ids = NULL;
}
Expand All @@ -455,6 +456,20 @@ bool acme_bootstrap(acme_t *a)
a->dir = a->json;
a->json = NULL;

if (a->profile) {
const json_value_t *meta = json_find(a->dir, "meta");
const json_value_t *profiles = json_find(meta, "profiles");
if (!profiles) {
warnx("server does not support profiles");
return false;
}
if (!json_find_string(profiles, a->profile)) {
warnx("profile must be a supported one:");
json_dump(stderr, profiles);
return false;
}
}

return true;
}

Expand Down Expand Up @@ -1021,7 +1036,9 @@ bool cert_issue(acme_t *a, char * const *names, const char *csr)
}

msg(1, "creating new order at %s", url);
if (acme_post(a, url, ids) != 201)
if (acme_post(a, url, "{%s%s%s%s}", a->profile ? "\"profile\": \"" : "",
a->profile ? a->profile : "",
a->profile ? "\", " : "", ids) != 201)
{
warnx("failed to create new order at %s", url);
acme_error(a);
Expand Down Expand Up @@ -1419,9 +1436,10 @@ void usage(const char *progname)
"usage: %s [-a|--acme-url URL] [-b|--bits BITS] [-c|--confdir DIR]\n"
"\t[-d|--days DAYS] [-e|--eab KEYID:KEY] [-f|--force] [-h|--hook PROG]\n"
"\t[-i|--no-ari] [-l|--alternate [N | SHA256]] [-m|--must-staple]\n"
"\t[-n|--never-create] [-o|--no-ocsp] [-r|--reason CODE] [-s|--staging]\n"
"\t[-t|--type RSA | EC] [-v|--verbose ...] [-V|--version] [-y|--yes]\n"
"\t[-?|--help] new [EMAIL] | update [EMAIL] | deactivate | newkey |\n"
"\t[-n|--never-create] [-o|--no-ocsp] [-p|--profile profile]\n"
"\t[-r|--reason CODE] [-s|--staging] [-t|--type RSA | EC]\n"
"\t[-v|--verbose ...] [-V|--version] [-y|--yes] [-?|--help]\n"
"\tnew [EMAIL] | update [EMAIL] | deactivate | newkey |\n"
"\tissue IDENTIFIER [ALTNAME ...]] | issue CSRFILE |\n"
"\trevoke CERTFILE [CERTKEYFILE]\n", progname);
}
Expand Down Expand Up @@ -1457,6 +1475,7 @@ int main(int argc, char **argv)
{"must-staple", no_argument, NULL, 'm'},
{"never-create", no_argument, NULL, 'n'},
{"no-ocsp", no_argument, NULL, 'o'},
{"profile", no_argument, NULL, 'p'},
{"reason", required_argument, NULL, 'r'},
{"staging", no_argument, NULL, 's'},
{"type", required_argument, NULL, 't'},
Expand Down Expand Up @@ -1520,7 +1539,7 @@ int main(int argc, char **argv)
while (1) {
char *endptr;
int option_index;
int c = getopt_long(argc, argv, "a:b:c:d:e:f?h:il:mnor:st:vVy",
int c = getopt_long(argc, argv, "a:b:c:d:e:f?h:il:mnop:r:st:vVy",
options, &option_index);
if (c == -1) break;
switch (c) {
Expand Down Expand Up @@ -1587,6 +1606,10 @@ int main(int argc, char **argv)
status_check = false;
break;

case 'p':
a.profile = optarg;
break;

case 'v':
g_loglevel++;
break;
Expand Down Expand Up @@ -1892,7 +1915,7 @@ int main(int argc, char **argv)

if (!csr) {
msg(1, "generating certificate request");
csr = csr_gen(names, status_req, key);
csr = csr_gen(names, status_req, a.profile != NULL, key);
if (!csr) {
warnx("failed to generate certificate request");
goto out;
Expand Down

0 comments on commit 4cd4f10

Please sign in to comment.