From 8d9a4d833f12b0669f053a504268d13a46c079ad Mon Sep 17 00:00:00 2001 From: "Dr. David von Oheimb" Date: Fri, 3 Apr 2020 10:43:58 +0200 Subject: [PATCH] Chunk 11 of CMP contribution to OpenSSL: CMP command-line interface Certificate Management Protocol (CMP, RFC 4210) extension to OpenSSL Also includes CRMF (RFC 4211) and HTTP transfer (RFC 6712). Adds the CMP and CRMF API to libcrypto and the "cmp" app to the CLI. Adds extensive documentation and tests. Reviewed-by: Matt Caswell Reviewed-by: David von Oheimb (Merged from https://github.com/openssl/openssl/pull/11470) --- CHANGES.md | 6 +- NEWS.md | 2 +- apps/build.info | 2 +- apps/cmp.c | 3329 +++++++++++++++++++++++++++++++++++ apps/openssl-vms.cnf | 56 + apps/openssl.cnf | 56 + doc/man1/build.info | 3 + doc/man1/openssl-cmp.pod.in | 1157 ++++++++++++ test/insta.priv.pem | 27 + test/insta_ca.cert.pem | 22 + 10 files changed, 4655 insertions(+), 5 deletions(-) create mode 100644 apps/cmp.c create mode 100644 doc/man1/openssl-cmp.pod.in create mode 100755 test/insta.priv.pem create mode 100755 test/insta_ca.cert.pem diff --git a/CHANGES.md b/CHANGES.md index 51ed264cb0..6ee0b1efde 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -93,10 +93,10 @@ OpenSSL 3.0 *Richard Levitte* * Added an implementation of CMP and CRMF (RFC 4210, RFC 4211 RFC 6712). - This adds crypto/cmp/, crpyto/crmf/, and test/cmp_*. - See L as starting point. + This adds crypto/cmp/, crpyto/crmf/, apps/cmp.c, and test/cmp_*. + See L and L as starting points. - *David von Oheimb* + *David von Oheimb, Martin Peylo* * Generalized the HTTP client code from crypto/ocsp/ into crpyto/http/. The legacy OCSP-focused and only partly documented API is retained. diff --git a/NEWS.md b/NEWS.md index ec5e754e0b..c09e9599a4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,7 +34,7 @@ OpenSSL 3.0 disabled; the project uses address sanitize/leak-detect instead. * Added a Certificate Management Protocol (CMP, RFC 4210) implementation also covering CRMF (RFC 4211) and HTTP transfer (RFC 6712). - It is part of the crypto lib, while a 'cmp' app using it is in preparation. + It is part of the crypto lib and adds a 'cmp' app with a demo configuration. All widely used CMP features are supported for both clients and servers. * Added a proper HTTP(S) client to libcrypto supporting GET and POST, redirection, plain and ASN.1-encoded contents, proxies, and timeouts. diff --git a/apps/build.info b/apps/build.info index f2c62c94dc..d51e825bc5 100644 --- a/apps/build.info +++ b/apps/build.info @@ -52,7 +52,7 @@ IF[{- !$disabled{'deprecated-3.0'} -}] ENDIF ENDIF IF[{- !$disabled{'cmp'} -}] - $OPENSSLSRC=$OPENSSLSRC cmp_mock_srv.c + $OPENSSLSRC=$OPENSSLSRC cmp.c cmp_mock_srv.c ENDIF IF[{- !$disabled{apps} -}] diff --git a/apps/cmp.c b/apps/cmp.c new file mode 100644 index 0000000000..24a7fcbe6c --- /dev/null +++ b/apps/cmp.c @@ -0,0 +1,3329 @@ +/* + * Copyright 2007-2019 The OpenSSL Project Authors. All Rights Reserved. + * Copyright Nokia 2007-2019 + * Copyright Siemens AG 2015-2019 + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include +#include + +#include "apps.h" +#include "http_server.h" +#include "s_apps.h" +#include "progs.h" + +#include "cmp_mock_srv.h" + +/* tweaks needed due to missing unistd.h on Windows */ +#ifdef _WIN32 +# define access _access +#endif +#ifndef F_OK +# define F_OK 0 +#endif + +#include +#include +#include + +/* explicit #includes not strictly needed since implied by the above: */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DEFINE_STACK_OF(X509) +DEFINE_STACK_OF(X509_EXTENSION) +DEFINE_STACK_OF(OSSL_CMP_ITAV) + +/* start TODO remove when PR #11755 is merged */ +static char *get_passwd(const char *pass, const char *desc) +{ + char *result = NULL; + + app_passwd(pass, NULL, &result, NULL); + return result; +} + +static void cleanse(char *str) +{ + if (str != NULL) + OPENSSL_cleanse(str, strlen(str)); +} + +static void clear_free(char *str) +{ + if (str != NULL) + OPENSSL_clear_free(str, strlen(str)); +} + +static int load_key_cert_crl(const char *uri, int maybe_stdin, + const char *pass, const char *desc, + EVP_PKEY **ppkey, X509 **pcert, X509_CRL **pcrl) +{ + PW_CB_DATA uidata; + OSSL_STORE_CTX *ctx = NULL; + int ret = 0; + + if (ppkey != NULL) + *ppkey = NULL; + if (pcert != NULL) + *pcert = NULL; + if (pcrl != NULL) + *pcrl = NULL; + + uidata.password = pass; + uidata.prompt_info = uri; + + ctx = OSSL_STORE_open(uri, get_ui_method(), &uidata, NULL, NULL); + if (ctx == NULL) { + BIO_printf(bio_err, "Could not open file or uri %s for loading %s\n", + uri, desc); + goto end; + } + + for (;;) { + OSSL_STORE_INFO *info = OSSL_STORE_load(ctx); + int type = info == NULL ? 0 : OSSL_STORE_INFO_get_type(info); + const char *infostr = + info == NULL ? NULL : OSSL_STORE_INFO_type_string(type); + int err = 0; + + if (info == NULL) { + if (OSSL_STORE_eof(ctx)) + ret = 1; + break; + } + + switch (type) { + case OSSL_STORE_INFO_PKEY: + if (ppkey != NULL && *ppkey == NULL) + err = ((*ppkey = OSSL_STORE_INFO_get1_PKEY(info)) == NULL); + break; + case OSSL_STORE_INFO_CERT: + if (pcert != NULL && *pcert == NULL) + err = ((*pcert = OSSL_STORE_INFO_get1_CERT(info)) == NULL); + break; + case OSSL_STORE_INFO_CRL: + if (pcrl != NULL && *pcrl == NULL) + err = ((*pcrl = OSSL_STORE_INFO_get1_CRL(info)) == NULL); + break; + default: + /* skip any other type */ + break; + } + OSSL_STORE_INFO_free(info); + if (err) { + BIO_printf(bio_err, "Could not read %s of %s from %s\n", + infostr, desc, uri); + break; + } + } + + end: + if (ctx != NULL) + OSSL_STORE_close(ctx); + if (!ret) + ERR_print_errors(bio_err); + return ret; +} + +static +EVP_PKEY *load_key_preliminary(const char *uri, int format, int may_stdin, + const char *pass, ENGINE *e, const char *desc) +{ + EVP_PKEY *pkey = NULL; + + if (desc == NULL) + desc = "private key"; + + if (format == FORMAT_ENGINE) { + if (e == NULL) { + BIO_printf(bio_err, "No engine specified for loading %s\n", desc); + } else { +#ifndef OPENSSL_NO_ENGINE + PW_CB_DATA cb_data; + + cb_data.password = pass; + cb_data.prompt_info = uri; + if (ENGINE_init(e)) { + pkey = ENGINE_load_private_key(e, uri, + (UI_METHOD *)get_ui_method(), + &cb_data); + ENGINE_finish(e); + } + if (pkey == NULL) { + BIO_printf(bio_err, "Cannot load %s from engine\n", desc); + ERR_print_errors(bio_err); + } +#else + BIO_printf(bio_err, "Engines not supported for loading %s\n", desc); +#endif + } + } else { + (void)load_key_cert_crl(uri, may_stdin, pass, desc, &pkey, NULL, NULL); + } + + if (pkey == NULL) { + BIO_printf(bio_err, "Unable to load %s\n", desc); + ERR_print_errors(bio_err); + } + return pkey; +} + +static X509 *load_cert_pass(const char *uri, int maybe_stdin, + const char *pass, const char *desc) +{ + X509 *cert = NULL; + + if (desc == NULL) + desc = "certificate"; + (void)load_key_cert_crl(uri, maybe_stdin, pass, desc, NULL, &cert, NULL); + if (cert == NULL) { + BIO_printf(bio_err, "Unable to load %s\n", desc); + ERR_print_errors(bio_err); + } + return cert; +} +/* end TODO remove when PR #11755 is merged */ + +static char *opt_config = NULL; +#define CMP_SECTION "cmp" +#define SECTION_NAME_MAX 40 /* max length of section name */ +#define DEFAULT_SECTION "default" +static char *opt_section = CMP_SECTION; + +#undef PROG +#define PROG cmp_main +static char *prog = "cmp"; + +static int read_config(void); + +static CONF *conf = NULL; /* OpenSSL config file context structure */ +static OSSL_CMP_CTX *cmp_ctx = NULL; /* the client-side CMP context */ + +/* TODO remove when new setup_engine_flags() is in apps/lib/apps.c (PR #4277) */ +static +ENGINE *setup_engine_flags(const char *engine, unsigned int flags, int debug) +{ + return setup_engine(engine, debug); +} + +/* the type of cmp command we want to send */ +typedef enum { + CMP_IR, + CMP_KUR, + CMP_CR, + CMP_P10CR, + CMP_RR, + CMP_GENM +} cmp_cmd_t; + +/* message transfer */ +static char *opt_server = NULL; +static char server_port_s[32] = { '\0' }; +static int server_port = 0; +static char *opt_proxy = NULL; +static char *opt_no_proxy = NULL; +static char *opt_path = "/"; +static int opt_msg_timeout = -1; +static int opt_total_timeout = -1; + +/* server authentication */ +static char *opt_trusted = NULL; +static char *opt_untrusted = NULL; +static char *opt_srvcert = NULL; +static char *opt_recipient = NULL; +static char *opt_expect_sender = NULL; +static int opt_ignore_keyusage = 0; +static int opt_unprotected_errors = 0; +static char *opt_extracertsout = NULL; +static char *opt_cacertsout = NULL; + +/* client authentication */ +static char *opt_ref = NULL; +static char *opt_secret = NULL; +static char *opt_cert = NULL; +static char *opt_key = NULL; +static char *opt_keypass = NULL; +static char *opt_digest = NULL; +static char *opt_mac = NULL; +static char *opt_extracerts = NULL; +static int opt_unprotected_requests = 0; + +/* generic message */ +static char *opt_cmd_s = NULL; +static int opt_cmd = -1; +static char *opt_geninfo = NULL; +static char *opt_infotype_s = NULL; +static int opt_infotype = NID_undef; + +/* certificate enrollment */ +static char *opt_newkey = NULL; +static char *opt_newkeypass = NULL; +static char *opt_subject = NULL; +static char *opt_issuer = NULL; +static int opt_days = 0; +static char *opt_reqexts = NULL; +static char *opt_sans = NULL; +static int opt_san_nodefault = 0; +static char *opt_policies = NULL; +static char *opt_policy_oids = NULL; +static int opt_policy_oids_critical = 0; +static int opt_popo = OSSL_CRMF_POPO_NONE - 1; +static char *opt_csr = NULL; +static char *opt_out_trusted = NULL; +static int opt_implicit_confirm = 0; +static int opt_disable_confirm = 0; +static char *opt_certout = NULL; + +/* certificate enrollment and revocation */ +static char *opt_oldcert = NULL; +static int opt_revreason = CRL_REASON_NONE; + +/* credentials format */ +static char *opt_certform_s = "PEM"; +static int opt_certform = FORMAT_PEM; +static char *opt_keyform_s = "PEM"; +static int opt_keyform = FORMAT_PEM; +static char *opt_certsform_s = "PEM"; +static int opt_certsform = FORMAT_PEM; +static char *opt_otherpass = NULL; +static char *opt_engine = NULL; + +/* TLS connection */ +static int opt_tls_used = 0; +static char *opt_tls_cert = NULL; +static char *opt_tls_key = NULL; +static char *opt_tls_keypass = NULL; +static char *opt_tls_extra = NULL; +static char *opt_tls_trusted = NULL; +static char *opt_tls_host = NULL; + +/* client-side debugging */ +static int opt_batch = 0; +static int opt_repeat = 1; +static char *opt_reqin = NULL; +static char *opt_reqout = NULL; +static char *opt_rspin = NULL; +static char *opt_rspout = NULL; +static int opt_use_mock_srv = 0; + +/* server-side debugging */ +static char *opt_port = NULL; +static int opt_max_msgs = 0; + +static char *opt_srv_ref = NULL; +static char *opt_srv_secret = NULL; +static char *opt_srv_cert = NULL; +static char *opt_srv_key = NULL; +static char *opt_srv_keypass = NULL; + +static char *opt_srv_trusted = NULL; +static char *opt_srv_untrusted = NULL; +static char *opt_rsp_cert = NULL; +static char *opt_rsp_extracerts = NULL; +static char *opt_rsp_capubs = NULL; +static int opt_poll_count = 0; +static int opt_check_after = 1; +static int opt_grant_implicitconf = 0; + +static int opt_pkistatus = OSSL_CMP_PKISTATUS_accepted; +static int opt_failure = INT_MIN; +static int opt_failurebits = 0; +static char *opt_statusstring = NULL; +static int opt_send_error = 0; +static int opt_send_unprotected = 0; +static int opt_send_unprot_err = 0; +static int opt_accept_unprotected = 0; +static int opt_accept_unprot_err = 0; +static int opt_accept_raverified = 0; + +static X509_VERIFY_PARAM *vpm = NULL; + +typedef enum OPTION_choice { + OPT_ERR = -1, OPT_EOF = 0, OPT_HELP, + OPT_CONFIG, OPT_SECTION, + + OPT_CMD, OPT_INFOTYPE, OPT_GENINFO, + + OPT_NEWKEY, OPT_NEWKEYPASS, OPT_SUBJECT, OPT_ISSUER, + OPT_DAYS, OPT_REQEXTS, + OPT_SANS, OPT_SAN_NODEFAULT, + OPT_POLICIES, OPT_POLICY_OIDS, OPT_POLICY_OIDS_CRITICAL, + OPT_POPO, OPT_CSR, + OPT_OUT_TRUSTED, OPT_IMPLICIT_CONFIRM, OPT_DISABLE_CONFIRM, + OPT_CERTOUT, + + OPT_OLDCERT, OPT_REVREASON, + + OPT_SERVER, OPT_PROXY, OPT_NO_PROXY, OPT_PATH, + OPT_MSG_TIMEOUT, OPT_TOTAL_TIMEOUT, + + OPT_TRUSTED, OPT_UNTRUSTED, OPT_SRVCERT, + OPT_RECIPIENT, OPT_EXPECT_SENDER, + OPT_IGNORE_KEYUSAGE, OPT_UNPROTECTED_ERRORS, + OPT_EXTRACERTSOUT, OPT_CACERTSOUT, + + OPT_REF, OPT_SECRET, OPT_CERT, OPT_KEY, OPT_KEYPASS, + OPT_DIGEST, OPT_MAC, OPT_EXTRACERTS, + OPT_UNPROTECTED_REQUESTS, + + OPT_CERTFORM, OPT_KEYFORM, OPT_CERTSFORM, + OPT_OTHERPASS, +#ifndef OPENSSL_NO_ENGINE + OPT_ENGINE, +#endif + OPT_PROV_ENUM, + + OPT_TLS_USED, OPT_TLS_CERT, OPT_TLS_KEY, + OPT_TLS_KEYPASS, + OPT_TLS_EXTRA, OPT_TLS_TRUSTED, OPT_TLS_HOST, + + OPT_BATCH, OPT_REPEAT, + OPT_REQIN, OPT_REQOUT, OPT_RSPIN, OPT_RSPOUT, + + OPT_USE_MOCK_SRV, OPT_PORT, OPT_MAX_MSGS, + OPT_SRV_REF, OPT_SRV_SECRET, + OPT_SRV_CERT, OPT_SRV_KEY, OPT_SRV_KEYPASS, + OPT_SRV_TRUSTED, OPT_SRV_UNTRUSTED, + OPT_RSP_CERT, OPT_RSP_EXTRACERTS, OPT_RSP_CAPUBS, + OPT_POLL_COUNT, OPT_CHECK_AFTER, + OPT_GRANT_IMPLICITCONF, + OPT_PKISTATUS, OPT_FAILURE, + OPT_FAILUREBITS, OPT_STATUSSTRING, + OPT_SEND_ERROR, OPT_SEND_UNPROTECTED, + OPT_SEND_UNPROT_ERR, OPT_ACCEPT_UNPROTECTED, + OPT_ACCEPT_UNPROT_ERR, OPT_ACCEPT_RAVERIFIED, + + OPT_V_ENUM +} OPTION_CHOICE; + +const OPTIONS cmp_options[] = { + /* entries must be in the same order as enumerated above!! */ + {"help", OPT_HELP, '-', "Display this summary"}, + {"config", OPT_CONFIG, 's', + "Configuration file to use. \"\" = none. Default from env variable OPENSSL_CONF"}, + {"section", OPT_SECTION, 's', + "Section(s) in config file to get options from. \"\" = 'default'. Default 'cmp'"}, + + OPT_SECTION("Generic message"), + {"cmd", OPT_CMD, 's', "CMP request to send: ir/cr/kur/p10cr/rr/genm"}, + {"infotype", OPT_INFOTYPE, 's', + "InfoType name for requesting specific info in genm, e.g. 'signKeyPairTypes'"}, + {"geninfo", OPT_GENINFO, 's', + "generalInfo integer values to place in request PKIHeader with given OID"}, + {OPT_MORE_STR, 0, 0, + "specified in the form :int:, e.g. \"1.2.3:int:987\""}, + + OPT_SECTION("Certificate enrollment"), + {"newkey", OPT_NEWKEY, 's', + "Private or public key for the requested cert. Default: CSR key or client key"}, + {"newkeypass", OPT_NEWKEYPASS, 's', "New private key pass phrase source"}, + {"subject", OPT_SUBJECT, 's', + "Distinguished Name (DN) of subject to use in the requested cert template"}, + {OPT_MORE_STR, 0, 0, + "For kur, default is the subject DN of the reference cert (see -oldcert);"}, + {OPT_MORE_STR, 0, 0, + "this default is used for ir and cr only if no Subject Alt Names are set"}, + {"issuer", OPT_ISSUER, 's', + "DN of the issuer to place in the requested certificate template"}, + {OPT_MORE_STR, 0, 0, + "also used as recipient if neither -recipient nor -srvcert are given"}, + {"days", OPT_DAYS, 'n', + "Requested validity time of the new certificate in number of days"}, + {"reqexts", OPT_REQEXTS, 's', + "Name of config file section defining certificate request extensions"}, + {"sans", OPT_SANS, 's', + "Subject Alt Names (IPADDR/DNS/URI) to add as (critical) cert req extension"}, + {"san_nodefault", OPT_SAN_NODEFAULT, '-', + "Do not take default SANs from reference certificate (see -oldcert)"}, + {"policies", OPT_POLICIES, 's', + "Name of config file section defining policies certificate request extension"}, + {"policy_oids", OPT_POLICY_OIDS, 's', + "Policy OID(s) to add as policies certificate request extension"}, + {"policy_oids_critical", OPT_POLICY_OIDS_CRITICAL, '-', + "Flag the policy OID(s) given with -policy_oids as critical"}, + {"popo", OPT_POPO, 'n', + "Proof-of-Possession (POPO) method to use for ir/cr/kur where"}, + {OPT_MORE_STR, 0, 0, + "-1 = NONE, 0 = RAVERIFIED, 1 = SIGNATURE (default), 2 = KEYENC"}, + {"csr", OPT_CSR, 's', + "CSR file in PKCS#10 format to use in p10cr for legacy support"}, + {"out_trusted", OPT_OUT_TRUSTED, 's', + "Certificates to trust when verifying newly enrolled certificates"}, + {"implicit_confirm", OPT_IMPLICIT_CONFIRM, '-', + "Request implicit confirmation of newly enrolled certificates"}, + {"disable_confirm", OPT_DISABLE_CONFIRM, '-', + "Do not confirm newly enrolled certificate w/o requesting implicit"}, + {OPT_MORE_STR, 0, 0, + "confirmation. WARNING: This leads to behavior violating RFC 4210"}, + {"certout", OPT_CERTOUT, 's', + "File to save newly enrolled certificate"}, + + OPT_SECTION("Certificate enrollment and revocation"), + + {"oldcert", OPT_OLDCERT, 's', + "Certificate to be updated (defaulting to -cert) or to be revoked in rr;"}, + {OPT_MORE_STR, 0, 0, + "also used as reference (defaulting to -cert) for subject DN and SANs."}, + {OPT_MORE_STR, 0, 0, + "Its issuer is used as recipient unless -srvcert, -recipient or -issuer given"}, + {"revreason", OPT_REVREASON, 'n', + "Reason code to include in revocation request (rr); possible values:"}, + {OPT_MORE_STR, 0, 0, + "0..6, 8..10 (see RFC5280, 5.3.1) or -1. Default -1 = none included"}, + + OPT_SECTION("Message transfer"), + {"server", OPT_SERVER, 's', + "[http[s]://]address[:port] of CMP server. Default port 80 or 443."}, + {OPT_MORE_STR, 0, 0, + "The address may be a DNS name or an IP address"}, + {"proxy", OPT_PROXY, 's', + "[http[s]://]address[:port][/path] of HTTP(S) proxy to use; path is ignored"}, + {"no_proxy", OPT_NO_PROXY, 's', + "List of addresses of servers not to use HTTP(S) proxy for"}, + {OPT_MORE_STR, 0, 0, + "Default from environment variable 'no_proxy', else 'NO_PROXY', else none"}, + {"path", OPT_PATH, 's', + "HTTP path (aka CMP alias) at the CMP server. Default \"/\""}, + {"msg_timeout", OPT_MSG_TIMEOUT, 'n', + "Timeout per CMP message round trip (or 0 for none). Default 120 seconds"}, + {"total_timeout", OPT_TOTAL_TIMEOUT, 'n', + "Overall time an enrollment incl. polling may take. Default 0 = infinite"}, + + OPT_SECTION("Server authentication"), + {"trusted", OPT_TRUSTED, 's', + "Trusted certs used for CMP server authentication when verifying responses"}, + {OPT_MORE_STR, 0, 0, "unless -srvcert is given"}, + {"untrusted", OPT_UNTRUSTED, 's', + "Intermediate certs for chain construction verifying CMP/TLS/enrolled certs"}, + {"srvcert", OPT_SRVCERT, 's', + "Specific CMP server cert to use and trust directly when verifying responses"}, + {"recipient", OPT_RECIPIENT, 's', + "Distinguished Name (DN) of the recipient to use unless -srvcert is given"}, + {"expect_sender", OPT_EXPECT_SENDER, 's', + "DN of expected response sender. Defaults to DN of -srvcert, if provided"}, + {"ignore_keyusage", OPT_IGNORE_KEYUSAGE, '-', + "Ignore CMP signer cert key usage, else 'digitalSignature' must be allowed"}, + {"unprotected_errors", OPT_UNPROTECTED_ERRORS, '-', + "Accept missing or invalid protection of regular error messages and negative"}, + {OPT_MORE_STR, 0, 0, + "certificate responses (ip/cp/kup), revocation responses (rp), and PKIConf"}, + {OPT_MORE_STR, 0, 0, + "WARNING: This setting leads to behavior allowing violation of RFC 4210"}, + {"extracertsout", OPT_EXTRACERTSOUT, 's', + "File to save extra certificates received in the extraCerts field"}, + {"cacertsout", OPT_CACERTSOUT, 's', + "File to save CA certificates received in the caPubs field of 'ip' messages"}, + + OPT_SECTION("Client authentication"), + {"ref", OPT_REF, 's', + "Reference value to use as senderKID in case no -cert is given"}, + {"secret", OPT_SECRET, 's', + "Password source for client authentication with a pre-shared key (secret)"}, + {"cert", OPT_CERT, 's', + "Client's current certificate (needed unless using -secret for PBM);"}, + {OPT_MORE_STR, 0, 0, + "any further certs included are appended in extraCerts field"}, + {"key", OPT_KEY, 's', "Private key for the client's current certificate"}, + {"keypass", OPT_KEYPASS, 's', + "Client private key (and cert and old cert file) pass phrase source"}, + {"digest", OPT_DIGEST, 's', + "Digest to use in message protection and POPO signatures. Default \"sha256\""}, + {"mac", OPT_MAC, 's', + "MAC algorithm to use in PBM-based message protection. Default \"hmac-sha1\""}, + {"extracerts", OPT_EXTRACERTS, 's', + "Certificates to append in extraCerts field of outgoing messages"}, + {"unprotected_requests", OPT_UNPROTECTED_REQUESTS, '-', + "Send messages without CMP-level protection"}, + + OPT_SECTION("Credentials format"), + {"certform", OPT_CERTFORM, 's', + "Format (PEM or DER) to use when saving a certificate to a file. Default PEM"}, + {OPT_MORE_STR, 0, 0, + "This also determines format to use for writing (not supported for P12)"}, + {"keyform", OPT_KEYFORM, 's', + "Format to assume when reading key files. Default PEM"}, + {"certsform", OPT_CERTSFORM, 's', + "Format (PEM/DER/P12) to try first reading multiple certs. Default PEM"}, + {"otherpass", OPT_OTHERPASS, 's', + "Pass phrase source potentially needed for loading certificates of others"}, +#ifndef OPENSSL_NO_ENGINE + {"engine", OPT_ENGINE, 's', + "Use crypto engine with given identifier, possibly a hardware device."}, + {OPT_MORE_STR, 0, 0, + "Engines may be defined in OpenSSL config file engine section."}, + {OPT_MORE_STR, 0, 0, + "Options like -key specifying keys held in the engine can give key IDs"}, + {OPT_MORE_STR, 0, 0, + "prefixed by 'engine:', e.g. '-key engine:pkcs11:object=mykey;pin-value=1234'"}, +#endif + OPT_PROV_OPTIONS, + + OPT_SECTION("TLS connection"), + {"tls_used", OPT_TLS_USED, '-', + "Enable using TLS (also when other TLS options are not set)"}, + {"tls_cert", OPT_TLS_CERT, 's', + "Client's TLS certificate. May include chain to be provided to TLS server"}, + {"tls_key", OPT_TLS_KEY, 's', + "Private key for the client's TLS certificate"}, + {"tls_keypass", OPT_TLS_KEYPASS, 's', + "Pass phrase source for the client's private TLS key (and TLS cert file)"}, + {"tls_extra", OPT_TLS_EXTRA, 's', + "Extra certificates to provide to TLS server during TLS handshake"}, + {"tls_trusted", OPT_TLS_TRUSTED, 's', + "Trusted certificates to use for verifying the TLS server certificate;"}, + {OPT_MORE_STR, 0, 0, "this implies host name validation"}, + {"tls_host", OPT_TLS_HOST, 's', + "Address to be checked (rather than -server) during TLS host name validation"}, + + OPT_SECTION("Client-side debugging"), + {"batch", OPT_BATCH, '-', + "Do not interactively prompt for input when a password is required etc."}, + {"repeat", OPT_REPEAT, 'n', + "Invoke the transaction the given number of times. Default 1"}, + {"reqin", OPT_REQIN, 's', "Take sequence of CMP requests from file(s)"}, + {"reqout", OPT_REQOUT, 's', "Save sequence of CMP requests to file(s)"}, + {"rspin", OPT_RSPIN, 's', + "Process sequence of CMP responses provided in file(s), skipping server"}, + {"rspout", OPT_RSPOUT, 's', "Save sequence of CMP responses to file(s)"}, + + {"use_mock_srv", OPT_USE_MOCK_SRV, '-', "Use mock server at API level, bypassing HTTP"}, + + OPT_SECTION("Mock server"), + {"port", OPT_PORT, 's', "Act as HTTP mock server listening on given port"}, + {"max_msgs", OPT_MAX_MSGS, 'n', + "max number of messages handled by HTTP mock server. Default: 0 = unlimited"}, + + {"srv_ref", OPT_SRV_REF, 's', + "Reference value to use as senderKID of server in case no -srv_cert is given"}, + {"srv_secret", OPT_SRV_SECRET, 's', + "Password source for server authentication with a pre-shared key (secret)"}, + {"srv_cert", OPT_SRV_CERT, 's', "Certificate of the server"}, + {"srv_key", OPT_SRV_KEY, 's', + "Private key used by the server for signing messages"}, + {"srv_keypass", OPT_SRV_KEYPASS, 's', + "Server private key (and cert) file pass phrase source"}, + + {"srv_trusted", OPT_SRV_TRUSTED, 's', + "Trusted certificates for client authentication"}, + {"srv_untrusted", OPT_SRV_UNTRUSTED, 's', + "Intermediate certs for constructing chains for CMP protection by client"}, + {"rsp_cert", OPT_RSP_CERT, 's', + "Certificate to be returned as mock enrollment result"}, + {"rsp_extracerts", OPT_RSP_EXTRACERTS, 's', + "Extra certificates to be included in mock certification responses"}, + {"rsp_capubs", OPT_RSP_CAPUBS, 's', + "CA certificates to be included in mock ip response"}, + {"poll_count", OPT_POLL_COUNT, 'n', + "Number of times the client must poll before receiving a certificate"}, + {"check_after", OPT_CHECK_AFTER, 'n', + "The check_after value (time to wait) to include in poll response"}, + {"grant_implicitconf", OPT_GRANT_IMPLICITCONF, '-', + "Grant implicit confirmation of newly enrolled certificate"}, + + {"pkistatus", OPT_PKISTATUS, 'n', + "PKIStatus to be included in server response. Possible values: 0..6"}, + {"failure", OPT_FAILURE, 'n', + "A single failure info bit number to include in server response, 0..26"}, + {"failurebits", OPT_FAILUREBITS, 'n', + "Number representing failure bits to include in server response, 0..2^27 - 1"}, + {"statusstring", OPT_STATUSSTRING, 's', + "Status string to be included in server response"}, + {"send_error", OPT_SEND_ERROR, '-', + "Force server to reply with error message"}, + {"send_unprotected", OPT_SEND_UNPROTECTED, '-', + "Send response messages without CMP-level protection"}, + {"send_unprot_err", OPT_SEND_UNPROT_ERR, '-', + "In case of negative responses, server shall send unprotected error messages,"}, + {OPT_MORE_STR, 0, 0, + "certificate responses (ip/cp/kup), and revocation responses (rp)."}, + {OPT_MORE_STR, 0, 0, + "WARNING: This setting leads to behavior violating RFC 4210"}, + {"accept_unprotected", OPT_ACCEPT_UNPROTECTED, '-', + "Accept missing or invalid protection of requests"}, + {"accept_unprot_err", OPT_ACCEPT_UNPROT_ERR, '-', + "Accept unprotected error messages from client"}, + {"accept_raverified", OPT_ACCEPT_RAVERIFIED, '-', + "Accept RAVERIFIED as proof-of-possession (POPO)"}, + + OPT_V_OPTIONS, + {NULL} +}; + +typedef union { + char **txt; + int *num; + long *num_long; +} varref; +static varref cmp_vars[] = { /* must be in same order as enumerated above! */ + {&opt_config}, {&opt_section}, + + {&opt_cmd_s}, {&opt_infotype_s}, {&opt_geninfo}, + + {&opt_newkey}, {&opt_newkeypass}, {&opt_subject}, {&opt_issuer}, + {(char **)&opt_days}, {&opt_reqexts}, + {&opt_sans}, {(char **)&opt_san_nodefault}, + {&opt_policies}, {&opt_policy_oids}, {(char **)&opt_policy_oids_critical}, + {(char **)&opt_popo}, {&opt_csr}, + {&opt_out_trusted}, + {(char **)&opt_implicit_confirm}, {(char **)&opt_disable_confirm}, + {&opt_certout}, + + {&opt_oldcert}, {(char **)&opt_revreason}, + + {&opt_server}, {&opt_proxy}, {&opt_no_proxy}, {&opt_path}, + {(char **)&opt_msg_timeout}, {(char **)&opt_total_timeout}, + + {&opt_trusted}, {&opt_untrusted}, {&opt_srvcert}, + {&opt_recipient}, {&opt_expect_sender}, + {(char **)&opt_ignore_keyusage}, {(char **)&opt_unprotected_errors}, + {&opt_extracertsout}, {&opt_cacertsout}, + + {&opt_ref}, {&opt_secret}, {&opt_cert}, {&opt_key}, {&opt_keypass}, + {&opt_digest}, {&opt_mac}, {&opt_extracerts}, + {(char **)&opt_unprotected_requests}, + + {&opt_certform_s}, {&opt_keyform_s}, {&opt_certsform_s}, + {&opt_otherpass}, +#ifndef OPENSSL_NO_ENGINE + {&opt_engine}, +#endif + + {(char **)&opt_tls_used}, {&opt_tls_cert}, {&opt_tls_key}, + {&opt_tls_keypass}, + {&opt_tls_extra}, {&opt_tls_trusted}, {&opt_tls_host}, + + {(char **)&opt_batch}, {(char **)&opt_repeat}, + {&opt_reqin}, {&opt_reqout}, {&opt_rspin}, {&opt_rspout}, + + {(char **)&opt_use_mock_srv}, {&opt_port}, {(char **)&opt_max_msgs}, + {&opt_srv_ref}, {&opt_srv_secret}, + {&opt_srv_cert}, {&opt_srv_key}, {&opt_srv_keypass}, + {&opt_srv_trusted}, {&opt_srv_untrusted}, + {&opt_rsp_cert}, {&opt_rsp_extracerts}, {&opt_rsp_capubs}, + {(char **)&opt_poll_count}, {(char **)&opt_check_after}, + {(char **)&opt_grant_implicitconf}, + {(char **)&opt_pkistatus}, {(char **)&opt_failure}, + {(char **)&opt_failurebits}, {&opt_statusstring}, + {(char **)&opt_send_error}, {(char **)&opt_send_unprotected}, + {(char **)&opt_send_unprot_err}, {(char **)&opt_accept_unprotected}, + {(char **)&opt_accept_unprot_err}, {(char **)&opt_accept_raverified}, + + {NULL} +}; + +#ifndef NDEBUG +# define FUNC (strcmp(OPENSSL_FUNC, "(unknown function)") == 0 \ + ? "CMP" : "OPENSSL_FUNC") +# define PRINT_LOCATION(bio) BIO_printf(bio, "%s:%s:%d:", \ + FUNC, OPENSSL_FILE, OPENSSL_LINE) +#else +# define PRINT_LOCATION(bio) ((void)0) +#endif +#define CMP_print(bio, prefix, msg, a1, a2, a3) \ + (PRINT_LOCATION(bio), \ + BIO_printf(bio, "CMP %s: " msg "\n", prefix, a1, a2, a3)) +#define CMP_INFO(msg, a1, a2, a3) CMP_print(bio_out, "info", msg, a1, a2, a3) +#define CMP_info(msg) CMP_INFO(msg"%s%s%s", "", "", "") +#define CMP_info1(msg, a1) CMP_INFO(msg"%s%s", a1, "", "") +#define CMP_info2(msg, a1, a2) CMP_INFO(msg"%s", a1, a2, "") +#define CMP_info3(msg, a1, a2, a3) CMP_INFO(msg, a1, a2, a3) +#define CMP_WARN(m, a1, a2, a3) CMP_print(bio_out, "warning", m, a1, a2, a3) +#define CMP_warn(msg) CMP_WARN(msg"%s%s%s", "", "", "") +#define CMP_warn1(msg, a1) CMP_WARN(msg"%s%s", a1, "", "") +#define CMP_warn2(msg, a1, a2) CMP_WARN(msg"%s", a1, a2, "") +#define CMP_warn3(msg, a1, a2, a3) CMP_WARN(msg, a1, a2, a3) +#define CMP_ERR(msg, a1, a2, a3) CMP_print(bio_err, "error", msg, a1, a2, a3) +#define CMP_err(msg) CMP_ERR(msg"%s%s%s", "", "", "") +#define CMP_err1(msg, a1) CMP_ERR(msg"%s%s", a1, "", "") +#define CMP_err2(msg, a1, a2) CMP_ERR(msg"%s", a1, a2, "") +#define CMP_err3(msg, a1, a2, a3) CMP_ERR(msg, a1, a2, a3) + +static int print_to_bio_out(const char *func, const char *file, int line, + OSSL_CMP_severity level, const char *msg) +{ + return OSSL_CMP_print_to_bio(bio_out, func, file, line, level, msg); +} + +/* code duplicated from crypto/cmp/cmp_util.c */ +static int sk_X509_add1_cert(STACK_OF(X509) *sk, X509 *cert, + int no_dup, int prepend) +{ + if (no_dup) { + /* + * not using sk_X509_set_cmp_func() and sk_X509_find() + * because this re-orders the certs on the stack + */ + int i; + + for (i = 0; i < sk_X509_num(sk); i++) { + if (X509_cmp(sk_X509_value(sk, i), cert) == 0) + return 1; + } + } + if (!X509_up_ref(cert)) + return 0; + if (!sk_X509_insert(sk, cert, prepend ? 0 : -1)) { + X509_free(cert); + return 0; + } + return 1; +} + +/* code duplicated from crypto/cmp/cmp_util.c */ +static int sk_X509_add1_certs(STACK_OF(X509) *sk, STACK_OF(X509) *certs, + int no_self_signed, int no_dups, int prepend) +/* compiler would allow 'const' for the list of certs, yet they are up-ref'ed */ +{ + int i; + + if (sk == NULL) + return 0; + if (certs == NULL) + return 1; + for (i = 0; i < sk_X509_num(certs); i++) { + X509 *cert = sk_X509_value(certs, i); + + if (!no_self_signed || X509_check_issued(cert, cert) != X509_V_OK) { + if (!sk_X509_add1_cert(sk, cert, no_dups, prepend)) + return 0; + } + } + return 1; +} + +/* TODO potentially move to apps/lib/apps.c */ +static char *next_item(char *opt) /* in list separated by comma and/or space */ +{ + /* advance to separator (comma or whitespace), if any */ + while (*opt != ',' && !isspace(*opt) && *opt != '\0') { + if (*opt == '\\' && opt[1] != '\0') + /* skip and unescape '\' escaped char */ + memmove(opt, opt + 1, strlen(opt)); + opt++; + } + if (*opt != '\0') { + /* terminate current item */ + *opt++ = '\0'; + /* skip over any whitespace after separator */ + while (isspace(*opt)) + opt++; + } + return *opt == '\0' ? NULL : opt; /* NULL indicates end of input */ +} + +static EVP_PKEY *load_key_pwd(const char *uri, int format, + const char *pass, ENGINE *e, const char *desc) +{ + char *pass_string = get_passwd(pass, desc); + EVP_PKEY *pkey = load_key_preliminary(uri, format, 0, pass_string, e, desc); + + clear_free(pass_string); + return pkey; +} + +static X509 *load_cert_pwd(const char *uri, const char *pass, const char *desc) +{ + X509 *cert; + char *pass_string = get_passwd(pass, desc); + + cert = load_cert_pass(uri, 0, pass_string, desc); + clear_free(pass_string); + return cert; +} + +/* TODO remove when PR #4930 is merged */ +static int load_pkcs12(BIO *in, const char *desc, + pem_password_cb *pem_cb, void *cb_data, + EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca) +{ + const char *pass; + char tpass[PEM_BUFSIZE]; + int len; + int ret = 0; + PKCS12 *p12 = d2i_PKCS12_bio(in, NULL); + + if (desc == NULL) + desc = "PKCS12 input"; + if (p12 == NULL) { + BIO_printf(bio_err, "error loading PKCS12 file for %s\n", desc); + goto die; + } + + /* See if an empty password will do */ + if (PKCS12_verify_mac(p12, "", 0) || PKCS12_verify_mac(p12, NULL, 0)) { + pass = ""; + } else { + if (pem_cb == NULL) + pem_cb = wrap_password_callback; + len = pem_cb(tpass, PEM_BUFSIZE, 0, cb_data); + if (len < 0) { + BIO_printf(bio_err, "passphrase callback error for %s\n", desc); + goto die; + } + if (len < PEM_BUFSIZE) + tpass[len] = 0; + if (!PKCS12_verify_mac(p12, tpass, len)) { + BIO_printf(bio_err, + "mac verify error (wrong password?) in PKCS12 file for %s\n", + desc); + goto die; + } + pass = tpass; + } + ret = PKCS12_parse(p12, pass, pkey, cert, ca); + die: + PKCS12_free(p12); + return ret; +} + +/* TODO potentially move this and related functions to apps/lib/apps.c */ +static int adjust_format(const char **infile, int format, int engine_ok) +{ + if (!strncasecmp(*infile, "http://", 7) + || !strncasecmp(*infile, "https://", 8)) { + format = FORMAT_HTTP; + } else if (engine_ok && strncasecmp(*infile, "engine:", 7) == 0) { + *infile += 7; + format = FORMAT_ENGINE; + } else { + if (strncasecmp(*infile, "file:", 5) == 0) + *infile += 5; + /* + * the following is a heuristic whether first to try PEM or DER + * or PKCS12 as the input format for files + */ + if (strlen(*infile) >= 4) { + const char *extension = *infile + strlen(*infile) - 4; + + if (strncasecmp(extension, ".crt", 4) == 0 + || strncasecmp(extension, ".pem", 4) == 0) + /* weak recognition of PEM format */ + format = FORMAT_PEM; + else if (strncasecmp(extension, ".cer", 4) == 0 + || strncasecmp(extension, ".der", 4) == 0) + /* weak recognition of DER format */ + format = FORMAT_ASN1; + else if (strncasecmp(extension, ".p12", 4) == 0) + /* weak recognition of PKCS#12 format */ + format = FORMAT_PKCS12; + /* else retain given format */ + } + } + return format; +} + +/* + * TODO potentially move this and related functions to apps/lib/ + * or even better extend OSSL_STORE with type OSSL_STORE_INFO_CRL + */ +static X509_REQ *load_csr_autofmt(const char *infile, const char *desc) +{ + X509_REQ *csr; + BIO *bio_bak = bio_err; + int can_retry; + int format = adjust_format(&infile, FORMAT_PEM, 0); + + can_retry = format == FORMAT_PEM || format == FORMAT_ASN1; + if (can_retry) + bio_err = NULL; /* do not show errors on more than one try */ + csr = load_csr(infile, format, desc); + bio_err = bio_bak; + if (csr == NULL && can_retry) { + ERR_clear_error(); + format = (format == FORMAT_PEM ? FORMAT_ASN1 : FORMAT_PEM); + csr = load_csr(infile, format, desc); + } + if (csr == NULL) { + ERR_print_errors(bio_err); + BIO_printf(bio_err, "error: unable to load %s from file '%s'\n", desc, + infile); + } + return csr; +} + +/* TODO replace by calling generalized load_certs() when PR #4930 is merged */ +static int load_certs_preliminary(const char *file, STACK_OF(X509) **certs, + int format, const char *pass, + const char *desc) +{ + X509 *cert = NULL; + int ret = 0; + + if (format == FORMAT_PKCS12) { + BIO *bio = bio_open_default(file, 'r', format); + + if (bio != NULL) { + EVP_PKEY *pkey = NULL; /* pkey is needed until PR #4930 is merged */ + PW_CB_DATA cb_data; + + cb_data.password = pass; + cb_data.prompt_info = file; + ret = load_pkcs12(bio, desc, wrap_password_callback, + &cb_data, &pkey, &cert, certs); + EVP_PKEY_free(pkey); + BIO_free(bio); + } + } else if (format == FORMAT_ASN1) { /* load only one cert in this case */ + CMP_warn1("can load only one certificate in DER format from %s", file); + cert = load_cert_pass(file, 0, pass, desc); + } + if (format == FORMAT_PKCS12 || format == FORMAT_ASN1) { + if (cert) { + if (*certs == NULL) + *certs = sk_X509_new_null(); + if (*certs != NULL) + ret = sk_X509_insert(*certs, cert, 0); + else + X509_free(cert); + } + } else { + ret = load_certs(file, certs, format, pass, desc); + } + return ret; +} + +static void warn_certs_expired(const char *file, STACK_OF(X509) **certs) +{ + int i, res; + X509 *cert; + char *subj; + + for (i = 0; i < sk_X509_num(*certs); i++) { + cert = sk_X509_value(*certs, i); + res = X509_cmp_timeframe(vpm, X509_get0_notBefore(cert), + X509_get0_notAfter(cert)); + if (res != 0) { + subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + CMP_warn3("certificate from '%s' with subject '%s' %s", file, subj, + res > 0 ? "has expired" : "not yet valid"); + OPENSSL_free(subj); + } + } +} + +/* + * TODO potentially move this and related functions to apps/lib/ + * or even better extend OSSL_STORE with type OSSL_STORE_INFO_CERTS + */ +static int load_certs_autofmt(const char *infile, STACK_OF(X509) **certs, + int exclude_http, const char *pass, + const char *desc) +{ + int ret = 0; + char *pass_string; + BIO *bio_bak = bio_err; + int format = adjust_format(&infile, opt_certsform, 0); + + if (exclude_http && format == FORMAT_HTTP) { + BIO_printf(bio_err, "error: HTTP retrieval not allowed for %s\n", desc); + return ret; + } + pass_string = get_passwd(pass, desc); + if (format != FORMAT_HTTP) + bio_err = NULL; /* do not show errors on more than one try */ + ret = load_certs_preliminary(infile, certs, format, pass_string, desc); + bio_err = bio_bak; + if (!ret && format != FORMAT_HTTP) { + int format2 = format == FORMAT_PEM ? FORMAT_ASN1 : FORMAT_PEM; + + ERR_clear_error(); + ret = load_certs_preliminary(infile, certs, format2, pass_string, desc); + } + clear_free(pass_string); + + if (ret) + warn_certs_expired(infile, certs); + return ret; +} + +/* set expected host name/IP addr and clears the email addr in the given ts */ +static int truststore_set_host_etc(X509_STORE *ts, char *host) +{ + X509_VERIFY_PARAM *ts_vpm = X509_STORE_get0_param(ts); + + /* first clear any host names, IP, and email addresses */ + if (!X509_VERIFY_PARAM_set1_host(ts_vpm, NULL, 0) + || !X509_VERIFY_PARAM_set1_ip(ts_vpm, NULL, 0) + || !X509_VERIFY_PARAM_set1_email(ts_vpm, NULL, 0)) + return 0; + X509_VERIFY_PARAM_set_hostflags(ts_vpm, + X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT | + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + return (host != NULL && X509_VERIFY_PARAM_set1_ip_asc(ts_vpm, host)) + || X509_VERIFY_PARAM_set1_host(ts_vpm, host, 0); +} + +static X509_STORE *sk_X509_to_store(X509_STORE *store /* may be NULL */, + const STACK_OF(X509) *certs /* may NULL */) +{ + int i; + + if (store == NULL) + store = X509_STORE_new(); + if (store == NULL) + return NULL; + for (i = 0; i < sk_X509_num(certs); i++) { + if (!X509_STORE_add_cert(store, sk_X509_value(certs, i))) { + X509_STORE_free(store); + return NULL; + } + } + return store; +} + +/* write OSSL_CMP_MSG DER-encoded to the specified file name item */ +static int write_PKIMESSAGE(const OSSL_CMP_MSG *msg, char **filenames) +{ + char *file; + BIO *bio; + + if (msg == NULL || filenames == NULL) { + CMP_err("NULL arg to write_PKIMESSAGE"); + return 0; + } + if (*filenames == NULL) { + CMP_err("Not enough file names provided for writing PKIMessage"); + return 0; + } + + file = *filenames; + *filenames = next_item(file); + bio = BIO_new_file(file, "wb"); + if (bio == NULL) { + CMP_err1("Cannot open file '%s' for writing", file); + return 0; + } + if (i2d_OSSL_CMP_MSG_bio(bio, msg) < 0) { + CMP_err1("Cannot write PKIMessage to file '%s'", file); + BIO_free(bio); + return 0; + } + BIO_free(bio); + return 1; +} + +/* read DER-encoded OSSL_CMP_MSG from the specified file name item */ +static OSSL_CMP_MSG *read_PKIMESSAGE(char **filenames) +{ + char *file; + BIO *bio; + OSSL_CMP_MSG *ret; + + if (filenames == NULL) { + CMP_err("NULL arg to read_PKIMESSAGE"); + return NULL; + } + if (*filenames == NULL) { + CMP_err("Not enough file names provided for reading PKIMessage"); + return NULL; + } + + file = *filenames; + *filenames = next_item(file); + bio = BIO_new_file(file, "rb"); + if (bio == NULL) { + CMP_err1("Cannot open file '%s' for reading", file); + return NULL; + } + ret = d2i_OSSL_CMP_MSG_bio(bio, NULL); + if (ret == NULL) + CMP_err1("Cannot read PKIMessage from file '%s'", file); + BIO_free(bio); + return ret; +} + +/*- + * Sends the PKIMessage req and on success place the response in *res + * basically like OSSL_CMP_MSG_http_perform(), but in addition allows + * to dump the sequence of requests and responses to files and/or + * to take the sequence of requests and responses from files. + */ +static OSSL_CMP_MSG *read_write_req_resp(OSSL_CMP_CTX *ctx, + const OSSL_CMP_MSG *req) +{ + OSSL_CMP_MSG *req_new = NULL; + OSSL_CMP_MSG *res = NULL; + OSSL_CMP_PKIHEADER *hdr; + + if (req != NULL && opt_reqout != NULL + && !write_PKIMESSAGE(req, &opt_reqout)) + goto err; + if (opt_reqin != NULL) { + if (opt_rspin != NULL) { + CMP_warn("-reqin is ignored since -rspin is present"); + } else { + if ((req_new = read_PKIMESSAGE(&opt_reqin)) == NULL) + goto err; + /*- + * The transaction ID in req_new may not be fresh. + * In this case the Insta Demo CA correctly complains: + * "Transaction id already in use." + * The following workaround unfortunately requires re-protection. + * See also https://github.com/mpeylo/cmpossl/issues/8 + */ +#if defined(USE_TRANSACTIONID_WORKAROUND) + hdr = OSSL_CMP_MSG_get0_header(req_new); + if (!OSSL_CMP_CTX_set1_transactionID(hdr, NULL) + || !ossl_cmp_msg_protect(ctx, req_new)) + goto err; +#endif + } + } + + if (opt_rspin != NULL) { + res = read_PKIMESSAGE(&opt_rspin); + } else { + const OSSL_CMP_MSG *actual_req = opt_reqin != NULL ? req_new : req; + + res = opt_use_mock_srv + ? OSSL_CMP_CTX_server_perform(ctx, actual_req) + : OSSL_CMP_MSG_http_perform(ctx, actual_req); + } + if (res == NULL) + goto err; + + if (opt_reqin != NULL || opt_rspin != NULL) { + /* need to satisfy nonce and transactionID checks */ + ASN1_OCTET_STRING *nonce; + ASN1_OCTET_STRING *tid; + + hdr = OSSL_CMP_MSG_get0_header(res); + nonce = OSSL_CMP_HDR_get0_recipNonce(hdr); + tid = OSSL_CMP_HDR_get0_transactionID(hdr); + if (!OSSL_CMP_CTX_set1_senderNonce(ctx, nonce) + || !OSSL_CMP_CTX_set1_transactionID(ctx, tid)) { + OSSL_CMP_MSG_free(res); + res = NULL; + goto err; + } + } + + if (opt_rspout != NULL && !write_PKIMESSAGE(res, &opt_rspout)) { + OSSL_CMP_MSG_free(res); + res = NULL; + } + + err: + OSSL_CMP_MSG_free(req_new); + return res; +} + +/* + * parse string as integer value, not allowing trailing garbage, see also + * https://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html + * + * returns integer value, or INT_MIN on error + */ +static int atoint(const char *str) +{ + char *tailptr; + long res = strtol(str, &tailptr, 10); + + if ((*tailptr != '\0') || (res < INT_MIN) || (res > INT_MAX)) + return INT_MIN; + else + return (int)res; +} + +static int parse_addr(char **opt_string, int port, const char *name) +{ + char *port_string; + + if (strncasecmp(*opt_string, OSSL_HTTP_PREFIX, + strlen(OSSL_HTTP_PREFIX)) == 0) { + *opt_string += strlen(OSSL_HTTP_PREFIX); + } else if (strncasecmp(*opt_string, OSSL_HTTPS_PREFIX, + strlen(OSSL_HTTPS_PREFIX)) == 0) { + *opt_string += strlen(OSSL_HTTPS_PREFIX); + if (port == 0) + port = 443; /* == integer value of OSSL_HTTPS_PORT */ + } + + if ((port_string = strrchr(*opt_string, ':')) == NULL) + return port; /* using default */ + *(port_string++) = '\0'; + port = atoint(port_string); + if ((port <= 0) || (port > 65535)) { + CMP_err2("invalid %s port '%s' given, sane range 1-65535", + name, port_string); + return -1; + } + return port; +} + +static int set1_store_parameters(X509_STORE *ts) +{ + if (ts == NULL) + return 0; + + /* copy vpm to store */ + if (!X509_STORE_set1_param(ts, vpm /* may be NULL */)) { + BIO_printf(bio_err, "error setting verification parameters\n"); + OSSL_CMP_CTX_print_errors(cmp_ctx); + return 0; + } + + X509_STORE_set_verify_cb(ts, X509_STORE_CTX_print_verify_cb); + + return 1; +} + +static int set_name(const char *str, + int (*set_fn) (OSSL_CMP_CTX *ctx, const X509_NAME *name), + OSSL_CMP_CTX *ctx, const char *desc) +{ + if (str != NULL) { + X509_NAME *n = parse_name(str, MBSTRING_ASC, 0); + + if (n == NULL) { + CMP_err2("cannot parse %s DN '%s'", desc, str); + return 0; + } + if (!(*set_fn) (ctx, n)) { + X509_NAME_free(n); + CMP_err("out of memory"); + return 0; + } + X509_NAME_free(n); + } + return 1; +} + +static int set_gennames(OSSL_CMP_CTX *ctx, char *names, const char *desc) +{ + char *next; + + for (; names != NULL; names = next) { + GENERAL_NAME *n; + + next = next_item(names); + if (strcmp(names, "critical") == 0) { + (void)OSSL_CMP_CTX_set_option(ctx, + OSSL_CMP_OPT_SUBJECTALTNAME_CRITICAL, + 1); + continue; + } + + /* try IP address first, then URI or domain name */ + (void)ERR_set_mark(); + n = a2i_GENERAL_NAME(NULL, NULL, NULL, GEN_IPADD, names, 0); + if (n == NULL) + n = a2i_GENERAL_NAME(NULL, NULL, NULL, + strchr(names, ':') != NULL ? GEN_URI : GEN_DNS, + names, 0); + (void)ERR_pop_to_mark(); + + if (n == NULL) { + CMP_err2("bad syntax of %s '%s'", desc, names); + return 0; + } + if (!OSSL_CMP_CTX_push1_subjectAltName(ctx, n)) { + GENERAL_NAME_free(n); + CMP_err("out of memory"); + return 0; + } + GENERAL_NAME_free(n); + } + return 1; +} + +/* TODO potentially move to apps/lib/apps.c */ +/* + * create cert store structure with certificates read from given file(s) + * returns pointer to created X509_STORE on success, NULL on error + */ +static X509_STORE *load_certstore(char *input, const char *desc) +{ + X509_STORE *store = NULL; + STACK_OF(X509) *certs = NULL; + + if (input == NULL) + goto err; + + while (input != NULL) { + char *next = next_item(input); \ + + if (!load_certs_autofmt(input, &certs, 1, opt_otherpass, desc) + || !(store = sk_X509_to_store(store, certs))) { + /* CMP_err("out of memory"); */ + X509_STORE_free(store); + store = NULL; + goto err; + } + sk_X509_pop_free(certs, X509_free); + certs = NULL; + input = next; + } + err: + sk_X509_pop_free(certs, X509_free); + return store; +} + +/* TODO potentially move to apps/lib/apps.c */ +static STACK_OF(X509) *load_certs_multifile(char *files, + const char *pass, const char *desc) +{ + STACK_OF(X509) *certs = NULL; + STACK_OF(X509) *result = sk_X509_new_null(); + + if (files == NULL) + goto err; + if (result == NULL) + goto oom; + + while (files != NULL) { + char *next = next_item(files); + + if (!load_certs_autofmt(files, &certs, 0, pass, desc)) + goto err; + if (!sk_X509_add1_certs(result, certs, 0, 1 /* no dups */, 0)) + goto oom; + sk_X509_pop_free(certs, X509_free); + certs = NULL; + files = next; + } + return result; + + oom: + BIO_printf(bio_err, "out of memory\n"); + err: + sk_X509_pop_free(certs, X509_free); + sk_X509_pop_free(result, X509_free); + return NULL; +} + +typedef int (*add_X509_stack_fn_t)(void *ctx, const STACK_OF(X509) *certs); +typedef int (*add_X509_fn_t)(void *ctx, const X509 *cert); + +static int setup_certs(char *files, const char *desc, void *ctx, + add_X509_stack_fn_t addn_fn, add_X509_fn_t add1_fn) +{ + int ret = 1; + + if (files != NULL) { + STACK_OF(X509) *certs = load_certs_multifile(files, opt_otherpass, + desc); + if (certs == NULL) { + ret = 0; + } else { + if (addn_fn != NULL) { + ret = (*addn_fn)(ctx, certs); + } else { + int i; + + for (i = 0; i < sk_X509_num(certs /* may be NULL */); i++) + ret &= (*add1_fn)(ctx, sk_X509_value(certs, i)); + } + sk_X509_pop_free(certs, X509_free); + } + } + return ret; +} + + +/* + * parse and transform some options, checking their syntax. + * Returns 1 on success, 0 on error + */ +static int transform_opts(void) +{ + if (opt_cmd_s != NULL) { + if (!strcmp(opt_cmd_s, "ir")) { + opt_cmd = CMP_IR; + } else if (!strcmp(opt_cmd_s, "kur")) { + opt_cmd = CMP_KUR; + } else if (!strcmp(opt_cmd_s, "cr")) { + opt_cmd = CMP_CR; + } else if (!strcmp(opt_cmd_s, "p10cr")) { + opt_cmd = CMP_P10CR; + } else if (!strcmp(opt_cmd_s, "rr")) { + opt_cmd = CMP_RR; + } else if (!strcmp(opt_cmd_s, "genm")) { + opt_cmd = CMP_GENM; + } else { + CMP_err1("unknown cmp command '%s'", opt_cmd_s); + return 0; + } + } else { + CMP_err("no cmp command to execute"); + return 0; + } + +#ifdef OPENSSL_NO_ENGINE +# define FORMAT_OPTIONS (OPT_FMT_PEMDER | OPT_FMT_PKCS12 | OPT_FMT_ENGINE) +#else +# define FORMAT_OPTIONS (OPT_FMT_PEMDER | OPT_FMT_PKCS12) +#endif + + if (opt_keyform_s != NULL + && !opt_format(opt_keyform_s, FORMAT_OPTIONS, &opt_keyform)) { + CMP_err("unknown option given for key loading format"); + return 0; + } + +#undef FORMAT_OPTIONS + + if (opt_certform_s != NULL + && !opt_format(opt_certform_s, OPT_FMT_PEMDER, &opt_certform)) { + CMP_err("unknown option given for certificate storing format"); + return 0; + } + + if (opt_certsform_s != NULL + && !opt_format(opt_certsform_s, OPT_FMT_PEMDER | OPT_FMT_PKCS12, + &opt_certsform)) { + CMP_err("unknown option given for certificate list loading format"); + return 0; + } + + return 1; +} + +static OSSL_CMP_SRV_CTX *setup_srv_ctx(ENGINE *e) +{ + OSSL_CMP_CTX *ctx; /* extra CMP (client) ctx partly used by server */ + OSSL_CMP_SRV_CTX *srv_ctx = ossl_cmp_mock_srv_new(); + + if (srv_ctx == NULL) + return NULL; + ctx = OSSL_CMP_SRV_CTX_get0_cmp_ctx(srv_ctx); + + if (opt_srv_ref == NULL) { + if (opt_srv_cert == NULL) { + /* opt_srv_cert should determine the sender */ + CMP_err("must give -srv_ref for server if no -srv_cert given"); + goto err; + } + } else { + if (!OSSL_CMP_CTX_set1_referenceValue(ctx, (unsigned char *)opt_srv_ref, + strlen(opt_srv_ref))) + goto err; + } + + if (opt_srv_secret != NULL) { + int res; + char *pass_str = get_passwd(opt_srv_secret, "PBMAC secret of server"); + + if (pass_str != NULL) { + cleanse(opt_srv_secret); + res = OSSL_CMP_CTX_set1_secretValue(ctx, (unsigned char *)pass_str, + strlen(pass_str)); + clear_free(pass_str); + if (res == 0) + goto err; + } + } else if (opt_srv_cert == NULL) { + CMP_err("server credentials must be given if -use_mock_srv or -port is used"); + goto err; + } else { + CMP_warn("server will not be able to handle PBM-protected requests since -srv_secret is not given"); + } + + if (opt_srv_secret == NULL + && ((opt_srv_cert == NULL) != (opt_srv_key == NULL))) { + CMP_err("must give both -srv_cert and -srv_key options or neither"); + goto err; + } + if (opt_srv_cert != NULL) { + X509 *srv_cert = load_cert_pwd(opt_srv_cert, opt_srv_keypass, + "certificate of the server"); + if (srv_cert == NULL || !OSSL_CMP_CTX_set1_clCert(ctx, srv_cert)) { + X509_free(srv_cert); + goto err; + } + X509_free(srv_cert); + } + if (opt_srv_key != NULL) { + EVP_PKEY *pkey = load_key_pwd(opt_srv_key, opt_keyform, + opt_srv_keypass, + e, "private key for server cert"); + + if (pkey == NULL || !OSSL_CMP_CTX_set1_pkey(ctx, pkey)) { + EVP_PKEY_free(pkey); + goto err; + } + EVP_PKEY_free(pkey); + } + cleanse(opt_srv_keypass); + + if (opt_srv_trusted != NULL) { + X509_STORE *ts = + load_certstore(opt_srv_trusted, "certificates trusted by server"); + + if (ts == NULL) + goto err; + if (!set1_store_parameters(ts) + || !truststore_set_host_etc(ts, NULL) + || !OSSL_CMP_CTX_set0_trustedStore(ctx, ts)) { + X509_STORE_free(ts); + goto err; + } + } else { + CMP_warn("server will not be able to handle signature-protected requests since -srv_trusted is not given"); + } + if (!setup_certs(opt_srv_untrusted, "untrusted certificates", ctx, + (add_X509_stack_fn_t)OSSL_CMP_CTX_set1_untrusted_certs, + NULL)) + goto err; + + if (opt_rsp_cert == NULL) { + CMP_err("must give -rsp_cert for mock server"); + goto err; + } else { + X509 *cert = load_cert_pwd(opt_rsp_cert, opt_keypass, + "cert to be returned by the mock server"); + + if (cert == NULL) + goto err; + /* from server perspective the server is the client */ + if (!ossl_cmp_mock_srv_set1_certOut(srv_ctx, cert)) { + X509_free(cert); + goto err; + } + X509_free(cert); + } + /* TODO find a cleaner solution not requiring type casts */ + if (!setup_certs(opt_rsp_extracerts, + "CMP extra certificates for mock server", srv_ctx, + (add_X509_stack_fn_t)ossl_cmp_mock_srv_set1_chainOut, + NULL)) + goto err; + if (!setup_certs(opt_rsp_capubs, "caPubs for mock server", srv_ctx, + (add_X509_stack_fn_t)ossl_cmp_mock_srv_set1_caPubsOut, + NULL)) + goto err; + (void)ossl_cmp_mock_srv_set_pollCount(srv_ctx, opt_poll_count); + (void)ossl_cmp_mock_srv_set_checkAfterTime(srv_ctx, opt_check_after); + if (opt_grant_implicitconf) + (void)OSSL_CMP_SRV_CTX_set_grant_implicit_confirm(srv_ctx, 1); + + if (opt_failure != INT_MIN) { /* option has been set explicity */ + if (opt_failure < 0 || OSSL_CMP_PKIFAILUREINFO_MAX < opt_failure) { + CMP_err1("-failure out of range, should be >= 0 and <= %d", + OSSL_CMP_PKIFAILUREINFO_MAX); + goto err; + } + if (opt_failurebits != 0) + CMP_warn("-failurebits overrides -failure"); + else + opt_failurebits = 1 << opt_failure; + } + if ((unsigned)opt_failurebits > OSSL_CMP_PKIFAILUREINFO_MAX_BIT_PATTERN) { + CMP_err("-failurebits out of range"); + goto err; + } + if (!ossl_cmp_mock_srv_set_statusInfo(srv_ctx, opt_pkistatus, + opt_failurebits, opt_statusstring)) + goto err; + + if (opt_send_error) + (void)ossl_cmp_mock_srv_set_send_error(srv_ctx, 1); + + if (opt_send_unprotected) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_UNPROTECTED_SEND, 1); + if (opt_send_unprot_err) + (void)OSSL_CMP_SRV_CTX_set_send_unprotected_errors(srv_ctx, 1); + if (opt_accept_unprotected) + (void)OSSL_CMP_SRV_CTX_set_accept_unprotected(srv_ctx, 1); + if (opt_accept_unprot_err) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_UNPROTECTED_ERRORS, 1); + if (opt_accept_raverified) + (void)OSSL_CMP_SRV_CTX_set_accept_raverified(srv_ctx, 1); + + return srv_ctx; + + err: + ossl_cmp_mock_srv_free(srv_ctx); + return NULL; +} + +/* + * set up verification aspects of OSSL_CMP_CTX w.r.t. opts from config file/CLI. + * Returns pointer on success, NULL on error + */ +static int setup_verification_ctx(OSSL_CMP_CTX *ctx) +{ + if (!setup_certs(opt_untrusted, "untrusted certificates", ctx, + (add_X509_stack_fn_t)OSSL_CMP_CTX_set1_untrusted_certs, + NULL)) + goto err; + + if (opt_srvcert != NULL || opt_trusted != NULL) { + X509_STORE *ts = NULL; + + if (opt_srvcert != NULL) { + X509 *srvcert; + + if (opt_trusted != NULL) { + CMP_warn("-trusted option is ignored since -srvcert option is present"); + opt_trusted = NULL; + } + if (opt_recipient != NULL) { + CMP_warn("-recipient option is ignored since -srvcert option is present"); + opt_recipient = NULL; + } + srvcert = load_cert_pwd(opt_srvcert, opt_otherpass, + "directly trusted CMP server certificate"); + if (srvcert == NULL) + /* + * opt_otherpass is needed in case + * opt_srvcert is an encrypted PKCS#12 file + */ + goto err; + if (!OSSL_CMP_CTX_set1_srvCert(ctx, srvcert)) { + X509_free(srvcert); + goto oom; + } + X509_free(srvcert); + if ((ts = X509_STORE_new()) == NULL) + goto oom; + } + if (opt_trusted != NULL + && (ts = load_certstore(opt_trusted, "trusted certificates")) + == NULL) + goto err; + if (!set1_store_parameters(ts) /* also copies vpm */ + /* + * clear any expected host/ip/email address; + * opt_expect_sender is used instead + */ + || !truststore_set_host_etc(ts, NULL) + || !OSSL_CMP_CTX_set0_trustedStore(ctx, ts)) { + X509_STORE_free(ts); + goto oom; + } + } + + if (opt_ignore_keyusage) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_IGNORE_KEYUSAGE, 1); + + if (opt_unprotected_errors) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_UNPROTECTED_ERRORS, 1); + + if (opt_out_trusted != NULL) { /* for use in OSSL_CMP_certConf_cb() */ + X509_VERIFY_PARAM *out_vpm = NULL; + X509_STORE *out_trusted = + load_certstore(opt_out_trusted, + "trusted certs for verifying newly enrolled cert"); + + if (out_trusted == NULL) + goto err; + /* any -verify_hostname, -verify_ip, and -verify_email apply here */ + if (!set1_store_parameters(out_trusted)) + goto oom; + /* ignore any -attime here, new certs are current anyway */ + out_vpm = X509_STORE_get0_param(out_trusted); + X509_VERIFY_PARAM_clear_flags(out_vpm, X509_V_FLAG_USE_CHECK_TIME); + + (void)OSSL_CMP_CTX_set_certConf_cb_arg(ctx, out_trusted); + } + + if (opt_disable_confirm) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_DISABLE_CONFIRM, 1); + + if (opt_implicit_confirm) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_IMPLICIT_CONFIRM, 1); + + (void)OSSL_CMP_CTX_set_certConf_cb(ctx, OSSL_CMP_certConf_cb); + + return 1; + + oom: + CMP_err("out of memory"); + err: + return 0; +} + +#ifndef OPENSSL_NO_SOCK +/* + * set up ssl_ctx for the OSSL_CMP_CTX based on options from config file/CLI. + * Returns pointer on success, NULL on error + */ +static SSL_CTX *setup_ssl_ctx(OSSL_CMP_CTX *ctx, ENGINE *e) +{ + STACK_OF(X509) *untrusted_certs = OSSL_CMP_CTX_get0_untrusted_certs(ctx); + EVP_PKEY *pkey = NULL; + X509_STORE *trust_store = NULL; + SSL_CTX *ssl_ctx; + int i; + + ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (ssl_ctx == NULL) + return NULL; + + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + + if (opt_tls_trusted != NULL) { + if ((trust_store = load_certstore(opt_tls_trusted, + "trusted TLS certificates")) == NULL) + goto err; + SSL_CTX_set_cert_store(ssl_ctx, trust_store); + /* for improved diagnostics on SSL_CTX_build_cert_chain() errors: */ + X509_STORE_set_verify_cb(trust_store, X509_STORE_CTX_print_verify_cb); + } + + if (opt_tls_cert != NULL && opt_tls_key != NULL) { + X509 *cert; + STACK_OF(X509) *certs = NULL; + + if (!load_certs_autofmt(opt_tls_cert, &certs, 0, opt_tls_keypass, + "TLS client certificate (optionally with chain)")) + /* + * opt_tls_keypass is needed in case opt_tls_cert is an encrypted + * PKCS#12 file + */ + goto err; + + cert = sk_X509_delete(certs, 0); + if (cert == NULL || SSL_CTX_use_certificate(ssl_ctx, cert) <= 0) { + CMP_err1("unable to use client TLS certificate file '%s'", + opt_tls_cert); + X509_free(cert); + sk_X509_pop_free(certs, X509_free); + goto err; + } + X509_free(cert); /* we do not need the handle any more */ + + /* + * Any further certs and any untrusted certs are used for constructing + * the client cert chain to be provided along with the TLS client cert + * to the TLS server. + */ + if (!SSL_CTX_set0_chain(ssl_ctx, certs)) { + CMP_err("could not set TLS client cert chain"); + sk_X509_pop_free(certs, X509_free); + goto err; + } + for (i = 0; i < sk_X509_num(untrusted_certs); i++) { + cert = sk_X509_value(untrusted_certs, i); + if (!SSL_CTX_add1_chain_cert(ssl_ctx, cert)) { + CMP_err("could not add untrusted cert to TLS client cert chain"); + goto err; + } + } + if (!SSL_CTX_build_cert_chain(ssl_ctx, + SSL_BUILD_CHAIN_FLAG_UNTRUSTED | + SSL_BUILD_CHAIN_FLAG_NO_ROOT)) { + CMP_warn("could not build cert chain for own TLS cert"); + OSSL_CMP_CTX_print_errors(ctx); + } + + /* If present we append to the list also the certs from opt_tls_extra */ + if (opt_tls_extra != NULL) { + STACK_OF(X509) *tls_extra = load_certs_multifile(opt_tls_extra, + opt_otherpass, + "extra certificates for TLS"); + int res = 1; + + if (tls_extra == NULL) + goto err; + for (i = 0; i < sk_X509_num(tls_extra); i++) { + cert = sk_X509_value(tls_extra, i); + if (res != 0) + res = SSL_CTX_add_extra_chain_cert(ssl_ctx, cert); + if (res == 0) + X509_free(cert); + } + sk_X509_free(tls_extra); + if (res == 0) { + BIO_printf(bio_err, "error: unable to add TLS extra certs\n"); + goto err; + } + } + + pkey = load_key_pwd(opt_tls_key, opt_keyform, opt_tls_keypass, + e, "TLS client private key"); + cleanse(opt_tls_keypass); + if (pkey == NULL) + goto err; + /* + * verify the key matches the cert, + * not using SSL_CTX_check_private_key(ssl_ctx) + * because it gives poor and sometimes misleading diagnostics + */ + if (!X509_check_private_key(SSL_CTX_get0_certificate(ssl_ctx), + pkey)) { + CMP_err2("TLS private key '%s' does not match the TLS certificate '%s'\n", + opt_tls_key, opt_tls_cert); + EVP_PKEY_free(pkey); + pkey = NULL; /* otherwise, for some reason double free! */ + goto err; + } + if (SSL_CTX_use_PrivateKey(ssl_ctx, pkey) <= 0) { + CMP_err1("unable to use TLS client private key '%s'", opt_tls_key); + EVP_PKEY_free(pkey); + pkey = NULL; /* otherwise, for some reason double free! */ + goto err; + } + EVP_PKEY_free(pkey); /* we do not need the handle any more */ + } + if (opt_tls_trusted != NULL) { + /* enable and parameterize server hostname/IP address check */ + if (!truststore_set_host_etc(trust_store, + opt_tls_host != NULL ? + opt_tls_host : opt_server)) + /* TODO: is the server host name correct for TLS via proxy? */ + goto err; + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + } + return ssl_ctx; + err: + SSL_CTX_free(ssl_ctx); + return NULL; +} +#endif + +/* + * set up protection aspects of OSSL_CMP_CTX based on options from config + * file/CLI while parsing options and checking their consistency. + * Returns 1 on success, 0 on error + */ +static int setup_protection_ctx(OSSL_CMP_CTX *ctx, ENGINE *e) +{ + if (!opt_unprotected_requests && opt_secret == NULL && opt_cert == NULL) { + CMP_err("must give client credentials unless -unprotected_requests is set"); + goto err; + } + + if (opt_ref == NULL && opt_cert == NULL && opt_subject == NULL) { + /* cert or subject should determine the sender */ + CMP_err("must give -ref if no -cert and no -subject given"); + goto err; + } + if (!opt_secret && ((opt_cert == NULL) != (opt_key == NULL))) { + CMP_err("must give both -cert and -key options or neither"); + goto err; + } + if (opt_secret != NULL) { + char *pass_string = get_passwd(opt_secret, "PBMAC"); + int res; + + if (pass_string != NULL) { + cleanse(opt_secret); + res = OSSL_CMP_CTX_set1_secretValue(ctx, + (unsigned char *)pass_string, + strlen(pass_string)); + clear_free(pass_string); + if (res == 0) + goto err; + } + if (opt_cert != NULL || opt_key != NULL) + CMP_warn("no signature-based protection used since -secret is given"); + } + if (opt_ref != NULL + && !OSSL_CMP_CTX_set1_referenceValue(ctx, (unsigned char *)opt_ref, + strlen(opt_ref))) + goto err; + + if (opt_key != NULL) { + EVP_PKEY *pkey = load_key_pwd(opt_key, opt_keyform, opt_keypass, e, + "private key for CMP client certificate"); + + if (pkey == NULL || !OSSL_CMP_CTX_set1_pkey(ctx, pkey)) { + EVP_PKEY_free(pkey); + goto err; + } + EVP_PKEY_free(pkey); + } + if (opt_secret == NULL && opt_srvcert == NULL && opt_trusted == NULL) { + CMP_err("missing -secret or -srvcert or -trusted"); + goto err; + } + + if (opt_cert != NULL) { + X509 *clcert; + STACK_OF(X509) *certs = NULL; + int ok; + + if (!load_certs_autofmt(opt_cert, &certs, 0, opt_keypass, + "CMP client certificate (and optionally extra certs)")) + /* opt_keypass is needed if opt_cert is an encrypted PKCS#12 file */ + goto err; + + clcert = sk_X509_delete(certs, 0); + if (clcert == NULL) { + CMP_err("no client certificate found"); + sk_X509_pop_free(certs, X509_free); + goto err; + } + ok = OSSL_CMP_CTX_set1_clCert(ctx, clcert); + X509_free(clcert); + + if (ok) { + /* add any remaining certs to the list of untrusted certs */ + STACK_OF(X509) *untrusted = OSSL_CMP_CTX_get0_untrusted_certs(ctx); + ok = untrusted != NULL ? + sk_X509_add1_certs(untrusted, certs, 0, 1 /* no dups */, 0) : + OSSL_CMP_CTX_set1_untrusted_certs(ctx, certs); + } + sk_X509_pop_free(certs, X509_free); + if (!ok) + goto oom; + } + + if (!setup_certs(opt_extracerts, "extra certificates for CMP", ctx, + (add_X509_stack_fn_t)OSSL_CMP_CTX_set1_extraCertsOut, + NULL)) + goto err; + cleanse(opt_otherpass); + + if (opt_unprotected_requests) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_UNPROTECTED_SEND, 1); + + if (opt_digest != NULL) { + int digest = OBJ_ln2nid(opt_digest); + + if (digest == NID_undef) { + CMP_err1("digest algorithm name not recognized: '%s'", opt_digest); + goto err; + } + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_DIGEST_ALGNID, digest); + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_OWF_ALGNID, digest); + } + + if (opt_mac != NULL) { + int mac = OBJ_ln2nid(opt_mac); + if (mac == NID_undef) { + CMP_err1("MAC algorithm name not recognized: '%s'", opt_mac); + goto err; + } + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_MAC_ALGNID, mac); + } + return 1; + + oom: + CMP_err("out of memory"); + err: + return 0; +} + +/* + * set up IR/CR/KUR/CertConf/RR specific parts of the OSSL_CMP_CTX + * based on options from config file/CLI. + * Returns pointer on success, NULL on error + */ +static int setup_request_ctx(OSSL_CMP_CTX *ctx, ENGINE *e) +{ + if (opt_subject == NULL && opt_oldcert == NULL && opt_cert == NULL) + CMP_warn("no -subject given, neither -oldcert nor -cert available as default"); + if (!set_name(opt_subject, OSSL_CMP_CTX_set1_subjectName, ctx, "subject") + || !set_name(opt_issuer, OSSL_CMP_CTX_set1_issuer, ctx, "issuer")) + goto err; + + if (opt_newkey != NULL) { + const char *file = opt_newkey; + const int format = opt_keyform; + const char *pass = opt_newkeypass; + const char *desc = "new private or public key for cert to be enrolled"; + EVP_PKEY *pkey = load_key_pwd(file, format, pass, e, NULL); + int priv = 1; + + if (pkey == NULL) { + ERR_clear_error(); + pkey = load_pubkey(file, format, 0, pass, e, desc); + priv = 0; + } + cleanse(opt_newkeypass); + if (pkey == NULL || !OSSL_CMP_CTX_set0_newPkey(ctx, priv, pkey)) { + EVP_PKEY_free(pkey); + goto err; + } + } + + if (opt_days > 0) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_VALIDITY_DAYS, + opt_days); + + if (opt_policies != NULL && opt_policy_oids != NULL) { + CMP_err("cannot have policies both via -policies and via -policy_oids"); + goto err; + } + + if (opt_reqexts != NULL || opt_policies != NULL) { + X509V3_CTX ext_ctx; + X509_EXTENSIONS *exts = sk_X509_EXTENSION_new_null(); + + if (exts == NULL) + goto err; + X509V3_set_ctx(&ext_ctx, NULL, NULL, NULL, NULL, 0); + X509V3_set_nconf(&ext_ctx, conf); + if (opt_reqexts != NULL + && !X509V3_EXT_add_nconf_sk(conf, &ext_ctx, opt_reqexts, &exts)) { + CMP_err1("cannot load certificate request extension section '%s'", + opt_reqexts); + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + goto err; + } + if (opt_policies != NULL + && !X509V3_EXT_add_nconf_sk(conf, &ext_ctx, opt_policies, &exts)) { + CMP_err1("cannot load policy cert request extension section '%s'", + opt_policies); + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + goto err; + } + OSSL_CMP_CTX_set0_reqExtensions(ctx, exts); + } + if (OSSL_CMP_CTX_reqExtensions_have_SAN(ctx) && opt_sans != NULL) { + CMP_err("cannot have Subject Alternative Names both via -reqexts and via -sans"); + goto err; + } + + if (!set_gennames(ctx, opt_sans, "Subject Alternative Name")) + goto err; + + if (opt_san_nodefault) { + if (opt_sans != NULL) + CMP_warn("-opt_san_nodefault has no effect when -sans is used"); + (void)OSSL_CMP_CTX_set_option(ctx, + OSSL_CMP_OPT_SUBJECTALTNAME_NODEFAULT, 1); + } + + if (opt_policy_oids_critical) { + if (opt_policy_oids == NULL) + CMP_warn("-opt_policy_oids_critical has no effect unless -policy_oids is given"); + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_POLICIES_CRITICAL, 1); + } + + while (opt_policy_oids != NULL) { + ASN1_OBJECT *policy; + POLICYINFO *pinfo; + char *next = next_item(opt_policy_oids); + + if ((policy = OBJ_txt2obj(opt_policy_oids, 1)) == 0) { + CMP_err1("unknown policy OID '%s'", opt_policy_oids); + goto err; + } + + if ((pinfo = POLICYINFO_new()) == NULL) { + ASN1_OBJECT_free(policy); + goto err; + } + pinfo->policyid = policy; + + if (!OSSL_CMP_CTX_push0_policy(ctx, pinfo)) { + CMP_err1("cannot add policy with OID '%s'", opt_policy_oids); + POLICYINFO_free(pinfo); + goto err; + } + opt_policy_oids = next; + } + + if (opt_popo >= OSSL_CRMF_POPO_NONE) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_POPO_METHOD, opt_popo); + + if (opt_csr != NULL) { + if (opt_cmd != CMP_P10CR) { + CMP_warn("-csr option is ignored for command other than p10cr"); + } else { + X509_REQ *csr = + load_csr_autofmt(opt_csr, "PKCS#10 CSR for p10cr"); + + if (csr == NULL) + goto err; + if (!OSSL_CMP_CTX_set1_p10CSR(ctx, csr)) { + X509_REQ_free(csr); + goto oom; + } + X509_REQ_free(csr); + } + } + + if (opt_oldcert != NULL) { + X509 *oldcert = load_cert_pwd(opt_oldcert, opt_keypass, + "certificate to be updated/revoked"); + /* opt_keypass is needed if opt_oldcert is an encrypted PKCS#12 file */ + + if (oldcert == NULL) + goto err; + if (!OSSL_CMP_CTX_set1_oldCert(ctx, oldcert)) { + X509_free(oldcert); + goto oom; + } + X509_free(oldcert); + } + cleanse(opt_keypass); + if (opt_revreason > CRL_REASON_NONE) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_REVOCATION_REASON, + opt_revreason); + + return 1; + + oom: + CMP_err("out of memory"); + err: + return 0; +} + +static int handle_opt_geninfo(OSSL_CMP_CTX *ctx) +{ + long value; + ASN1_OBJECT *type; + ASN1_INTEGER *aint; + ASN1_TYPE *val; + OSSL_CMP_ITAV *itav; + char *endstr; + char *valptr = strchr(opt_geninfo, ':'); + + if (valptr == NULL) { + CMP_err("missing ':' in -geninfo option"); + return 0; + } + valptr[0] = '\0'; + valptr++; + + if (strncasecmp(valptr, "int:", 4) != 0) { + CMP_err("missing 'int:' in -geninfo option"); + return 0; + } + valptr += 4; + + value = strtol(valptr, &endstr, 10); + if (endstr == valptr || *endstr != '\0') { + CMP_err("cannot parse int in -geninfo option"); + return 0; + } + + type = OBJ_txt2obj(opt_geninfo, 1); + if (type == NULL) { + CMP_err("cannot parse OID in -geninfo option"); + return 0; + } + + aint = ASN1_INTEGER_new(); + if (aint == NULL || !ASN1_INTEGER_set(aint, value)) + goto oom; + + val = ASN1_TYPE_new(); + if (val == NULL) { + ASN1_INTEGER_free(aint); + goto oom; + } + ASN1_TYPE_set(val, V_ASN1_INTEGER, aint); + itav = OSSL_CMP_ITAV_create(type, val); + if (itav == NULL) { + ASN1_TYPE_free(val); + goto oom; + } + + if (!OSSL_CMP_CTX_push0_geninfo_ITAV(ctx, itav)) { + OSSL_CMP_ITAV_free(itav); + return 0; + } + return 1; + + oom: + CMP_err("out of memory"); + return 0; +} + + +/* + * set up the client-side OSSL_CMP_CTX based on options from config file/CLI + * while parsing options and checking their consistency. + * Prints reason for error to bio_err. + * Returns 1 on success, 0 on error + */ +static int setup_client_ctx(OSSL_CMP_CTX *ctx, ENGINE *e) +{ + int ret = 0; + char server_buf[200] = { '\0' }; + char proxy_buf[200] = { '\0' }; + char *proxy_host = NULL; + char *proxy_port_str = NULL; + + if (opt_server == NULL) { + CMP_err("missing server address[:port]"); + goto err; + } else if ((server_port = + parse_addr(&opt_server, server_port, "server")) < 0) { + goto err; + } + if (server_port != 0) + BIO_snprintf(server_port_s, sizeof(server_port_s), "%d", server_port); + if (!OSSL_CMP_CTX_set1_server(ctx, opt_server) + || !OSSL_CMP_CTX_set_serverPort(ctx, server_port) + || !OSSL_CMP_CTX_set1_serverPath(ctx, opt_path)) + goto oom; + if (opt_proxy != NULL && !OSSL_CMP_CTX_set1_proxy(ctx, opt_proxy)) + goto oom; + (void)BIO_snprintf(server_buf, sizeof(server_buf), "http%s://%s%s%s/%s", + opt_tls_used ? "s" : "", opt_server, + server_port == 0 ? "" : ":", server_port_s, + opt_path[0] == '/' ? opt_path + 1 : opt_path); + + if (opt_proxy != NULL) + (void)BIO_snprintf(proxy_buf, sizeof(proxy_buf), " via %s", opt_proxy); + CMP_info2("will contact %s%s", server_buf, proxy_buf); + + if (!transform_opts()) + goto err; + + if (opt_cmd == CMP_IR || opt_cmd == CMP_CR || opt_cmd == CMP_KUR) { + if (opt_newkey == NULL && opt_key == NULL && opt_csr == NULL) { + CMP_err("missing -newkey (or -key) to be certified"); + goto err; + } + if (opt_certout == NULL) { + CMP_err("-certout not given, nowhere to save certificate"); + goto err; + } + } + if (opt_cmd == CMP_KUR) { + char *ref_cert = opt_oldcert != NULL ? opt_oldcert : opt_cert; + + if (ref_cert == NULL) { + CMP_err("missing -oldcert option for certificate to be updated"); + goto err; + } + if (opt_subject != NULL) + CMP_warn2("-subject '%s' given, which overrides the subject of '%s' in KUR", + opt_subject, ref_cert); + } + if (opt_cmd == CMP_RR && opt_oldcert == NULL) { + CMP_err("missing certificate to be revoked"); + goto err; + } + if (opt_cmd == CMP_P10CR && opt_csr == NULL) { + CMP_err("missing PKCS#10 CSR for p10cr"); + goto err; + } + + if (opt_recipient == NULL && opt_srvcert == NULL && opt_issuer == NULL + && opt_oldcert == NULL && opt_cert == NULL) + CMP_warn("missing -recipient, -srvcert, -issuer, -oldcert or -cert; recipient will be set to \"NULL-DN\""); + + if (opt_infotype_s != NULL) { + char id_buf[100] = "id-it-"; + + strncat(id_buf, opt_infotype_s, sizeof(id_buf) - strlen(id_buf) - 1); + if ((opt_infotype = OBJ_sn2nid(id_buf)) == NID_undef) { + CMP_err("unknown OID name in -infotype option"); + goto err; + } + } + + if (!setup_verification_ctx(ctx)) + goto err; + + if (opt_msg_timeout >= 0) /* must do this before setup_ssl_ctx() */ + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_MSG_TIMEOUT, + opt_msg_timeout); + if (opt_total_timeout >= 0) + (void)OSSL_CMP_CTX_set_option(ctx, OSSL_CMP_OPT_TOTAL_TIMEOUT, + opt_total_timeout); + + if (opt_reqin != NULL || opt_reqout != NULL + || opt_rspin != NULL || opt_rspout != NULL || opt_use_mock_srv) + (void)OSSL_CMP_CTX_set_transfer_cb(ctx, read_write_req_resp); + + if ((opt_tls_cert != NULL || opt_tls_key != NULL + || opt_tls_keypass != NULL || opt_tls_extra != NULL + || opt_tls_trusted != NULL || opt_tls_host != NULL) + && !opt_tls_used) + CMP_warn("TLS options(s) given but not -tls_used"); + if (opt_tls_used) { +#ifdef OPENSSL_NO_SOCK + BIO_printf(bio_err, "Cannot use TLS - sockets not supported\n"); + goto err; +#else + APP_HTTP_TLS_INFO *info; + + if (opt_tls_cert != NULL + || opt_tls_key != NULL || opt_tls_keypass != NULL) { + if (opt_tls_key == NULL) { + CMP_err("missing -tls_key option"); + goto err; + } else if (opt_tls_cert == NULL) { + CMP_err("missing -tls_cert option"); + goto err; + } + } + if (opt_use_mock_srv) { + CMP_err("cannot use TLS options together with -use_mock_srv"); + goto err; + } + if ((info = OPENSSL_zalloc(sizeof(*info))) == NULL) + goto err; + (void)OSSL_CMP_CTX_set_http_cb_arg(ctx, info); + /* info will be freed along with CMP ctx */ + info->server = opt_server; + info->port = server_port_s; + info->use_proxy = opt_proxy != NULL; + info->timeout = OSSL_CMP_CTX_get_option(ctx, OSSL_CMP_OPT_MSG_TIMEOUT); + info->ssl_ctx = setup_ssl_ctx(ctx, e); + if (info->ssl_ctx == NULL) + goto err; + (void)OSSL_CMP_CTX_set_http_cb(ctx, app_http_tls_cb); +#endif + } + + if (!setup_protection_ctx(ctx, e)) + goto err; + + if (!setup_request_ctx(ctx, e)) + goto err; + + if (!set_name(opt_recipient, OSSL_CMP_CTX_set1_recipient, ctx, "recipient") + || !set_name(opt_expect_sender, OSSL_CMP_CTX_set1_expected_sender, + ctx, "expected sender")) + goto oom; + + if (opt_geninfo != NULL && !handle_opt_geninfo(ctx)) + goto err; + + ret = 1; + + err: + OPENSSL_free(proxy_host); + OPENSSL_free(proxy_port_str); + return ret; + oom: + CMP_err("out of memory"); + goto err; +} + +/* + * write out the given certificate to the output specified by bio. + * Depending on options use either PEM or DER format. + * Returns 1 on success, 0 on error + */ +static int write_cert(BIO *bio, X509 *cert) +{ + if ((opt_certform == FORMAT_PEM && PEM_write_bio_X509(bio, cert)) + || (opt_certform == FORMAT_ASN1 && i2d_X509_bio(bio, cert))) + return 1; + if (opt_certform != FORMAT_PEM && opt_certform != FORMAT_ASN1) + BIO_printf(bio_err, + "error: unsupported type '%s' for writing certificates\n", + opt_certform_s); + return 0; +} + +/* + * writes out a stack of certs to the given file. + * Depending on options use either PEM or DER format, + * where DER does not make much sense for writing more than one cert! + * Returns number of written certificates on success, 0 on error. + */ +static int save_certs(OSSL_CMP_CTX *ctx, + STACK_OF(X509) *certs, char *destFile, char *desc) +{ + BIO *bio = NULL; + int i; + int n = sk_X509_num(certs); + + CMP_info3("received %d %s certificate(s), saving to file '%s'", + n, desc, destFile); + if (n > 1 && opt_certform != FORMAT_PEM) + CMP_warn("saving more than one certificate in non-PEM format"); + + if (destFile == NULL || (bio = BIO_new(BIO_s_file())) == NULL + || !BIO_write_filename(bio, (char *)destFile)) { + CMP_err1("could not open file '%s' for writing", destFile); + n = -1; + goto err; + } + + for (i = 0; i < n; i++) { + if (!write_cert(bio, sk_X509_value(certs, i))) { + CMP_err1("cannot write certificate to file '%s'", destFile); + n = -1; + goto err; + } + } + + err: + BIO_free(bio); + return n; +} + +static void print_itavs(STACK_OF(OSSL_CMP_ITAV) *itavs) +{ + OSSL_CMP_ITAV *itav = NULL; + char buf[128]; + int i; + int n = sk_OSSL_CMP_ITAV_num(itavs); /* itavs == NULL leads to 0 */ + + if (n == 0) { + CMP_info("genp contains no ITAV"); + return; + } + + for (i = 0; i < n; i++) { + itav = sk_OSSL_CMP_ITAV_value(itavs, i); + OBJ_obj2txt(buf, 128, OSSL_CMP_ITAV_get0_type(itav), 0); + CMP_info1("genp contains ITAV of type: %s", buf); + } +} + +static char opt_item[SECTION_NAME_MAX + 1]; +/* get previous name from a comma-separated list of names */ +static const char *prev_item(const char *opt, const char *end) +{ + const char *beg; + size_t len; + + if (end == opt) + return NULL; + beg = end; + while (beg != opt && beg[-1] != ',' && !isspace(beg[-1])) + beg--; + len = end - beg; + if (len > SECTION_NAME_MAX) + len = SECTION_NAME_MAX; + strncpy(opt_item, beg, len); + opt_item[SECTION_NAME_MAX] = '\0'; /* avoid gcc v8 O3 stringop-truncation */ + opt_item[len] = '\0'; + if (len > SECTION_NAME_MAX) + CMP_warn2("using only first %d characters of section name starting with \"%s\"", + SECTION_NAME_MAX, opt_item); + while (beg != opt && (beg[-1] == ',' || isspace(beg[-1]))) + beg--; + return beg; +} + +/* get str value for name from a comma-separated hierarchy of config sections */ +static char *conf_get_string(const CONF *src_conf, const char *groups, + const char *name) +{ + char *res = NULL; + const char *end = groups + strlen(groups); + + while ((end = prev_item(groups, end)) != NULL) { + if ((res = NCONF_get_string(src_conf, opt_item, name)) != NULL) + return res; + } + return res; +} + +/* get long val for name from a comma-separated hierarchy of config sections */ +static int conf_get_number_e(const CONF *conf_, const char *groups, + const char *name, long *result) +{ + char *str = conf_get_string(conf_, groups, name); + char *tailptr; + long res; + + if (str == NULL || *str == '\0') + return 0; + + res = strtol(str, &tailptr, 10); + if (res == LONG_MIN || res == LONG_MAX || *tailptr != '\0') + return 0; + + *result = res; + return 1; +} + +/* + * use the command line option table to read values from the CMP section + * of openssl.cnf. Defaults are taken from the config file, they can be + * overwritten on the command line. + */ +static int read_config(void) +{ + unsigned int i; + long num = 0; + char *txt = NULL; + const OPTIONS *opt; + int provider_option; + int verification_option; + + /* + * starting with offset OPT_SECTION because OPT_CONFIG and OPT_SECTION would + * not make sense within the config file. They have already been handled. + */ + for (i = OPT_SECTION - OPT_HELP, opt = &cmp_options[OPT_SECTION]; + opt->name; i++, opt++) { + if (!strcmp(opt->name, OPT_SECTION_STR) + || !strcmp(opt->name, OPT_MORE_STR)) { + i--; + continue; + } + provider_option = (OPT_PROV__FIRST <= opt->retval + && opt->retval < OPT_PROV__LAST); + verification_option = (OPT_V__FIRST <= opt->retval + && opt->retval < OPT_V__LAST); + if (provider_option || verification_option) + i--; + if (cmp_vars[i].txt == NULL) { + CMP_err1("internal: cmp_vars array too short, i=%d", i); + return 0; + } + switch (opt->valtype) { + case '-': + case 'n': + case 'l': + if (!conf_get_number_e(conf, opt_section, opt->name, &num)) { + ERR_clear_error(); + continue; /* option not provided */ + } + break; + /* + * do not use '<' in cmp_options. Incorrect treatment + * somewhere in args_verify() can wrongly set badarg = 1 + */ + case '<': + case 's': + case 'M': + txt = conf_get_string(conf, opt_section, opt->name); + if (txt == NULL) { + ERR_clear_error(); + continue; /* option not provided */ + } + break; + default: + CMP_err2("internal: unsupported type '%c' for option '%s'", + opt->valtype, opt->name); + return 0; + break; + } + if (provider_option || verification_option) { + int conf_argc = 1; + char *conf_argv[3]; + char arg1[82]; + + BIO_snprintf(arg1, 81, "-%s", (char *)opt->name); + conf_argv[0] = prog; + conf_argv[1] = arg1; + if (opt->valtype == '-') { + if (num != 0) + conf_argc = 2; + } else { + conf_argc = 3; + conf_argv[2] = conf_get_string(conf, opt_section, opt->name); + /* not NULL */ + } + if (conf_argc > 1) { + (void)opt_init(conf_argc, conf_argv, cmp_options); + + if (provider_option + ? !opt_provider(opt_next()) + : !opt_verify(opt_next(), vpm)) { + CMP_err2("for option '%s' in config file section '%s'", + opt->name, opt_section); + return 0; + } + } + } else { + switch (opt->valtype) { + case '-': + case 'n': + if (num < INT_MIN || INT_MAX < num) { + BIO_printf(bio_err, + "integer value out of range for option '%s'\n", + opt->name); + return 0; + } + *cmp_vars[i].num = (int)num; + break; + case 'l': + *cmp_vars[i].num_long = num; + break; + default: + if (txt != NULL && txt[0] == '\0') + txt = NULL; /* reset option on empty string input */ + *cmp_vars[i].txt = txt; + break; + } + } + } + + return 1; +} + +static char *opt_str(char *opt) +{ + char *arg = opt_arg(); + + if (arg[0] == '\0') { + CMP_warn1("argument of -%s option is empty string, resetting option", + opt); + arg = NULL; + } else if (arg[0] == '-') { + CMP_warn1("argument of -%s option starts with hyphen", opt); + } + return arg; +} + +static int opt_nat(void) +{ + int result = -1; + + if (opt_int(opt_arg(), &result) && result < 0) + BIO_printf(bio_err, "error: argument '%s' must not be negative\n", + opt_arg()); + return result; +} + +/* returns 1 on success, 0 on error, -1 on -help (i.e., stop with success) */ +static int get_opts(int argc, char **argv) +{ + OPTION_CHOICE o; + + prog = opt_init(argc, argv, cmp_options); + + while ((o = opt_next()) != OPT_EOF) { + switch (o) { + case OPT_EOF: + case OPT_ERR: + goto opt_err; + case OPT_HELP: + opt_help(cmp_options); + return -1; + case OPT_CONFIG: /* has already been handled */ + break; + case OPT_SECTION: /* has already been handled */ + break; + case OPT_SERVER: + opt_server = opt_str("server"); + break; + case OPT_PROXY: + opt_proxy = opt_str("proxy"); + break; + case OPT_NO_PROXY: + opt_no_proxy = opt_str("no_proxy"); + break; + case OPT_PATH: + opt_path = opt_str("path"); + break; + case OPT_MSG_TIMEOUT: + if ((opt_msg_timeout = opt_nat()) < 0) + goto opt_err; + break; + case OPT_TOTAL_TIMEOUT: + if ((opt_total_timeout = opt_nat()) < 0) + goto opt_err; + break; + case OPT_TLS_USED: + opt_tls_used = 1; + break; + case OPT_TLS_CERT: + opt_tls_cert = opt_str("tls_cert"); + break; + case OPT_TLS_KEY: + opt_tls_key = opt_str("tls_key"); + break; + case OPT_TLS_KEYPASS: + opt_tls_keypass = opt_str("tls_keypass"); + break; + case OPT_TLS_EXTRA: + opt_tls_extra = opt_str("tls_extra"); + break; + case OPT_TLS_TRUSTED: + opt_tls_trusted = opt_str("tls_trusted"); + break; + case OPT_TLS_HOST: + opt_tls_host = opt_str("tls_host"); + break; + case OPT_REF: + opt_ref = opt_str("ref"); + break; + case OPT_SECRET: + opt_secret = opt_str("secret"); + break; + case OPT_CERT: + opt_cert = opt_str("cert"); + break; + case OPT_KEY: + opt_key = opt_str("key"); + break; + case OPT_KEYPASS: + opt_keypass = opt_str("keypass"); + break; + case OPT_DIGEST: + opt_digest = opt_str("digest"); + break; + case OPT_MAC: + opt_mac = opt_str("mac"); + break; + case OPT_EXTRACERTS: + opt_extracerts = opt_str("extracerts"); + break; + case OPT_UNPROTECTED_REQUESTS: + opt_unprotected_requests = 1; + break; + + case OPT_TRUSTED: + opt_trusted = opt_str("trusted"); + break; + case OPT_UNTRUSTED: + opt_untrusted = opt_str("untrusted"); + break; + case OPT_SRVCERT: + opt_srvcert = opt_str("srvcert"); + break; + case OPT_RECIPIENT: + opt_recipient = opt_str("recipient"); + break; + case OPT_EXPECT_SENDER: + opt_expect_sender = opt_str("expect_sender"); + break; + case OPT_IGNORE_KEYUSAGE: + opt_ignore_keyusage = 1; + break; + case OPT_UNPROTECTED_ERRORS: + opt_unprotected_errors = 1; + break; + case OPT_EXTRACERTSOUT: + opt_extracertsout = opt_str("extracertsout"); + break; + case OPT_CACERTSOUT: + opt_cacertsout = opt_str("cacertsout"); + break; + + case OPT_V_CASES: + if (!opt_verify(o, vpm)) + goto opt_err; + break; + case OPT_CMD: + opt_cmd_s = opt_str("cmd"); + break; + case OPT_INFOTYPE: + opt_infotype_s = opt_str("infotype"); + break; + case OPT_GENINFO: + opt_geninfo = opt_str("geninfo"); + break; + + case OPT_NEWKEY: + opt_newkey = opt_str("newkey"); + break; + case OPT_NEWKEYPASS: + opt_newkeypass = opt_str("newkeypass"); + break; + case OPT_SUBJECT: + opt_subject = opt_str("subject"); + break; + case OPT_ISSUER: + opt_issuer = opt_str("issuer"); + break; + case OPT_DAYS: + if ((opt_days = opt_nat()) < 0) + goto opt_err; + break; + case OPT_REQEXTS: + opt_reqexts = opt_str("reqexts"); + break; + case OPT_SANS: + opt_sans = opt_str("sans"); + break; + case OPT_SAN_NODEFAULT: + opt_san_nodefault = 1; + break; + case OPT_POLICIES: + opt_policies = opt_str("policies"); + break; + case OPT_POLICY_OIDS: + opt_policy_oids = opt_str("policy_oids"); + break; + case OPT_POLICY_OIDS_CRITICAL: + opt_policy_oids_critical = 1; + break; + case OPT_POPO: + if (!opt_int(opt_arg(), &opt_popo) + || opt_popo < OSSL_CRMF_POPO_NONE + || opt_popo > OSSL_CRMF_POPO_KEYENC) { + CMP_err("invalid popo spec. Valid values are -1 .. 2"); + goto opt_err; + } + break; + case OPT_CSR: + opt_csr = opt_arg(); + break; + case OPT_OUT_TRUSTED: + opt_out_trusted = opt_str("out_trusted"); + break; + case OPT_IMPLICIT_CONFIRM: + opt_implicit_confirm = 1; + break; + case OPT_DISABLE_CONFIRM: + opt_disable_confirm = 1; + break; + case OPT_CERTOUT: + opt_certout = opt_str("certout"); + break; + case OPT_OLDCERT: + opt_oldcert = opt_str("oldcert"); + break; + case OPT_REVREASON: + if (!opt_int(opt_arg(), &opt_revreason) + || opt_revreason < CRL_REASON_NONE + || opt_revreason > CRL_REASON_AA_COMPROMISE + || opt_revreason == 7) { + CMP_err("invalid revreason. Valid values are -1 .. 6, 8 .. 10"); + goto opt_err; + } + break; + case OPT_CERTFORM: + opt_certform_s = opt_str("certform"); + break; + case OPT_KEYFORM: + opt_keyform_s = opt_str("keyform"); + break; + case OPT_CERTSFORM: + opt_certsform_s = opt_str("certsform"); + break; + case OPT_OTHERPASS: + opt_otherpass = opt_str("otherpass"); + break; +#ifndef OPENSSL_NO_ENGINE + case OPT_ENGINE: + opt_engine = opt_str("engine"); + break; +#endif + case OPT_PROV_CASES: + if (!opt_provider(o)) + goto opt_err; + break; + + case OPT_BATCH: + opt_batch = 1; + break; + case OPT_REPEAT: + opt_repeat = opt_nat(); + break; + case OPT_REQIN: + opt_reqin = opt_str("reqin"); + break; + case OPT_REQOUT: + opt_reqout = opt_str("reqout"); + break; + case OPT_RSPIN: + opt_rspin = opt_str("rspin"); + break; + case OPT_RSPOUT: + opt_rspout = opt_str("rspout"); + break; + case OPT_USE_MOCK_SRV: + opt_use_mock_srv = 1; + break; + case OPT_PORT: + opt_port = opt_str("port"); + break; + case OPT_MAX_MSGS: + if ((opt_max_msgs = opt_nat()) < 0) + goto opt_err; + break; + case OPT_SRV_REF: + opt_srv_ref = opt_str("srv_ref"); + break; + case OPT_SRV_SECRET: + opt_srv_secret = opt_str("srv_secret"); + break; + case OPT_SRV_CERT: + opt_srv_cert = opt_str("srv_cert"); + break; + case OPT_SRV_KEY: + opt_srv_key = opt_str("srv_key"); + break; + case OPT_SRV_KEYPASS: + opt_srv_keypass = opt_str("srv_keypass"); + break; + case OPT_SRV_TRUSTED: + opt_srv_trusted = opt_str("srv_trusted"); + break; + case OPT_SRV_UNTRUSTED: + opt_srv_untrusted = opt_str("srv_untrusted"); + break; + case OPT_RSP_CERT: + opt_rsp_cert = opt_str("rsp_cert"); + break; + case OPT_RSP_EXTRACERTS: + opt_rsp_extracerts = opt_str("rsp_extracerts"); + break; + case OPT_RSP_CAPUBS: + opt_rsp_capubs = opt_str("rsp_capubs"); + break; + case OPT_POLL_COUNT: + opt_poll_count = opt_nat(); + break; + case OPT_CHECK_AFTER: + opt_check_after = opt_nat(); + break; + case OPT_GRANT_IMPLICITCONF: + opt_grant_implicitconf = 1; + break; + case OPT_PKISTATUS: + opt_pkistatus = opt_nat(); + break; + case OPT_FAILURE: + opt_failure = opt_nat(); + break; + case OPT_FAILUREBITS: + opt_failurebits = opt_nat(); + break; + case OPT_STATUSSTRING: + opt_statusstring = opt_str("statusstring"); + break; + case OPT_SEND_ERROR: + opt_send_error = 1; + break; + case OPT_SEND_UNPROTECTED: + opt_send_unprotected = 1; + break; + case OPT_SEND_UNPROT_ERR: + opt_send_unprot_err = 1; + break; + case OPT_ACCEPT_UNPROTECTED: + opt_accept_unprotected = 1; + break; + case OPT_ACCEPT_UNPROT_ERR: + opt_accept_unprot_err = 1; + break; + case OPT_ACCEPT_RAVERIFIED: + opt_accept_raverified = 1; + break; + } + } + argc = opt_num_rest(); + argv = opt_rest(); + if (argc != 0) { + CMP_err1("unknown parameter %s", argv[0]); + goto opt_err; + } + return 1; + + opt_err: + CMP_err1("use -help for summary of '%s' options", prog); + return 0; +} + +int cmp_main(int argc, char **argv) +{ + char *configfile = NULL; + int i; + X509 *newcert = NULL; + ENGINE *e = NULL; + char mock_server[] = "mock server:1"; + int ret = 0; /* default: failure */ + + if (argc <= 1) { + opt_help(cmp_options); + goto err; + } + + /* + * handle OPT_CONFIG and OPT_SECTION upfront to take effect for other opts + */ + for (i = 1; i < argc - 1; i++) { + if (*argv[i] == '-') { + if (!strcmp(argv[i] + 1, cmp_options[OPT_CONFIG - OPT_HELP].name)) + opt_config = argv[i + 1]; + else if (!strcmp(argv[i] + 1, + cmp_options[OPT_SECTION - OPT_HELP].name)) + opt_section = argv[i + 1]; + } + } + if (opt_section[0] == '\0') /* empty string */ + opt_section = DEFAULT_SECTION; + + vpm = X509_VERIFY_PARAM_new(); + if (vpm == NULL) { + CMP_err("out of memory"); + goto err; + } + + /* read default values for options from config file */ + configfile = opt_config != NULL ? opt_config : default_config_file; + if (configfile && configfile[0] != '\0' /* non-empty string */ + && (configfile != default_config_file + || access(configfile, F_OK) != -1)) { + CMP_info1("using OpenSSL configuration file '%s'", configfile); + conf = app_load_config(configfile); + if (conf == NULL) { + goto err; + } else { + if (strcmp(opt_section, CMP_SECTION) == 0) { /* default */ + if (!NCONF_get_section(conf, opt_section)) + CMP_info2("no [%s] section found in config file '%s';" + " will thus use just [default] and unnamed section if present", + opt_section, configfile); + } else { + const char *end = opt_section + strlen(opt_section); + while ((end = prev_item(opt_section, end)) != NULL) { + if (!NCONF_get_section(conf, opt_item)) { + CMP_err2("no [%s] section found in config file '%s'", + opt_item, configfile); + goto err; + } + } + } + if (!read_config()) + goto err; + } + } + (void)BIO_flush(bio_err); /* prevent interference with opt_help() */ + + ret = get_opts(argc, argv); + if (ret <= 0) + goto err; + ret = 0; + + if (opt_batch) { +#ifndef OPENSSL_NO_ENGINE + UI_METHOD *ui_fallback_method; +# ifndef OPENSSL_NO_UI_CONSOLE + ui_fallback_method = UI_OpenSSL(); +# else + ui_fallback_method = (UI_METHOD *)UI_null(); +# endif + UI_method_set_reader(ui_fallback_method, NULL); +#endif + } + + if (opt_engine != NULL) + e = setup_engine_flags(opt_engine, 0 /* not: ENGINE_METHOD_ALL */, 0); + + if (opt_port != NULL) { + if (opt_use_mock_srv) { + CMP_err("cannot use both -port and -use_mock_srv options"); + goto err; + } + if (opt_server != NULL) { + CMP_err("cannot use both -port and -server options"); + goto err; + } + } + + if ((cmp_ctx = OSSL_CMP_CTX_new()) == NULL) { + CMP_err("out of memory"); + goto err; + } + if (!OSSL_CMP_CTX_set_log_cb(cmp_ctx, print_to_bio_out)) { + CMP_err1("cannot set up error reporting and logging for %s", prog); + goto err; + } + if ((opt_use_mock_srv || opt_port != NULL)) { + OSSL_CMP_SRV_CTX *srv_ctx; + + if ((srv_ctx = setup_srv_ctx(e)) == NULL) + goto err; + OSSL_CMP_CTX_set_transfer_cb_arg(cmp_ctx, srv_ctx); + if (!OSSL_CMP_CTX_set_log_cb(OSSL_CMP_SRV_CTX_get0_cmp_ctx(srv_ctx), + print_to_bio_out)) { + CMP_err1("cannot set up error reporting and logging for %s", prog); + goto err; + } + } + + + if (opt_port != NULL) { /* act as very basic CMP HTTP server */ +#ifdef OPENSSL_NO_SOCK + BIO_printf(bio_err, "Cannot act as server - sockets not supported\n"); +#else + BIO *acbio; + BIO *cbio = NULL; + int msgs = 0; + + if ((acbio = http_server_init_bio(prog, opt_port)) == NULL) + goto err; + while (opt_max_msgs <= 0 || msgs < opt_max_msgs) { + OSSL_CMP_MSG *req = NULL; + OSSL_CMP_MSG *resp = NULL; + + ret = http_server_get_asn1_req(ASN1_ITEM_rptr(OSSL_CMP_MSG), + (ASN1_VALUE **)&req, &cbio, acbio, + prog, 0, 0); + if (ret == 0) + continue; + if (ret++ == -1) + break; /* fatal error */ + + ret = 0; + msgs++; + if (req != NULL) { + resp = OSSL_CMP_CTX_server_perform(cmp_ctx, req); + OSSL_CMP_MSG_free(req); + if (resp == NULL) + break; /* treated as fatal error */ + ret = http_server_send_asn1_resp(cbio, "application/pkixcmp", + ASN1_ITEM_rptr(OSSL_CMP_MSG), + (const ASN1_VALUE *)resp); + OSSL_CMP_MSG_free(resp); + if (!ret) + break; /* treated as fatal error */ + } + BIO_free_all(cbio); + cbio = NULL; + } + BIO_free_all(cbio); + BIO_free_all(acbio); +#endif + goto err; + } + /* else act as CMP client */ + + if (opt_use_mock_srv) { + if (opt_server != NULL) { + CMP_err("cannot use both -use_mock_srv and -server options"); + goto err; + } + if (opt_proxy != NULL) { + CMP_err("cannot use both -use_mock_srv and -proxy options"); + goto err; + } + opt_server = mock_server; + opt_proxy = "API"; + } else { + if (opt_server == NULL) { + CMP_err("missing -server option"); + goto err; + } + } + + if (!setup_client_ctx(cmp_ctx, e)) { + CMP_err("cannot set up CMP context"); + goto err; + } + for (i = 0; i < opt_repeat; i++) { + /* everything is ready, now connect and perform the command! */ + switch (opt_cmd) { + case CMP_IR: + newcert = OSSL_CMP_exec_IR_ses(cmp_ctx); + if (newcert == NULL) + goto err; + break; + case CMP_KUR: + newcert = OSSL_CMP_exec_KUR_ses(cmp_ctx); + if (newcert == NULL) + goto err; + break; + case CMP_CR: + newcert = OSSL_CMP_exec_CR_ses(cmp_ctx); + if (newcert == NULL) + goto err; + break; + case CMP_P10CR: + newcert = OSSL_CMP_exec_P10CR_ses(cmp_ctx); + if (newcert == NULL) + goto err; + break; + case CMP_RR: + if (OSSL_CMP_exec_RR_ses(cmp_ctx) == NULL) + goto err; + break; + case CMP_GENM: + { + STACK_OF(OSSL_CMP_ITAV) *itavs; + + if (opt_infotype != NID_undef) { + OSSL_CMP_ITAV *itav = + OSSL_CMP_ITAV_create(OBJ_nid2obj(opt_infotype), NULL); + if (itav == NULL) + goto err; + OSSL_CMP_CTX_push0_genm_ITAV(cmp_ctx, itav); + } + + if ((itavs = OSSL_CMP_exec_GENM_ses(cmp_ctx)) == NULL) + goto err; + print_itavs(itavs); + sk_OSSL_CMP_ITAV_pop_free(itavs, OSSL_CMP_ITAV_free); + break; + } + default: + break; + } + + { + /* print PKIStatusInfo (this is in case there has been no error) */ + int status = OSSL_CMP_CTX_get_status(cmp_ctx); + char *buf = app_malloc(OSSL_CMP_PKISI_BUFLEN, "PKIStatusInfo buf"); + const char *string = + OSSL_CMP_CTX_snprint_PKIStatus(cmp_ctx, buf, + OSSL_CMP_PKISI_BUFLEN); + + CMP_print(bio_err, + status == OSSL_CMP_PKISTATUS_accepted ? "info" : + status == OSSL_CMP_PKISTATUS_rejection ? "server error" : + status == OSSL_CMP_PKISTATUS_waiting ? "internal error" + : "warning", + "received from %s %s %s", opt_server, + string != NULL ? string : "", ""); + OPENSSL_free(buf); + } + + if (opt_cacertsout != NULL) { + STACK_OF(X509) *certs = OSSL_CMP_CTX_get1_caPubs(cmp_ctx); + + if (sk_X509_num(certs) > 0 + && save_certs(cmp_ctx, certs, opt_cacertsout, "CA") < 0) { + sk_X509_pop_free(certs, X509_free); + goto err; + } + sk_X509_pop_free(certs, X509_free); + } + + if (opt_extracertsout != NULL) { + STACK_OF(X509) *certs = OSSL_CMP_CTX_get1_extraCertsIn(cmp_ctx); + if (sk_X509_num(certs) > 0 + && save_certs(cmp_ctx, certs, opt_extracertsout, + "extra") < 0) { + sk_X509_pop_free(certs, X509_free); + goto err; + } + sk_X509_pop_free(certs, X509_free); + } + + if (opt_certout != NULL && newcert != NULL) { + STACK_OF(X509) *certs = sk_X509_new_null(); + + if (certs == NULL || !sk_X509_push(certs, newcert) + || save_certs(cmp_ctx, certs, opt_certout, + "enrolled") < 0) { + sk_X509_free(certs); + goto err; + } + sk_X509_free(certs); + } + if (!OSSL_CMP_CTX_reinit(cmp_ctx)) + goto err; + } + ret = 1; + + err: + /* in case we ended up here on error without proper cleaning */ + cleanse(opt_keypass); + cleanse(opt_newkeypass); + cleanse(opt_otherpass); + cleanse(opt_tls_keypass); + cleanse(opt_secret); + cleanse(opt_srv_keypass); + cleanse(opt_srv_secret); + + if (ret != 1) + OSSL_CMP_CTX_print_errors(cmp_ctx); + + ossl_cmp_mock_srv_free(OSSL_CMP_CTX_get_transfer_cb_arg(cmp_ctx)); + { + APP_HTTP_TLS_INFO *http_tls_info = + OSSL_CMP_CTX_get_http_cb_arg(cmp_ctx); + + if (http_tls_info != NULL) { + SSL_CTX_free(http_tls_info->ssl_ctx); + OPENSSL_free(http_tls_info); + } + } + X509_STORE_free(OSSL_CMP_CTX_get_certConf_cb_arg(cmp_ctx)); + OSSL_CMP_CTX_free(cmp_ctx); + X509_VERIFY_PARAM_free(vpm); + release_engine(e); + + NCONF_free(conf); /* must not do as long as opt_... variables are used */ + OSSL_CMP_log_close(); + + return ret == 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/apps/openssl-vms.cnf b/apps/openssl-vms.cnf index e64cc9f3a6..c7e7abe994 100644 --- a/apps/openssl-vms.cnf +++ b/apps/openssl-vms.cnf @@ -348,3 +348,59 @@ ess_cert_id_chain = no # Must the ESS cert id chain be included? # (optional, default: no) ess_cert_id_alg = sha1 # algorithm to compute certificate # identifier (optional, default: sha1) + +[insta] # CMP using Insta Demo CA +# Message transfer +server = pki.certificate.fi:8700 +# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080 +# tls_use = 0 +path = pkix/ + +# Server authentication +recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer +ignore_keyusage = 1 # potentially needed quirk +unprotected_errors = 1 # potentially needed quirk +extracertsout = insta.extracerts.pem + +# Client authentication +ref = 3078 # user identification +secret = pass:insta # can be used for both client and server side + +# Generic message options +cmd = ir # default operation, can be overridden on cmd line with, e.g., kur + +# Certificate enrollment +subject = "/CN=openssl-cmp-test" +newkey = insta.priv.pem +out_trusted = insta.ca.crt +certout = insta.cert.pem + +[pbm] # Password-based protection for Insta CA +# Server and client authentication +ref = $insta::ref # 3078 +secret = $insta::secret # pass:insta + +[signature] # Signature-based protection for Insta CA +# Server authentication +trusted = insta.ca.crt # does not include keyUsage digitalSignature + +# Client authentication +secret = # disable PBM +key = $insta::newkey # insta.priv.pem +cert = $insta::certout # insta.cert.pem + +[ir] +cmd = ir + +[cr] +cmd = cr + +[kur] +# Certificate update +cmd = kur +oldcert = $insta::certout # insta.cert.pem + +[rr] +# Certificate revocation +cmd = rr +oldcert = $insta::certout # insta.cert.pem diff --git a/apps/openssl.cnf b/apps/openssl.cnf index 4acca4b044..52706ae166 100644 --- a/apps/openssl.cnf +++ b/apps/openssl.cnf @@ -348,3 +348,59 @@ ess_cert_id_chain = no # Must the ESS cert id chain be included? # (optional, default: no) ess_cert_id_alg = sha1 # algorithm to compute certificate # identifier (optional, default: sha1) + +[insta] # CMP using Insta Demo CA +# Message transfer +server = pki.certificate.fi:8700 +# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080 +# tls_use = 0 +path = pkix/ + +# Server authentication +recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer +ignore_keyusage = 1 # potentially needed quirk +unprotected_errors = 1 # potentially needed quirk +extracertsout = insta.extracerts.pem + +# Client authentication +ref = 3078 # user identification +secret = pass:insta # can be used for both client and server side + +# Generic message options +cmd = ir # default operation, can be overridden on cmd line with, e.g., kur + +# Certificate enrollment +subject = "/CN=openssl-cmp-test" +newkey = insta.priv.pem +out_trusted = insta.ca.crt +certout = insta.cert.pem + +[pbm] # Password-based protection for Insta CA +# Server and client authentication +ref = $insta::ref # 3078 +secret = $insta::secret # pass:insta + +[signature] # Signature-based protection for Insta CA +# Server authentication +trusted = insta.ca.crt # does not include keyUsage digitalSignature + +# Client authentication +secret = # disable PBM +key = $insta::newkey # insta.priv.pem +cert = $insta::certout # insta.cert.pem + +[ir] +cmd = ir + +[cr] +cmd = cr + +[kur] +# Certificate update +cmd = kur +oldcert = $insta::certout # insta.cert.pem + +[rr] +# Certificate revocation +cmd = rr +oldcert = $insta::certout # insta.cert.pem diff --git a/doc/man1/build.info b/doc/man1/build.info index c48ff0acbe..5b0b4eb6fd 100644 --- a/doc/man1/build.info +++ b/doc/man1/build.info @@ -4,6 +4,7 @@ DEPEND[]= \ openssl-ca.pod \ openssl-ciphers.pod \ openssl-cmds.pod \ + openssl-cmp.pod \ openssl-cms.pod \ openssl-crl2pkcs7.pod \ openssl-crl.pod \ @@ -58,6 +59,7 @@ DEPEND[openssl-asn1parse.pod]=../perlvars.pm DEPEND[openssl-ca.pod]=../perlvars.pm DEPEND[openssl-ciphers.pod]=../perlvars.pm DEPEND[openssl-cmds.pod]=../perlvars.pm +DEPEND[openssl-cmp.pod]=../perlvars.pm DEPEND[openssl-cms.pod]=../perlvars.pm DEPEND[openssl-crl2pkcs7.pod]=../perlvars.pm DEPEND[openssl-crl.pod]=../perlvars.pm @@ -112,6 +114,7 @@ GENERATE[openssl-asn1parse.pod]=openssl-asn1parse.pod.in GENERATE[openssl-ca.pod]=openssl-ca.pod.in GENERATE[openssl-ciphers.pod]=openssl-ciphers.pod.in GENERATE[openssl-cmds.pod]=openssl-cmds.pod.in +GENERATE[openssl-cmp.pod]=openssl-cmp.pod.in GENERATE[openssl-cms.pod]=openssl-cms.pod.in GENERATE[openssl-crl2pkcs7.pod]=openssl-crl2pkcs7.pod.in GENERATE[openssl-crl.pod]=openssl-crl.pod.in diff --git a/doc/man1/openssl-cmp.pod.in b/doc/man1/openssl-cmp.pod.in new file mode 100644 index 0000000000..b746d26c33 --- /dev/null +++ b/doc/man1/openssl-cmp.pod.in @@ -0,0 +1,1157 @@ +=pod +{- OpenSSL::safe::output_do_not_edit_headers(); -} + +=head1 NAME + +openssl-cmp - client for the Certificate Management Protocol (CMP, RFC 4210) + +=head1 SYNOPSIS + +B B +[B<-help>] +[B<-config> I] +[B<-section> I] + +[B<-server> I] +[B<-proxy> I<[http[s]://]address[:port][/path]>] +[B<-no_proxy> I] +[B<-path> I] +[B<-msg_timeout> I] +[B<-total_timeout> I] + +[B<-trusted> I] +[B<-untrusted> I] +[B<-srvcert> I] +[B<-recipient> I] +[B<-expect_sender> I] +[B<-ignore_keyusage>] +[B<-unprotected_errors>] +[B<-extracertsout> I] +[B<-cacertsout> I] + +[B<-ref> I] +[B<-secret> I] +[B<-cert> I] +[B<-key> I] +[B<-keypass> I] +[B<-digest> I] +[B<-mac> I] +[B<-extracerts> I] +[B<-unprotected_requests>] + +[B<-cmd> I] +[B<-infotype> I] +[B<-geninfo> I] + +[B<-newkey> I] +[B<-newkeypass> I] +[B<-subject> I] +[B<-issuer> I] +[B<-days> I] +[B<-reqexts> I] +[B<-sans> I] +[B<-san_nodefault>] +[B<-policies> I] +[B<-policy_oids> I] +[B<-policy_oids_critical>] +[B<-popo> I] +[B<-csr> I] +[B<-out_trusted> I] +[B<-verify_hostname> I] +[B<-verify_ip> I] +[B<-verify_email> I] +[B<-implicit_confirm>] +[B<-disable_confirm>] +[B<-certout> I] + +[B<-oldcert> I] +[B<-revreason> I] + +[B<-certform> I] +[B<-keyform> I] +[B<-certsform> I] +[B<-otherpass> I] +[B<-engine> I] +{- $OpenSSL::safe::opt_provider_synopsis -} + +[B<-tls_used>] +[B<-tls_cert> I] +[B<-tls_key> I] +[B<-tls_keypass> I] +[B<-tls_extra> I] +[B<-tls_trusted> I] +[B<-tls_host> I] + +[B<-batch>] +[B<-repeat> I] +[B<-reqin>] I +[B<-reqout>] I +[B<-rspin>] I +[B<-rspout>] I +[B<-use_mock_srv>] + +[B<-policy> I] +[B<-purpose> I] +[B<-verify_name> I] +[B<-verify_depth> I] +[B<-auth_level> I] +[B<-attime> I] +[B<-ignore_critical>] +[B<-issuer_checks>] +[B<-policy_check>] +[B<-explicit_policy>] +[B<-inhibit_any>] +[B<-inhibit_map>] +[B<-x509_strict>] +[B<-extended_crl>] +[B<-use_deltas>] +[B<-policy_print>] +[B<-check_ss_sig>] +[B<-crl_check>] +[B<-crl_check_all>] +[B<-trusted_first>] +[B<-suiteB_128_only>] +[B<-suiteB_128>] +[B<-suiteB_192>] +[B<-partial_chain>] +[B<-no_alt_chains>] +[B<-no_check_time>] +[B<-allow_proxy_certs>] + +[B<-port> I] +[B<-max_msgs> I] +[B<-srv_ref> I] +[B<-srv_secret> I] +[B<-srv_cert> I] +[B<-srv_key> I] +[B<-srv_keypass> I] +[B<-srv_trusted> I] +[B<-srv_untrusted> I] +[B<-rsp_cert> I] +[B<-rsp_extracerts> I] +[B<-rsp_capubs> I] +[B<-poll_count> I] +[B<-check_after> I] +[B<-grant_implicitconf>] +[B<-pkistatus> I] +[B<-failure> I] +[B<-failurebits> I] +[B<-statusstring> I] +[B<-send_error>] +[B<-send_unprotected>] +[B<-send_unprot_err>] +[B<-accept_unprotected>] +[B<-accept_unprot_err>] +[B<-accept_raverified>] + +=head1 DESCRIPTION + +The B command is a client implementation for the Certificate +Management Protocol (CMP) as defined in RFC4210. +It can be used to request certificates from a CA server, +update their certificates, +request certificates to be revoked, and perform other CMP requests. + +=head1 OPTIONS + +=over 4 + +=item B<-help> + +Display a summary of all options + +=item B<-config> I + +Configuration file to use. +An empty string C<""> means none. +Default filename is from the environment variable C. + +=item B<-section> I + +Section(s) to use within config file defining CMP options. +An empty string C<""> means no specific section. +Default is C. +Multiple section names may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Contents of sections named later may override contents of sections named before. +In any case, as usual, the C<[default]> section and finally the unnamed +section (as far as present) can provide per-option fallback values. + +=back + + +=head2 Generic message options + +=over 4 + +=item B<-cmd> I + +CMP command to execute. +Currently implemented commands are: + +=over 8 + +=item ir E - Initialization Request + +=item cr E - Certificate Request + +=item p10cr - PKCS#10 Certification Request (for legacy support) + +=item kur EE- Key Update Request + +=item rr E - Revocation Request + +=item genm - General Message + +=back + +B requests initialization of an End Entity into a PKI hierarchy by means of +issuance of a first certificate. + +B requests issuance of an additional certificate for an End Entity already +initialized to the PKI hierarchy. + +B requests issuance of an additional certificate similarly to B +but uses PKCS#10 CSR format. + +B requests (key) update for an existing, given certificate. + +B requests revocation of an existing, given certificate. + +B requests information using a General Message, where optionally +included Bs may be used to state which info is of interest. +Upon receipt of the General Response, information about all received +ITAV Bs is printed to stdout. + +=item B<-infotype> I + +Set InfoType name to use for requesting specific info in B, +e.g., C. + +=item B<-geninfo> I + +generalInfo integer values to place in request PKIHeader with given OID, +e.g., C<1.2.3:int:987>. + +=back + + +=head2 Certificate request options + +=over 4 + +=item B<-newkey> I + +The file containing the private or public key for the certificate requested +in Initialization Request (IR), Certification Request(CR), or +Key Update Request (KUR). +Default is the public key in the PKCS#10 CSR given with the B<-csr> option, +if any, or else the current client key, if given. + +=item B<-newkeypass> I + +Pass phrase source for the key given with the B<-newkey> option. +If not given here, the password will be prompted for if needed. + +For more information about the format of B see the +B section in L. + +=item B<-subject> I + +X509 Distinguished Name (DN) of subject to use in the requested certificate +template. +For KUR, it defaults to the subject DN of the reference certificate +(see B<-oldcert>). +This default is used for IR and CR only if no SANs are set. + +The argument must be formatted as I, +characters may be escaped by C<\>E(backslash), no spaces are skipped. + +In case B<-cert> is not set, for instance when using MSG_MAC_ALG, +the subject DN is also used as sender of the PKI message. + +=item B<-issuer> I + +X509 issuer Distinguished Name (DN) of the CA server +to place in the requested certificate template in IR/CR/KUR. + +The argument must be formatted as I, +characters may be escaped by C<\>E(backslash), no spaces are skipped. + +If neither B<-srvcert> nor B<-recipient> is available, +the name given in this option is also set as the recipient of the CMP message. + +=item B<-days> I + +Number of days the new certificate is requested to be valid for, counting from +the current time of the host. +Also triggers the explicit request that the +validity period starts from the current time (as seen by the host). + +=item B<-reqexts> I + +Name of section in OpenSSL config file defining certificate request extensions. + +=item B<-sans> I + +One or more IP addresses, DNS names, or URIs separated by commas or whitespace +(where in the latter case the whole argument must be enclosed in "...") +to add as Subject Alternative Name(s) (SAN) certificate request extension. +If the special element "critical" is given the SANs are flagged as critical. +Cannot be used if any Subject Alternative Name extension is set via B<-reqexts>. + +=item B<-san_nodefault> + +When Subject Alternative Names are not given via B<-sans> +nor defined via B<-reqexts>, +they are copied by default from the reference certificate (see B<-oldcert>). +This can be disabled by giving the B<-san_nodefault> option. + +=item B<-policies> I + +Name of section in OpenSSL config file defining policies to be set +as certificate request extension. +This option cannot be used together with B<-policy_oids>. + +=item B<-policy_oids> I + +One or more OID(s), separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "...") +to add as certificate policies request extension. +This option cannot be used together with B<-policies>. + +=item B<-policy_oids_critical> + +Flag the policies given with B<-policy_oids> as critical. + +=item B<-popo> I + +Proof-of-Possession (POPO) method to use for IR/CR/KUR; values: C<-1>..<2> where +C<-1> = NONE, C<0> = RAVERIFIED, C<1> = SIGNATURE (default), C<2> = KEYENC. + +Note that a signature-based POPO can only be produced if a private key +is provided via the B<-newkey> or B<-key> options. + +=item B<-csr> I + +CSR in PKCS#10 format to use in legacy P10CR messages. + +=item B<-out_trusted> I + +Trusted certificate(s) to use for verifying the newly enrolled certificate. + +Multiple filenames may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Each source may contain multiple certificates. + +=item B<-verify_hostname> I + +When verification of the newly enrolled certificate is enabled (with the +B<-out_trusted> option), check if any DNS Subject Alternative Name (or if no +DNS SAN is included, the Common Name in the subject) equals the given B. + +=item B<-verify_ip> I + +When verification of the newly enrolled certificate is enabled (with the +B<-out_trusted> option), check if there is +an IP address Subject Alternative Name matching the given IP address. + +=item B<-verify_email> I + +When verification of the newly enrolled certificate is enabled (with the +B<-out_trusted> option), check if there is +an email address Subject Alternative Name matching the given email address. + +=item B<-implicit_confirm> + +Request implicit confirmation of newly enrolled certificates. + +=item B<-disable_confirm> + +Do not send certificate confirmation message for newly enrolled certificate +without requesting implicit confirmation +to cope with broken servers not supporting implicit confirmation correctly. +B This leads to behavior violating RFC 4210. + +=item B<-certout> I + +The file where the newly enrolled certificate should be saved. + +=back + + +=head2 Certificate revocation options + +=over 4 + +=item B<-oldcert> I + +The certificate to be updated (i.e., renewed or re-keyed) in Key Update Request +(KUR) messages or to be revoked in Revocation Request (RR) messages. +It must be given for RR, while for KUR it defaults to B<-cert>. + +The reference certificate determined in this way, if any, is also used for +deriving default subject DN and Subject Alternative Names for IR, CR, and KUR. +Its issuer, if any, is used as default recipient in the CMP message header +if neither B<-srvcert>, B<-recipient>, nor B<-issuer> is available. + +=item B<-revreason> I + +Set CRLReason to be included in revocation request (RR); values: C<0>..C<10> +or C<-1> for none (which is the default). + +Reason numbers defined in RFC 5280 are: + + CRLReason ::= ENUMERATED { + unspecified (0), + keyCompromise (1), + cACompromise (2), + affiliationChanged (3), + superseded (4), + cessationOfOperation (5), + certificateHold (6), + -- value 7 is not used + removeFromCRL (8), + privilegeWithdrawn (9), + aACompromise (10) + } + +=back + + +=head2 Message transfer options + +=over 4 + +=item B<-server> I<[http[s]://]address[:port]> + +The IP address or DNS hostname and optionally port (defaulting to 80 or 443) +of the CMP server to connect to using HTTP(S) transport. +The optional "http://" or "https://" prefix is ignored. + +=item B<-proxy> I<[http[s]://]address[:port][/path]> + +The HTTP(S) proxy server to use for reaching the CMP server unless B +applies, see below. +The optional "http://" or "https://" prefix and any trailing path are ignored. +Defaults to the environment variable C if set, else C +in case no TLS is used, otherwise C if set, else C. + +=item B<-no_proxy> I +List of IP addresses and/or DNS names of servers +not to use an HTTP(S) proxy for, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Default is from the environment variable C if set, else C. + +=item B<-path> I + +HTTP path at the CMP server (aka CMP alias) to use for POST requests. +Defaults to "/". + +=item B<-msg_timeout> I + +Number of seconds (or 0 for infinite) a CMP request-response message round trip +is allowed to take before a timeout error is returned. +Default is 120. + +=item B<-total_timeout> I + +Maximum number seconds an overall enrollment transaction may take, +including attempts polling for certificates on C PKIStatus. +Default is 0 (infinite). + +=back + + +=head2 Server authentication options + +=over 4 + +=item B<-trusted> I + +When verifying signature-based protection of CMP response messages, +these are the CA certificate(s) to trust while checking certificate chains +during CMP server authentication. +This option gives more flexibility than the B<-srvcert> option because +it does not pin down the expected CMP server by allowing only one certificate. + +Multiple filenames may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Each source may contain multiple certificates. + +=item B<-untrusted> I + +Non-trusted intermediate certificate(s) that may be useful +for constructing the TLS client certificate chain (if TLS is enabled) and +for building certificate chains while verifying the CMP server certificate +(when checking signature-based CMP message protection) +and while verifying the newly enrolled certificate. +These may get added to the extraCerts field sent in requests as far as needed. + +Multiple filenames may be given, separated by commas and/or whitespace. +Each file may contain multiple certificates. + +=item B<-srvcert> I + +The specific CMP server certificate to use and directly trust (even if it is +expired) when verifying signature-based protection of CMP response messages. +May be set alternatively to the B<-trusted> option +if the certificate is available and only this one shall be accepted. + +If set, the issuer of the certificate is also used as the recipient of the CMP +request and as the expected sender of the CMP response, +overriding any potential B<-recipient> option. + +=item B<-recipient> I + +This option may be used to explicitly set the Distinguished Name (DN) +of the CMP message recipient, i.e., the CMP server (usually a CA or RA entity). + +The argument must be formatted as I, +characters may be escaped by C<\>E(backslash), no spaces are skipped. + +If a CMP server certificate is given with the B<-srvcert> option, its subject +name is taken as the recipient name and the B<-recipient> option is ignored. +If neither of the two are given, the recipient of the PKI message is +determined in the following order: from the B<-issuer> option if present, +the issuer of old cert given with the B<-oldcert> option if present, +the issuer of the client certificate (B<-cert> option) if present. + +The recipient field in the header of CMP messagese is mandatory. +If none of the options that enable the derivation of the recipient name are +given, no suitable value for the recipient in the PKIHeader is available. +As a last resort it is set to NULL-DN. + +When a response is received, its sender must match the recipient of the request. + +=item B<-expect_sender> I + +Distinguished Name (DN) of the expected sender of CMP response messages when +MSG_SIG_ALG is used for protection. +This can be used to ensure that only a particular entity is accepted +as the CMP server, and attackers are not able to use arbitrary certificates +of a trusted PKI hierarchy to fraudulently pose as a CMP server. +Note that this option gives slightly more freedom than B<-srvcert>, +which pins down the server to a particular certificate, +while B<-expect_sender> I will continue to match after updates of the +server cert. + +The argument must be formatted as I, +characters may be escaped by C<\>E(backslash), no spaces are skipped. + +If not given, the subject DN of B<-srvcert>, if provided, will be used. + +=item B<-ignore_keyusage> + +Ignore key usage restrictions in CMP signer certificates when verifying +signature-based protection of incoming CMP messages, +else C must be allowed for signer certificate. + +=item B<-unprotected_errors> + +Accept missing or invalid protection of negative responses from the server. +This applies to the following message types and contents: + +=over 4 + +=item * error messages + +=item * negative certificate responses (IP/CP/KUP) + +=item * negative revocation responses (RP) + +=item * negative PKIConf messages + +=back + +B This setting leads to unspecified behavior and it is meant +exclusively to allow interoperability with server implementations violating +RFC 4210, e.g.: + +=over 4 + +=item * section 5.1.3.1 allows exceptions from protecting only for special +cases: +"There MAY be cases in which the PKIProtection BIT STRING is deliberately not +used to protect a message [...] because other protection, external to PKIX, will +be applied instead." + +=item * section 5.3.21 is clear on ErrMsgContent: "The CA MUST always sign it +with a signature key." + +=item * appendix D.4 shows PKIConf message having protection + +=back + +=item B<-extracertsout> I + +The file where to save any extra certificates received in the extraCerts field +of response messages. + +=item B<-cacertsout> I + +The file where to save any CA certificates received in the caPubs field of +Initializiation Response (IP) messages. + +=back + + +=head2 Client authentication options + +=over 4 + +=item B<-ref> I + +Reference number/string/value to use as fallback senderKID; this is required +if no sender name can be determined from the B<-cert> or <-subject> options and +is typically used when authenticating with pre-shared key (password-based MAC). + +=item B<-secret> I + +Source of secret value to use for creating PBM-based protection of outgoing +messages and for verifying any PBM-based protection of incoming messages. +PBM stands for Password-Based Message Authentication Code. +This takes precedence over the B<-cert> option. + +For more information about the format of B see the +B section in L. + +=item B<-cert> I + +The client's current certificate. +Requires the corresponding key to be given with B<-key>. +The subject of this certificate will be used as the "sender" field +of outgoing CMP messages, while B<-subjectName> may provide a fallback value. +When using signature-based message protection, this "protection certificate" +will be included first in the extraCerts field of outgoing messages. +In Initialization Request (IR) messages this can be used for authenticating +using an external entity certificate as defined in appendix E.7 of RFC 4210. +For Key Update Request (KUR) messages this is also used as +the certificate to be updated if the B<-oldcert> option is not given. +If the file includes further certs, they are appended to the untrusted certs. +These may get added to the extraCerts field sent in requests as far as needed. + +=item B<-key> I + +The corresponding private key file for the client's current certificate given in +the B<-cert> option. +This will be used for signature-based message protection unless +the B<-secret> option indicating PBM or B<-unprotected_requests> is given. + +=item B<-keypass> I + +Pass phrase source for the private key given with the B<-key> option. +Also used for B<-cert> and B<-oldcert> in case it is an encrypted PKCS#12 file. +If not given here, the password will be prompted for if needed. + +For more information about the format of B see the +B section in L. + +=item B<-digest> I + +Specifies name of supported digest to use in RFC 4210's MSG_SIG_ALG +and as the one-way function (OWF) in MSG_MAC_ALG. +If applicable, this is used for message protection and +Proof-of-Possession (POPO) signatures. +To see the list of supported digests, use B. +Defaults to C. + +=item B<-mac> I + +Specifies the name of the MAC algorithm in MSG_MAC_ALG. +To get the names of supported MAC algorithms use B +and possibly combine such a name with the name of a supported digest algorithm, +e.g., hmacWithSHA256. +Defaults to C as per RFC 4210. + +=item B<-extracerts> I + +Certificates to append in the extraCerts field when sending messages. + +Multiple filenames or URLs may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Each source may contain multiple certificates. + +=item B<-unprotected_requests> + +Send messages without CMP-level protection. + +=back + + +=head2 Credentials format options + +=over 4 + +=item B<-certform> I + +File format to use when saving a certificate to a file. +Default value is PEM. + +=item B<-keyform> I + +Format to assume when reading key files. +Default value is PEM. + +=item B<-certsform> I + +Format to try first when reading multiple certificates from file(s). +Default value is PEM. + +=item B<-otherpass> I + +Pass phrase source for certificate given with the B<-trusted>, B<-untrusted>, +B<-out_trusted>, B<-extracerts>, B<-tls_extra>, or B<-tls_trusted> options. +If not given here, the password will be prompted for if needed. + +For more information about the format of B see the +B section in L. + +=item B<-engine> I + +Specifying a crypto engine B will lead to obtaining a functional +reference to the specified engine, initializing it if needed. +The engine will be used for all algorithms supported for keys +prefixed by C. +Engines may be defined in the OpenSSL config file as usual in an engine section. + +Options specifying keys, like B<-key>, B<-newkey>, B<-tls_key> can prefix +C to engine-specific identifiers for security tokens objects held by +the engine. + The following example utilizes the RFC 7512 PKCS #11 URI scheme +as supported, e.g., by libp11: +C<-key engine:pkcs11:object=my-private-key;type=private;pin-value=1234> + +{- $OpenSSL::safe::opt_provider_item -} + +=back + + +=head2 TLS options + +=over 4 + +=item B<-tls_used> + +Enable using TLS (even when other TLS_related options are not set) +when connecting to CMP server. + +=item B<-tls_cert> I + +Client's TLS certificate. +If the file includes further certificates, +they are used for constructing the client cert chain provided to the TLS server. + +=item B<-tls_key> I + +Private key for the client's TLS certificate. + +=item B<-tls_keypass> I + +Pass phrase source for client's private TLS key B. +Also used for B<-tls_cert> in case it is an encrypted PKCS#12 file. +If not given here, the password will be prompted for if needed. + +For more information about the format of B see the +B section in L. + +=item B<-tls_extra> I + +Extra certificates to provide to TLS server during TLS handshake + +=item B<-tls_trusted> I + +Trusted certificate(s) to use for verifying the TLS server certificate. +This implies hostname validation. + +Multiple filenames may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +Each source may contain multiple certificates. + +=item B<-tls_host> I + +Address to be checked during hostname validation. +This may be a DNS name or an IP address. +If not given it defaults to the B<-server> address. + +=back + + +=head2 Client-side debugging options + +=over 4 + +=item B<-batch> + +Do not interactively prompt for input, for instance when a password is needed. +This can be useful for batch processing and testing. + +=item B<-repeat> I + +Invoke the command the given number of times with the same parameters. +Default is one invocation. + +=item B<-reqin> I + +Take sequence of CMP requests from file(s). +Multiple filenames may be given, separated by commas and/or whitespace +(where in the latter case the whole argument must be enclosed in "..."). +As many files are read as needed for a complete transaction. + +=item B<-reqout> I + +Save sequence of CMP requests to file(s). +Multiple filenames may be given, separated by commas and/or whitespace. +As many files are written as needed to store the complete transaction. + +=item B<-rspin> I + +Process sequence of CMP responses provided in file(s), skipping server. +Multiple filenames may be given, separated by commas and/or whitespace. +As many files are read as needed for the complete transaction. + +=item B<-rspout> I + +Save sequence of CMP responses to file(s). +Multiple filenames may be given, separated by commas and/or whitespace. +As many files are written as needed to store the complete transaction. + +=item B<-use_mock_srv> + +Use the internal mock server for testing the client. +This works at API level, bypassing HTTP transport. + +=back + + +=head2 Certificate verification options, for both CMP and TLS + +=over 4 + +=item B<-policy>, B<-purpose>, B<-verify_name>, B<-verify_depth>, +B<-attime>, +B<-ignore_critical>, B<-issuer_checks>, +B<-policy_check>, +B<-explicit_policy>, B<-inhibit_any>, B<-inhibit_map>, +B<-x509_strict>, B<-extended_crl>, B<-use_deltas>, +B<-policy_print>, B<-check_ss_sig>, B<-crl_check>, B<-crl_check_all>, +B<-trusted_first>, +B<-suiteB_128_only>, B<-suiteB_128>, B<-suiteB_192>, +B<-partial_chain>, B<-no_alt_chains>, B<-no_check_time>, +B<-auth_level>, +B<-allow_proxy_certs> + +Set various options of certificate chain verification. +See L for details. + +=back + + +=head2 Mock server options, for testing purposes only + +=over 4 + +=item B<-port> I + +Act as CMP HTTP server mock-up listening on the given port. + +=item B<-max_msgs> I + +Maximum number of CMP (request) messages the CMP HTTP server mock-up +should handle, which must be non-negative. +The default value is 0, which means that no limit is imposed. +In any case the server terminates on internal errors, but not when it +detects a CMP-level error that it can successfully answer with an error message. + +=item B<-srv_ref> I + +Reference value to use as senderKID of server in case no B<-srv_cert> is given. + +=item B<-srv_secret> I + +Password source for server authentication with a pre-shared key (secret). + +=item B<-srv_cert> I + +Certificate of the server. + +=item B<-srv_key> I + +Private key used by the server for signing messages. + +=item B<-srv_keypass> I + +Server private key (and cert) file pass phrase source. + +=item B<-srv_trusted> I + +Trusted certificates for client authentication. + +=item B<-srv_untrusted> I + +Intermediate certs for constructing chains for CMP protection by client. + +=item B<-rsp_cert> I + +Certificate to be returned as mock enrollment result. + +=item B<-rsp_extracerts> I + +Extra certificates to be included in mock certification responses. + +=item B<-rsp_capubs> I + +CA certificates to be included in mock Initialization Response (IP) message. + +=item B<-poll_count> I + +Number of times the client must poll before receiving a certificate. + +=item B<-check_after> I + +The checkAfter value (number of seconds to wait) to include in poll response. + + +=item B<-grant_implicitconf> + +Grant implicit confirmation of newly enrolled certificate. + +=item B<-pkistatus> I + +PKIStatus to be included in server response. +Valid range is 0 (accepted) .. 6 (keyUpdateWarning). + +=item B<-failure> I + +A single failure info bit number to be included in server response. +Valid range is 0 (badAlg) .. 26 (duplicateCertReq). + +=item B<-failurebits> I +Number representing failure bits to be included in server response. +Valid range is 0 .. 2^27 - 1. + +=item B<-statusstring> I + +Text to be included as status string in server response. + +=item B<-send_error> + +Force server to reply with error message. + +=item B<-send_unprotected> + +Send response messages without CMP-level protection. + +=item B<-send_unprot_err> + +In case of negative responses, server shall send unprotected error messages, +certificate responses (IP/CP/KUP), and revocation responses (RP). +WARNING: This setting leads to behavior violating RFC 4210. + +=item B<-accept_unprotected> + +Accept missing or invalid protection of requests. + +=item B<-accept_unprot_err> + +Accept unprotected error messages from client. + +=item B<-accept_raverified> + +Accept RAVERIFED as proof-of-possession (POPO). + +=back + + +=head1 NOTES + +When setting up CMP configurations and experimenting with enrollment options +typically various errors occur until the configuration is correct and complete. +When the CMP server reports an error the client will by default +check the protection of the CMP response message. +Yet some CMP services tend not to protect negative responses. +In this case the client will reject them, and thus their contents are not shown +although they usually contain hints that would be helpful for diagnostics. +For assisting in such cases the CMP client offers a workaround via the +B<-unprotected_errors> option, which allows accepting such negative messages. + + +=head1 EXAMPLES + +=head2 Simple examples using the default OpenSSL configuration file + +This CMP client implementation comes with demonstrative CMP sections +in the example configuration file F, +which can be used to interact conveniently with the Insta Demo CA. + +In order to enroll an initial certificate from that CA it is sufficient +to issue the following shell commands. + + cd /path/to/openssl + export OPENSSL_CONF=openssl.cnf + wget 'http://pki.certificate.fi:8080/install-ca-cert.html/ca-certificate.crt\ + ?ca-id=632&download-certificate=1' -O insta.ca.crt + openssl genrsa -out insta.priv.pem + openssl cmp -section insta + +This should produce the file F containing a new certificate +for the private key held in F. +It can be viewed using, e.g., + + openssl x509 -noout -text -in insta.cert.pem + +In case the network setup requires using an HTTP proxy it may be given as usual +via the environment variable B or via the B option or +the CMP command-line argument B<-proxy>, for example + + -proxy http://192.168.1.1:8080 + +In the Insta Demo CA scenario both clients and the server may use the pre-shared +secret "insta" and the reference value "3078" to authenticate to each other. + +Alternatively, CMP messages may be protected in signature-based manner, +where the trust anchor in this case is F +and the client may use any certificate already obtained from that CA, +as specified in the B<[signature]> section of the example configuration. +This can be used in combination with the B<[insta]> section simply by + + openssl cmp -section insta,signature + +By default the CMP IR message type is used, yet CR works equally here. +This may be specified directly at the command line: + + openssl cmp -section insta -cmd cr + +or by referencing in addition the B<[cr]> section of the example configuration: + + openssl cmp -section insta,cr + +In order to update the enrolled certificate one may call + + openssl cmp -section insta,kur + +using with PBM-based protection or + + openssl cmp -section insta,kur,signature + +using signature-based protection. + +In a similar way any previously enrolled certificate may be revoked by + + openssl cmp -section insta,rr -trusted insta.ca.crt + +or + + openssl cmp -section insta,rr,signature + +Many more options can be used in the configuration file +and/or on the command line. + + +=head2 Certificate enrollment + +The following examples at first do not make use of a configuration file. +They assume that a CMP server can be contacted on the local TCP port 80 +and accepts requests under the alias "/pkix/". + +For enrolling its very first certificate the client generates a first client key +and sends an initial request message to the local CMP server +using a pre-shared secret key for mutual authentication. +In this example the client does not have the CA certificate yet, +so we specify the name of the CA with the B<-recipient> option +and save any CA certificates that we may receive in the C file. + +In below command line usage examples the C<\> at line ends is just used +for formatting; each of the command invocations should be on a single line. + + openssl genrsa -out cl_key.pem + openssl cmp -cmd ir -server 127.0.0.1:80 -path pkix/ \ + -ref 1234 -secret pass:1234-5678-1234-5678 \ + -recipient "/CN=CMPserver" \ + -newkey cl_key.pem -subject "/CN=MyName" \ + -cacertsout capubs.pem -certout cl_cert.pem + + +=head2 Certificate update + +Then, when the client certificate and its related key pair needs to be updated, +the client can send a key update request taking the certs in C +as trusted for authenticating the server and using the previous cert and key +for its own authentication. +Then it can start using the new cert and key. + + openssl genrsa -out cl_key_new.pem + openssl cmp -cmd kur -server 127.0.0.1:80 -path pkix/ \ + -trusted capubs.pem \ + -cert cl_cert.pem -key cl_key.pem \ + -newkey cl_key_new.pem -certout cl_cert.pem + cp cl_key_new.pem cl_key.pem + +This command sequence can be repated as often as needed. + + +=head2 Requesting information from CMP server + +Requesting "all relevant information" with an empty General Message. +This prints information about all received ITAV Bs to stdout. + + openssl cmp -cmd genm -server 127.0.0.1 -path pkix/ \ + -ref 1234 -secret pass:1234-5678-1234-5678 \ + -recipient "/CN=CMPserver" + + +=head2 Using a custom configuration file + +For CMP client invocations, in particular for certificate enrollment, +usually many parameters need to be set, which is tedious and error-prone to do +on the command line. +Therefore the client offers the possibility to read +options from sections of the OpenSSL config file, usually called B. +The values found there can still be extended and even overridden by any +subsequently loaded sections and on the command line. + +After including in the configuration file the following sections: + + [cmp] + server = 127.0.0.1 + path = pkix/ + trusted = capubs.pem + cert = cl_cert.pem + key = cl_key.pem + newkey = cl_key.pem + certout = cl_cert.pem + + [cmp-init] + recipient = "/CN=CMPserver" + trusted = + cert = + key = + ref = 1234 + secret = pass:1234-5678-1234-567 + subject = "/CN=MyName" + cacertsout = capubs.pem + +the above enrollment invocations reduce to + + openssl cmp -section cmp,cmp-init + openssl cmp -cmd kur -newkey cl_key_new.pem + +and the above genm call reduces to + + openssl cmp -section cmp,cmp-init -cmd genm + +=head1 SEE ALSO + +L, L, L, +L, L, L + +=head1 COPYRIGHT + +Copyright 2007-2019 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the OpenSSL license (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/test/insta.priv.pem b/test/insta.priv.pem new file mode 100755 index 0000000000..8612994d15 --- /dev/null +++ b/test/insta.priv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAoiNNxo5pwk1lD1em3madbpKz86GSYyGlQtd0ZhIX1tOUFo9l +Fex7n5Osv0A99pKb+7EKqB9Ghg6mJ29kIUUmLACnfZJ/q+U6s9T4zFrYyXweUNJv +QgbA2ojDPyVoRp2T1ekahPh4DpxPWNKfYECDRbrxkHMM3WiIqYFLU8hYvEMGSWRH +HbnS/vG7MTaVDkR8d0zixTOp0fST5c1UUTqppYlThac/BG1kk3hyjIjz5o7lspfX +3s/eAYgT9GhYHL6Uy4o4OqCleR39aVc0dMrrjb7hsmX6ecNwqJOE5AHHOG4Ti6Cb +weSOcdH5PRFzdpao5rlTErsFHlUSTca4mfVeWwIDAQABAoIBAQCUYAZevBjgbP8c +qTPVtsY/WBVB0Qwrl7CqmIy2k7wpJfoRIyx4ga8n+3ZMlredm9EO5ZdA/04EhAdd +czyIjcU+42JjMduQLPgpda4xJLnauLDteYXDQHbgBePXN55TcQTG7skMAm2rwTOD +r0uWQ7Nd7pP9gqu1OmJF7EJI68D4llCU1FrOrliwSDzHWP3p4QmCW3M9PQJ68xw1 +gE7X1QflROGivcFoRgcgeoJDzpxveGvPbEn6Q+05/FMRVxjqWhpxdZ9/SL7iRz1e +45T+P9a8OLgTyErT3Lp/f/vuHA1tlbAYumhSnxXsb+nHi80aDcImOrNQHAp076Ik +bkZ1NpOxAoGBAM3Ulgi2hUPdoAMFtHZF8eBHRzn+4uTfY2s33wObiUJQ8VbGDeJY +ifCfOwLThiAXAqktrs7ItwWDYmzd5xPYGQeWoKcBEoZ+dvaaOe8H7TCMnjB3R3i1 +ACSDHo/3c+NfFOnPJtXL85jeAqGYH50uOtYmYaBVe6xASTBgNvP7snYHAoGBAMmo +ZBQqgIhoqMRapGh6n4OpzH0Nt9ruOTJoteAfbLdAu7X+wAaMuxEaAmZQRDYj0lzX +Ty8DlKSy7vfvXtghxMBEv4+dsYpagXcUOeEZSPfu1c3e253dAov6C0MdREKWBT7P ++NwPBowPy0CP/yBeHaw7d/P7/SYIoPXLGraGl6ANAoGBAMmmce7LUjgw0mjjl+8f +i14cts08x3FO4YnTTOut34VW43oNwuBzuYBBn4CfVpHtuS+hj9cKkTQXib/6jj7E +wZDLo0y6Ijodf9MNOaDSdS/RM9Frqlu5iBA9XR3SYnjpWAXQas2eaGLlblJ+RMqq +1f2j0JVR6j3RJWL9gBj8B9TVAoGBALYZrs4bF1iXEhfGNoL2gIdX1QX0VluIFfR0 +ZBDQr87H0Ppm4qbHfMHTt+kGgKJXNMaL08CDvj4AKxWPfhk0XUS2kDmzUDi8w/5x +MFcaCy+A6Gdw4OcsRfl7QaJIknSCnpf7HCI0G1hthsB1iBCFjMwUI50ap54p2pg6 +4ZOD9PYdAoGAERi5Hlq7+rJeDi3VunKHySqV9mvbOPNclEUmAdKi1yuu3INF1Zgv +Lf432ZI/Ufk2g888ed5ZGE1IMULc2tgSIAMzdX4ZYI4uGFLkHWzSOM6a7NCeZuVt +W+NgUYa2qsqFEd9kqaoDbNry+nPvLM7fWXvBoE4oNkeJhHjOIabBPvw= +-----END RSA PRIVATE KEY----- diff --git a/test/insta_ca.cert.pem b/test/insta_ca.cert.pem new file mode 100755 index 0000000000..4b7e31b86d --- /dev/null +++ b/test/insta_ca.cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkDCCAnigAwIBAgIDCZU1MA0GCSqGSIb3DQEBBQUAMDoxCzAJBgNVBAYTAkZJ +MRMwEQYDVQQKEwpJbnN0YSBEZW1vMRYwFAYDVQQDEw1JbnN0YSBEZW1vIENBMB4X +DTA2MDEwMjA4NDgzOFoXDTI1MTIzMTA4NDgzOFowOjELMAkGA1UEBhMCRkkxEzAR +BgNVBAoTCkluc3RhIERlbW8xFjAUBgNVBAMTDUluc3RhIERlbW8gQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF57bSwj+hZnkgLyLtFsoNIN19qBv9 +GIoqFaCiPvw6VQgMXR15t+Z5sdYHdydGp875yJD4wDq2K7cjMoCXALxLeyp6dCY6 +WPC6Hk3QvZtMRuDAz8+0Nb5qaC4+O+7c7j1h/Gs8Jpj+TUuSkmtlCVIGPSWkWaQl +FhLWeUnKRW8bj1CJQguV7igF19kGQKUZ/VZj+n5xIXKHc8njC1ZrS/s0IBFViQkZ +63nTdNPLHQ4Xu8uKrbJbYEK1S4KVNH3L9yA4ut+brqX8n6OulTsKntvMdwNWZdor +KoM15D3lmM7QUGDflJdSQ/qvBVTda+ccrT21sp4hdwwiU01vxQguT26JAgMBAAGj +gZ4wgZswHwYDVR0jBBgwFoAUPHjduMGNV/UFKl5t4FhySvpEJWEwHQYDVR0OBBYE +FDx43bjBjVf1BSpebeBYckr6RCVhMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8E +CDAGAQH/AgEAMDUGCWCGSAGG+EIBDQQoFiZJbnN0YSBEZW1vIENBIC0gb25seSBm +b3IgZGVtbyBwdXJwb3NlczANBgkqhkiG9w0BAQUFAAOCAQEAuVRmRimTxVTZMNXi +3u4bRCq7GxJ4Lonx3mocxYiwBjCYwqn5dPAd4AHrA1HWYCEvIPo52FibpUNNljqH +v7CSoEBg2f4If6cFtwudobqNvf8Z50CAnxlwpPy4k+EbXlh49/uZBtu8+Lc2Ss7L +QaNHHiOeHxYeGX7pTcr6fnXQWAbbn4SLyqniW7ZTqjNJvC79Ym7KowMYzCbmozzv +3xqElA+g/MLFfxn52c/vl/obOVk5eBf3f7V68qKL2IDEip3fyZyoelhfTypq944m +sSJFQjoVzgd7ykgouEwOceOT8YMWWigNsWl/hsVJ03Ri7TxRX4+v8dMEbat+SsTL +AqTTgQ== +-----END CERTIFICATE----- -- 2.25.1