--- /dev/null
+---
+title: Incidence
+---
+
+[Documentation](index.html) > {{ page.title }}
+
+# {{ page.title }}
+
+## Index
+
+1. [Introduction](#introduction)
+2. [`incidences` definition](#incidences-definition)
+3. [Incidence types](#incidence-types)
+ 1. [Signed Object's hash algorithm has NULL object as parameters](#signed-objects-hash-algorithm-has-null-object-as-parameters)
+
+## Introduction
+
+The RPKI RFCs define fairly strict profiles for RPKI objects, and are unequivocal in stating that incorrectly-formed objects are supposed to be rejected by Relying Party validation. In practice, however, this does not stop a significant amount of Certificate Authorities from issuing incorrect objects.
+
+By default, Fort is as pedantic as it can possibly be. The `incidence` section of its configuration file is a means to modify its behavior upon encountering profile violations that, from experience, are often overlooked.
+
+## `incidences` definition
+
+`incidences` is a JSON array that contains (anonymous) incidence elements. Here's an example:
+
+```
+"incidences": [
+ {
+ "name": "Signed Object's hash algorithm has NULL object as parameters",
+ "action": "warn"
+ }
+]
+```
+
+`name` is the identifier of an incidence. It is case-sensitive and developer-defined. It states the particular error condition that will be handled by the remaining field.
+
+`action` is an enumeration that states the outcome of a violation of the corresponding incidence. It can take one of three values:
+
+1. `error`: Print error message in `error` log level, fail validation of the offending object (and all of its children).
+2. `warn`: Print error message in `warning` log level, continue validation as if nothing happened.
+3. `ignore`: Do not print error message, continue validation as if nothing happened.
+
+By Fort's pedantic nature, most incidences have an `action` of `error` by default.
+
+## Incidence types
+
+Presently, there is only one incidence type defined. This list is expected to grow when strict DER-parsing is implemented, and might also evolve further over time, depending on the state of the global RPKI and user demand.
+
+### Signed Object's hash algorithm has NULL object as parameters
+
+[RFC 6488](https://tools.ietf.org/html/rfc6488) (RPKI Signed Objects) defers digest algorithm specification to RFC 6485:
+
+```
+ The digestAlgorithms set contains the OIDs of the digest algorithm(s)
+ used in signing the encapsulated content. This set MUST contain
+ exactly one digest algorithm OID, and the OID MUST be selected from
+ those specified in [RFC6485].
+```
+
+[6485](https://tools.ietf.org/html/rfc6485) has been obsoleted by [7935](https://tools.ietf.org/html/rfc7935), which states the following:
+
+```
+ The object identifier and
+ parameters for SHA-256 (as defined in [RFC5754]) MUST be used for the
+ SignedData digestAlgorithms field and the SignerInfo digestAlgorithm
+ field.
+```
+
+[RFC 5754](https://tools.ietf.org/html/rfc5754):
+
+```
+ There are two possible encodings for the AlgorithmIdentifier
+ parameters field associated with these object identifiers. (...)
+ some implementations encode parameters as a NULL element
+ while others omit them entirely. The correct encoding is to omit the
+ parameters field;
+```
+
+As of 2019-05-21, many signed objects in the global RPKI break this rule.
+
+If not `ignore`d, Fort will report this incidence with the following error message:
+
+```
+<log level>: <offending file name>: The hash algorithm of the '<object>' has a NULL object as parameters
+```
+
+This only applies to digest parameters that have been defined as NULL objects; any other type of non-absent digest parameters will yield a different error message, and will therefore not be silenced.
4. [Running Fort](run.html)
5. [Fort usage](usage.html)
6. [SLURM](slurm.html)
+7. [Incidences](incidence.html)
17. [`rsync.program`](#rsyncprogram)
18. [`rsync.arguments-recursive`](#rsyncarguments-recursive)
19. [`rsync.arguments-flat`](#rsyncarguments-flat)
+ 20. [`incidences`](#incidences)
## Syntax
"$REMOTE",
"$LOCAL"
]
- }
+ },
+
+ "<a href="#incidences">incidences</a>": [
+ {
+ "name": "Signed Object's hash algorithm has NULL object as parameters",
+ "action": "ignore"
+ }
+ ]
}
</code></pre>
Arguments needed by [`rsync.program`](#rsyncprogram) to perform a single-file rsync.
Fort will replace `"$REMOTE"` with the remote URL it needs to download, and `"$LOCAL"` with the target local directory where the file is supposed to be dropped.
+
+### `incidences`
+
+- **Type:** JSON Object
+- **Availability:** JSON only
+
+A listing of actions to be performed by validation upon encountering certain error conditions. See [incidence](incidence.html).
fort_SOURCES += config/boolean.c config/boolean.h
fort_SOURCES += config/filename_format.h config/filename_format.c
+fort_SOURCES += config/incidences.h config/incidences.c
fort_SOURCES += config/str.c config/str.h
fort_SOURCES += config/string_array.h config/string_array.c
fort_SOURCES += config/sync_strategy.h config/sync_strategy.c
fort_SOURCES += data_structure/uthash.h
fort_SOURCES += data_structure/uthash_nonfatal.h
+fort_SOURCES += incidence/incidence.h incidence/incidence.c
+
fort_SOURCES += object/certificate.h object/certificate.c
fort_SOURCES += object/crl.h object/crl.c
fort_SOURCES += object/ghostbusters.h object/ghostbusters.c
#include <openssl/obj_mac.h>
#include "log.h"
+static bool
+is_asn1_null_object(ANY_t *any)
+{
+ return (any->size == 2) && (any->buf[0] == 5) && (any->buf[1] == 0);
+}
+
+static bool
+is_asn1_null(ANY_t *any)
+{
+ return (any == NULL) || is_asn1_null_object(any);
+}
+
/**
* This function can also be used for CRLs.
*/
* some implementations encode parameters as a NULL element
* while others omit them entirely. The correct encoding is to omit the
* parameters field;
+ *
+ * We will treat NULL object parameters as one type of error, and any
+ * other type of present parameters as a different error. The former
+ * will be silenceable, because many people are breaking the rule.
*/
- if (id->parameters != NULL)
- pr_warn("The hash algorithm of the '%s' has parameters", what);
-
- return 0;
+ if (id->parameters != NULL) {
+ error = is_asn1_null_object(id->parameters)
+ ? incidence(INID_HASHALG_HAS_PARAMS,
+ "The hash algorithm of the '%s' has a NULL object as parameters",
+ what)
+ : pr_err("The hash algorithm of the '%s' has parameters",
+ what);
+ }
+
+ return error;
}
int
return pr_err("The hash algorithm of the '%s' is not SHA256.", what);
}
-static int
-is_asn1_null(ANY_t *any)
-{
- if (any == NULL)
- return true;
-
- if (any->size != 2)
- return false;
- if (any->buf[0] != 5 || any->buf[1] != 0)
- return false;
-
- return true;
-}
-
int
validate_cms_signature_algorithm(AlgorithmIdentifier_t *id)
{
#include "json_handler.h"
#include "log.h"
#include "config/boolean.h"
+#include "config/incidences.h"
#include "config/str.h"
#include "config/uint.h"
#include "config/uint32.h"
.doc = "File name variant to print during debug/error messages",
},
+ /* Incidences */
+ {
+ .id = 4001,
+ .name = "incidences",
+ .type = >_incidences,
+ .doc = "Override actions on validation errors",
+ .availability = AVAILABILITY_JSON,
+ },
+
{ 0 },
};
--- /dev/null
+#include "config/incidences.h"
+
+#include <getopt.h>
+#include "incidence/incidence.h"
+
+static void
+incidences_print(struct option_field const *field, void *_value)
+{
+ incidence_print();
+}
+
+static int
+incidences_parse_json(struct option_field const *opt, json_t *json,
+ void *_result)
+{
+ return incidence_update(json);
+}
+
+const struct global_type gt_incidences = {
+ .print = incidences_print,
+ .parse.json = incidences_parse_json,
+};
--- /dev/null
+#ifndef SRC_CONFIG_INCIDENCES_H_
+#define SRC_CONFIG_INCIDENCES_H_
+
+#include "config/types.h"
+
+extern const struct global_type gt_incidences;
+
+#endif /* SRC_CONFIG_INCIDENCES_H_ */
--- /dev/null
+#include "incidence/incidence.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include "common.h"
+#include "json_parser.h"
+#include "log.h"
+#include "data_structure/common.h"
+
+struct incidence {
+ const enum incidence_id id;
+ char const *const name;
+ const enum incidence_action default_action;
+ enum incidence_action action;
+};
+
+static struct incidence incidences[__INID_MAX] = {
+ {
+ INID_HASHALG_HAS_PARAMS,
+ "Signed Object's hash algorithm has NULL object as parameters",
+ INAC_ERROR,
+ },
+};
+
+static int
+name2id(char const *name, enum incidence_id *id)
+{
+ array_index i;
+
+ for (i = 0; i < __INID_MAX; i++) {
+ if (strcmp(name, incidences[i].name) == 0) {
+ *id = i;
+ return 0;
+ }
+ }
+
+ return pr_err("Unknown incidence name: %s", name);
+}
+
+static char const *
+action2str(enum incidence_action action)
+{
+ switch (action) {
+ case INAC_IGNORE:
+ return "ignore";
+ case INAC_WARN:
+ return "warn";
+ case INAC_ERROR:
+ return "error";
+ }
+
+ return "unknown";
+}
+
+static int
+init_action(json_t *json)
+{
+ enum incidence_id id;
+ char const *name;
+ char const *action_str;
+ enum incidence_action action;
+ int error;
+
+ error = json_get_string(json, "name", &name);
+ if (error)
+ return error;
+ error = name2id(name, &id);
+ if (error)
+ return error;
+ error = json_get_string(json, "action", &action_str);
+ if (error)
+ return error;
+
+ if (strcmp("ignore", action_str) == 0)
+ action = INAC_IGNORE;
+ else if (strcmp("warn", action_str) == 0)
+ action = INAC_WARN;
+ else if (strcmp("error", action_str) == 0)
+ action = INAC_ERROR;
+ else
+ return pr_err("Unknown incidence action: '%s'", action_str);
+
+ if (action > incidences[id].action)
+ return pr_err("The '%s' incidence cannot have a more severe action than '%s'.",
+ name, action2str(incidences[id].action));
+
+ incidences[id].action = action;
+ return 0;
+}
+
+/**
+ * Concurrent inits are allowed.
+ */
+int
+incidence_init(void)
+{
+ array_index i;
+
+ /* Make sure the programmer didn't desync the id enum and the array. */
+ assert(__INID_MAX == ARRAY_LEN(incidences));
+ for (i = 0; i < __INID_MAX; i++) {
+ assert(i == incidences[i].id);
+ /* Also init. */
+ incidences[i].action = incidences[i].default_action;
+ }
+
+ return 0;
+}
+
+/**
+ * Concurrent calls to this function are allowed.
+ */
+int
+incidence_update(json_t *json)
+{
+ array_index i;
+ json_t *child;
+ int error;
+
+ if (!json_is_array(json))
+ return pr_err("The incidences JSON element is supposed to be an array.");
+
+ json_array_foreach(json, i, child) {
+ error = init_action(child);
+ if (error)
+ return error;
+ }
+
+ return 0;
+}
+
+void
+incidence_print(void)
+{
+ array_index i;
+ bool printed;
+
+ pr_info("Custom incidences:");
+ pr_indent_add();
+
+ printed = false;
+
+ for (i = 0; i < __INID_MAX; i++) {
+ if (incidences[i].action != incidences[i].default_action) {
+ pr_info("%s: %s", incidences[i].name,
+ action2str(incidences[i].action));
+ printed = true;
+ }
+ }
+
+ if (!printed)
+ pr_info("<None>");
+
+ pr_indent_rm();
+}
+
+enum incidence_action
+incidence_get_action(enum incidence_id id)
+{
+ return incidences[id].action;
+}
--- /dev/null
+#ifndef SRC_INCIDENCE_INCIDENCE_H_
+#define SRC_INCIDENCE_INCIDENCE_H_
+
+#include <jansson.h>
+
+/*
+ * Note: If you need to add, modify or delete an element from this enum,
+ * remember that you also need to add it to the incidences array. That's all.
+ */
+enum incidence_id {
+ INID_HASHALG_HAS_PARAMS,
+
+ __INID_MAX,
+};
+
+enum incidence_action {
+ /**
+ * Do not print error message, continue validation as if nothing
+ * happened.
+ */
+ INAC_IGNORE,
+ /**
+ * Print error message in warning log level, continue validation as if
+ * nothing happened.
+ */
+ INAC_WARN,
+ /**
+ * Print error message in error log level, fail validation of the
+ * offending object (and all of its children).
+ */
+ INAC_ERROR,
+};
+
+int incidence_init(void); /* incidence_destroy() is not needed. */
+int incidence_update(json_t *);
+
+void incidence_print(void);
+enum incidence_action incidence_get_action(enum incidence_id);
+
+#endif /* SRC_INCIDENCE_INCIDENCE_H_ */
PR_SUFFIX(STDERR);
return -EINVAL;
}
+
+/**
+ * Prints the [format, ...] error message using the configured logging severity
+ * of the @id incidence.
+ */
+int
+incidence(enum incidence_id id, const char *format, ...)
+{
+ enum incidence_action action;
+ va_list args;
+
+ action = incidence_get_action(id);
+ switch (action) {
+ case INAC_IGNORE:
+ return 0;
+ case INAC_WARN:
+ PR_PREFIX(STDERR, COLOR_WARNING, "WRN", args);
+ PR_SUFFIX(STDERR);
+ return 0;
+ case INAC_ERROR:
+ PR_PREFIX(STDERR, COLOR_ERROR, "ERR", args);
+ PR_SUFFIX(STDERR);
+ return -EINVAL;
+ }
+
+ return pr_crit("Unknown incidence action: %u", action);
+}
#ifndef SRC_LOG_H_
#define SRC_LOG_H_
+#include "incidence/incidence.h"
+
/*
* I know that the OpenBSD style guide says that we shouldn't declare our own
* error printing functions, but we kind of need to do it:
int pr_enomem(void);
int pr_crit(const char *, ...) CHECK_FORMAT(1, 2);
+int incidence(enum incidence_id, const char *, ...) CHECK_FORMAT(2, 3);
+
#define PR_DEBUG printf("%s:%d (%s())\n", __FILE__, __LINE__, __func__)
#define PR_DEBUG_MSG(msg, ...) printf("%s:%d (%s()): " msg "\n", \
__FILE__, __LINE__, __func__, ##__VA_ARGS__)
print_stack_trace_on_segfault();
error = thvar_init();
+ if (error)
+ return error;
+ error = incidence_init();
if (error)
return error;