commit uacme for openSUSE:Factory
Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package uacme for openSUSE:Factory checked in at 2025-01-05 15:28:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/uacme (Old) and /work/SRC/openSUSE:Factory/.uacme.new.1881 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "uacme" Sun Jan 5 15:28:26 2025 rev:6 rq:1234576 version:1.7.6 Changes: -------- --- /work/SRC/openSUSE:Factory/uacme/uacme.changes 2024-02-21 17:59:26.637872011 +0100 +++ /work/SRC/openSUSE:Factory/.uacme.new.1881/uacme.changes 2025-01-05 15:28:30.794992652 +0100 @@ -1,0 +2,13 @@ +Mon Dec 30 10:21:59 UTC 2024 - Martin Hauke <mardnh@gmx.de> + +- Update to version 1.7.6 + * Fix OpenSSL 3.x deprecated APIs. + * Fix cross compilation. + * uacme: Add environment variables. + * uacme: Add support for ACME Renewal Information (ARI). + * uacme: Try obtaining new Reply-Nonce if server doesn't supply + one. + * uacme: Add hook environment variables. + * uacme: Allow matching alternative chain by Authority Key Id. + +------------------------------------------------------------------- Old: ---- uacme-1.7.5.tar.gz New: ---- uacme-1.7.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ uacme.spec ++++++ --- /var/tmp/diff_new_pack.5OSwVi/_old 2025-01-05 15:28:31.427018648 +0100 +++ /var/tmp/diff_new_pack.5OSwVi/_new 2025-01-05 15:28:31.427018648 +0100 @@ -18,7 +18,7 @@ Name: uacme -Version: 1.7.5 +Version: 1.7.6 Release: 0 Summary: A minimal ACMEv2 client License: GPL-3.0-or-later ++++++ uacme-1.7.5.tar.gz -> uacme-1.7.6.tar.gz ++++++ ++++ 1869 lines of diff (skipped) ++++ retrying with extended exclude list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/ChangeLog new/uacme-1.7.6/ChangeLog --- old/uacme-1.7.5/ChangeLog 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/ChangeLog 2024-12-29 17:27:26.000000000 +0100 @@ -1,3 +1,21 @@ +2024-12-29 Nicola Di Lieto <nicola.dilieto@gmail.com> + * Release 1.7.6 + - Fix OpenSSL 3.x deprecated APIs + - Fix cross compilation + Closes https://github.com/ndilieto/uacme/issues/79 + - uacme: Add environment variables + Closes https://github.com/ndilieto/uacme/issues/63 + - uacme: Add support for ACME Renewal Information (ARI) + Closes https://github.com/ndilieto/uacme/issues/67 + - uacme: Try obtaining new Reply-Nonce if server doesn't supply one + Closes https://github.com/ndilieto/uacme/issues/82 + - uacme: Add hook environment variables + Closes https://github.com/ndilieto/uacme/issues/83 + - uacme: Allow matching alternative chain by Authority Key Id + Closes https://github.com/ndilieto/uacme/issues/85 + - Documentation update + - Add link to linode api hook + 2024-01-28 Nicola Di Lieto <nicola.dilieto@gmail.com> * Release 1.7.5 - fix ualpn exit code in client mode diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/NEWS new/uacme-1.7.6/NEWS --- old/uacme-1.7.5/NEWS 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/NEWS 2024-12-29 17:27:26.000000000 +0100 @@ -1,6 +1,24 @@ uacme NEWS Copyright (C) 2019-2024 Nicola Di Lieto <nicola.dilieto@gmail.com> +## [1.7.6] - 2024-12-29 +### Changed +- Fix OpenSSL 3.x deprecated APIs +- Fix cross compilation + Closes https://github.com/ndilieto/uacme/issues/79 +- uacme: Add environment variables + Closes https://github.com/ndilieto/uacme/issues/63 +- uacme: Add support for ACME Renewal Information (ARI) + Closes https://github.com/ndilieto/uacme/issues/67 +- uacme: Try obtaining new Reply-Nonce if server doesn't supply one + Closes https://github.com/ndilieto/uacme/issues/82 +- uacme: Add hook environment variables + Closes https://github.com/ndilieto/uacme/issues/83 +- uacme: Allow matching alternative chain by Authority Key Id + Closes https://github.com/ndilieto/uacme/issues/85 +- Documentation update +- Add link to linode api hook + ## [1.7.5] - 2024-01-28 ### Changed - fix ualpn exit code in client mode diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/README.md new/uacme-1.7.6/README.md --- old/uacme-1.7.5/README.md 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/README.md 2024-12-29 17:27:26.000000000 +0100 @@ -167,6 +167,9 @@ https://sr.ht/~jacksonchen666/uacme-desec-hook/ works with [deSEC.io][desec]. +https://gist.github.com/acamari/93db6e6d26c3d6f223840283f195d8be shows how to +integrate with [Linode API][Linode] + ## tls-alpn-01 challenge support [ualpn][ualpn] is a lightweight proxying [tls-alpn-01][RFC8737] challenge @@ -267,3 +270,4 @@ [splice]: https://en.wikipedia.org/wiki/Splice_%28system_call%29 [proxy]: http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt [desec]: https://desec.readthedocs.io/en/latest/ +[Linode]: https://techdocs.akamai.com/linode-api/reference/post-domain-record diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/configure.ac new/uacme-1.7.6/configure.ac --- old/uacme-1.7.5/configure.ac 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/configure.ac 2024-12-29 17:27:26.000000000 +0100 @@ -355,7 +355,8 @@ AC_DEFINE(HAVE_MAP_DEVZERO, 1, [if mmap("/dev/zero", MAP_SHARED) works]) AC_MSG_RESULT([yes]), AC_MSG_RESULT([no]) - AC_MSG_ERROR([ualpn requires MAP_ANON or mmap("/dev/zero", MAP_SHARED)])), + AC_MSG_ERROR([ualpn requires MAP_ANON or mmap("/dev/zero", MAP_SHARED)])) + ], [ AC_COMPILE_IFELSE([AC_LANG_SOURCE([#include <sys/mman.h> int main() {return mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0) == MAP_FAILED;}])], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/crypto.c new/uacme-1.7.6/crypto.c --- old/uacme-1.7.5/crypto.c 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/crypto.c 2024-12-29 17:27:26.000000000 +0100 @@ -38,6 +38,7 @@ #include "base64.h" #include "crypto.h" #include "curlwrap.h" +#include "json.h" #include "msg.h" #if !defined(USE_OPENSSL) #include "read-file.h" @@ -57,6 +58,9 @@ #include <openssl/bio.h> #include <openssl/bn.h> #include <openssl/crypto.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/core_names.h> +#endif #include <openssl/engine.h> #include <openssl/err.h> #include <openssl/evp.h> @@ -173,29 +177,11 @@ static bool openssl_hmac_fast(const EVP_MD *type, const void *key, size_t keylen, const void *input, size_t len, unsigned char *output) { - bool success = false; - HMAC_CTX *hmac = HMAC_CTX_new(); - if (!hmac) { - openssl_error("openssl_hmac_fast"); - goto out; - } - if (!HMAC_Init_ex(hmac, key, keylen, type, NULL)) { - openssl_error("openssl_hmac_fast"); - goto out; - } - if (!HMAC_Update(hmac, input, len)) { - openssl_error("openssl_hmac_fast"); - goto out; - } - if (!HMAC_Final(hmac, output, NULL)) { + if (HMAC(type, key, keylen, input, len, output, NULL) == NULL) { openssl_error("openssl_hmac_fast"); - goto out; + return false; } - success = true; -out: - if (hmac) - HMAC_CTX_free(hmac); - return success; + return true; } #elif defined(USE_MBEDTLS) #if MBEDTLS_VERSION_NUMBER < 0x02100000 @@ -269,6 +255,31 @@ } #endif +#if !HAVE_STRCASESTR +char *strcasestr(const char *haystack, const char *needle) +{ + char *ret = NULL; + char *_haystack = strdup(haystack); + char *_needle = strdup(needle); + + if (!_haystack || !_needle) + warn("strcasestr: strdup failed"); + else { + char *p; + for (p = _haystack; *p; p++) + *p = tolower(*p); + for (p = _needle; *p; p++) + *p = tolower(*p); + ret = strstr(_haystack, _needle); + if (ret) + ret = (char *)haystack + (ret - _haystack); + } + free(_haystack); + free(_needle); + return ret; +} +#endif + char *sha2_base64url(size_t bits, const char *format, ...) { char *input = NULL; @@ -556,6 +567,18 @@ } #elif defined(USE_OPENSSL) unsigned char *data = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bm = NULL; + BIGNUM *be = NULL; + if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_RSA_N, &bm)) { + openssl_error("rsa_params"); + goto out; + } + if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_RSA_E, &be)) { + openssl_error("rsa_params"); + goto out; + } +#else const BIGNUM *bm = NULL; const BIGNUM *be = NULL; RSA *rsa = EVP_PKEY_get0_RSA(key); @@ -564,6 +587,7 @@ goto out; } RSA_get0_key(rsa, &bm, &be, NULL); +#endif r = BN_num_bytes(bm); data = calloc(1, r); if (!data) { @@ -654,6 +678,12 @@ free(exp.data); #elif defined(USE_OPENSSL) free(data); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (bm) + BN_free(bm); + if (be) + BN_free(be); +#endif #elif defined(USE_MBEDTLS) free(data); mbedtls_mpi_free(&mn); @@ -728,6 +758,31 @@ openssl_error("ec_params"); goto out; } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + char group[0x20]; + if (!EVP_PKEY_get_utf8_string_param(key, OSSL_PKEY_PARAM_GROUP_NAME, + group, sizeof(group), NULL)) { + openssl_error("ec_params"); + goto out; + } + if (strcasecmp(group, "prime256v1") == 0) + bits = 256; + else if (strcasecmp(group, "secp384r1") == 0) + bits = 384; + else { + warnx("ec_params: only \"prime256v1\" and \"secp384r1\" " + "Elliptic Curves supported"); + goto out; + } + if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_X, &bx)) { + openssl_error("ec_params"); + goto out; + } + if (!EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_Y, &by)) { + openssl_error("ec_params"); + goto out; + } +#else EC_KEY *ec = EVP_PKEY_get0_EC_KEY(key); if (!ec) { openssl_error("ec_params"); @@ -760,6 +815,7 @@ openssl_error("ec_params"); goto out; } +#endif r = BN_num_bytes(bx); data = calloc(1, r); if (!data) { @@ -3382,6 +3438,78 @@ return ret; } +#if defined(USE_MBEDTLS) +static int mbedtls_crt_get_authority_key_id(mbedtls_x509_crt *crt, + unsigned char **akid, size_t *size) +{ + unsigned char *p = crt->v3_ext.p; + unsigned char *end = p + crt->v3_ext.len; + size_t len; + int r; + + if (!p || p == end) + return MBEDTLS_ERR_X509_INVALID_EXTENSIONS; + r = mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (r) + return r; + if (p + len != end) + return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH; + + while (p < end) { + unsigned char *end_ext; + + r = mbedtls_asn1_get_tag(&p, end, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (r) + return r; + + end_ext = p + len; + r = mbedtls_asn1_get_tag(&p, end_ext, &len, MBEDTLS_ASN1_OID); + if (r) + return r; + + if (len != MBEDTLS_OID_SIZE(MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER) || + memcmp(p, MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER, len)) { + p = end_ext; + continue; + } + p += len; + + r = mbedtls_asn1_get_tag(&p, end_ext, &len, MBEDTLS_ASN1_BOOLEAN); + if (r == 0) { + if (len != 1) + return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH; + p++; + } else if (r != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) + return r; + + r = mbedtls_asn1_get_tag(&p, end_ext, &len, + MBEDTLS_ASN1_OCTET_STRING); + if (r) + return r; + if (end_ext != p + len) + return MBEDTLS_ERR_ASN1_LENGTH_MISMATCH; + + r = mbedtls_asn1_get_tag(&p, p + len, &len, + MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE); + if (r) + return r; + + r = mbedtls_asn1_get_tag(&p, p + len, &len, + MBEDTLS_ASN1_CONTEXT_SPECIFIC); + if (r) + return r; + + *akid = p; + *size = len; + return 0; + } + + return MBEDTLS_ERR_X509_UNKNOWN_OID; +} +#endif + bool cert_match(const char *cert, unsigned char *fingerprint, size_t fingerprint_len) { @@ -3400,12 +3528,26 @@ return ret; } for (unsigned int i = 0; i < crt_size; i++) { - unsigned char fp[32]; - size_t s = sizeof(fp); - r = gnutls_x509_crt_get_fingerprint(crt[i], GNUTLS_DIG_SHA256, fp, &s); - if (r == 0 && fingerprint_len <= s && - memcmp(fp, fingerprint, fingerprint_len) == 0) - ret = true; + unsigned char fp[128]; + if (!ret) { + size_t s = sizeof(fp); + r = gnutls_x509_crt_get_fingerprint(crt[i], GNUTLS_DIG_SHA256, + fp, &s); + if (r == 0 && fingerprint_len <= s && + memcmp(fp, fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by fingerprint"); + ret = true; + } + } + if (!ret) { + size_t s = sizeof(fp); + r = gnutls_x509_crt_get_authority_key_id(crt[i], fp, &s, NULL); + if (r == 0 && fingerprint_len <= s && + memcmp(fp, fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by Authority Key Id"); + ret = true; + } + } gnutls_x509_crt_deinit(crt[i]); } gnutls_free(crt); @@ -3423,8 +3565,23 @@ break; if (X509_digest(crt, EVP_sha256(), fp, &s) && fingerprint_len <= s && - memcmp(fp, fingerprint, fingerprint_len) == 0) + memcmp(fp, fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by fingerprint"); ret = true; + } else { + AUTHORITY_KEYID *akid = X509_get_ext_d2i(crt, + NID_authority_key_identifier, NULL, NULL); + if (akid != NULL) { + if (akid->keyid != NULL && fingerprint_len <= + (size_t)ASN1_STRING_length(akid->keyid) && + memcmp(ASN1_STRING_get0_data(akid->keyid), + fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by Authority Key Id"); + ret = true; + } + AUTHORITY_KEYID_free(akid); + } + } X509_free(crt); } BIO_free(bio); @@ -3445,15 +3602,115 @@ } for (mbedtls_x509_crt *c = &crt; c; c = c->next) { unsigned char fp[32]; + unsigned char *akid; + size_t akid_len; r = mbedtls_hash_fast(MBEDTLS_MD_SHA256, c->raw.p, c->raw.len, fp); if (r == 0 && fingerprint_len <= sizeof(fp) && - memcmp(fp, fingerprint, fingerprint_len) == 0) + memcmp(fp, fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by fingerprint"); ret = true; + break; + } + r = mbedtls_crt_get_authority_key_id(c, &akid, &akid_len); + if (r == 0 && fingerprint_len <= akid_len && + memcmp(akid, fingerprint, fingerprint_len) == 0) { + msg(1, "certificate matched by Authority Key Id"); + ret = true; + break; + } } mbedtls_x509_crt_free(&crt); #endif return ret; } + +#if defined(USE_GNUTLS) +static char *crt_ari_url(gnutls_x509_crt_t crt, const char *prefix) +#elif defined(USE_OPENSSL) +static char *crt_ari_url(X509 *crt, const char *prefix) +#elif defined(USE_MBEDTLS) +static char *crt_ari_url(mbedtls_x509_crt *crt, const char *prefix) +#endif +{ + char *url = NULL; + unsigned char akid[128]; + char akid_b64[base64_ENCODED_LEN(sizeof(akid), + base64_VARIANT_URLSAFE_NO_PADDING)]; + unsigned char serial[128]; + char serial_b64[base64_ENCODED_LEN(sizeof(akid), + base64_VARIANT_URLSAFE_NO_PADDING)]; + size_t alen = sizeof(akid); + size_t slen = sizeof(serial); + +#if defined(USE_GNUTLS) + int r = gnutls_x509_crt_get_authority_key_id(crt, akid, &alen, NULL); + if (r) + return NULL; + r = gnutls_x509_crt_get_serial(crt, serial, &slen); + if (r) + return NULL; +#elif defined(USE_OPENSSL) + AUTHORITY_KEYID *ak = X509_get_ext_d2i(crt, + NID_authority_key_identifier, NULL, NULL); + if (ak != NULL) { + if (ak->keyid == NULL || + alen < (size_t)ASN1_STRING_length(ak->keyid)) + alen = 0; + else { + alen = (size_t)ASN1_STRING_length(ak->keyid); + memcpy(akid, ASN1_STRING_get0_data(ak->keyid), alen); + } + AUTHORITY_KEYID_free(ak); + if (alen == 0) + return NULL; + } else + return NULL; + const ASN1_INTEGER *sn = X509_get0_serialNumber(crt); + if (!sn) + return NULL; + BIGNUM *bn = ASN1_INTEGER_to_BN(sn, NULL); + if (!bn) { + openssl_error("cert_ari_url"); + return NULL; + } + if (slen < (size_t)BN_num_bytes(bn)) { + BN_free(bn); + return NULL; + } else { + slen = (size_t)BN_bn2bin(bn, serial); + BN_free(bn); + } +#elif defined(USE_MBEDTLS) + unsigned char *ak; + size_t len; + int r = mbedtls_crt_get_authority_key_id(crt, &ak, &len); + if (r) + return NULL; + if (alen < len) + return NULL; + alen = len; + memcpy(akid, ak, alen); + if (!crt->serial.p || slen < crt->serial.len) + return NULL; + slen = crt->serial.len; + memcpy(serial, crt->serial.p, slen); +#endif + if (!bin2base64(akid_b64, sizeof(akid_b64), akid, alen, + base64_VARIANT_URLSAFE_NO_PADDING) || + !bin2base64(serial_b64, sizeof(serial_b64), serial, slen, + base64_VARIANT_URLSAFE_NO_PADDING)) { + warnx("crt_ari_url: bin2base64 failed"); + return NULL; + } + + if (asprintf(&url, "%s/%s.%s", prefix, akid_b64, serial_b64) < 0) { + warnx("crt_ari_url: asprintf failed"); + url = NULL; + } + + return url; +} + #if defined(USE_GNUTLS) static bool ocsp_check(gnutls_x509_crt_t *crt) { @@ -4298,8 +4555,99 @@ } #endif -bool cert_valid(const char *certfile, char * const *names, int validity, - bool status_check) +#if defined(USE_GNUTLS) +int ari_check(gnutls_x509_crt_t crt, const char *ari_url) +#elif defined(USE_OPENSSL) +int ari_check(X509 *crt, const char *ari_url) +#elif defined(USE_MBEDTLS) +int ari_check(mbedtls_x509_crt *crt, const char *ari_url) +#endif +{ + int ret = -1; + json_value_t *json = NULL; + + if (!ari_url) + goto out; + + char *url = crt_ari_url(crt, ari_url); + if (!url) + goto out; + + msg(1, "checking certificate renewal info at %s", url); + curldata_t *c = curl_get(url); + free(url); + if (!c) { + warnx("ari_check: curl_get failed"); + goto out; + } + + if (c->headers) + msg(3, "ari_check: HTTP headers\n%s", c->headers); + if (c->body) + msg(3, "ari_check: HTTP body\n%s", c->body); + + char *p = find_header(c->headers, "Content-Type"); + if (p && strcasestr(p, "json")) + json = json_parse(c->body, c->body_len); + free(p); + curldata_free(c); + + if (!json) { + warnx("ari_check: failed to parse"); + goto out; + } + + const json_value_t *window = json_find(json, "suggestedWindow"); + if (!window) { + warnx("ari_check: missing suggestedWindow"); + goto out; + } + + const char *start = json_find_string(window, "start"); + const char *end = json_find_string(window, "end"); + if (!start || !end) { + warnx("ari_check: missing start or end"); + goto out; + } + msg(1, "certificate renewal window: start=%s end=%s", start, end); + struct tm start_tm, end_tm; + p = strptime(start, "%Y-%m-%dT%T%z", &start_tm); + if (!p || *p) { + warnx("ari_check: failed to parse start"); + goto out; + } + p = strptime(end, "%Y-%m-%dT%T%z", &end_tm); + if (!p || *p) { + warnx("ari_check: failed to parse end"); + goto out; + } + time_t start_t = mktime(&start_tm); + if (start_t == (time_t)-1) { + warnx("ari_check: invalid start"); + goto out; + } + time_t end_t = mktime(&end_tm); + if (end_t == (time_t)-1) { + warnx("ari_check: invalid end"); + goto out; + } + if (start_t >= end_t) { + warnx("ari_check: invalid start/end"); + goto out; + } + + if (time(NULL) > start_t + (end_t - start_t) * ((float)rand()/RAND_MAX)) + ret = 1; + else + ret = 0; + +out: + json_free(json); + return ret; +} + +bool cert_valid(const char *certfile, char * const *names, const char *ari_url, + int validity, bool status_check) { bool valid = false; #if defined(USE_GNUTLS) @@ -4316,7 +4664,10 @@ int days_left = (expiration - time(NULL))/(24*3600); msg(1, "%s expires in %d days", certfile, days_left); - if (days_left < validity) { + int ari = -1; + if (days_left > 0) + ari = ari_check(crt[0], ari_url); + if (ari > 0 || (ari < 0 && days_left < validity)) { msg(1, "%s is due for renewal", certfile); goto out; } @@ -4370,7 +4721,10 @@ goto out; } msg(1, "%s expires in %d days", certfile, days_left); - if (days_left < validity) { + int ari = -1; + if (days_left > 0) + ari = ari_check(crt[0], ari_url); + if (ari > 0 || (ari < 0 && days_left < validity)) { msg(1, "%s is due for renewal", certfile); goto out; } @@ -4496,7 +4850,10 @@ int days_left = (expiration - time(NULL))/(24*3600); msg(1, "%s expires in %d days", certfile, days_left); - if (days_left < validity) { + int ari = -1; + if (days_left > 0) + ari = ari_check(crt, ari_url); + if (ari > 0 || (ari < 0 && days_left < validity)) { msg(1, "%s is due for renewal", certfile); goto out; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/crypto.h new/uacme-1.7.6/crypto.h --- old/uacme-1.7.5/crypto.h 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/crypto.h 2024-12-29 17:27:26.000000000 +0100 @@ -82,8 +82,12 @@ char *csr_gen(char * const *, bool, privkey_t); char *csr_load(const char *, char ***); char *cert_der_base64url(const char *); -bool cert_valid(const char *, char * const *, int, bool); +bool cert_valid(const char *, char * const *, const char *, int, bool); bool cert_match(const char *, unsigned char *, size_t); +#if !HAVE_STRCASESTR +char *strcasestr(const char *haystack, const char *needle); +#endif + #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/curlwrap.c new/uacme-1.7.6/curlwrap.c --- old/uacme-1.7.5/curlwrap.c 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/curlwrap.c 2024-12-29 17:27:26.000000000 +0100 @@ -20,12 +20,14 @@ #include "config.h" #include <err.h> +#include <regex.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "curlwrap.h" +#include "msg.h" curldata_t *curldata_calloc(void) { @@ -89,6 +91,30 @@ return size * n; } +static void curl_env(CURL *curl) +{ + const char *cainfo = getenv("UACME_CAINFO"); + const char *capath = getenv("UACME_CAPATH"); + const char *dnssrv = getenv("UACME_DNS_SERVERS"); + const char *iface = getenv("UACME_INTERFACE"); + const char *proxy = getenv("UACME_PROXY"); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, + "uacme/" VERSION " (https://github.com/ndilieto/uacme)"); + if (g_loglevel > 3) + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (cainfo) + curl_easy_setopt(curl, CURLOPT_CAINFO, cainfo); + if (capath) + curl_easy_setopt(curl, CURLOPT_CAPATH, capath); + if (dnssrv) + curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, dnssrv); + if (iface) + curl_easy_setopt(curl, CURLOPT_INTERFACE, iface); + if (proxy) + curl_easy_setopt(curl, CURLOPT_PROXY, proxy); +} + curldata_t *curl_get(const char *url) { curldata_t *c = NULL; @@ -106,13 +132,12 @@ curl_easy_cleanup(curl); return NULL; } + curl_env(curl); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_wcb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, c); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_hcb); curl_easy_setopt(curl, CURLOPT_HEADERDATA, c); - curl_easy_setopt(curl, CURLOPT_USERAGENT, - "uacme/" VERSION " (https://github.com/ndilieto/uacme)"); res = curl_easy_perform(curl); if (res != CURLE_OK) { warnx("curl_get: GET %s failed: %s", url, @@ -155,13 +180,12 @@ curl_easy_cleanup(curl); return NULL; } + curl_env(curl); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_wcb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, c); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_hcb); curl_easy_setopt(curl, CURLOPT_HEADERDATA, c); - curl_easy_setopt(curl, CURLOPT_USERAGENT, - "uacme/" VERSION " (https://github.com/ndilieto/uacme)"); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, post_size); va_start(ap, header); @@ -193,3 +217,27 @@ } return c; } + +char *find_header(const char *headers, const char *name) +{ + char *regex = NULL; + if (asprintf(®ex, "^%s:[ \t]*(.*)\r\n", name) < 0) { + warnx("find_header: asprintf failed"); + return NULL; + } + char *ret = NULL; + regex_t reg; + if (regcomp(®, regex, REG_EXTENDED | REG_ICASE | REG_NEWLINE)) { + warnx("find_header: regcomp failed"); + } else { + regmatch_t m[2]; + if (regexec(®, headers, 2, m, 0) == 0) { + ret = strndup(headers + m[1].rm_so, m[1].rm_eo - m[1].rm_so); + if (!ret) + warn("find_header: strndup failed"); + } + } + free(regex); + regfree(®); + return ret; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/curlwrap.h new/uacme-1.7.6/curlwrap.h --- old/uacme-1.7.5/curlwrap.h 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/curlwrap.h 2024-12-29 17:27:26.000000000 +0100 @@ -35,5 +35,6 @@ curldata_t *curl_get(const char *url); curldata_t *curl_post(const char *url, void *post_data, size_t post_size, const char *header, ...); +char *find_header(const char *headers, const char *name); #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/docs/uacme.html new/uacme-1.7.6/docs/uacme.html --- old/uacme-1.7.5/docs/uacme.html 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/docs/uacme.html 2024-12-29 17:27:26.000000000 +0100 @@ -748,10 +748,11 @@ <div class="sectionbody"> <div class="paragraph"><p><strong>uacme</strong> [<strong>-a</strong>|<strong>--acme-url</strong> <em>URL</em>] [<strong>-b</strong>|<strong>--bits</strong> <em>BITS</em>] [<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>-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> …] [<strong>-V</strong>|<strong>--version</strong>] [<strong>-y</strong>|<strong>--yes</strong>] [<strong>-?</strong>|<strong>--help</strong>] + [<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> …] + [<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> …]] | <strong>issue</strong> <em>CSRFILE</em> | <strong>revoke</strong> <em>CERTFILE</em> [<em>CERTKEYFILE</em>]</p></div> @@ -854,7 +855,9 @@ <dd> <p> Do not reissue certificates that are still valid for longer - than <em>DAYS</em> (default 30). See also <strong>-o, --no-ocsp</strong>. + than <em>DAYS</em> (default 30). This only applies as a fallback + if no server renewal information is available. See also + <strong>-i, --no-ari</strong> and <strong>-o, --no-ocsp</strong>. </p> </dd> <dt class="hdlist1"> @@ -874,7 +877,8 @@ </dt> <dd> <p> - Force certificate reissuance regardless of expiration date. + Force certificate reissuance regardless of expiration date and + renewal information from the server. </p> </dd> <dt class="hdlist1"> @@ -964,6 +968,16 @@ </dl></div> </dd> <dt class="hdlist1"> +<strong>-i, --no-ari</strong> +</dt> +<dd> +<p> + Do not query or use the server’s certificate renewal information window + to decide whether to reissue an existing certificate. See also + <strong>-d, --days</strong> and <strong>-o, --no-ocsp</strong>. +</p> +</dd> +<dt class="hdlist1"> <strong>-l, --alternate</strong> <em>N</em> | <em>FP</em> </dt> <dd> @@ -975,9 +989,11 @@ in one of two ways. A positive integer <em>N</em> makes <strong>uacme</strong> select the Nth alternative chain in the order presented by the server. A colon (<em>:</em>) separated list of two or more 2-digit hexadecimal numbers - <em>FP</em> makes <strong>uacme</strong> select the first alternative chain containing a - certificate whose SHA256 fingerprint begins with <em>FP</em>. - In both cases <strong>uacme</strong> falls back to the main certificate URL if it cannot + <em>FP</em> makes <strong>uacme</strong> select the first alternative chain containing either a + certificate whose SHA256 fingerprint begins with <em>FP</em>, or a certificate + in which the Authority Key Identifier extension contains a keyIdentifier + field beginning with <em>FP</em>. + In all cases <strong>uacme</strong> falls back to the main certificate URL if it cannot match an alternative chain or the download thereof fails. </p> </dd> @@ -1202,6 +1218,126 @@ </div> </div> <div class="sect1"> +<h2 id="_environment">ENVIRONMENT</h2> +<div class="sectionbody"> +<div class="dlist"><dl> +<dt class="hdlist1"> +<strong>UACME_CAINFO</strong> +</dt> +<dd> +<p> + String naming a file holding one or more CA certificates to verify + the ACME server with. +</p> +</dd> +<dt class="hdlist1"> +<strong>UACME_CAPATH</strong> +</dt> +<dd> +<p> + String naming a directory holding multiple CA certificates to verify + the ACME server with. If libcurl is built against OpenSSL, the certificate + directory must be prepared using the OpenSSL c_rehash utility. +</p> +</dd> +<dt class="hdlist1"> +<strong>UACME_DNS_SERVERS</strong> +</dt> +<dd> +<p> + Comma separated list of DNS servers to be used instead of the system + default. The format of the dns servers option is + <em>host[:port][,host[:port]]…</em> +</p> +</dd> +<dt class="hdlist1"> +<strong>UACME_INTERFACE</strong> +</dt> +<dd> +<p> + String setting the interface name to use as outgoing network interface. + The name can be an interface name, an IP address, or a hostname. + If you prefer one of these, you can use the following special prefixes: +</p> +<div class="dlist"><dl> +<dt class="hdlist1"> +<em>if!<name></em> +</dt> +<dd> +<p> +Interface name +</p> +</dd> +<dt class="hdlist1"> +<em>host!<name></em> +</dt> +<dd> +<p> +IP address or hostname +</p> +</dd> +<dt class="hdlist1"> +<em>ifhost!<interface>!<host></em> +</dt> +<dd> +<p> +Interface name and IP address or hostname +</p> +</dd> +</dl></div> +</dd> +<dt class="hdlist1"> +<strong>UACME_PROXY</strong> +</dt> +<dd> +<p> + String holding the proxy hostname or dotted numerical IP address. + A numerical IPv6 address must be written within [brackets]. + To specify port number in this string, append :[port] to the end of + the host name. If not specified, default to using port 1080. + The proxy string may be prefixed with [scheme]:// to specify which + kind of proxy is used (http://, https://, socks4://, socks4a://, + socks5://, socks5h://). + The proxy can also be specified with its associated credentials like + for ordinary URLs in the style: <em>scheme://username:password@hostname</em> +</p> +</dd> +</dl></div> +</div> +</div> +<div class="sect1"> +<h2 id="_hook_environment">HOOK ENVIRONMENT</h2> +<div class="sectionbody"> +<div class="paragraph"><p>The following environment variables are exported for use by the hook program:</p></div> +<div class="dlist"><dl> +<dt class="hdlist1"> +<strong>UACME_CONFDIR</strong> +</dt> +<dd> +<p> + Path to <em>CONFDIR</em>, see <strong>-c, --confdir</strong> +</p> +</dd> +<dt class="hdlist1"> +<strong>UACME_VERBOSE</strong> +</dt> +<dd> +<p> + Verbosity, see <strong>-v, --verbose</strong> +</p> +</dd> +<dt class="hdlist1"> +<strong>UACME_METHOD</strong>, <strong>UACME_TYPE</strong>, <strong>UACME_IDENT</strong>, <strong>UACME_TOKEN</strong>, <strong>UACME_AUTH</strong> +</dt> +<dd> +<p> + Copies of the hook program arguments, see <strong>-h, --hook</strong> +</p> +</dd> +</dl></div> +</div> +</div> +<div class="sect1"> <h2 id="_exit_status">EXIT STATUS</h2> <div class="sectionbody"> <div class="dlist"><dl> @@ -1336,9 +1472,9 @@ <div id="footnotes"><hr></div> <div id="footer"> <div id="footer-text"> -Version 1.7.5<br> +Version 1.7.6<br> Last updated - 2024-01-28 20:29:11 CET + 2024-12-29 17:25:16 CET </div> </div> </body> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/docs/ualpn.html new/uacme-1.7.6/docs/ualpn.html --- old/uacme-1.7.5/docs/ualpn.html 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/docs/ualpn.html 2024-12-29 17:27:26.000000000 +0100 @@ -1188,9 +1188,9 @@ <div id="footnotes"><hr></div> <div id="footer"> <div id="footer-text"> -Version 1.7.5<br> +Version 1.7.6<br> Last updated - 2024-01-20 20:29:11 CET + 2024-12-29 17:25:21 CET </div> </div> </body> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/uacme.1 new/uacme-1.7.6/uacme.1 --- old/uacme-1.7.5/uacme.1 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/uacme.1 2024-12-29 17:27:26.000000000 +0100 @@ -2,12 +2,12 @@ .\" Title: uacme .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/> -.\" Date: 01/28/2024 +.\" Date: 12/29/2024 .\" Manual: User Commands -.\" Source: uacme 1.7.5 +.\" Source: uacme 1.7.6 .\" Language: English .\" -.TH "UACME" "1" "01/28/2024" "uacme 1\&.7\&.5" "User Commands" +.TH "UACME" "1" "12/29/2024" "uacme 1\&.7\&.6" "User Commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -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\-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\-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 @@ -95,7 +95,9 @@ .RS 4 Do not reissue certificates that are still valid for longer than \fIDAYS\fR -(default 30)\&. See also +(default 30)\&. This only applies as a fallback if no server renewal information is available\&. See also +\fB\-i, \-\-no\-ari\fR +and \fB\-o, \-\-no\-ocsp\fR\&. .RE .PP @@ -111,7 +113,7 @@ .PP \fB\-f, \-\-force\fR .RS 4 -Force certificate reissuance regardless of expiration date\&. +Force certificate reissuance regardless of expiration date and renewal information from the server\&. .RE .PP \fB\-h, \-\-hook\fR \fIPROGRAM\fR @@ -182,6 +184,14 @@ .RE .RE .PP +\fB\-i, \-\-no\-ari\fR +.RS 4 +Do not query or use the server\(cqs certificate renewal information window to decide whether to reissue an existing certificate\&. See also +\fB\-d, \-\-days\fR +and +\fB\-o, \-\-no\-ocsp\fR\&. +.RE +.PP \fB\-l, \-\-alternate\fR \fIN\fR | \fIFP\fR .RS 4 According to @@ -194,8 +204,9 @@ \fIFP\fR makes \fBuacme\fR -select the first alternative chain containing a certificate whose SHA256 fingerprint begins with -\fIFP\fR\&. In both cases +select the first alternative chain containing either a certificate whose SHA256 fingerprint begins with +\fIFP\fR, or a certificate in which the Authority Key Identifier extension contains a keyIdentifier field beginning with +\fIFP\fR\&. In all cases \fBuacme\fR falls back to the main certificate URL if it cannot match an alternative chain or the download thereof fails\&. .RE @@ -396,6 +407,71 @@ \fIrevoked\-TIMESTAMP\&.pem\fR\&. The reason code in the revocation request defaults to 0 but it can be specified by the user with \fB\-r, \-\-reason\fR\&. .RE +.SH "ENVIRONMENT" +.PP +\fBUACME_CAINFO\fR +.RS 4 +String naming a file holding one or more CA certificates to verify the ACME server with\&. +.RE +.PP +\fBUACME_CAPATH\fR +.RS 4 +String naming a directory holding multiple CA certificates to verify the ACME server with\&. If libcurl is built against OpenSSL, the certificate directory must be prepared using the OpenSSL c_rehash utility\&. +.RE +.PP +\fBUACME_DNS_SERVERS\fR +.RS 4 +Comma separated list of DNS servers to be used instead of the system default\&. The format of the dns servers option is +\fIhost[:port][,host[:port]]\&...\fR +.RE +.PP +\fBUACME_INTERFACE\fR +.RS 4 +String setting the interface name to use as outgoing network interface\&. The name can be an interface name, an IP address, or a hostname\&. If you prefer one of these, you can use the following special prefixes: +.PP +\fIif!<name>\fR +.RS 4 +Interface name +.RE +.PP +\fIhost!<name>\fR +.RS 4 +IP address or hostname +.RE +.PP +\fIifhost!<interface>!<host>\fR +.RS 4 +Interface name and IP address or hostname +.RE +.RE +.PP +\fBUACME_PROXY\fR +.RS 4 +String holding the proxy hostname or dotted numerical IP address\&. A numerical IPv6 address must be written within [brackets]\&. To specify port number in this string, append :[port] to the end of the host name\&. If not specified, default to using port 1080\&. The proxy string may be prefixed with [scheme]:// to specify which kind of proxy is used (http://, https://, socks4://, socks4a://, socks5://, socks5h://)\&. The proxy can also be specified with its associated credentials like for ordinary URLs in the style: +\fIscheme://username:password@hostname\fR +.RE +.SH "HOOK ENVIRONMENT" +.sp +The following environment variables are exported for use by the hook program: +.PP +\fBUACME_CONFDIR\fR +.RS 4 +Path to +\fICONFDIR\fR, see +\fB\-c, \-\-confdir\fR +.RE +.PP +\fBUACME_VERBOSE\fR +.RS 4 +Verbosity, see +\fB\-v, \-\-verbose\fR +.RE +.PP +\fBUACME_METHOD\fR, \fBUACME_TYPE\fR, \fBUACME_IDENT\fR, \fBUACME_TOKEN\fR, \fBUACME_AUTH\fR +.RS 4 +Copies of the hook program arguments, see +\fB\-h, \-\-hook\fR +.RE .SH "EXIT STATUS" .PP \fB0\fR diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/uacme.1.txt new/uacme-1.7.6/uacme.1.txt --- old/uacme-1.7.5/uacme.1.txt 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/uacme.1.txt 2024-12-29 17:27:26.000000000 +0100 @@ -15,10 +15,11 @@ -------- *uacme* [*-a*|*--acme-url* 'URL'] [*-b*|*--bits* 'BITS'] [*-c*|*--confdir* 'DIR'] [*-d*|*--days* 'DAYS'] [*-e*|*--eab* KEYID:KEY] - [*-f*|*--force*] [*-h*|*--hook* 'PROGRAM'] [*-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*] + [*-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*] *new* ['EMAIL'] | *update* ['EMAIL'] | *deactivate* | *newkey* | *issue* 'IDENTIFIER' ['ALTNAME' ...]] | *issue* 'CSRFILE' | *revoke* 'CERTFILE' ['CERTKEYFILE'] @@ -61,7 +62,9 @@ *-d, --days* 'DAYS':: Do not reissue certificates that are still valid for longer - than 'DAYS' (default 30). See also *-o, --no-ocsp*. + than 'DAYS' (default 30). This only applies as a fallback + if no server renewal information is available. See also + *-i, --no-ari* and *-o, --no-ocsp*. *-e, --eab* 'KEYID:KEY':: Specify RFC8555 External Account Binding credentials according @@ -71,7 +74,8 @@ 'KEYID' must be an ASCII string. 'KEY' must be base64url-encoded. *-f, --force*:: - Force certificate reissuance regardless of expiration date. + Force certificate reissuance regardless of expiration date and + renewal information from the server. *-h, --hook* 'PROGRAM':: Challenge hook program. If not specified *uacme* interacts with @@ -94,6 +98,11 @@ 'AUTH'::: The key authorization (for *dns-01* and *tls-alpn-01* already converted to the base64url-encoded SHA256 digest format) +*-i, --no-ari*:: + Do not query or use the server's certificate renewal information window + to decide whether to reissue an existing certificate. See also + *-d, --days* and *-o, --no-ocsp*. + *-l, --alternate* 'N' | 'FP':: According to <https://tools.ietf.org/html/rfc8555#section-7.4.2> the server MAY provide one or more additional certificate download URLs, @@ -102,9 +111,11 @@ in one of two ways. A positive integer 'N' makes *uacme* select the Nth alternative chain in the order presented by the server. A colon (':') separated list of two or more 2-digit hexadecimal numbers - 'FP' makes *uacme* select the first alternative chain containing a - certificate whose SHA256 fingerprint begins with 'FP'. - In both cases *uacme* falls back to the main certificate URL if it cannot + 'FP' makes *uacme* select the first alternative chain containing either a + certificate whose SHA256 fingerprint begins with 'FP', or a certificate + in which the Authority Key Identifier extension contains a keyIdentifier + field beginning with 'FP'. + In all cases *uacme* falls back to the main certificate URL if it cannot match an alternative chain or the download thereof fails. *-m, --must-staple*:: @@ -237,6 +248,57 @@ can be specified by the user with *-r, --reason*. +ENVIRONMENT +----------- +*UACME_CAINFO*:: + String naming a file holding one or more CA certificates to verify + the ACME server with. + +*UACME_CAPATH*:: + String naming a directory holding multiple CA certificates to verify + the ACME server with. If libcurl is built against OpenSSL, the certificate + directory must be prepared using the OpenSSL c_rehash utility. + +*UACME_DNS_SERVERS*:: + Comma separated list of DNS servers to be used instead of the system + default. The format of the dns servers option is + 'host[:port][,host[:port]]...' + +*UACME_INTERFACE*:: + String setting the interface name to use as outgoing network interface. + The name can be an interface name, an IP address, or a hostname. + If you prefer one of these, you can use the following special prefixes: + + 'if!<name>'::: Interface name + 'host!<name>'::: IP address or hostname + 'ifhost!<interface>!<host>'::: Interface name and IP address or hostname + +*UACME_PROXY*:: + String holding the proxy hostname or dotted numerical IP address. + A numerical IPv6 address must be written within [brackets]. + To specify port number in this string, append :[port] to the end of + the host name. If not specified, default to using port 1080. + The proxy string may be prefixed with [scheme]:// to specify which + kind of proxy is used (http://, https://, socks4://, socks4a://, + socks5://, socks5h://). + The proxy can also be specified with its associated credentials like + for ordinary URLs in the style: 'scheme://username:password@hostname' + + +HOOK ENVIRONMENT +---------------- +The following environment variables are exported for use by the hook program: + +*UACME_CONFDIR*:: + Path to 'CONFDIR', see *-c, --confdir* + +*UACME_VERBOSE*:: + Verbosity, see *-v, --verbose* + +*UACME_METHOD*, *UACME_TYPE*, *UACME_IDENT*, *UACME_TOKEN*, *UACME_AUTH*:: + Copies of the hook program arguments, see *-h, --hook* + + EXIT STATUS ----------- *0*:: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/uacme.c new/uacme-1.7.6/uacme.c --- old/uacme-1.7.5/uacme.c 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/uacme.c 2024-12-29 17:27:26.000000000 +0100 @@ -71,55 +71,6 @@ char *certprefix; } acme_t; -#if !HAVE_STRCASESTR -char *strcasestr(const char *haystack, const char *needle) -{ - char *ret = NULL; - char *_haystack = strdup(haystack); - char *_needle = strdup(needle); - - if (!_haystack || !_needle) - warn("strcasestr: strdup failed"); - else { - char *p; - for (p = _haystack; *p; p++) - *p = tolower(*p); - for (p = _needle; *p; p++) - *p = tolower(*p); - ret = strstr(_haystack, _needle); - if (ret) - ret = (char *)haystack + (ret - _haystack); - } - free(_haystack); - free(_needle); - return ret; -} -#endif - -char *find_header(const char *headers, const char *name) -{ - char *regex = NULL; - if (asprintf(®ex, "^%s:[ \t]*(.*)\r\n", name) < 0) { - warnx("find_header: asprintf failed"); - return NULL; - } - char *ret = NULL; - regex_t reg; - if (regcomp(®, regex, REG_EXTENDED | REG_ICASE | REG_NEWLINE)) { - warnx("find_header: regcomp failed"); - } else { - regmatch_t m[2]; - if (regexec(®, headers, 2, m, 0) == 0) { - ret = strndup(headers + m[1].rm_so, m[1].rm_eo - m[1].rm_so); - if (!ret) - warn("find_header: strndup failed"); - } - } - free(regex); - regfree(®); - return ret; -} - int acme_get(acme_t *a, const char *url) { int ret = 0; @@ -178,6 +129,50 @@ return ret; } +bool acme_error(acme_t *a) +{ + if (!a->json) return false; + + if (a->type && strcasestr(a->type, "application/problem+json")) { + warnx("the server reported the following error:"); + json_dump(stderr, a->json); + return true; + } + + const json_value_t *e = json_find(a->json, "error"); + if (e && e->type == JSON_OBJECT) { + warnx("the server reported the following error:"); + json_dump(stderr, e); + return true; + } + + return false; +} + +bool acme_nonce(acme_t *a) +{ + const char *url = json_find_string(a->dir, "newNonce"); + if (!url) + { + warnx("failed to find newNonce URL in directory"); + return false; + } + + msg(2, "fetching new nonce at %s", url); + if (acme_get(a, url) != 204) { + warnx("failed to fetch new nonce at %s", url); + acme_error(a); + return false; + } else if (acme_error(a)) + return false; + else if (!a->nonce) { + warnx("failed to find nonce in newNonce resource"); + return false; + } + + return true; +} + int acme_post(acme_t *a, const char *url, const char *format, ...) { int ret = 0; @@ -190,8 +185,8 @@ return 0; } - if (!a->nonce) { - warnx("acme_post: need a nonce first"); + if (!a->nonce && !acme_nonce(a)) { + warnx("acme_post: no nonce available"); return 0; } @@ -300,6 +295,11 @@ else warnx("hook_run: %s terminated abnormally", prog); } else { // child + setenv("UACME_METHOD", method, 1); + setenv("UACME_TYPE", type, 1); + setenv("UACME_IDENT", ident, 1); + setenv("UACME_TOKEN", token, 1); + setenv("UACME_AUTH", auth, 1); if (execl(prog, prog, method, type, ident, token, auth, (char *)NULL) < 0) { warn("hook_run: failed to execute %s", prog); @@ -377,26 +377,6 @@ return ids; } -bool acme_error(acme_t *a) -{ - if (!a->json) return false; - - if (a->type && strcasestr(a->type, "application/problem+json")) { - warnx("the server reported the following error:"); - json_dump(stderr, a->json); - return true; - } - - const json_value_t *e = json_find(a->json, "error"); - if (e && e->type == JSON_OBJECT) { - warnx("the server reported the following error:"); - json_dump(stderr, e); - return true; - } - - return false; -} - bool acme_bootstrap(acme_t *a) { msg(1, "fetching directory at %s", a->directory); @@ -410,21 +390,6 @@ a->dir = a->json; a->json = NULL; - const char *url = json_find_string(a->dir, "newNonce"); - if (!url) - { - warnx("failed to find newNonce URL in directory"); - return false; - } - - msg(2, "fetching new nonce at %s", url); - if (acme_get(a, url) != 204) { - warnx("failed to fetch new nonce at %s", url); - acme_error(a); - return false; - } else if (acme_error(a)) - return false; - return true; } @@ -1379,10 +1344,10 @@ fprintf(stderr, "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[-l|--alternate [N | SHA256]] [-m|--must-staple] [-n|--never-create]\n" - "\t[-o|--no-ocsp] [-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" + "\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" "\tissue IDENTIFIER [ALTNAME ...]] | issue CSRFILE |\n" "\trevoke CERTFILE [CERTKEYFILE]\n", progname); } @@ -1413,6 +1378,7 @@ {"force", no_argument, NULL, 'f'}, {"help", no_argument, NULL, '?'}, {"hook", required_argument, NULL, 'h'}, + {"no-ari", no_argument, NULL, 'i'}, {"alternate", required_argument, NULL, 'l'}, {"must-staple", no_argument, NULL, 'm'}, {"never-create", no_argument, NULL, 'n'}, @@ -1434,6 +1400,7 @@ bool custom_directory = false; bool status_req = false; bool status_check = true; + bool ari_check = true; int days = 30; int bits = 0; int reason = 0; @@ -1454,6 +1421,8 @@ return ret; } + srand(getpid() ^ time(NULL)); + #if LIBCURL_VERSION_NUM < 0x072600 #error libcurl version 7.38.0 or later is required #endif @@ -1477,7 +1446,7 @@ while (1) { char *endptr; int option_index; - int c = getopt_long(argc, argv, "a:b:c:d:e:f?h:l:mnor:st:vVy", + int c = getopt_long(argc, argv, "a:b:c:d:e:f?h:il:mnor:st:vVy", options, &option_index); if (c == -1) break; switch (c) { @@ -1523,6 +1492,10 @@ a.hook = optarg; break; + case 'i': + ari_check = false; + break; + case 'l': if (!alt_parse(&a, optarg)) goto out; @@ -1728,6 +1701,10 @@ } else msg(1, "version " PACKAGE_VERSION " starting on %s", buf); + snprintf(buf, sizeof(buf), "%d", g_loglevel); + setenv("UACME_VERBOSE", buf, 1); + setenv("UACME_CONFDIR", confdir, 1); + if (a.hook && access(a.hook, R_OK | X_OK) < 0) { warn("%s", a.hook); goto out; @@ -1823,8 +1800,13 @@ goto out; } + if (!acme_bootstrap(&a)) + goto out; + const char *ari_url = ari_check ? + json_find_string(a.dir, "renewalInfo") : NULL; + msg(1, "checking existence and expiration of %s", filename); - if (cert_valid(filename, names, days, status_check)) { + if (cert_valid(filename, names, ari_url, days, status_check)) { if (force) msg(1, "forcing reissue of %s", filename); else { @@ -1843,8 +1825,7 @@ } } - if (acme_bootstrap(&a) && account_retrieve(&a) - && cert_issue(&a, names, csr)) + if (account_retrieve(&a) && cert_issue(&a, names, csr)) ret = 0; } else if (strcmp(action, "revoke") == 0) { if (acme_bootstrap(&a) && (!a.keyprefix || account_retrieve(&a)) && diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/ualpn.1 new/uacme-1.7.6/ualpn.1 --- old/uacme-1.7.5/ualpn.1 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/ualpn.1 2024-12-29 17:27:26.000000000 +0100 @@ -2,12 +2,12 @@ .\" Title: ualpn .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/> -.\" Date: 01/28/2024 +.\" Date: 12/29/2024 .\" Manual: User Commands -.\" Source: ualpn 1.7.5 +.\" Source: ualpn 1.7.6 .\" Language: English .\" -.TH "UALPN" "1" "01/28/2024" "ualpn 1\&.7\&.5" "User Commands" +.TH "UALPN" "1" "12/29/2024" "ualpn 1\&.7\&.6" "User Commands" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/uacme-1.7.5/ualpn.c new/uacme-1.7.6/ualpn.c --- old/uacme-1.7.5/ualpn.c 2024-01-28 21:03:31.000000000 +0100 +++ new/uacme-1.7.6/ualpn.c 2024-12-29 17:27:26.000000000 +0100 @@ -833,7 +833,7 @@ } bn = BN_new(); - if (!bn || !BN_pseudo_rand(bn, 127, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) { + if (!bn || !BN_rand(bn, 127, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY)) { openssl_error("auth_crt"); goto out; }
participants (1)
-
Source-Sync