--- /dev/null
+.TH rpki-validator 8 2019-03-5 v0.0.1-beta "RPKI certificate path validator"
+
+.SH NAME
+rpki-validator - Actually still unnamed officially.
+
+.SH OPTIONS
+
+--help
+.RS 4
+Print long usage message.
+.RE
+.P
+
+--usage
+.RS 4
+Print short usage message.
+.RE
+.P
+
+--version
+.RS 4
+Print program version.
+.RE
+.P
+
+--configuration-file=<file>
+.RS 4
+Path to a TOML file from which additional configuration will be read.
+.RE
+.P
+
+--local-repository=<directory>
+.RS 4
+Path to a directory where the local cache of the repository will be stored
+and/or read.
+.RE
+.P
+
+--sync-strategy=(off|strict|root)
+.RS 4
+RSYNC download strategy.
+.P
+off
+.RS 4
+Skip all RSYNCs. (Validate the existing cache repository pointed by --local-repository.)
+.RE
+.P
+strict
+.RS 4
+RSYNC every repository publication point separately. Only skip publication
+points that have already been downloaded during the current validation cycle.
+(Assuming each synchronization is recursive.)
+.P
+For example, suppose the validator gets certificates whose caRepository access
+methods (in their Subject Information Access extensions) point to the following
+publication points:
+.P
+1. rsync://rpki.example.com/foo/bar/
+.br
+2. rsync://rpki.example.com/foo/qux/
+.br
+3. rsync://rpki.example.com/foo/bar/
+.br
+4. rsync://rpki.example.com/foo/corge/grault/
+.br
+5. rsync://rpki.example.com/foo/corge/
+.br
+6. rsync://rpki.example.com/foo/corge/waldo/
+.P
+A validator following the `strict` strategy would download `bar`, download
+`qux`, skip `bar`, download `corge/grault`, download `corge` and skip
+`corge/waldo`.
+.P
+This is the slowest, but also the strictly correct sync strategy.
+.RE
+.P
+root
+.RS 4
+For each publication point found, guess the root of its repository and RSYNC
+that instead. Then skip any subsequent children of said root.
+.P
+(To guess the root of a repository, the validator counts four slashes, and
+prunes the rest of the URL.)
+.P
+Reusing the caRepository URLs from the `strict` strategy (above) as example, a
+validator following the `root` strategy would download
+`rsync://rpki.example.com/foo`, and then skip everything else.
+.P
+Assuming that the repository is specifically structured to be found within as
+few roots as possible, and they contain minimal RPKI-unrelated noise files, this
+is the fastest synchronization strategy. At time of writing, this is true for
+all the current official repositories.
+.RE
+.RE
+.P
+
+--maximum-certificate-depth=<unsigned integer>
+.RS 4
+Maximum allowable certificate chain length.
+.P
+(Required to prevent loops and "other degenerate forms of the logical RPKI
+hierarchy." (RFC 6481))
+.RE
+.P
+
+--tal=<file>
+.RS 4
+Path to the TAL file the validation will sprawl from.
+.P
+The TAL ("Trust Anchor Locator") is a text file that lists a few URLs which can
+be used to access the "Trust Anchor" (the root of a particular RPKI tree) and
+its public key. (See RFC 7730.)
+.RE
+.P
+
+--shuffle-uris
+.RS 4
+Shuffle URIs in the TAL before accessing them.
+.RE
+.P
+
+--color-output
+.RS 4
+Print ANSI color codes?
+.RE
+.P
+
+--output-file-name-format=(global-url|local-path|file-name)
+.RS 4
+Decides which version of file names should be printed during most debug/error
+messages.
+.P
+Suppose a certificate was downloaded from `rsync://rpki.example.com/foo/bar/baz.cer` into the local cache `repository/`:
+.P
+global-url
+.RS 4
+will print the certificate's name as `rsync://rpki.example.com/foo/bar/baz.cer`.
+.RE
+.P
+local-path
+.RS 4
+will print the certificate's name as `repository/rpki.example.com/foo/bar/baz.cer`.
+.RE
+.P
+file-name
+.RS 4
+will print the certificate's name as `baz.cer`.
+.RE
+.P
+.RE
\ No newline at end of file
# I don't mind too much increasing these.
# Just make sure that you know what you're doing.
-GCC_WARNS += -Wlarger-than=1024 -Walloc-size-larger-than=4096
+GCC_WARNS += -Wlarger-than=2048 -Walloc-size-larger-than=4096
GCC_WARNS += -Wframe-larger-than=1024 -Wstack-usage=1024
# Can't use because of dependencies: -Waggregate-return
*
* So, nothing to do for now.
*
- * TODO "In the certificate, the OID appears in the signature and
- * signatureAlgorithm fields [RFC4055]." So it has to be the same as
+ * TODO (field) "In the certificate, the OID appears in the signature
+ * and signatureAlgorithm fields [RFC4055]." So it has to be the same as
* some other field?
*/
* Prevents arbitrarily long paths and loops.
*/
unsigned int maximum_certificate_depth;
- /** Print ANSI color codes? */
- bool color_output;
struct {
char *program;
struct string_array args;
} rsync;
-};
+ struct {
+ /** Print ANSI color codes? */
+ bool color;
+ /** Format in which file names will be printed. */
+ enum filename_format filename_format;
+ } output;
+};
static void print_usage(FILE *, bool);
DECLARE_PRINT_FN(print_string);
DECLARE_PRINT_FN(print_string_array);
DECLARE_PRINT_FN(print_sync_strategy);
+DECLARE_PRINT_FN(print_filename_format);
#define DECLARE_PARSE_ARGV_FN(name) \
static int name( \
DECLARE_PARSE_ARGV_FN(parse_argv_u_int);
DECLARE_PARSE_ARGV_FN(parse_argv_string);
DECLARE_PARSE_ARGV_FN(parse_argv_sync_strategy);
+DECLARE_PARSE_ARGV_FN(parse_argv_filename_format);
#define DECLARE_PARSE_TOML_FN(name) \
static int name( \
DECLARE_PARSE_TOML_FN(parse_toml_string);
DECLARE_PARSE_TOML_FN(parse_toml_sync_strategy);
DECLARE_PARSE_TOML_FN(parse_toml_string_array);
+DECLARE_PARSE_TOML_FN(parse_toml_filename_format);
#define DECLARE_HANDLE_FN(name) \
static int name( \
.arg_doc = SYNC_VALUE_OFF "|" SYNC_VALUE_STRICT "|" SYNC_VALUE_ROOT,
};
+static const struct global_type gt_filename_format = {
+ .has_arg = required_argument,
+ .size = sizeof(enum filename_format),
+ .print = print_filename_format,
+ .parse.argv = parse_argv_filename_format,
+ .parse.toml = parse_toml_filename_format,
+ .arg_doc = FNF_VALUE_GLOBAL "|" FNF_VALUE_LOCAL "|" FNF_VALUE_NAME,
+};
+
/**
* An option that takes no arguments, is not correlated to any rpki_config
* fields, and is entirely managed by its handler function.
.name = "configuration-file",
.type = >_string,
.handler = handle_toml,
- .doc = "TOML file the configuration will be read from.",
+ .doc = "TOML file additional configuration will be read from",
.arg_doc = "<file>",
.availability = AVAILABILITY_GETOPT,
}, {
.name = "local-repository",
.type = >_string,
.offset = offsetof(struct rpki_config, local_repository),
- .doc = "Local repository path.",
+ .doc = "Directory where the repository local cache will be stored/read",
.arg_doc = "<directory>",
}, {
.id = 1001,
.name = "sync-strategy",
.type = >_sync_strategy,
.offset = offsetof(struct rpki_config, sync_strategy),
- .doc = "RSYNC download strategy.",
+ .doc = "RSYNC download strategy",
}, {
- .id = 'c',
- .name = "color-output",
- .type = >_bool,
- .offset = offsetof(struct rpki_config, color_output),
- .doc = "Print ANSI color codes?",
- .availability = AVAILABILITY_GETOPT,
+ .id = 1002,
+ .name = "maximum-certificate-depth",
+ .type = >_u_int,
+ .offset = offsetof(struct rpki_config,
+ maximum_certificate_depth),
+ .doc = "Maximum allowable certificate chain length",
+ .min = 1,
+ /**
+ * It cannot be UINT_MAX, because then the actual number will
+ * overflow and will never be bigger than this.
+ */
+ .max = UINT_MAX - 1,
},
{ 0 },
};
.name = "tal",
.type = >_string,
.offset = offsetof(struct rpki_config, tal),
- .doc = "TAL file path",
- .arg_doc = "<file name>",
+ .doc = "Path to the TAL file",
+ .arg_doc = "<file>",
}, {
.id = 2000,
.name = "shuffle-uris",
.type = >_bool,
.offset = offsetof(struct rpki_config, shuffle_uris),
- .doc = "Shuffle URIs in the TAL.",
- }, {
- .id = 2001,
- .name = "maximum-certificate-depth",
- .type = >_u_int,
- .offset = offsetof(struct rpki_config,
- maximum_certificate_depth),
- .doc = "Prevents arbitrarily long paths and loops.",
- .min = 1,
- /**
- * It cannot be UINT_MAX, because then the actual number will
- * overflow and will never be bigger than this.
- */
- .max = UINT_MAX - 1,
+ .doc = "Shuffle URIs in the TAL before accessing them",
},
{ 0 },
};
.name = "program",
.type = >_string,
.offset = offsetof(struct rpki_config, rsync.program),
- .doc = "Name of the program needed to execute an RSYNC.",
+ .doc = "Name of the program needed to execute an RSYNC",
.arg_doc = "<path to program>",
.availability = AVAILABILITY_TOML,
}, {
.name = "arguments",
.type = >_string_array,
.offset = offsetof(struct rpki_config, rsync.args),
- .doc = "Arguments to send to the RSYNC program call.",
+ .doc = "Arguments to send to the RSYNC program call",
.availability = AVAILABILITY_TOML,
},
{ 0 },
};
+static const struct option_field output_fields[] = {
+ {
+ .id = 'c',
+ .name = "color-output",
+ .type = >_bool,
+ .offset = offsetof(struct rpki_config, output.color),
+ .doc = "Print ANSI color codes?",
+ }, {
+ .id = 4000,
+ .name = "output-file-name-format",
+ .type = >_filename_format,
+ .offset = offsetof(struct rpki_config, output.filename_format),
+ .doc = "File name variant to print during debug/error messages",
+ },
+ { 0 },
+};
+
static const struct group_fields groups[] = {
{
.name = "root",
}, {
.name = "rsync",
.options = rsync_fields,
+ }, {
+ .name = "output",
+ .options = output_fields,
},
{ NULL },
};
pr_info("%s.%s: %s", group->name, field->name, str);
}
+void
+print_filename_format(struct group_fields const *group,
+ struct option_field const *field, void *value)
+{
+ enum filename_format *format = value;
+ char const *str = "<unknown>";
+
+ switch (*format) {
+ case FNF_GLOBAL:
+ str = FNF_VALUE_GLOBAL;
+ break;
+ case FNF_LOCAL:
+ str = FNF_VALUE_LOCAL;
+ break;
+ case FNF_NAME:
+ str = FNF_VALUE_NAME;
+ break;
+ }
+
+ pr_info("%s.%s: %s", group->name, field->name, str);
+}
+
static int
parse_argv_bool(struct option_field const *field, char const *str, void *result)
{
return 0;
}
+static int
+parse_argv_filename_format(struct option_field const *field, char const *str,
+ void *_result)
+{
+ enum filename_format *result = _result;
+
+ if (strcmp(str, FNF_VALUE_GLOBAL) == 0)
+ *result = FNF_GLOBAL;
+ else if (strcmp(str, FNF_VALUE_LOCAL) == 0)
+ *result = FNF_LOCAL;
+ else if (strcmp(str, FNF_VALUE_NAME) == 0)
+ *result = FNF_NAME;
+ else
+ return pr_err("Unknown file name format: '%s'", str);
+
+ return 0;
+}
+
static int
parse_toml_bool(struct option_field const *opt, struct toml_table_t *toml,
void *_result)
return error;
}
+static int
+parse_toml_filename_format(struct option_field const *opt,
+ struct toml_table_t *toml, void *_result)
+{
+ int error;
+ char *string;
+
+ error = parse_toml_string(opt, toml, &string);
+ if (error)
+ return error;
+
+ error = parse_argv_filename_format(opt, string, _result);
+
+ free(string);
+ return error;
+}
+
static int
handle_help(struct option_field const *field, char *arg)
{
rpki_config.sync_strategy = SYNC_STRICT;
rpki_config.shuffle_uris = false;
rpki_config.maximum_certificate_depth = 32;
- rpki_config.color_output = false;
rpki_config.rsync.program = strdup("rsync");
if (rpki_config.rsync.program == NULL)
goto revert_rsync_args;
}
+ rpki_config.output.color = false;
+ rpki_config.output.filename_format = FNF_GLOBAL;
+
return 0;
revert_rsync_args:
char const *arg_doc;
fprintf(stream, "Usage: %s\n", program_name);
-
FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) {
fprintf(stream, "\t[");
fprintf(stream, "--%s", option->name);
bool
config_get_color_output(void)
{
- return rpki_config.color_output;
+ return rpki_config.output.color;
}
+enum filename_format
+config_get_filename_format(void)
+{
+ return rpki_config.output.filename_format;
+}
char *
config_get_rsync_program(void)
#define SYNC_VALUE_STRICT "strict"
#define SYNC_VALUE_ROOT "root"
+enum filename_format {
+ /** Example: "rsync://repository.lacnic.net/rpki/foo/bar/baz.cer" */
+ FNF_GLOBAL,
+ /** Example: "/tmp/repo/repository.lacnic.net/rpki/foo/bar/baz.cer" */
+ FNF_LOCAL,
+ /** Example: "baz.cer" */
+ FNF_NAME,
+};
+
+#define FNF_VALUE_GLOBAL "global-url"
+#define FNF_VALUE_LOCAL "local-path"
+#define FNF_VALUE_NAME "file-name"
+
struct rpki_config;
struct group_fields;
handler_function handler;
/**
- * Explanation of the field, for user consumption.
+ * Explanation of the field, for user consumption during --help.
+ * Meant to be short; the bulk of it should be found in the manpage.
+ * Probably should not include punctuation at the end.
* Mandatory.
*/
const char *doc;
bool config_get_shuffle_uris(void);
unsigned int config_get_max_cert_depth(void);
bool config_get_color_output(void);
+enum filename_format config_get_filename_format(void);
char *config_get_rsync_program(void);
struct string_array const *config_get_rsync_args(void);
if (error)
return -abs(error);
- pr_debug_add("TAL URI %s {", uri->global);
+ pr_debug_add("TAL URI '%s' {", uri_get_printable(uri));
if (!uri_is_certificate(uri)) {
pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')",
- uri->global);
+ uri_get_printable(uri));
error = -EINVAL;
goto end;
}
static int
handle_caRepository(struct rpki_uri *uri, void *arg)
{
- pr_debug("caRepository: %s", uri->global);
+ pr_debug("caRepository: %s", uri_get_printable(uri));
return download_files(uri);
}
handle_signedObject(struct rpki_uri *uri, void *arg)
{
struct certificate_refs *refs = arg;
- pr_debug("signedObject: %s", uri->global);
+ pr_debug("signedObject: %s", uri_get_printable(uri));
refs->signedObject = uri->global;
uri->global = NULL;
return 0;
if (sk_X509_num(validation_certs(state)) >= config_get_max_cert_depth())
return pr_err("Certificate chain maximum depth exceeded.");
- pr_debug_add("%s Certificate %s {", is_ta ? "TA" : "CA",
- cert_uri->global);
- fnstack_push(cert_uri->global);
+ pr_debug_add("%s Certificate '%s' {", is_ta ? "TA" : "CA",
+ uri_get_printable(cert_uri));
+ fnstack_push_uri(cert_uri);
memset(&refs, 0, sizeof(refs));
/* -- Validate the certificate (@cert) -- */
crl_load(struct rpki_uri const *uri, X509_CRL **result)
{
int error;
- pr_debug_add("CRL %s {", uri->global);
+ pr_debug_add("CRL '%s' {", uri_get_printable(uri));
error = __crl_load(uri, result);
if (!error)
struct signed_object_args sobj_args;
int error;
- pr_debug_add("Ghostbusters %s {", uri->global);
- fnstack_push(uri->global);
+ pr_debug_add("Ghostbusters '%s' {", uri->global);
+ fnstack_push_uri(uri);
error = signed_object_args_init(&sobj_args, uri, crls, true);
if (error)
struct manifest mft;
int error;
- pr_debug_add("Manifest %s {", uri->global);
- fnstack_push(uri->global);
+ pr_debug_add("Manifest '%s' {", uri_get_printable(uri));
+ fnstack_push_uri(uri);
error = signed_object_args_init(&sobj_args, uri, crls, false);
if (error)
struct RouteOriginAttestation *roa;
int error;
- pr_debug_add("ROA %s {", uri->global);
- fnstack_push(uri->global);
+ pr_debug_add("ROA '%s' {", uri_get_printable(uri));
+ fnstack_push_uri(uri);
error = signed_object_args_init(&sobj_args, uri, crls, false);
if (error)
pp->crl = *uri;
pp->crl_set = true;
- pr_debug("Manifest CRL: %s", uri->global);
return 0;
}
int error;
int idx;
- fnstack_push(pp->crl.global);
+ fnstack_push_uri(&pp->crl);
error = crl_load(&pp->crl, &crl);
if (error)
/**
* Does not assume that @string is NULL-terminated.
*/
-int
+static int
string_clone(void const *string, size_t size, char **clone)
{
char *result;
#include <openssl/asn1.h>
#include <openssl/x509.h>
-int string_clone(void const *, size_t, char **);
int ia5s2string(ASN1_IA5STRING *, char **);
int BN2string(BIGNUM *, char **);
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
-
#include <sys/socket.h>
+#include "config.h"
+
static pthread_key_t state_key;
static pthread_key_t filenames_key;
return files;
}
-static char const *
-get_filename(char const *file_path)
-{
- /* char *slash = strrchr(file_path, '/'); */
- return /* (slash != NULL) ? (slash + 1) : */ file_path;
-}
-
/**
* Call this function every time you're about to start processing a new file.
* Any pr_err()s and friends will now include the new file name.
* Use fnstack_pop() to revert back to the previously stacked file name.
+ * @file is not cloned; it's expected to outlive the push/pop operation.
*/
void
-fnstack_push(char const *file_path)
+fnstack_push(char const *file)
{
struct filename_stack *files;
char const **tmp;
files->size *= 2;
}
- files->filenames[files->len++] = get_filename(file_path);
+ files->filenames[files->len++] = file;
+}
+
+/** See fnstack_push(). */
+void
+fnstack_push_uri(struct rpki_uri const *uri)
+{
+ fnstack_push(uri_get_printable(uri));
}
/* Returns the file name on the top of the file name stack. */
void fnstack_store(void);
void fnstack_push(char const *);
+void fnstack_push_uri(struct rpki_uri const *);
char const *fnstack_peek(void);
void fnstack_pop(void);
* This function does not assume that @str is null-terminated.
*/
static int
-str2global(void const *str, size_t str_len, struct rpki_uri *uri)
+str2global(char const *str, size_t str_len, struct rpki_uri *uri)
{
int error;
size_t i;
- error = string_clone(str, str_len, &uri->global);
- if (error)
- return error;
- uri->global_len = str_len;
-
for (i = 0; i < str_len; i++) {
- error = validate_url_character(uri->global[i]);
+ error = validate_url_character(str[i]);
if (error)
return error;
}
+ uri->global = strdup(str);
+ if (uri->global == NULL)
+ return pr_enomem();
+ uri->global_len = str_len;
+
return 0;
}
{
return uri_has_extension(uri, ".cer");
}
+
+static char const *
+get_filename(char const *file_path)
+{
+ char *slash = strrchr(file_path, '/');
+ return (slash != NULL) ? (slash + 1) : file_path;
+}
+
+char const *
+uri_get_printable(struct rpki_uri const *uri)
+{
+ enum filename_format format;
+
+ format = config_get_filename_format();
+ switch (format) {
+ case FNF_GLOBAL:
+ return uri->global;
+ case FNF_LOCAL:
+ return uri->local;
+ case FNF_NAME:
+ return get_filename(uri->global);
+ }
+
+ pr_crit("Unknown file name format: %u", format);
+ return uri->global;
+}
* Because we need to generate @local from @global, @global's allowed character
* set must be a subset of @local. Because this is Unix, @local must never
* contain NULL (except as a terminating character). Therefore, even though IA5
- * allows NULL, @global won't. TODO (NOW) validate this on constructors.
+ * allows NULL, @global won't.
*
* Because we will simply embed @global (minus "rsync://") into @local, @local's
* encoding must be IA5-compatible. In other words, UTF-16 and UTF-32 are out of
bool uri_has_extension(struct rpki_uri const *, char const *);
bool uri_is_certificate(struct rpki_uri const *);
+char const *uri_get_printable(struct rpki_uri const *);
#endif /* SRC_URI_H_ */