diff --git a/ChangeLog b/ChangeLog
index 9674c05d..de90bdff 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+ * New directive "MDStaplingKeepResponse" for controlling how long OCSP responses are
+ kept in the store and older ones get removed at start up.
+ * Errors reports by an ACME CA may include "subproblems", where several causes may be reported.
+ These are now part of the md-status reporting and also logged. Test cases added.
* OCSP status and validity now part of md-status resource.
* The number of log entries for a single job is now limited to 128. New entries will
cause the oldest ones to be removed.
diff --git a/README.md b/README.md
index aed62f93..10393079 100644
--- a/README.md
+++ b/README.md
@@ -1213,8 +1213,12 @@ checks by mod_md in v1.1.x which are now eliminated. If you have many domains, t
* [MDRenewWindow](#mdrenewwindow--when-to-renew)
* [MDWarnWindow](#mdwarnwindow)
* [MDServerStatus](#mdserverstatus)
+* [MDStapling](#mdstapling)
+* [MDStapleOthers](#mdstapleothers)
+* [MDStaplingKeepResponse](#mdstaplingkeepresponse)
* [MDStoreDir](#mdstoredir)
+
## MDomain
***Define list of domain names that belong to one group***
@@ -1525,6 +1529,46 @@ Default: `on`
Controls if Managed Domains respond to public requests for `/.httpd/certificate-status` or not.
+## MDStapling
+
+***Enable stapling for all or a particular MDomain.***
+`MDStapling on|off`
+Default: `off`
+
+`mod_md` has its own implementation for providing OCSP stapling information. This is an
+alternative to the one provided by `mod_ssl`. For backward compatiblity reasons, this is
+disabled by default.
+
+The new stapling can be switched on for all certificates on the server or for an individual MDomain. This
+will replace any stapling configurtion in `mod_ssl` for these hosts. When disabled, the `mod_ssl`
+stapling (if configured) will do the work. This allows for a gradual shift over from one
+implementation to the other.
+
+The stapling of `mod_md` will also work for domains where the certificates are not managed
+by this module (see MDStapleOthers for how to control this). This allows use of the new stapling
+without using any ACME certificate management.
+
+## MDStapleOthers
+
+***Enable stapling for certificates not managed by mod_md.***
+`MDStapling on|off`
+Default: `on`
+
+This setting only takes effect when `MDStapling` is enabled. It controls if `mod_md` should
+also provide stapling information for certificates that are not directly controlled by it, e.g.
+renewed via an ACME CA.
+
+## MDStaplingKeepResponse
+
+***Controls when responses are considered old and will be removed.***
+`MDStaplingKeepResponse duration`
+Default: 7d
+
+This time window specifies when OCSP response data used in stapling shall be removed
+from the store again on start up. Response information older than 7 days (default) is
+deleted. This keeps the store from growing when certificates are renewed/reconfigured
+frequently.
+
## ServerAdmin / Contact Information
diff --git a/mod_md.xcodeproj/project.pbxproj b/mod_md.xcodeproj/project.pbxproj
index 7ddc6269..2956b1ae 100644
--- a/mod_md.xcodeproj/project.pbxproj
+++ b/mod_md.xcodeproj/project.pbxproj
@@ -23,6 +23,7 @@
B2123B8D1F2DD7A200267CAF /* test_md_json.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = test_md_json.c; sourceTree = ""; };
B2123B8E1F2DD7A200267CAF /* test_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = test_common.h; sourceTree = ""; };
B2307ED520E2562100487AD7 /* test.ini */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = test.ini; sourceTree = ""; };
+ B23C47E722F1A45900B8F494 /* test_0740_acme_errors.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = test_0740_acme_errors.py; sourceTree = ""; };
B24052151EF9145000E36701 /* md_core.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md_core.c; sourceTree = ""; };
B24052161EF9145000E36701 /* md_crypt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md_crypt.c; sourceTree = ""; };
B24052171EF9145000E36701 /* md_crypt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md_crypt.h; sourceTree = ""; };
@@ -315,6 +316,7 @@
B2C1BE0621F5CB38002EF7B4 /* test_0710_migration.py */,
B2C1BE082201AC02002EF7B4 /* test_0720_wildcard.py */,
B2830C2422B27C3F005DBFBF /* test_0730_static.py */,
+ B23C47E722F1A45900B8F494 /* test_0740_acme_errors.py */,
B2FEEDDF1FFB910D0029E19C /* test_0800_must_staple.py */,
B2A3E34D22CA34C2009D35AE /* test_0801_stapling.py */,
B2FFC9E920C5776A004A8F58 /* test_0900_notify.py */,
diff --git a/src/md.h b/src/md.h
index aba2f489..b8c09108 100644
--- a/src/md.h
+++ b/src/md.h
@@ -41,6 +41,7 @@ struct md_pkey_spec_t;
#define MD_TIME_LIFE_NORM (apr_time_from_sec(100 * MD_SECS_PER_DAY))
#define MD_TIME_RENEW_WINDOW_DEF (apr_time_from_sec(33 * MD_SECS_PER_DAY))
#define MD_TIME_WARN_WINDOW_DEF (apr_time_from_sec(10 * MD_SECS_PER_DAY))
+#define MD_TIME_OCSP_KEEP_NORM (apr_time_from_sec(7 * MD_SECS_PER_DAY))
#define MD_OTHER "other"
@@ -193,6 +194,7 @@ struct md_t {
#define MD_KEY_STATE "state"
#define MD_KEY_STATUS "status"
#define MD_KEY_STORE "store"
+#define MD_KEY_SUBPROBLEMS "subproblems"
#define MD_KEY_TEMPORARY "temporary"
#define MD_KEY_TOKEN "token"
#define MD_KEY_TOTAL "total"
diff --git a/src/md_acme.c b/src/md_acme.c
index 02c2b833..2f6d66aa 100644
--- a/src/md_acme.c
+++ b/src/md_acme.c
@@ -175,7 +175,10 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
ptype = md_json_gets(problem, MD_KEY_TYPE, NULL);
pdetail = md_json_gets(problem, MD_KEY_DETAIL, NULL);
req->rv = problem_status_get(ptype);
- md_result_problem_set(req->result, req->rv, ptype, pdetail);
+ md_result_problem_set(req->result, req->rv, ptype, pdetail,
+ md_json_getj(problem, MD_KEY_SUBPROBLEMS, NULL));
+
+
if (APR_STATUS_IS_EAGAIN(req->rv)) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
@@ -494,7 +497,8 @@ void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t
md_result_set(result, rv, NULL);
}
else {
- md_result_problem_set(result, acme->last->status, acme->last->problem, acme->last->detail);
+ md_result_problem_set(result, acme->last->status, acme->last->problem,
+ acme->last->detail, acme->last->subproblems);
}
}
diff --git a/src/md_acme_authz.c b/src/md_acme_authz.c
index 4a82c35f..45062e2b 100644
--- a/src/md_acme_authz.c
+++ b/src/md_acme_authz.c
@@ -157,6 +157,7 @@ static int copy_challenge_error(void *baton, size_t index, md_json_t *json)
if (md_json_has_key(json, MD_KEY_ERROR, NULL)) {
ctx->authz->error_type = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_TYPE, NULL);
ctx->authz->error_detail = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_DETAIL, NULL);
+ ctx->authz->error_subproblems = md_json_dupj(ctx->p, json, MD_KEY_ERROR, MD_KEY_SUBPROBLEMS, NULL);
}
return 1;
}
@@ -177,6 +178,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
json = NULL;
authz->error_type = authz->error_detail = NULL;
+ authz->error_subproblems = NULL;
err = "unable to parse response";
log_level = MD_LOG_ERR;
diff --git a/src/md_acme_authz.h b/src/md_acme_authz.h
index 3a0cfe37..fe1abe55 100644
--- a/src/md_acme_authz.h
+++ b/src/md_acme_authz.h
@@ -51,6 +51,7 @@ struct md_acme_authz_t {
apr_time_t expires;
const char *error_type;
const char *error_detail;
+ const struct md_json_t *error_subproblems;
struct md_json_t *resource;
};
diff --git a/src/md_acme_order.c b/src/md_acme_order.c
index 24b5f8fe..b70c527f 100644
--- a/src/md_acme_order.c
+++ b/src/md_acme_order.c
@@ -468,7 +468,7 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a
case MD_ACME_AUTHZ_S_INVALID:
rv = APR_EINVAL;
if (authz->error_type) {
- md_result_problem_set(result, rv, authz->error_type, authz->error_detail);
+ md_result_problem_set(result, rv, authz->error_type, authz->error_detail, NULL);
goto leave;
}
/* fall through */
diff --git a/src/md_json.c b/src/md_json.c
index 3777f866..4391cf64 100644
--- a/src/md_json.c
+++ b/src/md_json.c
@@ -107,12 +107,12 @@ void md_json_destroy(md_json_t *json)
}
}
-md_json_t *md_json_copy(apr_pool_t *pool, md_json_t *json)
+md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json)
{
return json_create(pool, json_copy(json->j));
}
-md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json)
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json)
{
return json_create(pool, json_deep_copy(json->j));
}
@@ -314,7 +314,7 @@ int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
return 0;
}
-static const char *md_json_type_name(md_json_t *json)
+static const char *md_json_type_name(const md_json_t *json)
{
json_t *j = json->j;
if (json_is_object(j)) return "object";
@@ -463,6 +463,22 @@ md_json_t *md_json_getj(md_json_t *json, ...)
return NULL;
}
+md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...)
+{
+ json_t *j;
+ va_list ap;
+
+ va_start(ap, json);
+ j = jselect(json, ap);
+ va_end(ap);
+
+ if (j) {
+ json_incref(j);
+ return json_create(p, j);
+ }
+ return NULL;
+}
+
const md_json_t *md_json_getcj(const md_json_t *json, ...)
{
json_t *j;
@@ -482,7 +498,7 @@ const md_json_t *md_json_getcj(const md_json_t *json, ...)
return NULL;
}
-apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
+apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
@@ -510,7 +526,7 @@ apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
return rv;
}
-apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...)
+apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...)
{
va_list ap;
apr_status_t rv;
@@ -675,7 +691,7 @@ apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void
return md_json_setj(md_json_clone(p, value), json, NULL);
}
-apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton)
{
(void)baton;
*pvalue = md_json_clone(p, json);
@@ -876,7 +892,7 @@ apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
/* formatting, parsing */
typedef struct {
- md_json_t *json;
+ const md_json_t *json;
md_json_fmt_t fmt;
const char *fname;
apr_file_t *f;
@@ -901,7 +917,7 @@ static int dump_cb(const char *buffer, size_t len, void *baton)
return (rv == APR_SUCCESS)? 0 : -1;
}
-apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
{
int rv = json_dump_callback(json->j, dump_cb, bb, fmt_to_flags(fmt));
return rv? APR_EGENERAL : APR_SUCCESS;
@@ -921,7 +937,7 @@ static int chunk_cb(const char *buffer, size_t len, void *baton)
return 0;
}
-const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
{
apr_array_header_t *chunks;
int rv;
@@ -944,7 +960,7 @@ const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
}
}
-apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
{
apr_status_t rv;
const char *s;
@@ -963,7 +979,7 @@ apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, a
return rv;
}
-apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms)
{
apr_status_t rv;
@@ -987,7 +1003,7 @@ static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
return rv;
}
-apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms)
{
j_write_ctx ctx;
@@ -1171,7 +1187,7 @@ apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
return rv;
}
-const char *md_json_dump_state(md_json_t *json, apr_pool_t *p)
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p)
{
if (!json) return "NULL";
return apr_psprintf(p, "%s, refc=%ld", md_json_type_name(json), (long)json->j->refcount);
diff --git a/src/md_json.h b/src/md_json.h
index ab736bb1..dc612834 100644
--- a/src/md_json.h
+++ b/src/md_json.h
@@ -47,8 +47,8 @@ typedef enum {
md_json_t *md_json_create(apr_pool_t *pool);
void md_json_destroy(md_json_t *json);
-md_json_t *md_json_copy(apr_pool_t *pool, md_json_t *json);
-md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json);
+md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json);
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json);
int md_json_has_key(const md_json_t *json, ...);
@@ -74,9 +74,10 @@ apr_status_t md_json_sets(const char *s, md_json_t *json, ...);
/* json manipulation */
md_json_t *md_json_getj(md_json_t *json, ...);
+md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...);
const md_json_t *md_json_getcj(const md_json_t *json, ...);
-apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...);
-apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...);
apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...);
/* Array/Object manipulation */
@@ -96,7 +97,7 @@ apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, vo
/* conversions from json to json in specified pool */
apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
-apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton);
/* Manipulating/Iteration on generic Arrays */
apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb,
@@ -118,13 +119,13 @@ apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json
apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...);
/* serialization & parsing */
-apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
-const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
-apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p,
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p,
md_json_fmt_t fmt, struct apr_file_t *f);
-apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms);
-apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt,
const char *fpath, apr_fileperms_t perms);
apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, struct apr_bucket_brigade *bb);
@@ -140,7 +141,7 @@ apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool,
apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...);
-const char *md_json_dump_state(md_json_t *json, apr_pool_t *p);
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p);
apr_status_t md_json_timeperiod_set(struct md_timeperiod_t *tp, md_json_t *json, ...);
diff --git a/src/md_ocsp.c b/src/md_ocsp.c
index d444ae7d..d153e14c 100644
--- a/src/md_ocsp.c
+++ b/src/md_ocsp.c
@@ -242,7 +242,7 @@ static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *pte
md_data_t resp_der;
md_timeperiod_t resp_valid;
md_ocsp_cert_stat_t resp_stat;
-
+ /* Check if the store holds a newer response than the one we have */
mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
if (mtime <= ostat->resp_mtime) goto leave;
rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp);
@@ -402,8 +402,7 @@ apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
*pder = NULL;
*pderlen = 0;
if (ostat->resp_der.len <= 0) {
- /* No resonse known, check the store if out watchdog retrieved one
- * in the meantime. */
+ /* No response known, check store for new response. */
ocsp_status_refresh(ostat, p);
if (ostat->resp_der.len <= 0) {
md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p,
diff --git a/src/md_result.c b/src/md_result.c
index c51308fd..589b7a39 100644
--- a/src/md_result.c
+++ b/src/md_result.c
@@ -75,6 +75,7 @@ void md_result_activity_setn(md_result_t *result, const char *activity)
{
result->activity = activity;
result->problem = result->detail = NULL;
+ result->subproblems = NULL;
on_change(result);
}
@@ -92,15 +93,18 @@ void md_result_set(md_result_t *result, apr_status_t status, const char *detail)
result->status = status;
result->problem = NULL;
result->detail = detail? apr_pstrdup(result->p, detail) : NULL;
+ result->subproblems = NULL;
on_change(result);
}
void md_result_problem_set(md_result_t *result, apr_status_t status,
- const char *problem, const char *detail)
+ const char *problem, const char *detail,
+ const md_json_t *subproblems)
{
result->status = status;
result->problem = dup_trim(result->p, problem);
result->detail = apr_pstrdup(result->p, detail);
+ result->subproblems = subproblems? md_json_clone(result->p, subproblems) : NULL;
on_change(result);
}
@@ -115,6 +119,7 @@ void md_result_problem_printf(md_result_t *result, apr_status_t status,
va_start(ap, fmt);
result->detail = apr_pvsprintf(result->p, fmt, ap);
va_end(ap);
+ result->subproblems = NULL;
on_change(result);
}
@@ -126,6 +131,7 @@ void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt,
va_start(ap, fmt);
result->detail = apr_pvsprintf(result->p, fmt, ap);
va_end(ap);
+ result->subproblems = NULL;
on_change(result);
}
@@ -147,7 +153,7 @@ md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p)
result->activity = md_json_dups(p, json, MD_KEY_ACTIVITY, NULL);
s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
if (s && *s) result->ready_at = apr_date_parse_rfc(s);
-
+ result->subproblems = md_json_dupj(p, json, MD_KEY_SUBPROBLEMS, NULL);
return result;
}
@@ -170,6 +176,9 @@ struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p)
apr_rfc822_date(ts, result->ready_at);
md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
}
+ if (result->subproblems) {
+ md_json_setj(result->subproblems, json, MD_KEY_SUBPROBLEMS, NULL);
+ }
return json;
}
@@ -201,6 +210,7 @@ void md_result_assign(md_result_t *dest, const md_result_t *src)
dest->detail = src->detail;
dest->activity = src->activity;
dest->ready_at = src->ready_at;
+ dest->subproblems = src->subproblems;
}
void md_result_dup(md_result_t *dest, const md_result_t *src)
@@ -210,6 +220,7 @@ void md_result_dup(md_result_t *dest, const md_result_t *src)
dest->detail = src->detail? apr_pstrdup(dest->p, src->detail) : NULL;
dest->activity = src->activity? apr_pstrdup(dest->p, src->activity) : NULL;
dest->ready_at = src->ready_at;
+ dest->subproblems = src->subproblems? md_json_clone(dest->p, src->subproblems) : NULL;
on_change(dest);
}
@@ -235,6 +246,11 @@ void md_result_log(md_result_t *result, int level)
msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
sep = " ";
}
+ if (result->subproblems) {
+ msg = apr_psprintf(result->p, "%s%ssubproblems[%s]", msg, sep,
+ md_json_writep(result->subproblems, result->p, MD_JSON_FMT_COMPACT));
+ sep = " ";
+ }
md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
}
}
diff --git a/src/md_result.h b/src/md_result.h
index 21823735..f4fb19a7 100644
--- a/src/md_result.h
+++ b/src/md_result.h
@@ -30,6 +30,7 @@ struct md_result_t {
apr_status_t status;
const char *problem;
const char *detail;
+ const struct md_json_t *subproblems;
const char *activity;
apr_time_t ready_at;
md_result_change_cb *on_change;
@@ -46,7 +47,8 @@ void md_result_activity_printf(md_result_t *result, const char *fmt, ...);
void md_result_set(md_result_t *result, apr_status_t status, const char *detail);
void md_result_problem_set(md_result_t *result, apr_status_t status,
- const char *problem, const char *detail);
+ const char *problem, const char *detail,
+ const struct md_json_t *subproblems);
void md_result_problem_printf(md_result_t *result, apr_status_t status,
const char *problem, const char *fmt, ...);
diff --git a/src/mod_md_config.c b/src/mod_md_config.c
index e711efd9..7819b8fb 100644
--- a/src/mod_md_config.c
+++ b/src/mod_md_config.c
@@ -41,6 +41,11 @@
#define MD_DEFAULT_BASE_DIR "md"
#endif
+static md_timeslice_t def_ocsp_keep_window = {
+ 0,
+ MD_TIME_OCSP_KEEP_NORM,
+};
+
/* Default settings for the global conf */
static md_mod_conf_t defmc = {
NULL, /* list of mds */
@@ -67,6 +72,7 @@ static md_mod_conf_t defmc = {
0, /* dry_run flag */
1, /* server_status_enabled */
1, /* certificate_status_enabled */
+ &def_ocsp_keep_window, /* default time to keep ocsp responses */
};
static md_timeslice_t def_renew_window = {
@@ -852,6 +858,19 @@ static const char *md_config_set_certificate_status(cmd_parms *cmd, void *dc, co
return set_on_off(&sc->mc->certificate_status_enabled, value, cmd->pool);
}
+static const char *md_config_set_ocsp_keep_window(cmd_parms *cmd, void *dc, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err;
+
+ (void)dc;
+ if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+ return err;
+ }
+ err = md_timeslice_parse(&sc->mc->ocsp_keep_window, cmd->pool, value, MD_TIME_OCSP_KEEP_NORM);
+ if (err) return apr_psprintf(cmd->pool, "MDStaplingKeepResponse %s", err);
+ return NULL;
+}
const command_rec md_cmds[] = {
AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF,
@@ -917,6 +936,8 @@ const command_rec md_cmds[] = {
"Enable/Disable OCSP Stapling for this/all Managed Domain(s)."),
AP_INIT_TAKE1("MDStapleOthers", md_config_set_staple_others, NULL, RSRC_CONF,
"Enable/Disable OCSP Stapling for certificates not in Managed Domains."),
+ AP_INIT_TAKE1("MDStaplingKeepResponse", md_config_set_ocsp_keep_window, NULL, RSRC_CONF,
+ "The amount of time to keep an OCSP response in the store."),
AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
diff --git a/src/mod_md_config.h b/src/mod_md_config.h
index a6962ba1..573ebf50 100644
--- a/src/mod_md_config.h
+++ b/src/mod_md_config.h
@@ -67,6 +67,7 @@ struct md_mod_conf_t {
int dry_run; /* != 0 iff config dry run */
int server_status_enabled; /* if module should add to server-status handler */
int certificate_status_enabled; /* if module should expose /.httpd/certificate-status */
+ md_timeslice_t *ocsp_keep_window; /* time that we keep ocsp responses around */
};
typedef struct md_srv_conf_t {
diff --git a/src/mod_md_ocsp.c b/src/mod_md_ocsp.c
index 23b7f5de..15b46907 100644
--- a/src/mod_md_ocsp.c
+++ b/src/mod_md_ocsp.c
@@ -161,7 +161,20 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
return APR_SUCCESS;
}
-apr_status_t md_ocsp_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+static apr_status_t ocsp_remove_old_responses(md_mod_conf_t *mc, apr_pool_t *p)
+{
+ md_timeperiod_t keep_norm, keep;
+
+ keep_norm.end = apr_time_now();
+ keep_norm.start = keep.end - MD_TIME_OCSP_KEEP_NORM;
+ keep = md_timeperiod_slice_before_end(&keep_norm, mc->ocsp_keep_window);
+ /* remove any ocsp response older than keep.start */
+ /* TODO */
+ (void)p;
+ return APR_ENOTIMPL;
+}
+
+apr_status_t md_ocsp_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
{
apr_allocator_t *allocator;
md_ocsp_ctx_t *octx;
@@ -196,9 +209,24 @@ apr_status_t md_ocsp_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr
octx->s = s;
octx->mc = mc;
- /* TODO: make sure that store is prepped, has correct permissions and
- * perform house-keeping, such as removing OCSP responses no longer used
- * and older than 24 hours. */
+ /* Time for some house keeping, before the server goes live (again):
+ * - we store OCSP responses for each certificate individually by its SHA-1 id
+ * - this means, as long as certificate do not change, the number of response
+ * files remains stable.
+ * - But when a certificate changes (is replaced), the response is obsolete
+ * - we do not get notified when a certificate is no longer used. An admin
+ * might just reconfigure or change the content of a file (backup/restore etc.)
+ * - also, certificates might be added by some openssl config commands or other
+ * modules that we do not immediately see right at startup. We cannot assume
+ * that any OCSP response we cannot relate to a certificate RIGHT NOW, is no
+ * longer needed.
+ * - since the response files are relatively small, we have no problem with
+ * keeping them around for a while. We just do not want an ever growing store.
+ * - The simplest and effective way seems to be to just remove files older
+ * a certain amount of time. Take a 7 day default and let the admin configure
+ * it for very special setups.
+ */
+ ocsp_remove_old_responses(mc, octx->p);
rv = wd_get_instance(&octx->watchdog, MD_OCSP_WATCHDOG_NAME, 0, 1, octx->p);
if (APR_SUCCESS != rv) {
diff --git a/test/test_0740_acme_errors.py b/test/test_0740_acme_errors.py
index e69de29b..839c3a4d 100644
--- a/test/test_0740_acme_errors.py
+++ b/test/test_0740_acme_errors.py
@@ -0,0 +1,79 @@
+# test ACME error responses and their processing
+
+import json
+import os
+import pytest
+import re
+import socket
+import ssl
+import sys
+import time
+
+from datetime import datetime
+from httplib import HTTPSConnection
+from test_base import TestEnv
+from test_base import HttpdConf
+from test_base import CertUtil
+
+
+def setup_module(module):
+ print("setup_module module:%s" % module.__name__)
+ TestEnv.initv2()
+ TestEnv.APACHE_CONF_SRC = "data/test_auto"
+ TestEnv.check_acme()
+ TestEnv.clear_store()
+ TestEnv.install_test_conf();
+ assert TestEnv.apache_start() == 0
+
+
+def teardown_module(module):
+ print("teardown_module module:%s" % module.__name__)
+ assert TestEnv.apache_stop() == 0
+
+
+class TestAcmeErrors:
+
+ def setup_method(self, method):
+ print("setup_method: %s" % method.__name__)
+ self.test_domain = TestEnv.get_method_domain(method)
+
+ def teardown_method(self, method):
+ print("teardown_method: %s" % method.__name__)
+
+ #-----------------------------------------------------------------------------------------------
+ # test case: MD with 2 names, one invalid
+ #
+ def test_740_000(self):
+ domain = self.test_domain
+ domains = [ domain, "invalid!." + domain ]
+ conf = HttpdConf()
+ conf.add_admin( "admin@not-forbidden.org" )
+ conf.add_md( domains )
+ conf.add_vhost(domains)
+ conf.install()
+ assert TestEnv.apache_restart() == 0
+ md = TestEnv.await_error(domain)
+ assert md
+ assert md['renewal']['errors'] > 0
+ assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:rejectedIdentifier'
+ assert md['renewal']['last']['detail'] == ("Error creating new order :: Cannot issue for \"%s\": Invalid character in DNS name" % (domains[1]))
+
+ # test case: MD with 3 names, 2 invalid
+ #
+ def test_740_001(self):
+ domain = self.test_domain
+ domains = [ domain, "invalid1!." + domain, "invalid2!." + domain ]
+ conf = HttpdConf()
+ conf.add_admin( "admin@not-forbidden.org" )
+ conf.add_md( domains )
+ conf.add_vhost(domains)
+ conf.install()
+ assert TestEnv.apache_restart() == 0
+ md = TestEnv.await_error(domain)
+ assert md
+ assert md['renewal']['errors'] > 0
+ assert md['renewal']['last']['problem'] == 'urn:ietf:params:acme:error:rejectedIdentifier'
+ assert md['renewal']['last']['detail'] == ("Error creating new order :: Cannot issue for \"%s\": Invalid character in DNS name (and 1 more problems. Refer to sub-problems for more information.)" % (domains[1]))
+ assert md['renewal']['last']['subproblems']
+ assert len(md['renewal']['last']['subproblems']) == 2
+