From: Andreas Steffen Date: Mon, 22 Aug 2022 10:42:09 +0000 (+0200) Subject: pki: use libtls for pki --est|--estca X-Git-Tag: 5.9.8dr1~2^2~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=784606a82778c3f41702d4f7b0a443e469e49cca;p=thirdparty%2Fstrongswan.git pki: use libtls for pki --est|--estca --- diff --git a/src/pki/Makefile.am b/src/pki/Makefile.am index 22fb11fe8d..b4c05318a7 100644 --- a/src/pki/Makefile.am +++ b/src/pki/Makefile.am @@ -20,7 +20,7 @@ pki_SOURCES = pki.c pki.h pki_cert.c pki_cert.h command.c command.h \ commands/self.c \ commands/signcrl.c \ commands/verify.c \ - est/est.h est/est.c est/est_tls.h est/est_tls.c \ + est/est_tls.h est/est_tls.c \ scep/scep.h scep/scep.c pki_LDADD = \ diff --git a/src/pki/commands/est.c b/src/pki/commands/est.c index 8f39b0f527..67e011f963 100644 --- a/src/pki/commands/est.c +++ b/src/pki/commands/est.c @@ -19,7 +19,6 @@ #include "pki.h" #include "pki_cert.h" -#include "est/est.h" #include "est/est_tls.h" #include @@ -64,7 +63,7 @@ static int est() case 'i': /* --in */ file = arg; continue; - case 'c': /* --cacert */ + case 'C': /* --cacert */ cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); if (!cacert) @@ -74,7 +73,7 @@ static int est() } creds->add_cert(creds, TRUE, cacert); continue; - case 'o': /* --cert */ + case 'c': /* --cert */ client_cert_file = arg; continue; case 'X': /* --certid */ @@ -193,7 +192,7 @@ static int est() { chunk_t handle; - if (client_cert_file) /* loadold certificate file */ + if (client_cert_file) /* load old certificate file */ { client_cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, @@ -362,9 +361,9 @@ static void __attribute__ ((constructor))reg() {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the EST server"}, {"in", 'i', 1, "PKCS#10 input file, default: stdin"}, - {"cacert", 'c', 1, "CA certificate"}, - {"cert", 'o', 1, "old certificate about to be renewed"}, - {"certid", 'X', 1, "smartcard or TPM certificate object handle" }, + {"cacert", 'C', 1, "CA certificate"}, + {"cert", 'c', 1, "old certificate about to be renewed"}, + {"certid", 'X', 1, "smartcard or TPM certificate object handle" }, {"key", 'k', 1, "old private key about to be replaced"}, {"keyid", 'x', 1, "smartcard or TPM private key object handle"}, {"userpass", 'p', 1, "username:password for http basic auth"}, diff --git a/src/pki/commands/estca.c b/src/pki/commands/estca.c index 02161ddd04..f6a584bb7d 100644 --- a/src/pki/commands/estca.c +++ b/src/pki/commands/estca.c @@ -16,7 +16,7 @@ #include "pki.h" #include "pki_cert.h" -#include "est/est.h" +#include "est/est_tls.h" #include #include @@ -29,35 +29,55 @@ static int estca() { cred_encoding_type_t form = CERT_ASN1_DER; chunk_t est_response = chunk_empty; - char *arg, *url = NULL, *caout = NULL; + certificate_t *cacert; + mem_cred_t *creds = NULL; + est_tls_t *est_tls; + char *arg, *error = NULL, *url = NULL, *caout = NULL; bool force = FALSE, success; u_int http_code = 0; + status_t status = 1; + + /* initialize CA certificate storage */ + creds = mem_cred_create(); + lib->credmgr->add_set(lib->credmgr, &creds->set); while (TRUE) { switch (command_getopt(&arg)) { - case 'h': - return command_usage(NULL); - case 'u': + case 'h': /* --help */ + goto usage; + case 'u': /* --url */ url = arg; continue; - case 'c': + case 'C': /* --cacert */ + cacert = lib->creds->create(lib->creds, CRED_CERTIFICATE, + CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); + if (!cacert) + { + DBG1(DBG_APP, "could not load cacert file '%s'", arg); + goto err; + } + creds->add_cert(creds, TRUE, cacert); + continue; + case 'c': /* --caout */ caout = arg; continue; - case 'f': + case 'f': /* --outform */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { - return command_usage("invalid certificate output format"); + error ="invalid certificate output format"; + goto usage; } continue; - case 'F': + case 'F': /* --force */ force = TRUE; continue; case EOF: break; default: - return command_usage("invalid --estca option"); + error ="invalid --estca option"; + goto usage; } break; } @@ -67,17 +87,38 @@ static int estca() return command_usage("--url is required"); } - if (!est_https_request(url, EST_CACERTS, FALSE, chunk_empty, &est_response, - &http_code)) + est_tls = est_tls_create(url, NULL, NULL); + if (!est_tls) + { + DBG1(DBG_APP, "TLS connection to EST server was not established"); + goto err; + } + success = est_tls->request(est_tls, EST_CACERTS, chunk_empty, &est_response, + &http_code, NULL); + est_tls->destroy(est_tls); + + if (!success) + { + DBG1(DBG_APP, "EST request failed: HTTP %u", http_code); + goto err; + } + if (pki_cert_extract_cacerts(est_response, caout, NULL, TRUE, form, force)) { - DBG1(DBG_APP, "did not receive a valid EST response: HTTP %u", http_code); - return 1; + status = 0; } - success = pki_cert_extract_cacerts(est_response, caout, NULL, TRUE, form, - force); + +err: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); chunk_free(&est_response); - return success ? 0 : 1; + return status; + +usage: + lib->credmgr->remove_set(lib->credmgr, &creds->set); + creds->destroy(creds); + + return command_usage(error); } /** @@ -88,10 +129,11 @@ static void __attribute__ ((constructor))reg() command_register((command_t) { estca, 'e', "estca", "get CA certificate[s] from a EST server", - {"--url url [--caout file] [--outform der|pem] [--force]"}, + {"--url url [--cacert file]+ [--caout file] [--outform der|pem] [--force]"}, { {"help", 'h', 0, "show usage information"}, {"url", 'u', 1, "URL of the SCEP server"}, + {"cacert", 'C', 1, "TLS CA certificate"}, {"caout", 'c', 1, "CA certificate [template]"}, {"outform", 'f', 1, "encoding of stored certificates, default: der"}, {"force", 'F', 0, "force overwrite of existing files"}, diff --git a/src/pki/commands/scep.c b/src/pki/commands/scep.c index 500735d17b..f7db744d5a 100644 --- a/src/pki/commands/scep.c +++ b/src/pki/commands/scep.c @@ -39,7 +39,7 @@ static int scep() { char *arg, *url = NULL, *file = NULL, *dn = NULL, *error = NULL; char *ca_enc_file = NULL, *ca_sig_file = NULL; - char *old_cert_file = NULL, *old_key_file = NULL; + char *client_cert_file = NULL, *client_key_file = NULL; cred_encoding_type_t form = CERT_ASN1_DER; chunk_t scep_response = chunk_empty; chunk_t challenge_password = chunk_empty; @@ -95,33 +95,33 @@ static int scep() { switch (command_getopt(&arg)) { - case 'h': + case 'h': /* --help */ goto usage; - case 'u': + case 'u': /* --url */ url = arg; continue; - case 'i': + case 'i': /* --in */ file = arg; continue; - case 'd': + case 'd': /* --dn */ dn = arg; continue; - case 'a': + case 'a': /* --san */ san->insert_last(san, identification_create_from_string(arg)); continue; - case 'P': + case 'P': /* --profile */ cert_type = chunk_create(arg, strlen(arg)); continue; - case 'p': + case 'p': /* --password */ challenge_password = chunk_create(arg, strlen(arg)); continue; - case 'e': + case 'e': /* --cacert-enc */ ca_enc_file = arg; continue; - case 's': + case 's': /* --cacert-sig */ ca_sig_file = arg; continue; - case 'c': + case 'C': /* --cacert */ cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, arg, BUILD_END); if (!cert) @@ -131,13 +131,13 @@ static int scep() } creds->add_cert(creds, TRUE, cert); continue; - case 'o': - old_cert_file = arg; + case 'c': /* --cert */ + client_cert_file = arg; continue; - case 'k': - old_key_file = arg; + case 'k': /* --key */ + client_key_file = arg; continue; - case 'C': + case 'E': /* --cipher */ if (strcaseeq(arg, "des3")) { cipher = ENCR_3DES; @@ -154,14 +154,14 @@ static int scep() goto usage; } continue; - case 'g': + case 'g': /* --digest */ if (!enum_from_name(hash_algorithm_short_names, arg, &digest_alg)) { error = "invalid --digest type"; goto usage; } continue; - case 'R': + case 'R': /* --rsa-padding */ if (streq(arg, "pss")) { pss = TRUE; @@ -186,7 +186,7 @@ static int scep() case 'm': /* --maxpolltime */ max_poll_time = atoi(optarg); continue; - case 'f': + case 'f': /* --form */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { error = "invalid certificate output format"; @@ -220,7 +220,7 @@ static int scep() goto usage; } - if (old_cert_file && !old_key_file) + if (client_cert_file && !client_key_file) { error = "--oldkey is required if --oldcert is set"; goto usage; @@ -374,7 +374,7 @@ static int scep() } DBG1(DBG_APP, "transaction ID: %.*s", (int)transID.len, transID.ptr); - if (old_cert_file) + if (client_cert_file) { /* check support of Renewal Operation */ if (!(caps_flags & SCEP_CAPS_RENEWAL)) @@ -390,19 +390,21 @@ static int scep() /* load old client certificate */ x509_signer = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, - BUILD_FROM_FILE, old_cert_file, BUILD_END); + BUILD_FROM_FILE, client_cert_file, BUILD_END); if (!x509_signer) { - DBG1(DBG_APP, "could not load old cert file '%s'", old_cert_file); + DBG1(DBG_APP, "loading client cert file '%s' failed", + client_cert_file); goto err; } /* load old RSA private key */ priv_signer = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA, - BUILD_FROM_FILE, old_key_file, BUILD_END); + BUILD_FROM_FILE, client_key_file, BUILD_END); if (!priv_signer) { - DBG1(DBG_APP, "parsing old private key failed"); + DBG1(DBG_APP, "loading client private key file '%s' failed", + client_key_file); goto err; } } @@ -456,7 +458,7 @@ static int scep() DBG1(DBG_APP, "could not load signature cacert file '%s'", ca_sig_file); goto end; } - creds->add_cert(creds, TRUE, x509_ca_sig->get_ref(x509_ca_sig)); + x509_ca_sig = creds->add_cert_ref(creds, TRUE, x509_ca_sig); /* build pkcs7 request */ pkcs7_req = scep_build_request(pkcs10_encoding, transID, scep_msg_type, @@ -642,10 +644,10 @@ static void __attribute__ ((constructor))reg() {"password", 'p', 1, "challengePassword to include in cert request"}, {"cacert-enc", 'e', 1, "CA certificate for encryption"}, {"cacert-sig", 's', 1, "CA certificate for signature verification"}, - {"cacert", 'c', 1, "Additional CA certificates"}, - {"oldcert", 'o', 1, "Old certificate about to be renewed"}, - {"oldkey", 'k', 1, "Old RSA private key about to be replaced"}, - {"cipher", 'C', 1, "encryption cipher, default: aes"}, + {"cacert", 'C', 1, "Additional CA certificates"}, + {"cert", 'c', 1, "Old certificate about to be renewed"}, + {"key", 'k', 1, "Old RSA private key about to be replaced"}, + {"cipher", 'E', 1, "encryption cipher, default: aes"}, {"digest", 'g', 1, "digest for signature creation, default: sha256"}, {"rsa-padding", 'R', 1, "padding for RSA signatures, default: pkcs1"}, {"interval", 't', 1, "poll interval, default: 60s"}, diff --git a/src/pki/commands/scepca.c b/src/pki/commands/scepca.c index 32df55de72..9ba72d41cf 100644 --- a/src/pki/commands/scepca.c +++ b/src/pki/commands/scepca.c @@ -37,24 +37,24 @@ static int scepca() { switch (command_getopt(&arg)) { - case 'h': + case 'h': /* --help */ return command_usage(NULL); - case 'u': + case 'u': /* --url */ url = arg; continue; - case 'c': + case 'c': /* --caout */ caout = arg; continue; - case 'r': + case 'r': /* --raout */ raout = arg; continue; - case 'f': + case 'f': /* --form */ if (!get_form(arg, &form, CRED_CERTIFICATE)) { return command_usage("invalid certificate output format"); } continue; - case 'F': + case 'F': /* --force */ force = TRUE; continue; case EOF: diff --git a/src/pki/est/est.c b/src/pki/est/est.c deleted file mode 100644 index c24bf5c3ee..0000000000 --- a/src/pki/est/est.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 Andreas Steffen, strongSec GmbH - * - * Copyright (C) secunet Security Networks AG - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See . - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#define _GNU_SOURCE -#include - -#include "est.h" - -#define HTTP_CODE_OK 200 - -static const char *operations[] = { - "cacerts", - "simpleenroll", - "simplereenroll", - "fullcmc", - "serverkeygen", - "csrattrs" -}; - -static const char *request_types[] = { - "", - "application/pkcs10", - "application/pkcs10", - "application/pkcs7-mime", - "application/pkcs10", - "" -}; - -/** - * Send an EST request via HTTPS and wait for a response - */ -bool est_https_request(const char *url, est_op_t op, bool http_post, - chunk_t data, chunk_t *response, u_int *http_code) -{ - host_t *srcip = NULL; - char *complete_url = NULL; - status_t status; - - uint32_t http_timeout = lib->settings->get_time(lib->settings, - "%s.est.http_timeout", 30, lib->ns); - - char *http_bind = lib->settings->get_str(lib->settings, - "%s.est.http_bind", NULL, lib->ns); - - /* initialize response */ - *response = chunk_empty; - *http_code = 0; - - /* construct complete EST URL */ - if (asprintf(&complete_url, "%s/.well-known/est/%s", url, operations[op]) == -1) - { - DBG1(DBG_APP, "could not allocate complete_url string"); - return FALSE; - } - DBG2(DBG_APP, "sending EST request to '%s'", url); - - if (http_bind) - { - srcip = host_create_from_string(http_bind, 0); - } - - if (http_post) - { - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_timeout, - FETCH_REQUEST_DATA, data, - FETCH_REQUEST_TYPE, request_types[op], - FETCH_REQUEST_HEADER, "Expect:", - FETCH_SOURCEIP, srcip, - FETCH_RESPONSE_CODE, http_code, - FETCH_END); - } - else /* HTTP_GET */ - { - status = lib->fetcher->fetch(lib->fetcher, complete_url, response, - FETCH_TIMEOUT, http_timeout, - FETCH_SOURCEIP, srcip, - FETCH_RESPONSE_CODE, http_code, - FETCH_END); - } - DESTROY_IF(srcip); - free(complete_url); - - if (status != SUCCESS) - { - return FALSE; - } - - if (*http_code == HTTP_CODE_OK) - { - chunk_t base64_response = *response; - - *response = chunk_from_base64(base64_response, NULL); - chunk_free(&base64_response); - } - - return TRUE; -} - diff --git a/src/pki/est/est.h b/src/pki/est/est.h deleted file mode 100644 index 3d9bdd3cf4..0000000000 --- a/src/pki/est/est.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022 Andreas Steffen, strongSec GmbH - * - * Copyright (C) secunet Security Networks AG - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See . - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef _EST_H -#define _EST_H - -#include - -/** - * EST (RFC 7030) Operations - */ -typedef enum { - EST_CACERTS, - EST_SIMPLE_ENROLL, - EST_SIMPLE_REENROLL, - EST_FULL_CMC, - EST_SERVER_KEYGEN, - EST_CSR_ATTRS -} est_op_t; - -/** - * Send an EST request via HTTPS and wait for a response - */ -bool est_https_request(const char *url, est_op_t op, bool http_post, - chunk_t data, chunk_t *response, u_int *http_code); - -#endif /* _EST_H */ diff --git a/src/pki/est/est_tls.c b/src/pki/est/est_tls.c index a10ed1416b..ccc03280df 100644 --- a/src/pki/est/est_tls.c +++ b/src/pki/est/est_tls.c @@ -16,8 +16,6 @@ #define _GNU_SOURCE /* for asprintf() */ #include -#include -#include #include #include @@ -133,7 +131,8 @@ static chunk_t build_http_request(private_est_tls_t *this, est_op_t op, chunk_t len = asprintf(&http_header, "GET %s/.well-known/est/%s HTTP/1.1\r\n" "Host: %s\r\n" - "%s", + "%s" + "\r\n", this->http_path, operations[op], this->http_host, http_auth); if (len > 0) { @@ -154,6 +153,11 @@ static bool parse_http_header(chunk_t *in, u_int *http_code, u_int *content_len *content_len = 0; *base64 = FALSE; + if (retry_after) + { + *retry_after = 0; + } + /* Process HTTP protocol version and HTTP status code */ if (!fetchline(in, &line) || !extract_token(&version, ' ', &line) || !match("HTTP/1.1", &version) || sscanf(line.ptr, "%d", http_code) != 1) @@ -210,7 +214,11 @@ METHOD(est_tls_t, request, bool, /* initialize output variables */ *out = chunk_empty; *http_code = 0; - *retry_after = 0; + + if (retry_after) + { + *retry_after = 0; + } http = build_http_request(this, op, in); diff --git a/src/pki/est/est_tls.h b/src/pki/est/est_tls.h index d05831c718..ded2aeb19a 100644 --- a/src/pki/est/est_tls.h +++ b/src/pki/est/est_tls.h @@ -22,8 +22,6 @@ #ifndef EST_TLS_H_ #define EST_TLS_H_ -#include "est.h" - #include #include @@ -32,6 +30,18 @@ typedef struct est_tls_t est_tls_t; +/** + * EST (RFC 7030) Operations + */ +typedef enum { + EST_CACERTS, + EST_SIMPLE_ENROLL, + EST_SIMPLE_REENROLL, + EST_FULL_CMC, + EST_SERVER_KEYGEN, + EST_CSR_ATTRS +} est_op_t; + /** * TLS Interface for sending and receiving HTTPS messages */ diff --git a/src/pki/pki_cert.c b/src/pki/pki_cert.c index 202284e43e..34f8d4691b 100644 --- a/src/pki/pki_cert.c +++ b/src/pki/pki_cert.c @@ -73,11 +73,15 @@ static bool print_cert_info(certificate_t *cert, pki_cert_type_t cert_type) char digest_buf[HASH_SIZE_SHA256]; char base64_buf[HASH_SIZE_SHA256]; chunk_t cert_digest = {digest_buf, HASH_SIZE_SHA256}; - chunk_t cert_id, encoding = chunk_empty; + chunk_t cert_id, serial, encoding = chunk_empty; + x509_t *x509; bool success = FALSE; DBG1(DBG_APP, "%s cert \"%Y\"", cert_type_label[cert_type], cert->get_subject(cert)); + x509 = (x509_t*)cert; + serial = x509->get_serial(x509); + DBG1(DBG_APP, " serial: %#B", &serial); if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoding)) { @@ -299,26 +303,53 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, { enumerator_t *enumerator; pkcs7_t *pkcs7 = (pkcs7_t*)container; + certificate_t *cert_found; + enumerator_t *certs; + bool trusted; enumerator = pkcs7->create_cert_enumerator(pkcs7); while (enumerator->enumerate(enumerator, &cert)) { + trusted = FALSE; + cert_type = get_pki_cert_type(cert); if (cert_type == CERT_TYPE_ROOT_CA) { - /* trust in root CA has to be established manuallly */ - creds->add_cert(creds, TRUE, cert->get_ref(cert)); - - cert_type_count[cert_type]++; - if (!print_cert_info(cert, cert_type)) { goto end; } + + /* same root CA as trusted TLS root CA already in cred set? */ + certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, + KEY_ANY, cert->get_subject(cert), FALSE); + while (certs->enumerate(certs, &cert_found, NULL)) + { + if (cert->equals(cert, cert_found)) + { + DBG1(DBG_APP, "Root CA equals trusted TLS Root CA"); + trusted = TRUE; + break; + } + else + { + DBG1(DBG_APP, "non-matching TLS Root CA of same name"); + } + } + certs->destroy(certs); + + /* otherwise trust in root CA has to be established manuallly */ + if (!trusted) + { + creds->add_cert(creds, TRUE, cert->get_ref(cert)); + trusted = FALSE; + } + cert_type_count[cert_type]++; + if (build_pathname(&path, cert_type, cert_type_count, caout, raout, form)) { - written = write_cert(cert, cert_type, FALSE, path, form, + written = write_cert(cert, cert_type, trusted, path, form, force); free(path); } @@ -344,24 +375,31 @@ bool pki_cert_extract_cacerts(chunk_t data, char *caout, char *raout, while (enumerator->enumerate(enumerator, &cert)) { written = FALSE; + trusted = FALSE; cert_type = get_pki_cert_type(cert); if (cert_type != CERT_TYPE_ROOT_CA) { - certificate_t *cert_found = NULL; - enumerator_t *certs; - bool trusted; - if (!print_cert_info(cert, cert_type)) { break; } - /* establish trust relativ to root CA */ + /* establish trust relative to root CA */ certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, KEY_ANY, cert->get_subject(cert), FALSE); - trusted = certs->enumerate(certs, &cert_found, NULL) && - (cert_found == cert); + while (certs->enumerate(certs, &cert_found, NULL)) + { + if (cert->equals(cert, cert_found)) + { + trusted = TRUE; + break; + } + else + { + DBG1(DBG_APP, "non-matching TLS Sub CA of same name"); + } + } certs->destroy(certs); cert_type_count[cert_type]++; @@ -434,7 +472,7 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form) if (!(x509->get_flags(x509) & X509_CA)) { - DBG1(DBG_APP, "issued certificate \"%Y\"", cert->get_subject(cert)); + DBG1(DBG_APP, "Issued certificate \"%Y\"", cert->get_subject(cert)); serial = x509->get_serial(x509); DBG1(DBG_APP, " serial: %#B", &serial); @@ -453,7 +491,7 @@ bool pki_cert_extract_cert(chunk_t data, cred_encoding_type_t form) certs->destroy(certs); valid = cert->get_validity(cert, NULL, &from, &until); - DBG1(DBG_APP, "issued certificate is %strusted, " + DBG1(DBG_APP, "Issued certificate is %strusted, " "valid from %T until %T (currently %svalid)", trusted ? "" : "not ", &from, FALSE, &until, FALSE, valid ? "" : "not ");