+
+#ifndef DEFINE_STACK_OF_EVP_PKEY
+DEFINE_STACK_OF(EVP_PKEY)
+#endif
+
+#if MODSSL_HAVE_OPENSSL_STORE
+
+static apr_status_t ssl_init_uri_cleanup(void *data)
+{
+ modssl_ctx_uri_t *uctx = (modssl_ctx_uri_t *)data;
+
+ sk_X509_pop_free(uctx->cert_list, X509_free);
+ sk_EVP_PKEY_pop_free(uctx->key_list, EVP_PKEY_free);
+ sk_X509_pop_free(uctx->ca_list, X509_free);
+
+ return APR_SUCCESS;
+}
+
+/*
+ * Sort certificates oldest to newest (last one wins).
+ */
+static int compare_certs_asc(const X509 *const *a, const X509 *const *b)
+{
+ const ASN1_TIME *time_a = X509_get0_notBefore(*a);
+ const ASN1_TIME *time_b = X509_get0_notBefore(*b);
+
+ /* ASN1_TIME_compare returns:
+ * -1 if time_a is earlier than time_b
+ * 0 if they are identical
+ * 1 if time_a is later than time_b
+ */
+ return ASN1_TIME_compare(time_a, time_b);
+}
+
+static int cert_match(apr_pool_t *p, X509 *cert, char *id)
+{
+ if (id[0] == '[') {
+ const char *end = strchr(id, ']');
+ if (end && X509_check_ip_asc(cert,
+ apr_pstrndup(p, id + 1, end - id - 1), 0) == 1) {
+ return 1;
+ }
+ return 0;
+ }
+ if (X509_check_ip_asc(cert, id, 0) == 1) {
+ return 1;
+ }
+ if (X509_check_host(cert, id, 0, 0, NULL) == 1) {
+ return 1;
+ }
+ return 0;
+}
+
+static apr_status_t ssl_init_uri(server_rec *s,
+ apr_pool_t *ptemp,
+ const char *uri,
+ int depth,
+ modssl_ctx_uri_t *uctx)
+{
+ OSSL_STORE_CTX *sctx;
+ OSSL_STORE_INFO *info;
+
+ apr_status_t rv = APR_SUCCESS;
+
+ if (!uri) {
+ return rv;
+ }
+
+ if ((!(sctx = OSSL_STORE_open_ex(uri, uctx->mctx->libctx, NULL,
+ modssl_get_passphrase_ui(ptemp),
+ modssl_get_passphrase_cb(s, ptemp,
+ uctx->mctx->sc->vhost_id, uri),
+ NULL, NULL, NULL)))) {
+ return APR_EGENERAL;
+ }
+
+ while (!OSSL_STORE_eof(sctx) && !OSSL_STORE_error(sctx)) {
+
+ if (!(info = OSSL_STORE_load(sctx))) {
+ continue;
+ }
+
+ switch(OSSL_STORE_INFO_get_type(info)) {
+ case OSSL_STORE_INFO_NAME: {
+
+ if (depth > 0) {
+ rv = ssl_init_uri(s, ptemp,
+ OSSL_STORE_INFO_get0_NAME(info),
+ depth - 1, uctx);
+ if (APR_SUCCESS != rv) {
+ OSSL_STORE_close(sctx);
+ return rv;
+ }
+ }
+
+ break;
+ }
+ case OSSL_STORE_INFO_CERT: {
+
+ X509 *cert;
+
+ if (!(cert = OSSL_STORE_INFO_get1_CERT(info))) {
+ return APR_EGENERAL;
+ }
+ else if (X509_check_ca(cert)) {
+
+ if (X509_self_signed(cert, 1)) {
+
+ uctx->num_ca_certs++;
+
+ /* ignore root certificates */
+ X509_free(cert);
+ continue;
+ }
+
+ if (sk_X509_push(uctx->ca_list, cert) <= 0) {
+ X509_free(cert);
+ OSSL_STORE_close(sctx);
+ return APR_EGENERAL;
+ }
+
+ uctx->num_intermediate_certs++;
+
+ }
+ else {
+
+ uctx->num_leaf_certs++;
+
+ if (!X509_check_purpose(cert, X509_PURPOSE_SSL_SERVER, 0)) {
+ /* ignore non server certs */
+ X509_free(cert);
+ continue;
+ }
+
+ /* check for a match on server name */
+ if (s->server_hostname) {
+ if (!cert_match(ptemp, cert, s->server_hostname)) {
+ X509_free(cert);
+ continue;
+ }
+ }
+
+ /* check for a match on all server aliases */
+ if (s->names && !apr_is_empty_array(s->names)) {
+ char **aliases = (char **)s->names->elts;
+ int i;
+ for (i = 0; i < s->names->nelts; i++) {
+ if (!cert_match(ptemp, cert, aliases[i])) {
+ X509_free(cert);
+ continue;
+ }
+ }
+ }
+
+ /* If we get here and a server name or server alias was
+ * not specified, we use the most recently issued leaf
+ * certificate in scope and assume the admin knows what
+ * they are doing.
+ */
+
+ if (sk_X509_push(uctx->cert_list, cert) <= 0) {
+ X509_free(cert);
+ OSSL_STORE_close(sctx);
+ return APR_EGENERAL;
+ }
+
+ uctx->num_server_certs++;
+
+ }
+
+ uctx->num_certs++;
+
+ break;
+ }
+ case OSSL_STORE_INFO_PKEY: {
+
+ EVP_PKEY *key;
+
+ if (!(key = OSSL_STORE_INFO_get1_PKEY(info))) {
+ OSSL_STORE_close(sctx);
+ return APR_EGENERAL;
+ }
+ if (sk_EVP_PKEY_push(uctx->key_list, key) <= 0) {
+ EVP_PKEY_free(key);
+ OSSL_STORE_close(sctx);
+ return APR_EGENERAL;
+ }
+
+ uctx->num_keys++;
+
+ break;
+ }
+ }
+ }
+
+ OSSL_STORE_close(sctx);
+
+ return rv;
+}
+
+
+/*
+ * Load certs from all URIs.
+ *
+ * The end user might point their URI at a single set of
+ * PEM encoded certs using the file: scheme, or might point
+ * the URI at pkcs11: or the whole MacOS keychain and
+ * expect us to figure it out for them. Lets help as much
+ * as possible.
+ *
+ * - Load all certs across all uris.
+ * - Consider intermediate certs, add them to the store
+ * - Consider leaf certs that match the ServerName and
+ * ServerAliases and drop if no match.
+ * - Sort certs by start date, oldest to newest
+ * - Load all keys across all uris.
+ * - Consider certs with a private key, drop the rest.
+ * - Pass each cert and key, in order, using
+ * SSL_CTX_use_certificate and SSL_CTX_use_PrivateKey.
+ * - End result, the most recent cert for each type (RSA,
+ * ECDSA, etc) wins.
+ *
+ */
+
+static apr_status_t ssl_init_server_uris(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx,
+ apr_array_header_t *pphrases)
+{
+ const char *uri;
+ int i, k;
+ int found = 0;
+ apr_status_t rv = APR_SUCCESS;
+
+ modssl_ctx_uri_t *uctx = apr_pcalloc(ptemp, sizeof(modssl_ctx_uri_t));
+
+ uctx->mctx = mctx;
+
+ uctx->cert_list = sk_X509_new_null();
+ uctx->key_list = sk_EVP_PKEY_new_null();
+ uctx->ca_list = sk_X509_new_null();
+
+ apr_pool_cleanup_register(ptemp, uctx,
+ ssl_init_uri_cleanup,
+ apr_pool_cleanup_null);
+
+ sk_X509_set_cmp_func(uctx->cert_list, compare_certs_asc);
+
+ /* no OpenSSL default prompts for any of the SSL_CTX_use_* calls, please */
+ SSL_CTX_set_default_passwd_cb(mctx->ssl_ctx, ssl_no_passwd_prompt_cb);
+
+ /* Iterate over the SSLCertificateURI array */
+ for (i = 0; (i < mctx->pks->uris->nelts) &&
+ (uri = APR_ARRAY_IDX(mctx->pks->uris, i,
+ const char *));
+ i++) {
+
+ if (ssl_init_uri(s, ptemp, uri, 1, uctx) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10603)
+ "Host %s: Failed to open URI `%s'",
+ mctx->sc->vhost_id, uri);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ }
+
+ /* oldest to newest, last one wins */
+ sk_X509_sort(uctx->cert_list);
+
+ /* Match certs to keys */
+ for (i = sk_X509_num(uctx->cert_list) - 1; i >= 0; i--) {
+ X509 *cert = sk_X509_value(uctx->cert_list, i);
+
+ for (k = 0; k < sk_EVP_PKEY_num(uctx->key_list); k++) {
+ EVP_PKEY *pkey = sk_EVP_PKEY_value(uctx->key_list, k);
+
+ if (X509_check_private_key(cert, pkey) == 1) {
+
+ if (SSL_CTX_use_certificate(mctx->ssl_ctx, cert) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10604)
+ "Host %s: Failed to use certificate: %s",
+ mctx->sc->vhost_id,
+ modssl_X509_NAME_to_string(ptemp,
+ X509_get_subject_name(cert), 0));
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ if (SSL_CTX_use_PrivateKey(mctx->ssl_ctx, pkey) < 1) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10605)
+ "Host %s: Failed to use private key: %s",
+ mctx->sc->vhost_id,
+ modssl_X509_NAME_to_string(ptemp,
+ X509_get_subject_name(cert), 0));
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10606)
+ "Host %s: Server certificate from URI: %s",
+ mctx->sc->vhost_id,
+ modssl_X509_NAME_to_string(ptemp,
+ X509_get_subject_name(cert), 0));
+
+ found = 1;
+ break;
+ }
+
+ }
+ }
+
+ if (!found) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10607)
+ "Host %s: No matching certificate/key pairs found among "
+ "%d certs, %d CA certs, %d intermediate certs, "
+ "%d leaf certs, %d server certs, %d keys.",
+ mctx->sc->vhost_id,
+ uctx->num_certs, uctx->num_ca_certs, uctx->num_intermediate_certs,
+ uctx->num_leaf_certs,
+ uctx->num_server_certs, uctx->num_keys);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ /* Handle intermediates, must happen after cert handling */
+ for (i = sk_X509_num(uctx->ca_list) - 1; i >= 0; i--) {
+ X509 *cert = sk_X509_value(uctx->ca_list, i);
+ if (!SSL_CTX_add1_chain_cert(mctx->ssl_ctx, cert)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10608)
+ "Host %s: Failed to add intermediate certificate: %s",
+ mctx->sc->vhost_id,
+ modssl_X509_NAME_to_string(ptemp,
+ X509_get_subject_name(cert), 0));
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+ }
+
+ /*
+ * Do our best to build as much of the chain as possible with
+ * the certs we were provided.
+ */
+
+ if (!SSL_CTX_build_cert_chain(mctx->ssl_ctx, SSL_BUILD_CHAIN_FLAG_NO_ROOT |
+ SSL_BUILD_CHAIN_FLAG_UNTRUSTED |
+ SSL_BUILD_CHAIN_FLAG_IGNORE_ERROR |
+ SSL_BUILD_CHAIN_FLAG_CLEAR_ERROR)) {
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10609)
+ "Host %s: Could not build the certificate chain from "
+ "%d certs, %d CA certs, %d intermediate certs, "
+ "%d leaf certs, %d server certs, %d keys.",
+ mctx->sc->vhost_id,
+ uctx->num_certs, uctx->num_ca_certs, uctx->num_intermediate_certs,
+ uctx->num_leaf_certs,
+ uctx->num_server_certs, uctx->num_keys);
+ ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
+ return APR_EGENERAL;
+ }
+
+ return rv;
+}
+#else
+static apr_status_t ssl_init_server_uris(server_rec *s,
+ apr_pool_t *p,
+ apr_pool_t *ptemp,
+ modssl_ctx_t *mctx,
+ apr_array_header_t *pphrases)
+{
+ const char *vhost_id = mctx->sc->vhost_id;
+
+ ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10610)
+ "Host %s: Server certificate URIs are not supported on this platform.",
+ mctx->sc->vhost_id);
+
+ return APR_ENOTIMPL;
+}
+#endif
+