From eac7c17c2fbd7dbe8a38b85b0598e6af9da5f34d Mon Sep 17 00:00:00 2001 From: Ken Giusti Date: Tue, 1 Oct 2024 09:54:39 -0400 Subject: [PATCH] Issue #1599: make sslProfile configuration updatable (#1600) This does not solve #1572. It is part of a series of changes that will address #1572. --- include/qpid/dispatch/connection_manager.h | 19 - include/qpid/dispatch/protocol_adaptor.h | 1 + include/qpid/dispatch/server.h | 12 +- include/qpid/dispatch/tls_amqp.h | 55 ++ include/qpid/dispatch/tls_common.h | 183 ++++ include/qpid/dispatch/tls_raw.h | 147 +++ .../skupper_router/management/skrouter.json | 32 +- python/skupper_router_internal/dispatch.py | 9 +- .../management/agent.py | 12 +- .../management/config.py | 2 +- src/CMakeLists.txt | 6 +- src/adaptors/adaptor_common.h | 4 - src/adaptors/amqp/amqp_adaptor.c | 30 +- src/adaptors/amqp/connection_manager.c | 216 ++--- src/adaptors/amqp/qd_connection.c | 400 +------- src/adaptors/amqp/qd_connection.h | 7 +- src/adaptors/amqp/qd_connector.c | 4 + src/adaptors/amqp/qd_connector.h | 10 +- src/adaptors/amqp/qd_listener.c | 5 +- src/adaptors/amqp/qd_listener.h | 2 + src/adaptors/amqp/server_config.c | 30 +- src/adaptors/amqp/server_config.h | 61 +- src/adaptors/http1/http1_adaptor.c | 2 +- src/adaptors/http1/http1_client.c | 2 +- src/adaptors/http1/http1_server.c | 2 +- src/adaptors/http2/http2_adaptor.h | 2 +- src/adaptors/http_common.c | 2 +- src/adaptors/{adaptor_tls.c => legacy_tls.c} | 54 +- src/adaptors/{adaptor_tls.h => legacy_tls.h} | 16 +- src/adaptors/tcp/tcp_adaptor.c | 160 ++-- src/adaptors/tcp/tcp_adaptor.h | 10 +- src/dispatch.c | 8 +- src/dispatch_private.h | 5 - src/http-libwebsockets.c | 25 +- src/router_core/agent_connection.c | 28 +- src/router_core/connections.c | 51 +- src/router_core/router_core_private.h | 8 +- src/server.c | 52 -- src/server_private.h | 1 - src/tls/README.txt | 211 +++++ src/tls/display_name.c | 85 ++ src/tls/private.h | 136 +++ src/tls/tls.c | 868 ++++++++++++++++++ src/tls/tls_amqp.c | 356 +++++++ src/tls/tls_raw.c | 531 +++++++++++ tests/TCP_echo_server.py | 9 +- .../test_connection_manager_static.cpp | 34 +- tests/displayname_files/profile_names1.json | 2 +- tests/displayname_files/profile_names2.json | 2 +- tests/ssl_certs/README | 8 +- tests/ssl_certs/bad-ca-certificate.pem | 53 +- tests/ssl_certs/bad-ca-private-key.pem | 54 ++ tests/ssl_certs/ca-certificate.pem | 53 +- tests/ssl_certs/ca-private-key.pem | 54 ++ tests/ssl_certs/ca2-certificate.pem | 33 + tests/ssl_certs/ca2-private-key.pem | 54 ++ tests/ssl_certs/chained.pem | 100 +- tests/ssl_certs/client-certificate.pem | 48 +- .../ssl_certs/client-private-key-no-pass.pem | 80 +- tests/ssl_certs/client-private-key.pem | 84 +- tests/ssl_certs/client-request.pem | 18 - tests/ssl_certs/client2-certificate.pem | 33 + .../ssl_certs/client2-private-key-no-pass.pem | 52 ++ tests/ssl_certs/client2-private-key.pem | 54 ++ tests/ssl_certs/gencerts.sh | 82 +- tests/ssl_certs/gencerts_openssl.sh | 78 -- tests/ssl_certs/server-certificate.pem | 47 +- .../ssl_certs/server-private-key-no-pass.pem | 80 +- tests/ssl_certs/server-private-key.pem | 84 +- tests/ssl_certs/server-request.pem | 16 - tests/ssl_certs/server2-certificate.pem | 32 + .../ssl_certs/server2-private-key-no-pass.pem | 52 ++ tests/ssl_certs/server2-private-key.pem | 54 ++ tests/system_test.py | 83 +- tests/system_tests_one_router.py | 180 +++- tests/system_tests_router_mesh.py | 2 +- tests/system_tests_skmanage.py | 40 +- tests/system_tests_ssl.py | 839 +++++++++++++---- tests/system_tests_tcp_adaptor_tls.py | 462 ++++++++-- tests/system_tests_topology.py | 10 +- tests/system_tests_user_id.py | 39 +- 81 files changed, 5314 insertions(+), 1553 deletions(-) create mode 100644 include/qpid/dispatch/tls_amqp.h create mode 100644 include/qpid/dispatch/tls_common.h create mode 100644 include/qpid/dispatch/tls_raw.h rename src/adaptors/{adaptor_tls.c => legacy_tls.c} (98%) rename src/adaptors/{adaptor_tls.h => legacy_tls.h} (96%) create mode 100644 src/tls/README.txt create mode 100644 src/tls/display_name.c create mode 100644 src/tls/private.h create mode 100644 src/tls/tls.c create mode 100644 src/tls/tls_amqp.c create mode 100644 src/tls/tls_raw.c create mode 100644 tests/ssl_certs/bad-ca-private-key.pem create mode 100644 tests/ssl_certs/ca-private-key.pem create mode 100644 tests/ssl_certs/ca2-certificate.pem create mode 100644 tests/ssl_certs/ca2-private-key.pem delete mode 100644 tests/ssl_certs/client-request.pem create mode 100644 tests/ssl_certs/client2-certificate.pem create mode 100644 tests/ssl_certs/client2-private-key-no-pass.pem create mode 100644 tests/ssl_certs/client2-private-key.pem delete mode 100755 tests/ssl_certs/gencerts_openssl.sh delete mode 100644 tests/ssl_certs/server-request.pem create mode 100644 tests/ssl_certs/server2-certificate.pem create mode 100644 tests/ssl_certs/server2-private-key-no-pass.pem create mode 100644 tests/ssl_certs/server2-private-key.pem diff --git a/include/qpid/dispatch/connection_manager.h b/include/qpid/dispatch/connection_manager.h index 31b5bf909..0d92dc45c 100644 --- a/include/qpid/dispatch/connection_manager.h +++ b/include/qpid/dispatch/connection_manager.h @@ -27,24 +27,10 @@ #include "qpid/dispatch/server.h" typedef struct qd_connection_manager_t qd_connection_manager_t; -typedef struct qd_config_ssl_profile_t qd_config_ssl_profile_t; typedef struct qd_connection_t qd_connection_t; typedef void (*qd_connection_manager_handler_t) (void *context, qd_connection_t *conn); -struct qd_config_ssl_profile_t { - DEQ_LINKS(qd_config_ssl_profile_t); - char *name; - char *ssl_password; - char *ssl_trusted_certificate_db; - char *ssl_uid_format; - char *uid_name_mapping_file; - char *ssl_certificate_file; - char *ssl_private_key_file; - char *ssl_ciphers; - char *ssl_protocols; -}; - /** * Allocate a connection manager * @@ -69,9 +55,4 @@ void qd_connection_manager_free(qd_connection_manager_t *cm); */ QD_EXPORT void qd_connection_manager_start(qd_dispatch_t *qd); -/** - * Find named qd_config_ssl_profile_t object - */ -qd_config_ssl_profile_t *qd_find_ssl_profile(const qd_connection_manager_t *cm, const char *name); - #endif diff --git a/include/qpid/dispatch/protocol_adaptor.h b/include/qpid/dispatch/protocol_adaptor.h index 02dcd4b8d..60ad0be03 100644 --- a/include/qpid/dispatch/protocol_adaptor.h +++ b/include/qpid/dispatch/protocol_adaptor.h @@ -932,6 +932,7 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, bool connection_trunking); void qdr_connection_info_set_group_correlator(qdr_connection_info_t *info, const char *correlator); +void qdr_connection_info_set_tls(qdr_connection_info_t *info, bool enabled, char *version, char *ciphers, int ssf); void qd_adaptor_listener_init(void); void qd_adaptor_listener_finalize(void); diff --git a/include/qpid/dispatch/server.h b/include/qpid/dispatch/server.h index c5de40383..fa17b31a7 100644 --- a/include/qpid/dispatch/server.h +++ b/include/qpid/dispatch/server.h @@ -24,7 +24,6 @@ #include #include -#include typedef struct qd_server_t qd_server_t; typedef struct qd_container_t qd_container_t; @@ -108,14 +107,6 @@ typedef enum { */ void qd_server_set_container(qd_dispatch_t *qd, struct qd_container_t *container); -/** - * Store address of display name service py object for C code use - * - * @param qd The dispatch handle returned by qd_dispatch. - * @param display_name_service address of python object - */ -qd_error_t qd_register_display_name_service(qd_dispatch_t *qd, void *display_name_service); - pn_proactor_t *qd_server_proactor(const qd_server_t *qd_server); qd_http_server_t *qd_server_http(const qd_server_t *qd_server); uint64_t qd_server_allocate_connection_id(qd_server_t *server); @@ -130,8 +121,7 @@ typedef struct qd_handler_context_t { qd_server_event_handler_t handler; } qd_handler_context_t; -// Use displayName lookup to translate user_id to user name -char *qd_server_query_user_name(const qd_server_t *server, const char *ssl_profile, const char *user_id); + const char *qd_server_get_container_name(const qd_server_t *server); sys_mutex_t *qd_server_get_activation_lock(qd_server_t *server); diff --git a/include/qpid/dispatch/tls_amqp.h b/include/qpid/dispatch/tls_amqp.h new file mode 100644 index 000000000..b4356f9fb --- /dev/null +++ b/include/qpid/dispatch/tls_amqp.h @@ -0,0 +1,55 @@ +#ifndef __tls_amqp_h__ +#define __tls_amqp_h__ 1 +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_common.h" + +typedef struct pn_transport_t pn_transport_t; + +/** + * API for TLS operations specific to Proton AMQP connections. + * + * Note well: these APIs apply only to TLS config/sessions of type QD_TLS_TYPE_PROTON_AMQP! Proton raw connection based + * TLS sessions are not supported. See tls.h and tls_raw_io.h. + */ + + +/** + * Create a new TLS session + * + * @param config the TLS configuration used to create the session + * @param tport transport associated with the session's connection + * @param allow_unencrypted if true permit accepting incoming unencrypted connections + * @return a new TLS session or 0 on error. If error qd_error() is set. + */ +qd_tls_session_t *qd_tls_session_amqp(qd_tls_config_t *config, pn_transport_t *tport, bool allow_unencrypted); + + +/** + * Get the user identifier associated with the TLS session. + * + * @param session the active TLS session to retrieve the user id from. + * @return string containing user name if query succeeds else 0. Caller must free() returned user name string when no + * longer used. + */ +char *qd_tls_session_get_user_id(qd_tls_session_t *session); + +#endif + diff --git a/include/qpid/dispatch/tls_common.h b/include/qpid/dispatch/tls_common.h new file mode 100644 index 000000000..406472fa5 --- /dev/null +++ b/include/qpid/dispatch/tls_common.h @@ -0,0 +1,183 @@ +#ifndef __tls_common_h__ +#define __tls_common_h__ 1 +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/**@file + * Management of TLS configuration and state + */ + + +#include "qpid/dispatch/log.h" + +#include +#include +#include + + +typedef struct qd_tls_config_t qd_tls_config_t; // run-time TLS configuration state +typedef struct qd_tls_session_t qd_tls_session_t; // per connection TLS state +typedef struct qd_ssl2_profile_t qd_ssl2_profile_t; // sslProfile configuration record + +// Proton has two different TLS implementations: one for AMQP and a buffer-based one for use with Raw Connections: +typedef enum { + QD_TLS_TYPE_NONE = 0, // unset + QD_TLS_TYPE_PROTON_AMQP, // for use with AMQP transport + QD_TLS_TYPE_PROTON_RAW, // use raw connection/qd_buffer_t interface +} qd_tls_type_t; + +typedef enum { + QD_TLS_CONFIG_MODE_NONE = 0, // unset + QD_TLS_CONFIG_SERVER_MODE, // Operate as a TLS server (i.e. listener socket) + QD_TLS_CONFIG_CLIENT_MODE, // Operate as an TLS client (i.e. outgoing connections) +} qd_tls_config_mode_t; + +// sslProfile configuration record +struct qd_ssl2_profile_t { + char *ciphers; + char *protocols; + char *trusted_certificate_db; + char *certificate_file; + char *private_key_file; + char *password; + + /** + * Holds the list of component fields of the client certificate from which a unique identifier is constructed. For + * e.g, this field could have the format of 'cou' indicating that the uid will consist of c - common name + * concatenated with o - organization-company name concatenated with u - organization unit + * + * Allowed values can be any combination of the comma separated codes (no duplicates): + * 'c'( ISO3166 two character country code), + * 's'(state or province), + * 'l'(Locality; generally - city), + * 'o'(Organization - Company Name), + * 'u'(Organization Unit - typically certificate type or brand), + * 'n'(CommonName - typically a user name for client certificates) + * + * and one of the following: + * '1'(sha1 certificate fingerprint, the fingerprint, as displayed in the fingerprints section when looking at a certificate + * with say a web browser is the hash of the entire certificate in DER form) + * '2'(sha256 certificate fingerprint) + * '5'(sha512 certificate fingerprint) + */ + char *uid_format; + + /** + * Full path to the file that contains the uid to display name mapping. + */ + char *uid_name_mapping_file; + + /** + * version: Version assigned to the current configuration + * oldest_valid_version: Previous sslProfile updates with versions values < oldest_valid_version have expired. + */ + long version; + long oldest_valid_version; +}; + +/** + * Create a new TLS qd_tls_config_t instance with the given configuration + * + * @param ssl_profile_name the name of the sslProfile configuration to use + * @param p_type protocol type for the child connections (TCP or AMQP) + * @param mode the operational use case (TLS Server or Client) + * @param verify_hostname enforce host name checking (Client mode) + * @param authenticate_peer validate peer's certificate (Server mode) + * + * @return a new qd_tls_config_t instance or 0 on error. qd_error() set if error. + */ +qd_tls_config_t *qd_tls_config(const char *ssl_profile_name, + qd_tls_type_t p_type, + qd_tls_config_mode_t mode, + bool verify_hostname, + bool authenticate_peer); + + +/** + * Release a reference to the qd_tls_config_t + * + * @param config to be released. The config pointer must no longer be referenced + */ +void qd_tls_config_decref(qd_tls_config_t *config); + + +/** + * Release a TLS session context. + * + * See the session constructor API in tls_amqp.h and tls_raw.h + * + * @param session the session to free. It must no longer be referenced after this call. + */ +void qd_tls_session_free(qd_tls_session_t *session); + + +/** + * Get the version of TLS in use by the session. + * + * @param session to be queried. + * @return Null terminated string containing the TLS version description. Returned string buffer must be free()d by + * caller. Return 0 if version not known. + */ +char *qd_tls_session_get_protocol_version(const qd_tls_session_t *session); + +/** + * Get the cipher in use by the session. + * + * @param session to be queried. + * @return Null terminated string containing a description of the active cipher. Returned string buffer must be free()d + * by caller. Return 0 if version not known. + */ +char *qd_tls_session_get_protocol_ciphers(const qd_tls_session_t *session); + +/** + * Get the Security Strength Factor (SSF) of the Cipher in use by the session + * + * @param session to be queried. + * @return the SSF value of the session + */ +int qd_tls_session_get_ssf(const qd_tls_session_t *session); + + +/** + * Fill out the given *profile with the configuration from the named sslProfile record. + * + * @param the name of the sslProfile + * @param a pointer to an uninitialized qd_ssl2_profile_t instance. + * @return a pointer to the passed in qd_ssl2_profile_t on success else 0. Use qd_tls_cleanup_ssl_profile() release + * resources in use by *profile when done. + */ +qd_ssl2_profile_t *qd_tls_read_ssl_profile(const char *ssl_profile_name, qd_ssl2_profile_t *profile); + +/** + * Release any resources allocated by qd_tls_get_ssl_profile() and reset the profile. + * + * @param a pointer to an qd_ssl2_profile_t instance initialized by qd_tls_read_ssl_profile(). + * + * Note this only releases internal resources associated with the profile, the memory pointed to by *profile is owned + * by the caller. + */ +void qd_tls_cleanup_ssl_profile(qd_ssl2_profile_t *profile); + + +// Module initialization/finalization +void qd_tls_initialize(void); +void qd_tls_finalize(void); + +#endif + diff --git a/include/qpid/dispatch/tls_raw.h b/include/qpid/dispatch/tls_raw.h new file mode 100644 index 000000000..f92a2f569 --- /dev/null +++ b/include/qpid/dispatch/tls_raw.h @@ -0,0 +1,147 @@ +#ifndef __tls_raw_h__ +#define __tls_raw_h__ 1 +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_common.h" +#include "qpid/dispatch/buffer.h" + +typedef struct pn_raw_connection_t pn_raw_connection_t; + +/** + * API for TLS operations specific to Proton Raw connections. + * + * Note well: these APIs apply only to TLS configuration/sessions of type QD_TLS_TYPE_PROTON_RAW ONLY! AMQP-based TLS + * encryption/decryption is done internally by Proton - these APIs are not necessary for AMQP TLS and should not be used + * with AMQP connections! See tls.h and tls_amqp.h. + */ + + +/** + * Create a new TLS session + * + * @param config the TLS configuration used to create the session + * @param peer_hostname the expected name of the peer host (for verification) + * @param alpn_protocols optional ALPN protocols for negotiation + * @param alpn_protocol_count length of alpn_protocols array (must be zero if no ALPN) + * @param context passed to the on_secure callback + * @param on_secure optional callback that is invoked if/when the TLS handshake succeeds + * @return the new TLS session or 0 on error. If error qd_error() is set. + */ +typedef void qd_tls_session_on_secure_cb_t(qd_tls_session_t *session, void *context); +qd_tls_session_t *qd_tls_session_raw(qd_tls_config_t *config, + const char *peer_hostname, + const char **alpn_protocols, size_t alpn_protocol_count, + void *context, qd_tls_session_on_secure_cb_t *on_secure); + +/** + * Get the negotiated ALPN value from the session. + * + * @param session the TLS session to query. + * @return null terminated string containing the negotiated ALPN value. Must be free()d by caller. Return 0 if no ALPN + * (yet) negotiated. + */ +char *qd_tls_session_get_alpn_protocol(const qd_tls_session_t *session); + +/** + * Fetch output (cleartext) buffers from the application for encryption and transmission. + * + * This callback is supplied by the application when calling the qd_tls_session_do_io() work loop. The work loop will + * call this callback to get output data buffers from the application. The work loop will encrypt these buffers via TLS + * and send them out the raw connection. + * + * @param context - application supplied context (see qd_tls_session_do_io()) + * @param blist - buffer list for output buffers. The application should append output buffers to the end of the list in + * FIFO order of transmission. + * @param limit - limit the number of buffers that can be appended to the list during the call. The application may + * append up to limit buffers but not more. + * @return - the total number of octets worth of data appended to blist. Zero if there is no output available at the + * time of the call. QD_IO_EOS to force the work loop to close the output side of the stream. No buffers + * should be appended to blist when QD_IO_EOS is returned. Once QD_IO_EOS is returned no further output + * buffers will be taken/sent. + */ +#define QD_IO_EOS (-1) +typedef int64_t qd_tls_take_output_buffers_cb_t(void *context, qd_buffer_list_t *blist, size_t limit); + + +/** + * TLS I/O work loop. + * + * This API will perform TLS data encryption and decryption between a raw connection and and application. Outgoing + * application cleartext data will be fetched from the application as needed via the take_output_cb() callback. The + * cleartext data will be encrypted and written to the raw connection (write buffers). On return any incoming decrypted + * (cleartext) data will be appended to the input_data list. Ownership of the input_data buffers is transferred to the + * caller: the application must release them when no longer needed. + * + * This function will close the raw_conn connection when TLS cleanly closes or if a TLS error occurs. + * + * @param session - the TLS session context + * @param raw_conn - the raw connection for reading/writing encrypted buffers. + * @param take_output_cb - invoked by the I/O loop to get outgoing cleartext application data + * @param take_output_context - passed back to take_output_cb() + * @param input_data - incoming decrypted data is appended to this list. + * @param input_data_count - (output) total number of cleartext octets added to input_data + * @param log_module - log module for logging output + * @param conn_id - connection id for logging + * + * @return 0 if I/O in progress, QD_TLS_DONE if the TLS session has closed, or fatal error if < 0 + */ +#define QD_TLS_DONE 1 +int qd_tls_session_do_io(qd_tls_session_t *session, + pn_raw_connection_t *raw_conn, + qd_tls_take_output_buffers_cb_t *take_output_cb, + void *take_output_context, + qd_buffer_list_t *input_data, + uint64_t *input_data_count, + qd_log_module_t log_module, + uint64_t conn_id); + +/* True if the given session has failed + */ +bool qd_tls_session_is_error(const qd_tls_session_t *session); + +/* True after the TLS handshake has completed successfully + */ +bool qd_tls_session_is_secure(const qd_tls_session_t *session); + +/** + * True if all input (decrypt) data has been received and the receive side of the raw connection has closed. No further + * input data is available from this TLS session. + * + * Sets close_notify to true if a proper close_notify was received from the peer. A missing close_notify is allowed in + * HTTP/1.x if the received message has an explicit length and the framing is valid (see RFC9112, section TLS Connection + * Closure) + */ +bool qd_tls_session_is_input_drained(const qd_tls_session_t *session, bool *close_notify); + +/** + * True if the output (encrypt) side of the TLS session is closed and all pending output has been written to the raw + * connection (including close_notify). + */ +bool qd_tls_session_is_output_flushed(const qd_tls_session_t *session); + +/** + * Retrieve octet counters for encrypted I/O. It is expected that the application maintains counters for the cleartext data + * itself. + */ +uint64_t qd_tls_session_encrypted_output_octet_count(const qd_tls_session_t *session); // outbound to network +uint64_t qd_tls_session_encrypted_input_octet_count(const qd_tls_session_t *session); // inbound from network + +#endif + diff --git a/python/skupper_router/management/skrouter.json b/python/skupper_router/management/skrouter.json index 96aad1a58..b710b3643 100644 --- a/python/skupper_router/management/skrouter.json +++ b/python/skupper_router/management/skrouter.json @@ -668,39 +668,45 @@ "description":"Attributes for setting TLS/SSL configuration for connections.", "referential": true, "extends": "configurationEntity", - "operations": ["CREATE", "DELETE"], + "operations": ["CREATE", "UPDATE", "DELETE"], "attributes": { "ciphers": { "type": "string", "description": "Specifies the enabled ciphers so the SSL Ciphers can be hardened. In other words, use this field to disable weak ciphers. The ciphers are specified in the format understood by the OpenSSL library. For example, ciphers can be set to ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; -- The full list of allowed ciphers can be viewed using the openssl ciphers command", - "create": true + "create": true, + "update": true }, "protocols": { "type": "string", "description": "The TLS protocols that this sslProfile can use. You can specify a list of one or more of TLSv1, TLSv1.1, or TLSv1.2. To specify multiple protocols, separate the protocols with a space. For example, to permit the sslProfile to use TLS v1.1 and TLS v1.2 only, you would set the value to TLSv1.1 TLSv1.2. If you do not specify a value, the sslProfile uses the TLS protocol specified by the system-wide configuration.", - "create": true + "create": true, + "update": true }, "caCertFile": { "type": "path", "description": "The absolute path to the database that contains the public certificates of trusted certificate authorities (CA).", - "create": true + "create": true, + "update": true }, "certFile": { "type": "path", "description": "The absolute path to the file containing the PEM-formatted public certificate to be used on the local end of any connections using this profile.", - "create": true + "create": true, + "update": true }, "privateKeyFile": { "type": "path", "description": "The absolute path to the file containing the PEM-formatted private key for the above certificate.", - "create": true + "create": true, + "update": true }, "password": { "type": "string", "description": "The password that unlocks the certificate key. You can specify the password by specifying an environment variable that stores the password, a file that stores the password, or by entering the password in clear text. To use an environment variable, specify 'password: env:'. Use this option with caution, because the environment of other processes is visible on certain platforms (for example, ps on certain Unix OSs). To use a file, specify 'password: file:'. This option is the most secure, because permissions can be set on the file that contains the password. To specify the password in clear text, specify 'password: pass:', or 'password: literal:', or 'password: '. This option is insecure, so it should only be used if security is not a concern.", - "create": true + "create": true, + "update": true }, "uidFormat": { @@ -712,6 +718,18 @@ "type": "string", "description": "The absolute path to the file containing the unique id to display name mapping", "create": true + }, + "version": { + "type": "integer", + "description": "RESERVED FOR FUTURE USE", + "create": true, + "update": true + }, + "oldestValidVersion": { + "type": "integer", + "description": "RESERVED FOR FUTURE USE", + "create": true, + "update": true } } }, diff --git a/python/skupper_router_internal/dispatch.py b/python/skupper_router_internal/dispatch.py index 789e59073..cb87ae7ca 100644 --- a/python/skupper_router_internal/dispatch.py +++ b/python/skupper_router_internal/dispatch.py @@ -108,7 +108,6 @@ def __init__(self) -> None: self._prototype(self.qd_dispatch_prepare, None, [self.qd_dispatch_p]) self._prototype(self.qd_dispatch_configure_listener, c_void_p, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_configure_connector, c_void_p, [self.qd_dispatch_p, py_object]) - self._prototype(self.qd_dispatch_configure_ssl_profile, c_void_p, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_configure_tcp_listener, c_void_p, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_configure_tcp_connector, c_void_p, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_configure_http_listener, c_void_p, [self.qd_dispatch_p, py_object]) @@ -119,7 +118,11 @@ def __init__(self) -> None: self._prototype(self.qd_dispatch_delete_http_connector, None, [self.qd_dispatch_p, c_void_p]) self._prototype(self.qd_connection_manager_delete_listener, None, [self.qd_dispatch_p, c_void_p]) self._prototype(self.qd_connection_manager_delete_connector, None, [self.qd_dispatch_p, c_void_p]) - self._prototype(self.qd_connection_manager_delete_ssl_profile, c_bool, [self.qd_dispatch_p, c_void_p]) + + self._prototype(self.qd_tls_configure_ssl_profile, c_void_p, [self.qd_dispatch_p, py_object]) + self._prototype(self.qd_tls_update_ssl_profile, c_void_p, [self.qd_dispatch_p, py_object, c_void_p]) + self._prototype(self.qd_tls_delete_ssl_profile, None, [self.qd_dispatch_p, c_void_p]) + self._prototype(self.qd_tls_register_display_name_service, None, [py_object]) self._prototype(self.qd_dispatch_configure_address, None, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_configure_auto_link, None, [self.qd_dispatch_p, py_object]) @@ -132,8 +135,6 @@ def __init__(self) -> None: self._prototype(self.qd_dispatch_policy_host_pattern_remove, None, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_dispatch_policy_host_pattern_lookup, c_char_p, [self.qd_dispatch_p, py_object]) - self._prototype(self.qd_dispatch_register_display_name_service, None, [self.qd_dispatch_p, py_object]) - self._prototype(self.qd_dispatch_set_agent, None, [self.qd_dispatch_p, py_object]) self._prototype(self.qd_router_setup_late, None, [self.qd_dispatch_p]) diff --git a/python/skupper_router_internal/management/agent.py b/python/skupper_router_internal/management/agent.py index 3de68a366..30a041897 100644 --- a/python/skupper_router_internal/management/agent.py +++ b/python/skupper_router_internal/management/agent.py @@ -379,10 +379,18 @@ def _host_port_name_identifier(entity): class SslProfileEntity(EntityAdapter): def create(self): - return self._qd.qd_dispatch_configure_ssl_profile(self._dispatch, self) + ssl_profile = self._qd.qd_tls_configure_ssl_profile(self._dispatch, self) + if ssl_profile is None: + raise ValidationError("Invalid sslProfile configuration: see logs for details.") + return ssl_profile def _delete(self): - self._qd.qd_connection_manager_delete_ssl_profile(self._dispatch, self._implementations[0].key) + self._qd.qd_tls_delete_ssl_profile(self._dispatch, self._implementations[0].key) + + def _update(self): + tmp = self._qd.qd_tls_update_ssl_profile(self._dispatch, self, self._implementations[0].key) + if tmp is None: + raise ValidationError("sslProfile configuration update failed: see logs for details.") def _identifier(self): return self.name diff --git a/python/skupper_router_internal/management/config.py b/python/skupper_router_internal/management/config.py index 4f29ba91a..5aba1ce27 100644 --- a/python/skupper_router_internal/management/config.py +++ b/python/skupper_router_internal/management/config.py @@ -310,7 +310,7 @@ def configure(attributes): from skupper_router_internal.display_name.display_name import DisplayNameService displayname_service = DisplayNameService() - qd.qd_dispatch_register_display_name_service(dispatch, displayname_service) + qd.qd_tls_register_display_name_service(displayname_service) # Configure policy and policy manager before vhosts policyDir = config.by_type('policy')[0]['policyDir'] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 79608521b..03d1a416b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,7 +38,7 @@ add_custom_command ( # Build the skupper-router library. set(qpid_dispatch_SOURCES - adaptors/adaptor_tls.c + adaptors/legacy_tls.c adaptors/reference_adaptor.c adaptors/adaptor_buffer.c adaptors/http_common.c @@ -138,6 +138,10 @@ set(qpid_dispatch_SOURCES router_core/modules/stuck_delivery_detection/delivery_tracker.c router_core/modules/mobile_sync/mobile.c router_core/modules/streaming_link_scrubber/streaming_link_scrubber.c + tls/tls.c + tls/tls_raw.c + tls/tls_amqp.c + tls/display_name.c router_pynode.c schema_enum.c static_assert.c diff --git a/src/adaptors/adaptor_common.h b/src/adaptors/adaptor_common.h index d647f2161..b73f804c1 100644 --- a/src/adaptors/adaptor_common.h +++ b/src/adaptors/adaptor_common.h @@ -32,10 +32,6 @@ #include "qpid/dispatch/router_core.h" #include -#include - -#define QD_IO_EOS -2 // I/O layer: signal clean end-of-output stream -#define QD_IO_EOS_ERR -3 // I/O layer: signal error end-of-output stream #define RAW_BUFFER_BATCH 4 diff --git a/src/adaptors/amqp/amqp_adaptor.c b/src/adaptors/amqp/amqp_adaptor.c index 61c792c11..1b9f90bbe 100644 --- a/src/adaptors/amqp/amqp_adaptor.c +++ b/src/adaptors/amqp/amqp_adaptor.c @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -1377,7 +1378,6 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool pn_connection_t *pn_conn = qd_connection_pn(conn); pn_transport_t *tport = 0; pn_sasl_t *sasl = 0; - pn_ssl_t *ssl = 0; const char *mech = 0; const char *user = 0; const char *container = conn->pn_conn ? pn_connection_remote_container(conn->pn_conn) : 0; @@ -1387,7 +1387,6 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool conn->strip_annotations_out = false; if (conn->pn_conn) { tport = pn_connection_transport(conn->pn_conn); - ssl = conn->ssl; } if (tport) { sasl = pn_sasl(tport); @@ -1537,22 +1536,16 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool } } - char proto[50]; - memset(proto, 0, 50); - char cipher[50]; - memset(cipher, 0, 50); - + char *proto = 0; + char *cipher = 0; int ssl_ssf = 0; - bool is_ssl = false; - if (ssl) { - pn_ssl_get_protocol_name(ssl, proto, 50); - pn_ssl_get_cipher_name(ssl, cipher, 50); - ssl_ssf = pn_ssl_get_ssf(ssl); - is_ssl = true; + if (conn->ssl) { + proto = qd_tls_session_get_protocol_version(conn->ssl); + cipher = qd_tls_session_get_protocol_ciphers(conn->ssl); + ssl_ssf = qd_tls_session_get_ssf(conn->ssl); } - bool encrypted = tport && pn_transport_is_encrypted(tport); bool authenticated = tport && pn_transport_is_authenticated(tport); @@ -1568,7 +1561,7 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool container, props, ssl_ssf, - is_ssl, + !!conn->ssl, rversion, streaming_links, connection_trunking); @@ -1611,6 +1604,9 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool authenticated ? mech : "no", (char*) user, container); sys_mutex_unlock(&conn->connector->lock); } + + free(proto); + free(cipher); } @@ -2394,8 +2390,7 @@ static void qd_amqp_adaptor_final(void *adaptor_context) pn_connection_set_context(ctx->pn_conn, 0); } qd_connection_invoke_deferred_calls(ctx, true); // Discard any pending deferred calls - if (ctx->free_user_id) - free((char*)ctx->user_id); + free(ctx->user_id); sys_mutex_free(&ctx->deferred_call_lock); free(ctx->name); free(ctx->role); @@ -2409,6 +2404,7 @@ static void qd_amqp_adaptor_final(void *adaptor_context) qd_listener_remove_connection(ctx->listener, ctx); ctx->listener = 0; } + qd_tls_session_free(ctx->ssl); sys_atomic_destroy(&ctx->wake_core); sys_atomic_destroy(&ctx->wake_cutthrough_inbound); sys_atomic_destroy(&ctx->wake_cutthrough_outbound); diff --git a/src/adaptors/amqp/connection_manager.c b/src/adaptors/amqp/connection_manager.c index a12e0b63a..d9fb4ea74 100644 --- a/src/adaptors/amqp/connection_manager.c +++ b/src/adaptors/amqp/connection_manager.c @@ -31,6 +31,7 @@ #include "qpid/dispatch/failoverlist.h" #include "qpid/dispatch/threading.h" #include "qpid/dispatch/vanflow.h" +#include "qpid/dispatch/tls_amqp.h" #include @@ -41,107 +42,20 @@ #define CHECK() if (qd_error_code()) goto error -DEQ_DECLARE(qd_config_ssl_profile_t, qd_config_ssl_profile_list_t); - struct qd_connection_manager_t { qd_server_t *server; qd_listener_list_t listeners; qd_connector_list_t connectors; qd_connector_list_t data_connectors; - qd_config_ssl_profile_list_t config_ssl_profiles; }; -/** - * Search the list of config_ssl_profiles for an ssl-profile that matches the passed in name - */ -qd_config_ssl_profile_t *qd_find_ssl_profile(const qd_connection_manager_t *cm, const char *ssl_profile_name) -{ - qd_config_ssl_profile_t *ssl_profile = DEQ_HEAD(cm->config_ssl_profiles); - while (ssl_profile) { - if (strcmp(ssl_profile->name, ssl_profile_name) == 0) - return ssl_profile; - ssl_profile = DEQ_NEXT(ssl_profile); - } - - return 0; -} - - -static bool config_ssl_profile_free(qd_connection_manager_t *cm, qd_config_ssl_profile_t *ssl_profile) -{ - DEQ_REMOVE(cm->config_ssl_profiles, ssl_profile); - - free(ssl_profile->name); - free(ssl_profile->ssl_password); - free(ssl_profile->ssl_trusted_certificate_db); - free(ssl_profile->ssl_uid_format); - free(ssl_profile->uid_name_mapping_file); - free(ssl_profile->ssl_certificate_file); - free(ssl_profile->ssl_private_key_file); - free(ssl_profile->ssl_ciphers); - free(ssl_profile->ssl_protocols); - free(ssl_profile); - return true; - -} - - -QD_EXPORT qd_config_ssl_profile_t *qd_dispatch_configure_ssl_profile(qd_dispatch_t *qd, qd_entity_t *entity) -{ - qd_error_clear(); - qd_connection_manager_t *cm = qd->connection_manager; - - qd_config_ssl_profile_t *ssl_profile = NEW(qd_config_ssl_profile_t); - ZERO(ssl_profile); - DEQ_ITEM_INIT(ssl_profile); - DEQ_INSERT_TAIL(cm->config_ssl_profiles, ssl_profile); - ssl_profile->name = qd_entity_opt_string(entity, "name", 0); CHECK(); - ssl_profile->ssl_certificate_file = qd_entity_opt_string(entity, "certFile", 0); CHECK(); - ssl_profile->ssl_private_key_file = qd_entity_opt_string(entity, "privateKeyFile", 0); CHECK(); - ssl_profile->ssl_password = qd_entity_opt_string(entity, "password", 0); CHECK(); - - if (ssl_profile->ssl_password) { - // - // Process the password to handle any modifications or lookups needed - // - char *actual_pass = 0; - bool is_file_path = 0; - qd_server_config_process_password(&actual_pass, ssl_profile->ssl_password, &is_file_path, true); - CHECK(); - if (actual_pass) { - if (is_file_path) { - qd_set_password_from_file(actual_pass, &ssl_profile->ssl_password); - free(actual_pass); - } - else { - free(ssl_profile->ssl_password); - ssl_profile->ssl_password = actual_pass; - } - } - } - - ssl_profile->ssl_ciphers = qd_entity_opt_string(entity, "ciphers", 0); CHECK(); - ssl_profile->ssl_protocols = qd_entity_opt_string(entity, "protocols", 0); CHECK(); - ssl_profile->ssl_trusted_certificate_db = qd_entity_opt_string(entity, "caCertFile", 0); CHECK(); - ssl_profile->ssl_uid_format = qd_entity_opt_string(entity, "uidFormat", 0); CHECK(); - ssl_profile->uid_name_mapping_file = qd_entity_opt_string(entity, "uidNameMappingFile", 0); CHECK(); - - qd_log(LOG_CONN_MGR, QD_LOG_INFO, "Created SSL Profile with name %s ", ssl_profile->name); - return ssl_profile; - - error: - qd_log(LOG_CONN_MGR, QD_LOG_ERROR, "Unable to create ssl profile: %s", qd_error_message()); - config_ssl_profile_free(cm, ssl_profile); - return 0; -} - static void log_config(qd_server_config_t *c, const char *what, bool create) { // Log creation/deletion of config objects at INFO level. qd_log(LOG_CONN_MGR, QD_LOG_INFO, "%s %s: %s proto=%s, role=%s%s%s%s", create ? "Configured ": "Deleted ", what, c->host_port, c->socket_address_family ? c->socket_address_family : "any", c->role, c->http ? ", http" : "", - c->ssl_profile ? ", sslProfile=" : "", c->ssl_profile ? c->ssl_profile : ""); + c->ssl_profile_name ? ", sslProfile=" : "", c->ssl_profile_name ? c->ssl_profile_name : ""); } @@ -154,6 +68,22 @@ QD_EXPORT qd_listener_t *qd_dispatch_configure_listener(qd_dispatch_t *qd, qd_en qd_listener_decref(li); return 0; } + + if (li->config.ssl_profile_name) { + li->tls_config = qd_tls_config(li->config.ssl_profile_name, + QD_TLS_TYPE_PROTON_AMQP, + QD_TLS_CONFIG_SERVER_MODE, + li->config.verify_host_name, + li->config.ssl_require_peer_authentication); + if (!li->tls_config) { + // qd_tls_config() sets qd_error_message(): + qd_log(LOG_CONN_MGR, QD_LOG_ERROR, "Failed to configure TLS for Listener %s: %s", + li->config.name, qd_error_message()); + qd_listener_decref(li); + return 0; + } + } + char *fol = qd_entity_opt_string(entity, "failoverUrls", 0); if (fol) { li->config.failover_list = qd_failover_list(fol); @@ -350,14 +280,34 @@ QD_EXPORT qd_connector_t *qd_dispatch_configure_connector(qd_dispatch_t *qd, qd_ { qd_connection_manager_t *cm = qd->connection_manager; qd_connector_t *ct = qd_server_connector(qd->server); + qd_connector_list_t data_connectors = DEQ_EMPTY; qd_error_clear(); - if (ct && qd_server_config_load(qd, &ct->config, entity, false, 0) == QD_ERROR_NONE) { + if (!ct) { + char *name = qd_entity_opt_string(entity, "name", "UNKNOWN"); + qd_error(QD_ERROR_CONFIG, "Failed to create Connector %s: resource allocation failed", name); + free(name); + return 0; + } + + if (qd_server_config_load(qd, &ct->config, entity, false, 0) == QD_ERROR_NONE) { ct->policy_vhost = qd_entity_opt_string(entity, "policyVhost", 0); CHECK(); - DEQ_ITEM_INIT(ct); - DEQ_INSERT_TAIL(cm->connectors, ct); - log_config(&ct->config, "Connector", true); + + // + // If an sslProfile is configured allocate a TLS config for this connector's connections + // + if (ct->config.ssl_profile_name) { + ct->tls_config = qd_tls_config(ct->config.ssl_profile_name, + QD_TLS_TYPE_PROTON_AMQP, + QD_TLS_CONFIG_CLIENT_MODE, + ct->config.verify_host_name, + ct->config.ssl_require_peer_authentication); + if (!ct->tls_config) { + // qd_tls2_config() has set the qd_error_message(), which is logged below + goto error; + } + } uint32_t connection_count = 0; // @@ -392,23 +342,46 @@ QD_EXPORT qd_connector_t *qd_dispatch_configure_connector(qd_dispatch_t *qd, qd_ qd_generate_discriminator(ct->group_correlator); for (uint32_t i = 0; i < connection_count; i++) { qd_connector_t *dc = qd_server_connector(qd->server); - if (dc && qd_server_config_load(qd, &dc->config, entity, false, "inter-router-data") == QD_ERROR_NONE) { - strncpy(dc->group_correlator, ct->group_correlator, QD_DISCRIMINATOR_SIZE); - dc->is_data_connector = true; - DEQ_INSERT_TAIL(cm->data_connectors, dc); - qd_failover_item_t *item = NEW(qd_failover_item_t); - ZERO(item); - if (dc->config.ssl_required) - item->scheme = strdup("amqps"); - else - item->scheme = strdup("amqp"); - item->host = strdup(dc->config.host); - item->port = strdup(dc->config.port); - int hplen = strlen(item->host) + strlen(item->port) + 2; - item->host_port = malloc(hplen); - snprintf(item->host_port, hplen, "%s:%s", item->host , item->port); - DEQ_INSERT_TAIL(dc->conn_info_list, item); + if (!dc) { + qd_error(QD_ERROR_CONFIG, "Failed to create data Connector %s: resource allocation failed", ct->config.name); + goto error; + } + + if (qd_server_config_load(qd, &dc->config, entity, false, "inter-router-data") != QD_ERROR_NONE) { + // qd_server_config_load will set qd_error() + qd_connector_decref(dc); + goto error; } + + if (ct->tls_config) { + dc->tls_config = qd_tls_config(ct->config.ssl_profile_name, + QD_TLS_TYPE_PROTON_AMQP, + QD_TLS_CONFIG_CLIENT_MODE, + ct->config.verify_host_name, + ct->config.ssl_require_peer_authentication); + if (!dc->tls_config) { + // qd_tls2_config() has set the qd_error_message(), which is logged below + qd_connector_decref(dc); + goto error; + } + } + + strncpy(dc->group_correlator, ct->group_correlator, QD_DISCRIMINATOR_SIZE); + dc->is_data_connector = true; + qd_failover_item_t *item = NEW(qd_failover_item_t); + ZERO(item); + if (dc->config.ssl_required) + item->scheme = strdup("amqps"); + else + item->scheme = strdup("amqp"); + item->host = strdup(dc->config.host); + item->port = strdup(dc->config.port); + int hplen = strlen(item->host) + strlen(item->port) + 2; + item->host_port = malloc(hplen); + snprintf(item->host_port, hplen, "%s:%s", item->host , item->port); + DEQ_INSERT_TAIL(dc->conn_info_list, item); + + DEQ_INSERT_TAIL(data_connectors, dc); } } @@ -429,6 +402,7 @@ QD_EXPORT qd_connector_t *qd_dispatch_configure_connector(qd_dispatch_t *qd, qd_ int hplen = strlen(item->host) + strlen(item->port) + 2; item->host_port = malloc(hplen); snprintf(item->host_port, hplen, "%s:%s", item->host , item->port); + DEQ_INSERT_TAIL(ct->conn_info_list, item); // // Set up the vanflow record for this connector (LINK) @@ -452,12 +426,19 @@ QD_EXPORT qd_connector_t *qd_dispatch_configure_connector(qd_dispatch_t *qd, qd_ vflow_add_rate(ct->vflow_record, VFLOW_ATTRIBUTE_OCTETS_REVERSE, VFLOW_ATTRIBUTE_OCTET_RATE_REVERSE); } - DEQ_INSERT_TAIL(ct->conn_info_list, item); + DEQ_APPEND(cm->data_connectors, data_connectors); + DEQ_INSERT_TAIL(cm->connectors, ct); + log_config(&ct->config, "Connector", true); return ct; } error: qd_log(LOG_CONN_MGR, QD_LOG_ERROR, "Unable to create connector: %s", qd_error_message()); + for (qd_connector_t *dc = DEQ_HEAD(data_connectors); dc; dc = DEQ_HEAD(data_connectors)) { + DEQ_REMOVE_HEAD(data_connectors); + dc->state = CXTR_STATE_DELETED; + qd_connector_decref(dc); + } ct->state = CXTR_STATE_DELETED; qd_connector_decref(ct); return 0; @@ -474,7 +455,6 @@ qd_connection_manager_t *qd_connection_manager(qd_dispatch_t *qd) DEQ_INIT(cm->listeners); DEQ_INIT(cm->connectors); DEQ_INIT(cm->data_connectors); - DEQ_INIT(cm->config_ssl_profiles); return cm; } @@ -523,13 +503,6 @@ void qd_connection_manager_free(qd_connection_manager_t *cm) connector = DEQ_HEAD(to_free); } - qd_config_ssl_profile_t *sslp = DEQ_HEAD(cm->config_ssl_profiles); - while (sslp) { - config_ssl_profile_free(cm, sslp); - sslp = DEQ_HEAD(cm->config_ssl_profiles); - } - - free(cm); } @@ -600,15 +573,6 @@ QD_EXPORT void qd_connection_manager_delete_listener(qd_dispatch_t *qd, void *im } -QD_EXPORT void qd_connection_manager_delete_ssl_profile(qd_dispatch_t *qd, void *impl) -{ - qd_config_ssl_profile_t *ssl_profile = (qd_config_ssl_profile_t*) impl; - - qd_log(LOG_CONN_MGR, QD_LOG_INFO, "Deleted SSL Profile with name %s ", ssl_profile->name); - - config_ssl_profile_free(qd->connection_manager, ssl_profile); -} - static void deferred_close(void *context, bool discard) { if (!discard) { pn_connection_close((pn_connection_t*)context); diff --git a/src/adaptors/amqp/qd_connection.c b/src/adaptors/amqp/qd_connection.c index 70a768015..acbaeec28 100644 --- a/src/adaptors/amqp/qd_connection.c +++ b/src/adaptors/amqp/qd_connection.c @@ -28,6 +28,7 @@ #include "qpid/dispatch/protocol_adaptor.h" #include "qpid/dispatch/timer.h" #include "qpid/dispatch/vanflow.h" +#include "qpid/dispatch/tls_amqp.h" #include #include @@ -41,18 +42,6 @@ ALLOC_DEFINE_SAFE(qd_connection_t); const char *MECH_EXTERNAL = "EXTERNAL"; -//Allowed uidFormat fields. -const char CERT_COUNTRY_CODE = 'c'; -const char CERT_STATE = 's'; -const char CERT_CITY_LOCALITY = 'l'; -const char CERT_ORGANIZATION_NAME = 'o'; -const char CERT_ORGANIZATION_UNIT = 'u'; -const char CERT_COMMON_NAME = 'n'; -const char CERT_FINGERPRINT_SHA1 = '1'; -const char CERT_FINGERPRINT_SHA256 = '2'; -const char CERT_FINGERPRINT_SHA512 = '5'; -const char *COMPONENT_SEPARATOR = ";"; - /** * This function is set as the pn_transport->tracer and is invoked when proton tries to write the log message to pn_transport->tracer @@ -76,206 +65,6 @@ void qd_connection_transport_tracer(pn_transport_t *transport, const char *messa } } -/** - * Returns a char pointer to a user id which is constructed from components specified in the config->ssl_uid_format. - * Parses through each component and builds a semi-colon delimited string which is returned as the user id. - */ -static const char *transport_get_user(qd_connection_t *conn, pn_transport_t *tport) -{ - const qd_server_config_t *config = - conn->connector ? &conn->connector->config : &conn->listener->config; - - if (config->ssl_uid_format) { - // The ssl_uid_format length cannot be greater that 7 - assert(strlen(config->ssl_uid_format) < 8); - - // - // The tokens in the uidFormat strings are delimited by comma. Load the individual components of the uidFormat - // into the components[] array. The maximum number of components that are allowed are 7 namely, c, s, l, o, u, n, (1 or 2 or 5) - // - char components[8]; - - //The strcpy() function copies the string pointed to by src, including the terminating null byte ('\0'), to the buffer pointed to by dest. - strncpy(components, config->ssl_uid_format, 7); - - const char *country_code = 0; - const char *state = 0; - const char *locality_city = 0; - const char *organization = 0; - const char *org_unit = 0; - const char *common_name = 0; - // - // SHA1 is 20 octets (40 hex characters); SHA256 is 32 octets (64 hex characters). - // SHA512 is 64 octets (128 hex characters) - // - char fingerprint[129] = "\0"; - - int uid_length = 0; - int semi_colon_count = -1; - - int component_count = strlen(components); - - for (int x = 0; x < component_count ; x++) { - // accumulate the length into uid_length on each pass so we definitively know the number of octets to malloc. - if (components[x] == CERT_COUNTRY_CODE) { - country_code = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_COUNTRY_NAME); - if (country_code) { - uid_length += strlen((const char *)country_code); - semi_colon_count++; - } - } - else if (components[x] == CERT_STATE) { - state = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE); - if (state) { - uid_length += strlen((const char *)state); - semi_colon_count++; - } - } - else if (components[x] == CERT_CITY_LOCALITY) { - locality_city = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY); - if (locality_city) { - uid_length += strlen((const char *)locality_city); - semi_colon_count++; - } - } - else if (components[x] == CERT_ORGANIZATION_NAME) { - organization = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME); - if(organization) { - uid_length += strlen((const char *)organization); - semi_colon_count++; - } - } - else if (components[x] == CERT_ORGANIZATION_UNIT) { - org_unit = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT); - if(org_unit) { - uid_length += strlen((const char *)org_unit); - semi_colon_count++; - } - } - else if (components[x] == CERT_COMMON_NAME) { - common_name = pn_ssl_get_remote_subject_subfield(pn_ssl(tport), PN_SSL_CERT_SUBJECT_COMMON_NAME); - if(common_name) { - uid_length += strlen((const char *)common_name); - semi_colon_count++; - } - } - else if (components[x] == CERT_FINGERPRINT_SHA1 || components[x] == CERT_FINGERPRINT_SHA256 || components[x] == CERT_FINGERPRINT_SHA512) { - // Allocate the memory for message digest - int out = 0; - - int fingerprint_length = 0; - if(components[x] == CERT_FINGERPRINT_SHA1) { - fingerprint_length = 40; - out = pn_ssl_get_cert_fingerprint(pn_ssl(tport), fingerprint, fingerprint_length + 1, PN_SSL_SHA1); - } - else if (components[x] == CERT_FINGERPRINT_SHA256) { - fingerprint_length = 64; - out = pn_ssl_get_cert_fingerprint(pn_ssl(tport), fingerprint, fingerprint_length + 1, PN_SSL_SHA256); - } - else if (components[x] == CERT_FINGERPRINT_SHA512) { - fingerprint_length = 128; - out = pn_ssl_get_cert_fingerprint(pn_ssl(tport), fingerprint, fingerprint_length + 1, PN_SSL_SHA512); - } - - (void) out; // avoid 'out unused' compiler warnings if NDEBUG undef'ed - assert (out != PN_ERR); - - uid_length += fingerprint_length; - semi_colon_count++; - } - else { - // This is an unrecognized component. log a critical error - qd_log(LOG_SERVER, QD_LOG_CRITICAL, - "[C%" PRIu64 "] Unrecognized component '%c' in uidFormat ", conn->connection_id, components[x]); - return 0; - } - } - - if(uid_length > 0) { - char *user_id = malloc((uid_length + semi_colon_count + 1) * sizeof(char)); // the +1 is for the '\0' character - // - // We have allocated memory for user_id. We are responsible for freeing this memory. Set conn->free_user_id - // to true so that we know that we have to free the user_id - // - conn->free_user_id = true; - memset(user_id, 0, uid_length + semi_colon_count + 1); - - // The components in the user id string must appear in the same order as it appears in the component string. that is - // why we have this loop - for (int x=0; x < component_count ; x++) { - if (components[x] == CERT_COUNTRY_CODE) { - if (country_code) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) country_code); - } - } - else if (components[x] == CERT_STATE) { - if (state) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) state); - } - } - else if (components[x] == CERT_CITY_LOCALITY) { - if (locality_city) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) locality_city); - } - } - else if (components[x] == CERT_ORGANIZATION_NAME) { - if (organization) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) organization); - } - } - else if (components[x] == CERT_ORGANIZATION_UNIT) { - if (org_unit) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) org_unit); - } - } - else if (components[x] == CERT_COMMON_NAME) { - if (common_name) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) common_name); - } - } - else if (components[x] == CERT_FINGERPRINT_SHA1 || components[x] == CERT_FINGERPRINT_SHA256 || components[x] == CERT_FINGERPRINT_SHA512) { - if (strlen((char *) fingerprint) > 0) { - if(*user_id != '\0') - strcat(user_id, COMPONENT_SEPARATOR); - strcat(user_id, (char *) fingerprint); - } - } - } - if (config->ssl_uid_name_mapping_file) { - // Translate extracted id into display name if possible: - char *result = qd_server_query_user_name(conn->server, config->ssl_profile, user_id); - if (result) { - free(user_id); - user_id = result; - } else { - qd_log(LOG_SERVER, QD_LOG_DEBUG, - "[C%" PRIu64 "] Internal: failed to read displaynameservice query result", - conn->connection_id); - // use original user_id instead... - } - } - qd_log(LOG_SERVER, QD_LOG_DEBUG, "User id is '%s' ", user_id); - return user_id; - } - } - else //config->ssl_uid_format not specified, just return the username provided by the proton transport. - return pn_transport_get_user(tport); - - return 0; -} - /* Wake function for proactor-managed connections */ static void connection_wake(qd_connection_t *ctx) @@ -539,13 +328,25 @@ void qd_connection_set_user(qd_connection_t *conn) pn_transport_t *tport = pn_connection_transport(conn->pn_conn); pn_sasl_t *sasl = pn_sasl(tport); if (sasl) { + char *user_id = 0; const char *mech = pn_sasl_get_mech(sasl); - conn->user_id = pn_transport_get_user(tport); + // We want to set the user name only if it is not already set and the selected sasl mechanism is EXTERNAL - if (mech && strcmp(mech, MECH_EXTERNAL) == 0) { - const char *user_id = transport_get_user(conn, tport); - if (user_id) - conn->user_id = user_id; + if (mech && strcmp(mech, MECH_EXTERNAL) == 0 && conn->ssl) { + user_id = qd_tls_session_get_user_id(conn->ssl); + } + + if (!user_id) { + const char *tuid = pn_transport_get_user(tport); + if (tuid) { + user_id = qd_strdup(tuid); + } + } + + assert(!conn->user_id); + conn->user_id = user_id; + if (conn->user_id) { + qd_log(LOG_SERVER, QD_LOG_DEBUG, "[C%" PRIu64 "] User id is '%s'", conn->connection_id, conn->user_id); } } } @@ -576,10 +377,11 @@ static void qd_connection_free(qd_connection_t *qd_conn, const char *condition_n qd_connection_invoke_deferred_calls(qd_conn, true); // Discard any pending deferred calls sys_mutex_free(&qd_conn->deferred_call_lock); qd_policy_settings_free(qd_conn->policy_settings); - if (qd_conn->free_user_id) free((char*)qd_conn->user_id); + free(qd_conn->user_id); if (qd_conn->timer) qd_timer_free(qd_conn->timer); free(qd_conn->name); free(qd_conn->role); + qd_tls_session_free(qd_conn->ssl); sys_atomic_destroy(&qd_conn->wake_core); sys_atomic_destroy(&qd_conn->wake_cutthrough_inbound); sys_atomic_destroy(&qd_conn->wake_cutthrough_outbound); @@ -743,67 +545,6 @@ uint64_t qd_connection_max_message_size(const qd_connection_t *c) } -static qd_error_t listener_setup_ssl(qd_connection_t *ctx, const qd_server_config_t *config, pn_transport_t *tport) -{ - pn_ssl_domain_t *domain = pn_ssl_domain(PN_SSL_MODE_SERVER); - if (!domain) return qd_error(QD_ERROR_RUNTIME, "No SSL support"); - - // setup my identifying cert: - if (pn_ssl_domain_set_credentials(domain, - config->ssl_certificate_file, - config->ssl_private_key_file, - config->ssl_password)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot set SSL credentials"); - } - - // for peer authentication: - if (config->ssl_trusted_certificate_db) { - if (pn_ssl_domain_set_trusted_ca_db(domain, config->ssl_trusted_certificate_db)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot set trusted SSL CA" ); - } - } - - if (config->ssl_ciphers) { - if (pn_ssl_domain_set_ciphers(domain, config->ssl_ciphers)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot set ciphers. The ciphers string might be invalid. Use openssl ciphers -v to validate"); - } - } - - if (config->ssl_protocols) { - if (pn_ssl_domain_set_protocols(domain, config->ssl_protocols)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot set protocols. The protocols string might be invalid. This list is a space separated string of the allowed TLS protocols (TLSv1 TLSv1.1 TLSv1.2)"); - } - } - - const char *trusted = config->ssl_trusted_certificate_db; - - // do we force the peer to send a cert? - if (config->ssl_require_peer_authentication) { - if (!trusted || pn_ssl_domain_set_peer_authentication(domain, PN_SSL_VERIFY_PEER, trusted)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot set peer authentication"); - } - } - - ctx->ssl = pn_ssl(tport); - if (!ctx->ssl || pn_ssl_init(ctx->ssl, domain, 0)) { - pn_ssl_domain_free(domain); - return qd_error(QD_ERROR_RUNTIME, "Cannot initialize SSL"); - } - - // By default adding ssl to a transport forces encryption to be required, so if it's not set that here - if (!config->ssl_required) { - pn_transport_require_encryption(tport, false); - } - - pn_ssl_domain_free(domain); - return QD_ERROR_NONE; -} - /* Log the description, set the transport condition (name, description) close the transport tail. */ void connect_fail(qd_connection_t *ctx, const char *name, const char *description, ...) __attribute__((format(printf, 3, 4))); @@ -853,94 +594,16 @@ static bool setup_ssl_sasl_and_open(qd_connection_t *ctx) pn_transport_t *tport = pn_connection_transport(ctx->pn_conn); // - // Set up SSL if appropriate + // Create an SSL session if required // - if (config->ssl_profile) { - pn_ssl_domain_t *domain = pn_ssl_domain(PN_SSL_MODE_CLIENT); - - if (!domain) { - qd_error(QD_ERROR_RUNTIME, "SSL domain allocation failed for connection [C%"PRIu64"] to %s:%s", - ctx->connection_id, config->host, config->port); - return false; - } - - bool failed = false; - - // set our trusted database for checking the peer's cert: - if (config->ssl_trusted_certificate_db) { - if (pn_ssl_domain_set_trusted_ca_db(domain, config->ssl_trusted_certificate_db)) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL CA configuration failed for connection [C%" PRIu64 "] to %s:%s", ctx->connection_id, - config->host, config->port); - failed = true; - } - } - - // peer must provide a cert - if (pn_ssl_domain_set_peer_authentication(domain, - PN_SSL_VERIFY_PEER, - config->ssl_trusted_certificate_db)) { + if (ct->tls_config) { + ctx->ssl = qd_tls_session_amqp(ct->tls_config, tport, false); + if (!ctx->ssl) { qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL peer auth configuration failed for connection [C%" PRIu64 "] to %s:%s", ctx->connection_id, - config->host, config->port); - failed = true; - } - - // configure our certificate if the peer requests one: - if (config->ssl_certificate_file) { - if (pn_ssl_domain_set_credentials(domain, - config->ssl_certificate_file, - config->ssl_private_key_file, - config->ssl_password)) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL local certificate configuration failed for connection [C%" PRIu64 "] to %s:%s", - ctx->connection_id, config->host, config->port); - failed = true; - } - } - - if (config->ssl_ciphers) { - if (pn_ssl_domain_set_ciphers(domain, config->ssl_ciphers)) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL cipher configuration failed for connection [C%" PRIu64 "] to %s:%s", ctx->connection_id, - config->host, config->port); - failed = true; - } - } - - if (config->ssl_protocols) { - if (pn_ssl_domain_set_protocols(domain, config->ssl_protocols)) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "Permitted TLS protocols configuration failed for connection [C%" PRIu64 "] to %s:%s", - ctx->connection_id, config->host, config->port); - failed = true; - } - } - - //If ssl is enabled and verify_host_name is true, instruct proton to verify peer name - if (config->verify_host_name) { - if (pn_ssl_domain_set_peer_authentication(domain, PN_SSL_VERIFY_PEER_NAME, NULL)) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL peer host name verification configuration failed for connection [C%" PRIu64 "] to %s:%s", - ctx->connection_id, config->host, config->port); - failed = true; - } - } - - if (!failed) { - ctx->ssl = pn_ssl(tport); - if (pn_ssl_init(ctx->ssl, domain, 0) != 0) { - qd_log(LOG_SERVER, QD_LOG_ERROR, - "SSL domain internal initialization failed for connection [C%" PRIu64 "] to %s:%s", - ctx->connection_id, config->host, config->port); - failed = true; - } - } - pn_ssl_domain_free(domain); - if (failed) { + "Failed to create TLS session for connection [C%" PRIu64 "] to %s:%s (%s)", + ctx->connection_id, config->host, config->port, qd_error_message()); return false; } - } // @@ -989,11 +652,12 @@ static void on_connection_bound(qd_server_t *server, pn_event_t *e) { return; } - // Set up SSL - if (config->ssl_profile) { - qd_log(LOG_SERVER, QD_LOG_DEBUG, "[C%" PRIu64 "] Configuring SSL on %s", ctx->connection_id, - name); - if (listener_setup_ssl(ctx, config, tport) != QD_ERROR_NONE) { + // Set up TLS + if (ctx->listener->tls_config) { + qd_log(LOG_SERVER, QD_LOG_DEBUG, "[C%" PRIu64 "] Configuring SSL on %s", ctx->connection_id, name); + + ctx->ssl = qd_tls_session_amqp(ctx->listener->tls_config, tport, config->ssl_required == false); + if (!ctx->ssl) { connect_fail(ctx, QD_AMQP_COND_INTERNAL_ERROR, "%s on %s", qd_error_message(), name); return; } diff --git a/src/adaptors/amqp/qd_connection.h b/src/adaptors/amqp/qd_connection.h index 3a371534e..862d424f7 100644 --- a/src/adaptors/amqp/qd_connection.h +++ b/src/adaptors/amqp/qd_connection.h @@ -44,8 +44,8 @@ typedef struct qd_connector_t qd_connector_t; typedef struct qd_policy_settings_t qd_policy_settings_t; typedef struct pn_connection_t pn_connection_t; typedef struct pn_session_t pn_session_t; -typedef struct pn_ssl_t pn_ssl_t; typedef struct qd_timer_t qd_timer_t; +typedef struct qd_tls_session_t qd_tls_session_t; // defer a function call to be invoked on the connections proactor thread at @@ -79,15 +79,14 @@ struct qd_connection_t { qd_timer_t *timer; // Timer for initial-setup pn_connection_t *pn_conn; pn_session_t *pn_sessions[QD_SSN_CLASS_COUNT]; - pn_ssl_t *ssl; + qd_tls_session_t *ssl; qd_listener_t *listener; qd_connector_t *connector; void *context; // context from listener or connector void *user_context; void *link_context; // Context shared by this connection's links uint64_t connection_id; // A unique identifier for the qd_connection_t. The underlying pn_connection already has one but it is long and clunky. - const char *user_id; // A unique identifier for the user on the connection. This is currently populated from the client ssl cert. See ssl_uid_format in server.h for more info - bool free_user_id; + char *user_id; // A unique identifier for the user on the connection. This is currently populated from the client ssl cert. See sslProfile.uidFormat for more info qd_policy_settings_t *policy_settings; int n_sessions; int n_senders; diff --git a/src/adaptors/amqp/qd_connector.c b/src/adaptors/amqp/qd_connector.c index ea9db8405..ac1f8b9a7 100644 --- a/src/adaptors/amqp/qd_connector.c +++ b/src/adaptors/amqp/qd_connector.c @@ -24,6 +24,7 @@ #include "qpid/dispatch/alloc_pool.h" #include "qpid/dispatch/timer.h" #include "qpid/dispatch/vanflow.h" +#include "qpid/dispatch/tls_amqp.h" #include @@ -120,6 +121,7 @@ qd_connector_t *qd_server_connector(qd_server_t *server) ZERO(connector); sys_atomic_init(&connector->ref_count, 1); DEQ_INIT(connector->conn_info_list); + DEQ_ITEM_INIT(connector); sys_mutex_init(&connector->lock); connector->timer = qd_timer(amqp_adaptor.dispatch, try_open_cb, connector); @@ -186,6 +188,8 @@ void qd_connector_decref(qd_connector_t* connector) item = DEQ_HEAD(connector->conn_info_list); } if (connector->policy_vhost) free(connector->policy_vhost); + qd_tls_config_decref(connector->tls_config); + free_qd_connector_t(connector); } } diff --git a/src/adaptors/amqp/qd_connector.h b/src/adaptors/amqp/qd_connector.h index 254041be0..5ebdca9d2 100644 --- a/src/adaptors/amqp/qd_connector.h +++ b/src/adaptors/amqp/qd_connector.h @@ -31,8 +31,9 @@ typedef struct qd_timer_t qd_timer_t; typedef struct qd_server_t qd_server_t; -typedef struct qd_connection_t qd_connection_t; -typedef struct vflow_record_t vflow_record_t; +typedef struct qd_connection_t qd_connection_t; +typedef struct vflow_record_t vflow_record_t; +typedef struct qd_tls_config_t qd_tls_config_t; typedef enum { CXTR_STATE_INIT = 0, @@ -65,9 +66,8 @@ typedef struct qd_connector_t { /* This conn_list contains all the connection information needed to make a connection. It also includes failover connection information */ qd_failover_item_list_t conn_info_list; int conn_index; // Which connection in the connection list to connect to next. - - /* Optional policy vhost name */ - char *policy_vhost; + char *policy_vhost; /* Optional policy vhost name */ + qd_tls_config_t *tls_config; /* Connection group state */ bool is_data_connector; diff --git a/src/adaptors/amqp/qd_listener.c b/src/adaptors/amqp/qd_listener.c index c45079e81..254b9e21b 100644 --- a/src/adaptors/amqp/qd_listener.c +++ b/src/adaptors/amqp/qd_listener.c @@ -24,6 +24,7 @@ #include "qpid/dispatch/server.h" #include "qpid/dispatch/log.h" #include "qpid/dispatch/vanflow.h" +#include "qpid/dispatch/tls_amqp.h" #include #include @@ -162,7 +163,7 @@ bool qd_listener_listen(qd_listener_t *li) } -void qd_listener_decref(qd_listener_t* li) +void qd_listener_decref(qd_listener_t *li) { if (li && sys_atomic_dec(&li->ref_count) == 1) { if (!!li->vflow_record) { @@ -170,6 +171,8 @@ void qd_listener_decref(qd_listener_t* li) li->vflow_record = 0; } qd_server_config_free(&li->config); + qd_tls_config_decref(li->tls_config); + free_qd_listener_t(li); } } diff --git a/src/adaptors/amqp/qd_listener.h b/src/adaptors/amqp/qd_listener.h index 92a91d2d4..85e82fbb2 100644 --- a/src/adaptors/amqp/qd_listener.h +++ b/src/adaptors/amqp/qd_listener.h @@ -31,6 +31,7 @@ typedef struct qd_server_t qd_server_t; typedef struct pn_listener_t pn_listener_t; typedef struct vflow_record_t vflow_record_t; typedef struct qd_connection_t qd_connection_t; +typedef struct qd_tls_config_t qd_tls_config_t; /** * Listener objects represent the desire to accept incoming AMQP transport connections. @@ -48,6 +49,7 @@ struct qd_listener_t { DEQ_LINKS(qd_listener_t); bool exit_on_error; vflow_record_t *vflow_record; + qd_tls_config_t *tls_config; }; DEQ_DECLARE(qd_listener_t, qd_listener_list_t); diff --git a/src/adaptors/amqp/server_config.c b/src/adaptors/amqp/server_config.c index 6ba655deb..94543eb2b 100644 --- a/src/adaptors/amqp/server_config.c +++ b/src/adaptors/amqp/server_config.c @@ -26,6 +26,7 @@ #include "entity.h" #include +#include #include @@ -37,7 +38,7 @@ // FIXME: I cannot justify this - never call goto from a MACRO! #define CHECK() if (qd_error_code()) goto error -#define SSTRDUP(S) ((S) ? strdup(S) : NULL) +#define CHECKED_STRDUP(S) ((S) ? qd_strdup(S) : NULL) /** @@ -143,7 +144,7 @@ qd_error_t qd_server_config_load(qd_dispatch_t *qd, qd_server_config_t *config, config->sasl_username = qd_entity_opt_string(entity, "saslUsername", 0); CHECK(); config->sasl_password = qd_entity_opt_string(entity, "saslPassword", 0); CHECK(); config->sasl_mechanisms = qd_entity_opt_string(entity, "saslMechanisms", 0); CHECK(); - config->ssl_profile = qd_entity_opt_string(entity, "sslProfile", 0); CHECK(); + config->ssl_profile_name = qd_entity_opt_string(entity, "sslProfile", 0); CHECK(); config->link_capacity = qd_entity_opt_long(entity, "linkCapacity", 0); CHECK(); config->multi_tenant = qd_entity_opt_bool(entity, "multiTenant", false); CHECK(); config->policy_vhost = qd_entity_opt_string(entity, "policyVhost", 0); CHECK(); @@ -249,23 +250,10 @@ qd_error_t qd_server_config_load(qd_dispatch_t *qd, qd_server_config_t *config, config->requireAuthentication = authenticatePeer; config->requireEncryption = requireEncryption || requireSsl; - if (config->ssl_profile) { + if (config->ssl_profile_name) { config->ssl_required = requireSsl; config->ssl_require_peer_authentication = config->sasl_mechanisms && strstr(config->sasl_mechanisms, "EXTERNAL") != 0; - - qd_config_ssl_profile_t *ssl_profile = - qd_find_ssl_profile(qd->connection_manager, config->ssl_profile); - if (ssl_profile) { - config->ssl_certificate_file = SSTRDUP(ssl_profile->ssl_certificate_file); - config->ssl_private_key_file = SSTRDUP(ssl_profile->ssl_private_key_file); - config->ssl_ciphers = SSTRDUP(ssl_profile->ssl_ciphers); - config->ssl_protocols = SSTRDUP(ssl_profile->ssl_protocols); - config->ssl_password = SSTRDUP(ssl_profile->ssl_password); - config->ssl_trusted_certificate_db = SSTRDUP(ssl_profile->ssl_trusted_certificate_db); - config->ssl_uid_format = SSTRDUP(ssl_profile->ssl_uid_format); - config->ssl_uid_name_mapping_file = SSTRDUP(ssl_profile->uid_name_mapping_file); - } } return QD_ERROR_NONE; @@ -289,19 +277,11 @@ void qd_server_config_free(qd_server_config_t *cf) if (cf->sasl_username) free(cf->sasl_username); if (cf->sasl_password) free(cf->sasl_password); if (cf->sasl_mechanisms) free(cf->sasl_mechanisms); - if (cf->ssl_profile) free(cf->ssl_profile); + if (cf->ssl_profile_name) free(cf->ssl_profile_name); if (cf->failover_list) qd_failover_list_free(cf->failover_list); if (cf->log_message) free(cf->log_message); if (cf->policy_vhost) free(cf->policy_vhost); - if (cf->ssl_certificate_file) free(cf->ssl_certificate_file); - if (cf->ssl_private_key_file) free(cf->ssl_private_key_file); - if (cf->ssl_ciphers) free(cf->ssl_ciphers); - if (cf->ssl_protocols) free(cf->ssl_protocols); - if (cf->ssl_password) free(cf->ssl_password); - if (cf->ssl_trusted_certificate_db) free(cf->ssl_trusted_certificate_db); - if (cf->ssl_uid_format) free(cf->ssl_uid_format); - if (cf->ssl_uid_name_mapping_file) free(cf->ssl_uid_name_mapping_file); free(cf->data_connection_count); if (cf->conn_props) pn_data_free(cf->conn_props); diff --git a/src/adaptors/amqp/server_config.h b/src/adaptors/amqp/server_config.h index e07cba790..a0d8ae846 100644 --- a/src/adaptors/amqp/server_config.h +++ b/src/adaptors/amqp/server_config.h @@ -157,55 +157,9 @@ typedef struct qd_server_config_t { int link_capacity; /** - * Path to the file containing the PEM-formatted public certificate for the local end - * of the connection. + * The name of the related sslProfile configuration. */ - char *ssl_certificate_file; - - /** - * Path to the file containing the PEM-formatted private key for the local end of the - * connection. - */ - char *ssl_private_key_file; - - /** - * Holds the list of component fields of the client certificate from which a unique identifier is constructed. - * For e.g, this field could have the format of 'cou' indicating that the uid will consist of - * c - common name concatenated with o - organization-company name concatenated with u - organization unit - * Allowed components are - * Allowed values can be any combination of comma separated - * 'c'( ISO3166 two character country code), - * 's'(state or province), - * 'l'(Locality; generally - city), - * 'o'(Organization - Company Name), - * 'u'(Organization Unit - typically certificate type or brand), - * 'n'(CommonName - typically a user name for client certificates) and - * '1'(sha1 certificate fingerprint, the fingerprint, as displayed in the fingerprints section when looking at a certificate - * with say a web browser is the hash of the entire certificate in DER form) - * '2'(sha256 certificate fingerprint) - * '5'(sha512 certificate fingerprint) - */ - char *ssl_uid_format; - - /** - * The name of the related ssl profile. - */ - char *ssl_profile; - - /** - * Full path to the file that contains the uid to display name mapping. - */ - char *ssl_uid_name_mapping_file; - - /** - * The password used to sign the private key, or NULL if the key is not protected. - */ - char *ssl_password; - - /** - * Path to the file containing the PEM-formatted set of certificates of trusted CAs. - */ - char *ssl_trusted_certificate_db; + char *ssl_profile_name; /** * Iff true, require that the peer's certificate be supplied and that it be authentic @@ -213,17 +167,6 @@ typedef struct qd_server_config_t { */ bool ssl_require_peer_authentication; - /** - * Specifies the enabled ciphers so the SSL Ciphers can be hardened. - */ - char *ssl_ciphers; - - /** - * This list is a space separated string of the allowed TLS protocols. The current possibilities are TLSv1 TLSv1.1 TLSv1.2. - * For example, if you want to permit only TLSv.1.1 and TLSv1.2, your value for the protocols would be TLSv1.1 TLSv1.2. If this attribute is not set, then all the TLS protocols are allowed. - */ - char *ssl_protocols; - /** * Allow the connection to be redirected by the peer (via CLOSE->Redirect). This is * meaningful for outgoing (connector) connections only. diff --git a/src/adaptors/http1/http1_adaptor.c b/src/adaptors/http1/http1_adaptor.c index 9367a20f7..d9dabdd11 100644 --- a/src/adaptors/http1/http1_adaptor.c +++ b/src/adaptors/http1/http1_adaptor.c @@ -17,7 +17,7 @@ * under the License. */ -#include "adaptors/adaptor_tls.h" +#include "adaptors/legacy_tls.h" #include "http1_private.h" #include diff --git a/src/adaptors/http1/http1_client.c b/src/adaptors/http1/http1_client.c index 7ad12aa73..ce824a58f 100644 --- a/src/adaptors/http1/http1_client.c +++ b/src/adaptors/http1/http1_client.c @@ -20,7 +20,7 @@ #include "python_private.h" #include "adaptors/adaptor_common.h" -#include "adaptors/adaptor_tls.h" +#include "adaptors/legacy_tls.h" #include "http1_private.h" #include "qpid/dispatch/protocol_adaptor.h" diff --git a/src/adaptors/http1/http1_server.c b/src/adaptors/http1/http1_server.c index 52d43e6e7..9bfdb3eb6 100644 --- a/src/adaptors/http1/http1_server.c +++ b/src/adaptors/http1/http1_server.c @@ -17,7 +17,7 @@ * under the License. */ -#include "adaptors/adaptor_tls.h" +#include "adaptors/legacy_tls.h" #include "http1_private.h" #include "qpid/dispatch/connection_counters.h" diff --git a/src/adaptors/http2/http2_adaptor.h b/src/adaptors/http2/http2_adaptor.h index c3dd53f21..ec4c909a5 100644 --- a/src/adaptors/http2/http2_adaptor.h +++ b/src/adaptors/http2/http2_adaptor.h @@ -21,7 +21,7 @@ */ #include "adaptors/adaptor_buffer.h" #include "adaptors/adaptor_common.h" -#include "adaptors/adaptor_tls.h" +#include "adaptors/legacy_tls.h" #include "adaptors/http_common.h" #include "server_private.h" diff --git a/src/adaptors/http_common.c b/src/adaptors/http_common.c index 4f04b08e3..1e7e0e767 100644 --- a/src/adaptors/http_common.c +++ b/src/adaptors/http_common.c @@ -20,7 +20,7 @@ #include "http_common.h" #include "adaptor_common.h" -#include "adaptor_tls.h" +#include "legacy_tls.h" #include #include diff --git a/src/adaptors/adaptor_tls.c b/src/adaptors/legacy_tls.c similarity index 98% rename from src/adaptors/adaptor_tls.c rename to src/adaptors/legacy_tls.c index 7422d4ac1..be04033f8 100644 --- a/src/adaptors/adaptor_tls.c +++ b/src/adaptors/legacy_tls.c @@ -17,7 +17,7 @@ * under the License. */ -#include "adaptor_tls.h" +#include "legacy_tls.h" #include "router_core_private.h" @@ -25,6 +25,7 @@ #include "qpid/dispatch/atomic.h" #include "qpid/dispatch/connection_manager.h" #include "qpid/dispatch/ctools.h" +#include "qpid/dispatch/tls_common.h" #include @@ -229,12 +230,12 @@ qd_tls_domain_t *qd_tls_domain_clone(const qd_tls_domain_t *src) static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) { const char *role = tls_domain->is_listener ? "listener" : "connector"; + qd_ssl2_profile_t tmp = {0}; + qd_ssl2_profile_t *config_ssl_profile = 0; do { // find the ssl profile - qd_connection_manager_t *cm = qd_dispatch_connection_manager(tls_domain->qd_dispatch); - assert(cm); - qd_config_ssl_profile_t *config_ssl_profile = qd_find_ssl_profile(cm, tls_domain->ssl_profile_name); + config_ssl_profile = qd_tls_read_ssl_profile(tls_domain->ssl_profile_name, &tmp); if (!config_ssl_profile) { qd_log(tls_domain->log_module, QD_LOG_ERROR, @@ -258,9 +259,9 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) break; } - if (config_ssl_profile->ssl_trusted_certificate_db) { + if (config_ssl_profile->trusted_certificate_db) { res = pn_tls_config_set_trusted_certs(tls_domain->pn_tls_config, - config_ssl_profile->ssl_trusted_certificate_db); + config_ssl_profile->trusted_certificate_db); if (res != 0) { qd_log(tls_domain->log_module, QD_LOG_ERROR, @@ -268,18 +269,18 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) role, tls_domain->name, tls_domain->ssl_profile_name, - config_ssl_profile->ssl_trusted_certificate_db, + config_ssl_profile->trusted_certificate_db, res); break; } } // Call pn_tls_config_set_credentials only if "certFile" is provided. - if (config_ssl_profile->ssl_certificate_file) { + if (config_ssl_profile->certificate_file) { res = pn_tls_config_set_credentials(tls_domain->pn_tls_config, - config_ssl_profile->ssl_certificate_file, - config_ssl_profile->ssl_private_key_file, - config_ssl_profile->ssl_password); + config_ssl_profile->certificate_file, + config_ssl_profile->private_key_file, + config_ssl_profile->password); if (res != 0) { qd_log(tls_domain->log_module, QD_LOG_ERROR, @@ -287,7 +288,7 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) role, tls_domain->name, tls_domain->ssl_profile_name, - config_ssl_profile->ssl_certificate_file, + config_ssl_profile->certificate_file, res); break; } @@ -300,8 +301,8 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) tls_domain->ssl_profile_name); } - if (!!config_ssl_profile->ssl_ciphers) { - res = pn_tls_config_set_impl_ciphers(tls_domain->pn_tls_config, config_ssl_profile->ssl_ciphers); + if (!!config_ssl_profile->ciphers) { + res = pn_tls_config_set_impl_ciphers(tls_domain->pn_tls_config, config_ssl_profile->ciphers); if (res != 0) { qd_log(tls_domain->log_module, QD_LOG_ERROR, @@ -309,7 +310,7 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) role, tls_domain->name, tls_domain->ssl_profile_name, - config_ssl_profile->ssl_ciphers, + config_ssl_profile->ciphers, res); break; } @@ -318,7 +319,7 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) if (tls_domain->is_listener) { if (tls_domain->authenticate_peer) { res = pn_tls_config_set_peer_authentication( - tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER, config_ssl_profile->ssl_trusted_certificate_db); + tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER, config_ssl_profile->trusted_certificate_db); } else { res = pn_tls_config_set_peer_authentication(tls_domain->pn_tls_config, PN_TLS_ANONYMOUS_PEER, 0); } @@ -326,10 +327,10 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) // Connector. if (tls_domain->verify_host_name) { res = pn_tls_config_set_peer_authentication( - tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER_NAME, config_ssl_profile->ssl_trusted_certificate_db); + tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER_NAME, config_ssl_profile->trusted_certificate_db); } else { res = pn_tls_config_set_peer_authentication( - tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER, config_ssl_profile->ssl_trusted_certificate_db); + tls_domain->pn_tls_config, PN_TLS_VERIFY_PEER, config_ssl_profile->trusted_certificate_db); } } @@ -363,6 +364,8 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) } } + qd_tls_cleanup_ssl_profile(config_ssl_profile); + qd_log(tls_domain->log_module, QD_LOG_INFO, "Adaptor %s %s successfully configured sslProfile %s", @@ -375,6 +378,7 @@ static qd_tls_domain_t *_tls_domain_init(qd_tls_domain_t *tls_domain) // If we get here, the configuration setup failed + qd_tls_cleanup_ssl_profile(config_ssl_profile); qd_tls_domain_decref(tls_domain); return 0; } @@ -423,17 +427,17 @@ void qd_tls_update_connection_info(qd_tls_t *tls, qdr_connection_info_t *conn_in // connection_info. This same lock is being used in the agent_connection.c's qdr_connection_insert_column_CT // sys_mutex_lock(&conn_info->connection_info_lock); - free(conn_info->ssl_cipher); - conn_info->ssl_cipher = 0; - free(conn_info->ssl_proto); - conn_info->ssl_proto = 0; - conn_info->ssl = true; + free(conn_info->tls_cipher); + conn_info->tls_cipher = 0; + free(conn_info->tls_proto); + conn_info->tls_proto = 0; + conn_info->tls = true; conn_info->is_encrypted = true; if (protocol_cipher) { - conn_info->ssl_cipher = protocol_cipher; + conn_info->tls_cipher = protocol_cipher; } if (protocol_ver) { - conn_info->ssl_proto = protocol_ver; + conn_info->tls_proto = protocol_ver; } sys_mutex_unlock(&conn_info->connection_info_lock); diff --git a/src/adaptors/adaptor_tls.h b/src/adaptors/legacy_tls.h similarity index 96% rename from src/adaptors/adaptor_tls.h rename to src/adaptors/legacy_tls.h index bef5259a3..1985c5967 100644 --- a/src/adaptors/adaptor_tls.h +++ b/src/adaptors/legacy_tls.h @@ -1,5 +1,5 @@ -#ifndef __adaptor_tls_h__ -#define __adaptor_tls_h__ 1 +#ifndef __legacy_tls_h__ +#define __legacy_tls_h__ 1 /* * Licensed to the Apache Software Foundation (ASF) under one @@ -26,16 +26,12 @@ #include "qpid/dispatch/protocol_adaptor.h" #define QD_TLS_ERROR -1 +#define QD_IO_EOS -2 // I/O layer: signal clean end-of-output stream +#define QD_IO_EOS_ERR -3 // I/O layer: signal error end-of-output stream typedef struct qd_tls_domain_t qd_tls_domain_t; typedef struct qd_tls_t qd_tls_t; - -// ALPN Protocol Property Map Key -// When ALPN is negotiated with a TLS client the agreed upon value needs to be proxied to the server facing end of the -// connection. The server-facing handshake will use that negotiated ALPN value. Typically the negotiated ALPN value is -// added to the message application property map. This defines the map key used. -#define QD_TLS_ALPN_KEY "alpn" -#define QD_TLS_ALPN_KEY_LEN 4 +typedef struct pn_tls_t pn_tls_t; /** @@ -273,4 +269,4 @@ int qd_tls_do_io2(qd_tls_t *tls, void qd_tls_free2(qd_tls_t *tls); -#endif // __adaptor_tls_h__ +#endif // __legacy_tls_h__ diff --git a/src/adaptors/tcp/tcp_adaptor.c b/src/adaptors/tcp/tcp_adaptor.c index 490d61e19..416670b35 100644 --- a/src/adaptors/tcp/tcp_adaptor.c +++ b/src/adaptors/tcp/tcp_adaptor.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -72,11 +73,23 @@ ENUM_DEFINE(qd_tcp_connection_state, state_names); #define CONNECTION_CLOSE_TIME 10000 #define RAW_BUFFER_BATCH_SIZE 16 +// // AMQP Message Application Properties // #define MAPPING_VERSION_KEY "V" // ISSUE-1602: AMQP mapping version #define MAPPING_VERSION 1 +// ALPN Protocol Property Map Key +// When ALPN is negotiated with a TLS client the agreed upon value needs to be proxied to the server facing end of the +// connection. The server-facing handshake will use that negotiated ALPN value. Typically the negotiated ALPN value is +// added to the message application property map. This defines the map key used. +#define ALPN_KEY "alpn" +#define ALPN_KEY_LEN 4 + +#define TCP_NUM_ALPN_PROTOCOLS 2 +static const char *tcp_alpn_protocols[TCP_NUM_ALPN_PROTOCOLS] = {"http/1.1", "h2"}; + + // // Global Adaptor State // @@ -133,9 +146,10 @@ static void connection_run_CSIDE_IO(qd_tcp_connection_t *conn); static void connection_run_XSIDE_IO(qd_tcp_connection_t *conn); static uint64_t validate_outbound_message(const qdr_delivery_t *out_dlv); static void on_accept(qd_adaptor_listener_t *listener, pn_listener_t *pn_listener, void *context); -static void on_tls_connection_secured(qd_tls_t *tls, void *user_context); +static void on_tls_connection_secured(qd_tls_session_t *tls, void *user_context); static char *get_tls_negotiated_alpn(qd_message_t *msg); // caller must free() returned string! -static int setup_tls_session(qd_tcp_connection_t *conn, const qd_tls_domain_t *parent_domain, const char *alpn_protocol); +static int setup_tls_session(qd_tcp_connection_t *conn, qd_tls_config_t *parent_config, const char *peer_hostname, + const char **alpn_protocols, size_t alpn_protocol_count); static void free_tcp_resource(qd_tcp_common_t *resource); //================================================================================= @@ -197,7 +211,7 @@ static void qd_tcp_listener_free(qd_tcp_listener_t *listener) qdpo_free(listener->protocol_observer); qdpo_config_free(listener->protocol_observer_config); - qd_tls_domain_decref(listener->tls_domain); + qd_tls_config_decref(listener->tls_config); qd_free_adaptor_config(listener->adaptor_config); sys_mutex_free(&listener->lock); free_qd_tcp_listener_t(listener); @@ -233,7 +247,7 @@ static void qd_tcp_connector_free(qd_tcp_connector_t *connector) "Deleted TcpConnector for %s, %s:%s", connector->adaptor_config->address, connector->adaptor_config->host, connector->adaptor_config->port); - qd_tls_domain_decref(connector->tls_domain); + qd_tls_config_decref(connector->tls_config); qd_free_adaptor_config(connector->adaptor_config); // Pass connector to Core for final deallocation. The Core will free the cr->lock. @@ -630,8 +644,7 @@ static void close_connection_XSIDE_IO(qd_tcp_connection_t *conn) qd_connection_counter_dec(QD_PROTOCOL_TCP); } - qd_tls_free2(conn->tls); - qd_tls_domain_decref(conn->tls_domain); + qd_tls_session_free(conn->tls_session); free(conn->alpn_protocol); free(conn->reply_to); @@ -644,8 +657,7 @@ static void close_connection_XSIDE_IO(qd_tcp_connection_t *conn) conn->outbound_delivery = 0; conn->observer_handle = 0; conn->common.vflow = 0; - conn->tls = 0; - conn->tls_domain = 0; + conn->tls_session = 0; // No thread assertion here - can be RAW_IO or TIMER_IO qd_log(LOG_TCP_ADAPTOR, QD_LOG_DEBUG, "[C%"PRIu64"] Cleaning up resources", conn->conn_id); @@ -837,7 +849,7 @@ static void copy_message_body_TLS_XSIDE_IO(qd_tcp_connection_t *conn, qd_message { size_t offset = 0; - assert(conn->tls); + assert(conn->tls_session); if (!conn->outbound_body) { assert(!conn->outbound_body_complete); @@ -1002,7 +1014,7 @@ static bool try_compose_and_send_client_stream_LSIDE_IO(qd_tcp_connection_t *con qd_compose_insert_uint(message, MAPPING_VERSION); if (conn->alpn_protocol) { // add the ALPN protocol as negotiated with the remote via TLS. - qd_compose_insert_string_n(message, (const char *) QD_TLS_ALPN_KEY, QD_TLS_ALPN_KEY_LEN); + qd_compose_insert_string_n(message, (const char *) ALPN_KEY, ALPN_KEY_LEN); qd_compose_insert_string_n(message, (const char *) conn->alpn_protocol, strlen(conn->alpn_protocol)); } @@ -1543,10 +1555,10 @@ static int64_t tls_consume_data_buffers(void *context, qd_buffer_list_t *buffers static bool manage_tls_flow_XSIDE_IO(qd_tcp_connection_t *conn) { ASSERT_RAW_IO; - assert(conn->tls); + assert(conn->tls_session); assert(conn->raw_conn); - if (qd_tls_is_error(conn->tls)) + if (qd_tls_session_is_error(conn->tls_session)) return false; qd_buffer_list_t decrypted_buffers = DEQ_EMPTY; @@ -1558,9 +1570,9 @@ static bool manage_tls_flow_XSIDE_IO(qd_tcp_connection_t *conn) grant_read_buffers_XSIDE_IO(conn, pn_raw_connection_read_buffers_capacity(conn->raw_conn)); - const int tls_status = qd_tls_do_io2(conn->tls, conn->raw_conn, tls_consume_data_buffers, conn, - (can_produce) ? &decrypted_buffers : 0, - &decrypted_octets); + const int tls_status = qd_tls_session_do_io(conn->tls_session, conn->raw_conn, tls_consume_data_buffers, conn, + (can_produce) ? &decrypted_buffers : 0, + &decrypted_octets, LOG_TCP_ADAPTOR, conn->conn_id); // // Process inbound cleartext data. @@ -1609,7 +1621,7 @@ static bool manage_tls_flow_XSIDE_IO(qd_tcp_connection_t *conn) // Check for end of inbound message data // bool ignore; - if (qd_tls_is_input_drained(conn->tls, &ignore) && conn->inbound_stream) { + if (qd_tls_session_is_input_drained(conn->tls_session, &ignore) && conn->inbound_stream) { qd_log(LOG_TCP_ADAPTOR, QD_LOG_DEBUG, DLV_FMT " TLS inbound stream receive complete, producer activation cancelled", DLV_ARGS(conn->inbound_delivery)); qd_message_set_receive_complete(conn->inbound_stream); @@ -1625,7 +1637,7 @@ static bool manage_tls_flow_XSIDE_IO(qd_tcp_connection_t *conn) // // Check for end of outbound message data // - if (qd_tls_is_output_flushed(conn->tls) && conn->outbound_stream) { + if (qd_tls_session_is_output_flushed(conn->tls_session) && conn->outbound_stream) { qd_log(LOG_TCP_ADAPTOR, QD_LOG_DEBUG, DLV_FMT " TLS outbound stream send complete, consumer activation cancelled", DLV_ARGS(conn->outbound_delivery)); qd_message_set_send_complete(conn->outbound_stream); @@ -1656,8 +1668,9 @@ static void connection_run_LSIDE_IO(qd_tcp_connection_t *conn) case LSIDE_INITIAL: if (IS_ATOMIC_FLAG_SET(&conn->raw_opened)) { // raw connection is active qd_tcp_listener_t *li = (qd_tcp_listener_t *) conn->common.parent; - if (li->tls_domain) { - if (setup_tls_session(conn, li->tls_domain, 0) != 0) { + if (li->tls_config) { + if (setup_tls_session(conn, li->tls_config, li->adaptor_config->host, + tcp_alpn_protocols, TCP_NUM_ALPN_PROTOCOLS) != 0) { // TLS setup failed: check logs for details close_raw_connection(conn, "TLS-connection-failed", "Error loading credentials"); set_state_XSIDE_IO(conn, XSIDE_CLOSING); // prevent further connection I/O @@ -1679,18 +1692,18 @@ static void connection_run_LSIDE_IO(qd_tcp_connection_t *conn) // for two reasons: 1) don't establish router links and reply addresses if the client's creditials are bad // and 2) we need the negotiated ALPN value *before* we can construct the inbound message headers. // - assert(conn->tls); - if (!qd_tls_is_secure(conn->tls) && !qd_tls_is_error(conn->tls)) { + assert(conn->tls_session); + if (!qd_tls_session_is_secure(conn->tls_session) && !qd_tls_session_is_error(conn->tls_session)) { assert(conn->raw_conn); grant_read_buffers_XSIDE_IO(conn, pn_raw_connection_read_buffers_capacity(conn->raw_conn)); - int rc = qd_tls_do_io2(conn->tls, conn->raw_conn, 0, 0, 0, 0); + int rc = qd_tls_session_do_io(conn->tls_session, conn->raw_conn, 0, 0, 0, 0, LOG_TCP_ADAPTOR, conn->conn_id); if (rc < 0) { // // TLS failed! Error logged, raw connection close initiated and error condition set. Clean up in // DISCONNECTED raw connection event. // set_state_XSIDE_IO(conn, XSIDE_CLOSING); // prevent further connection I/O - } else if (qd_tls_is_secure(conn->tls)) { + } else if (qd_tls_session_is_secure(conn->tls_session)) { // // Handshake completed, begin the setup of the inbound and outbound links for this connection. // @@ -1718,7 +1731,7 @@ static void connection_run_LSIDE_IO(qd_tcp_connection_t *conn) // LSIDE_FLOW state and let the streaming begin. // if (!!conn->outbound_stream) { - set_state_XSIDE_IO(conn, (conn->tls) ? LSIDE_TLS_FLOW: LSIDE_FLOW); + set_state_XSIDE_IO(conn, (conn->tls_session) ? LSIDE_TLS_FLOW: LSIDE_FLOW); repeat = true; } break; @@ -1776,10 +1789,13 @@ static void connection_run_CSIDE_IO(qd_tcp_connection_t *conn) case CSIDE_INITIAL: if (IS_ATOMIC_FLAG_SET(&conn->raw_opened)) { // raw connection is active qd_tcp_connector_t *connector = (qd_tcp_connector_t *) conn->common.parent; - if (connector->tls_domain) { + if (connector->tls_config) { assert(conn->outbound_stream); char *alpn = get_tls_negotiated_alpn(conn->outbound_stream); - int rc = setup_tls_session(conn, connector->tls_domain, alpn); + size_t alpn_count = alpn ? 1 : 0; + const char *alpn_protocols[1] = {alpn}; + int rc = setup_tls_session(conn, connector->tls_config, connector->adaptor_config->host, + alpn_count ? alpn_protocols : 0, alpn_count); free(alpn); if (rc != 0) { // TLS setup failed: check logs for details @@ -1798,7 +1814,7 @@ static void connection_run_CSIDE_IO(qd_tcp_connection_t *conn) if (credit) { compose_and_send_server_stream_CSIDE_IO(conn); - set_state_XSIDE_IO(conn, (conn->tls) ? CSIDE_TLS_FLOW : CSIDE_FLOW); + set_state_XSIDE_IO(conn, (conn->tls_session) ? CSIDE_TLS_FLOW : CSIDE_FLOW); repeat = true; } break; @@ -1897,20 +1913,23 @@ static uint64_t validate_outbound_message(const qdr_delivery_t *out_dlv) // Callback from TLS I/O handler when the TLS handshake succeeds // -static void on_tls_connection_secured(qd_tls_t *tls, void *user_context) +static void on_tls_connection_secured(qd_tls_session_t *tls_session, void *user_context) { qd_tcp_connection_t *conn = (qd_tcp_connection_t *) user_context; assert(conn); qd_log(LOG_TCP_ADAPTOR, QD_LOG_DEBUG, "[C%"PRIu64"] TLS handshake succeeded!", conn->conn_id); if (conn->core_conn && conn->core_conn->connection_info) { - qd_tls_update_connection_info(conn->tls, conn->core_conn->connection_info); + char *p_version = qd_tls_session_get_protocol_version(conn->tls_session); + char *p_ciphers = qd_tls_session_get_protocol_ciphers(conn->tls_session); + qdr_connection_info_set_tls(conn->core_conn->connection_info, true, p_version, p_ciphers, + qd_tls_session_get_ssf(conn->tls_session)); } // check if we need to propagate client ALPN to server if (conn->listener_side) { assert(!conn->alpn_protocol); - qd_tls_get_alpn_protocol(conn->tls, &conn->alpn_protocol); + conn->alpn_protocol = qd_tls_session_get_alpn_protocol(conn->tls_session); } } @@ -1934,7 +1953,7 @@ static char *get_tls_negotiated_alpn(qd_message_t *msg) break; } qd_iterator_t *key_iter = qd_parse_raw(key); - if (!!key_iter && qd_iterator_equal(key_iter, (const unsigned char *) QD_TLS_ALPN_KEY)) { + if (!!key_iter && qd_iterator_equal(key_iter, (const unsigned char *) ALPN_KEY)) { qd_parsed_field_t *alpn_field = qd_parse_sub_value(ap, i); qd_iterator_t *alpn_iter = qd_parse_raw(alpn_field); alpn_protocol = (char *) qd_iterator_copy(alpn_iter); @@ -1951,31 +1970,24 @@ static char *get_tls_negotiated_alpn(qd_message_t *msg) /** * Create a new TLS session for the given connection. * - * @param parent_domain Reference to parent connector/listener TLS domain context + * @param parent_config Reference to parent connector/listener TLS configuration context * @param alpn_protocol If CSIDE the optional alpn protocol value to pass to the remote server * * @return 0 on success, non-zero on error */ -static int setup_tls_session(qd_tcp_connection_t *conn, const qd_tls_domain_t *parent_domain, const char *alpn_protocol) -{ - conn->tls_domain = qd_tls_domain_clone(parent_domain); - if (!conn->tls_domain) - return -1; // error logged in qd_tls_domain_clone() - - if (alpn_protocol) { - const char *alpn_protocols[] = {alpn_protocol}; - int rc = qd_tls_set_alpn_protocols(conn->tls_domain, alpn_protocols, 1); - if (rc != 0) { - qd_log(LOG_TCP_ADAPTOR, QD_LOG_ERROR, - "[%" PRIu64 "] failed to configure ALPN protocol '%s' (%d)", conn->conn_id, alpn_protocol, rc); - return -1; - } +static int setup_tls_session(qd_tcp_connection_t *conn, qd_tls_config_t *parent_config, + const char *peer_hostname, + const char **alpn_protocols, size_t alpn_count) +{ + conn->tls_session = qd_tls_session_raw(parent_config, peer_hostname, + alpn_protocols, alpn_count, + (void *) conn, on_tls_connection_secured); + if (!conn->tls_session) { + qd_log(LOG_TCP_ADAPTOR, QD_LOG_ERROR, "[C%"PRIu64"] Failed to create TLS session: %s", + conn->conn_id, qd_error_message()); + return -1; } - conn->tls = qd_tls(conn->tls_domain, conn, conn->conn_id, on_tls_connection_secured); - if (!conn->tls) - return -1; // error logged in qd_tls() - return 0; } @@ -2334,9 +2346,6 @@ static void CORE_connection_trace(void *context, qdr_connection_t *conn, bool tr //================================================================================= // Entrypoints for Management //================================================================================= -#define TCP_NUM_ALPN_PROTOCOLS 2 -// const char *tcp_alpn_protocols[TCP_NUM_ALPN_PROTOCOLS] = {"h2", "http/1.1", "http/1.0"}; -static const char *tcp_alpn_protocols[TCP_NUM_ALPN_PROTOCOLS] = {"http/1.1", "h2"}; QD_EXPORT void *qd_dispatch_configure_tcp_listener(qd_dispatch_t *qd, qd_entity_t *entity) { @@ -2357,25 +2366,18 @@ QD_EXPORT void *qd_dispatch_configure_tcp_listener(qd_dispatch_t *qd, qd_entity_ if (listener->adaptor_config->ssl_profile_name) { // On the TCP TLS listener side, send "http/1.1", "http/1.0" and "h2" as ALPN protocols - listener->tls_domain = qd_tls_domain(listener->adaptor_config, qd, LOG_TCP_ADAPTOR, tcp_alpn_protocols, - TCP_NUM_ALPN_PROTOCOLS, true); - if (!listener->tls_domain) { - // note qd_tls_domain logged the error + listener->tls_config = qd_tls_config(listener->adaptor_config->ssl_profile_name, + QD_TLS_TYPE_PROTON_RAW, + QD_TLS_CONFIG_SERVER_MODE, + listener->adaptor_config->verify_host_name, + listener->adaptor_config->authenticate_peer); + if (!listener->tls_config) { + qd_log(LOG_TCP_ADAPTOR, QD_LOG_ERROR, "tcpListener %s TLS configuration failed: %s", + listener->adaptor_config->name, qd_error_message()); qd_free_adaptor_config(listener->adaptor_config); free_qd_tcp_listener_t(listener); return 0; } - - // sanity check the configuration by creating a temporary TLS session. Is this fails - // an error will be logged by the call to qd_tls() - qd_tls_t *test = qd_tls(listener->tls_domain, 0, 0, 0); - if (!test) { - qd_free_adaptor_config(listener->adaptor_config); - qd_tls_domain_decref(listener->tls_domain); - free_qd_tcp_listener_t(listener); - return 0; - } - qd_tls_free2(test); } qd_log(LOG_TCP_ADAPTOR, QD_LOG_INFO, @@ -2540,24 +2542,18 @@ qd_tcp_connector_t *qd_dispatch_configure_tcp_connector(qd_dispatch_t *qd, qd_en } if (connector->adaptor_config->ssl_profile_name) { - connector->tls_domain = qd_tls_domain(connector->adaptor_config, qd, LOG_TCP_ADAPTOR, 0, 0, false); - if (!connector->tls_domain) { - // note qd_tls_domain() logged the error - qd_free_adaptor_config(connector->adaptor_config); - free_qd_tcp_connector_t(connector); - return 0; - } - - // sanity check the configuration by creating a temporary TLS session. Is this fails - // an error will be logged by the call to qd_tls() - qd_tls_t *test = qd_tls(connector->tls_domain, 0, 0, 0); - if (!test) { + connector->tls_config = qd_tls_config(connector->adaptor_config->ssl_profile_name, + QD_TLS_TYPE_PROTON_RAW, + QD_TLS_CONFIG_CLIENT_MODE, + connector->adaptor_config->verify_host_name, + connector->adaptor_config->authenticate_peer); + if (!connector->tls_config) { + qd_log(LOG_TCP_ADAPTOR, QD_LOG_ERROR, "tcpConnector %s TLS configuration failed: %s", + connector->adaptor_config->name, qd_error_message()); qd_free_adaptor_config(connector->adaptor_config); - qd_tls_domain_decref(connector->tls_domain); free_qd_tcp_connector_t(connector); return 0; } - qd_tls_free2(test); } connector->activate_timer = qd_timer(tcp_context->qd, on_core_activate_TIMER_IO, connector); diff --git a/src/adaptors/tcp/tcp_adaptor.h b/src/adaptors/tcp/tcp_adaptor.h index 30fbae5f1..7d0412bb8 100644 --- a/src/adaptors/tcp/tcp_adaptor.h +++ b/src/adaptors/tcp/tcp_adaptor.h @@ -23,7 +23,6 @@ #include "delivery.h" #include "adaptors/adaptor_common.h" #include "adaptors/adaptor_listener.h" -#include "adaptors/adaptor_tls.h" #include @@ -31,6 +30,8 @@ typedef struct qd_tcp_common_t qd_tcp_common_t; typedef struct qd_tcp_listener_t qd_tcp_listener_t; typedef struct qd_tcp_connector_t qd_tcp_connector_t; typedef struct qd_tcp_connection_t qd_tcp_connection_t; +typedef struct qd_tls_config_t qd_tls_config_t; +typedef struct qd_tls_session_t qd_tls_session_t; ALLOC_DECLARE(qd_tcp_listener_t); ALLOC_DECLARE(qd_tcp_connector_t); @@ -60,7 +61,7 @@ struct qd_tcp_listener_t { DEQ_LINKS(qd_tcp_listener_t); sys_mutex_t lock; qd_adaptor_config_t *adaptor_config; - qd_tls_domain_t *tls_domain; + qd_tls_config_t *tls_config; qd_adaptor_listener_t *adaptor_listener; qd_tcp_connection_list_t connections; qdpo_config_t *protocol_observer_config; @@ -78,7 +79,7 @@ typedef struct qd_tcp_connector_t { sys_mutex_t lock; qd_timer_t *activate_timer; qd_adaptor_config_t *adaptor_config; - qd_tls_domain_t *tls_domain; + qd_tls_config_t *tls_config; qdr_connection_t *core_conn; // dispatcher conn and link char *process_ref; // VanFlow Process ID uint64_t conn_id; @@ -145,8 +146,7 @@ typedef struct qd_tcp_connection_t { qd_buffer_t *outbound_body; pn_condition_t *error; char *reply_to; - qd_tls_domain_t *tls_domain; // if configured, owned by this connection - qd_tls_t *tls; // tls session if configured + qd_tls_session_t *tls_session; // tls session if configured for TLS char *alpn_protocol; // negotiated by TLS else 0 qd_handler_context_t context; qd_tcp_connection_state_t state; diff --git a/src/dispatch.c b/src/dispatch.c index 9f81ce1e4..dd0a427e4 100644 --- a/src/dispatch.c +++ b/src/dispatch.c @@ -37,6 +37,7 @@ #include "qpid/dispatch/discriminator.h" #include "qpid/dispatch/server.h" #include "qpid/dispatch/static_assert.h" +#include "qpid/dispatch/tls_common.h" #include #include @@ -116,6 +117,7 @@ qd_dispatch_t *qd_dispatch(const char *python_pkgdir, bool test_hooks) qd_entity_cache_initialize(); /* Must be first */ qd_alloc_initialize(); qd_log_initialize(); + qd_tls_initialize(); qd_error_initialize(); if (qd_error_code()) { qd_dispatch_free(qd); return 0; } @@ -290,11 +292,6 @@ QD_EXPORT qd_error_t qd_dispatch_register_policy_manager(qd_dispatch_t *qd, qd_e } -QD_EXPORT qd_error_t qd_dispatch_register_display_name_service(qd_dispatch_t *qd, void *object) -{ - return qd_register_display_name_service(qd, object); -} - QD_EXPORT PyObject *qd_dispatch_policy_c_counts_alloc(void) { return PyCapsule_New(qd_policy_c_counts_alloc(), "qd_policy_c_counts", qd_dispatch_policy_c_counts_free); @@ -395,6 +392,7 @@ void qd_dispatch_free(qd_dispatch_t *qd) Py_XDECREF((PyObject*) qd->agent); qd_router_free(qd->router); qd_server_free(qd->server); + qd_tls_finalize(); qd_log_finalize(); qd_alloc_finalize(); qd_python_finalize(); diff --git a/src/dispatch_private.h b/src/dispatch_private.h index 5f4a7fbcf..e3e2a7708 100644 --- a/src/dispatch_private.h +++ b/src/dispatch_private.h @@ -100,11 +100,6 @@ QD_EXPORT qd_error_t qd_dispatch_configure_policy(qd_dispatch_t *qd, qd_entity_t */ QD_EXPORT qd_error_t qd_dispatch_register_policy_manager(qd_dispatch_t *qd, qd_entity_t *entity); -/** - * Configure display name service, must be called after qd_dispatch_prepare - */ -QD_EXPORT qd_error_t qd_dispatch_register_display_name_service(qd_dispatch_t *qd, void *object); - /** * \brief Configure the logging module from the * parsed configuration file. This must be called after the diff --git a/src/http-libwebsockets.c b/src/http-libwebsockets.c index 62bcd9eb6..deaffd616 100644 --- a/src/http-libwebsockets.c +++ b/src/http-libwebsockets.c @@ -33,6 +33,7 @@ #include "qpid/dispatch/threading.h" #include "qpid/dispatch/timer.h" #include "qpid/dispatch/connection_counters.h" +#include "qpid/dispatch/tls_common.h" #include #include @@ -281,6 +282,7 @@ static work_t work_pop(qd_http_server_t *hs) { struct qd_lws_listener_t { qd_listener_t *listener; qd_http_server_t *server; + qd_ssl2_profile_t ssl_config; struct lws_vhost *vhost; struct lws_http_mount mount; struct lws_http_mount metrics; @@ -293,6 +295,7 @@ void qd_lws_listener_free(qd_lws_listener_t *hl) { hl->listener->http = NULL; qd_listener_decref(hl->listener); } + qd_tls_cleanup_ssl_profile(&hl->ssl_config); free(hl); } @@ -303,6 +306,15 @@ static qd_lws_listener_t *qd_lws_listener(qd_http_server_t *hs, qd_listener_t *l hl->listener = li; li->http = hl; sys_atomic_inc(&li->ref_count); /* Keep it around till qd_http_server_free() */ + if (li->config.ssl_profile_name) { + if (!qd_tls_read_ssl_profile(li->config.ssl_profile_name, &hl->ssl_config)) { + qd_log(LOG_HTTP, QD_LOG_CRITICAL, "Invalid SSL configuration for HTTP listener %s on %s", + li->config.name, li->config.host_port); + qd_lws_listener_free(hl); + return 0; + } + } + } else { qd_log(LOG_HTTP, QD_LOG_CRITICAL, "No memory for HTTP listen on %s", li->config.host_port); } @@ -384,13 +396,12 @@ static void listener_start(qd_lws_listener_t *hl, qd_http_server_t *hs) { qd_log(LOG_HTTP, QD_LOG_INFO, "Disabling ipv6 on %s", config->host_port); info.options |= LWS_SERVER_OPTION_DISABLE_IPV6; } - if (config->ssl_profile) { - info.ssl_cert_filepath = config->ssl_certificate_file; - info.ssl_private_key_filepath = config->ssl_private_key_file; - info.ssl_private_key_password = config->ssl_password; - info.ssl_ca_filepath = config->ssl_trusted_certificate_db; - info.ssl_cipher_list = config->ssl_ciphers; - + if (config->ssl_profile_name) { + info.ssl_cert_filepath = hl->ssl_config.certificate_file; + info.ssl_private_key_filepath = hl->ssl_config.private_key_file; + info.ssl_private_key_password = hl->ssl_config.password; + info.ssl_ca_filepath = hl->ssl_config.trusted_certificate_db; + info.ssl_cipher_list = hl->ssl_config.ciphers; info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | (config->ssl_required ? 0 : LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT | LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER) | diff --git a/src/router_core/agent_connection.c b/src/router_core/agent_connection.c index f5c2da0c8..b4b8f6fdc 100644 --- a/src/router_core/agent_connection.c +++ b/src/router_core/agent_connection.c @@ -35,12 +35,12 @@ #define QDR_CONNECTION_IS_AUTHENTICATED 8 #define QDR_CONNECTION_USER 9 #define QDR_CONNECTION_IS_ENCRYPTED 10 -#define QDR_CONNECTION_SSLPROTO 11 -#define QDR_CONNECTION_SSLCIPHER 12 +#define QDR_CONNECTION_TLSPROTO 11 +#define QDR_CONNECTION_TLSCIPHER 12 #define QDR_CONNECTION_PROPERTIES 13 -#define QDR_CONNECTION_SSLSSF 14 +#define QDR_CONNECTION_TLSSSF 14 #define QDR_CONNECTION_TYPE 15 -#define QDR_CONNECTION_SSL 16 +#define QDR_CONNECTION_TLS 16 #define QDR_CONNECTION_OPENED 17 #define QDR_CONNECTION_ACTIVE 18 #define QDR_CONNECTION_ADMIN_STATUS 19 @@ -195,30 +195,30 @@ static void qdr_connection_insert_column_CT(qdr_core_t *core, qdr_connection_t * qd_compose_insert_bool(body, conn->connection_info->is_encrypted); break; - case QDR_CONNECTION_SSLPROTO: - if (conn->connection_info->ssl_proto[0] != '\0') - qd_compose_insert_string(body, conn->connection_info->ssl_proto); + case QDR_CONNECTION_TLSPROTO: + if (conn->connection_info->tls_proto && conn->connection_info->tls_proto[0] != '\0') + qd_compose_insert_string(body, conn->connection_info->tls_proto); else qd_compose_insert_null(body); break; - case QDR_CONNECTION_SSLCIPHER: - if (conn->connection_info->ssl_cipher[0] != '\0') - qd_compose_insert_string(body, conn->connection_info->ssl_cipher); + case QDR_CONNECTION_TLSCIPHER: + if (conn->connection_info->tls_cipher && conn->connection_info->tls_cipher[0] != '\0') + qd_compose_insert_string(body, conn->connection_info->tls_cipher); else qd_compose_insert_null(body); break; - case QDR_CONNECTION_SSLSSF: - qd_compose_insert_long(body, conn->connection_info->ssl_ssf); + case QDR_CONNECTION_TLSSSF: + qd_compose_insert_long(body, conn->connection_info->tls_ssf); break; case QDR_CONNECTION_TYPE: qd_compose_insert_string(body, CONNECTION_TYPE); break; - case QDR_CONNECTION_SSL: - qd_compose_insert_bool(body, conn->connection_info->ssl); + case QDR_CONNECTION_TLS: + qd_compose_insert_bool(body, conn->connection_info->tls); break; case QDR_CONNECTION_OPENED: diff --git a/src/router_core/connections.c b/src/router_core/connections.c index c4df19459..e722ef958 100644 --- a/src/router_core/connections.c +++ b/src/router_core/connections.c @@ -145,7 +145,7 @@ qdr_connection_t *qdr_connection_opened(qdr_core_t *core, "] Connection Opened: dir=%s host=%s encrypted=%s" " auth=%s user=%s container_id=%s props=%s", management_id, incoming ? "in" : "out", connection_info->host, - connection_info->is_encrypted ? connection_info->ssl_proto : "no", + connection_info->is_encrypted ? connection_info->tls_proto : "no", connection_info->is_authenticated ? connection_info->sasl_mechanisms : "no", connection_info->user, connection_info->container, props_str); @@ -186,13 +186,13 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, char *sasl_mechanisms, qd_direction_t dir, const char *host, - const char *ssl_proto, - const char *ssl_cipher, + const char *tls_proto, + const char *tls_cipher, const char *user, const char *container, pn_data_t *connection_properties, - int ssl_ssf, - bool ssl, + int tls_ssf, + bool tls, const char *version, bool streaming_links, bool connection_trunking) @@ -210,10 +210,10 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, connection_info->dir = dir; if (host) connection_info->host = strdup(host); - if (ssl_proto) - connection_info->ssl_proto = strdup(ssl_proto); - if (ssl_cipher) - connection_info->ssl_cipher = strdup(ssl_cipher); + if (tls_proto) + connection_info->tls_proto = strdup(tls_proto); + if (tls_cipher) + connection_info->tls_cipher = strdup(tls_cipher); if (user) connection_info->user = strdup(user); if (version) @@ -224,8 +224,8 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, pn_data_copy(qdr_conn_properties, connection_properties); connection_info->connection_properties = qdr_conn_properties; - connection_info->ssl_ssf = ssl_ssf; - connection_info->ssl = ssl; + connection_info->tls_ssf = tls_ssf; + connection_info->tls = tls; connection_info->streaming_links = streaming_links; connection_info->connection_trunking = connection_trunking; sys_mutex_init(&connection_info->connection_info_lock); @@ -239,13 +239,38 @@ void qdr_connection_info_set_group_correlator(qdr_connection_info_t *info, const } +void qdr_connection_info_set_tls(qdr_connection_info_t *conn_info, bool enabled, char *version, char *ciphers, int ssf) +{ + // + // Lock using the connection_info_lock before setting the values on the + // connection_info. This same lock is being used in the agent_connection.c's qdr_connection_insert_column_CT + // + sys_mutex_lock(&conn_info->connection_info_lock); + free(conn_info->tls_cipher); + free(conn_info->tls_proto); + conn_info->tls = enabled; + conn_info->is_encrypted = enabled; + if (enabled) { + conn_info->tls_proto = version; + conn_info->tls_cipher = ciphers; + conn_info->tls_ssf = ssf; + } else { + assert(!version && !ciphers); + conn_info->tls_cipher = 0; + conn_info->tls_proto = 0; + conn_info->tls_ssf = 0; + } + sys_mutex_unlock(&conn_info->connection_info_lock); +} + + static void qdr_connection_info_free(qdr_connection_info_t *ci) { free(ci->container); free(ci->sasl_mechanisms); free(ci->host); - free(ci->ssl_proto); - free(ci->ssl_cipher); + free(ci->tls_proto); + free(ci->tls_cipher); free(ci->user); free(ci->version); sys_mutex_free(&ci->connection_info_lock); diff --git a/src/router_core/router_core_private.h b/src/router_core/router_core_private.h index 6fb36334a..b6e7b6c73 100644 --- a/src/router_core/router_core_private.h +++ b/src/router_core/router_core_private.h @@ -639,8 +639,8 @@ struct qdr_connection_info_t { char *container; char *sasl_mechanisms; char *host; - char *ssl_proto; - char *ssl_cipher; + char *tls_proto; + char *tls_cipher; char *user; bool is_authenticated; bool is_encrypted; @@ -650,8 +650,8 @@ struct qdr_connection_info_t { qd_direction_t dir; qdr_connection_role_t role; pn_data_t *connection_properties; - bool ssl; - int ssl_ssf; //ssl strength factor + bool tls; + int tls_ssf; // TLS strength factor char *version; // if role is router or edge sys_mutex_t connection_info_lock; char group_correlator[QD_DISCRIMINATOR_SIZE]; // Used to associate inter-router-data connections to their inter-router connection diff --git a/src/server.c b/src/server.c index 0783167fa..1953fbd68 100644 --- a/src/server.c +++ b/src/server.c @@ -70,33 +70,11 @@ struct qd_server_t { int pause_next_sequence; int pause_now_serving; uint64_t next_connection_id; - void *py_displayname_obj; qd_http_server_t *http; sys_mutex_t conn_activation_lock; }; -/** - * Save displayNameService object instance and ImportModule address - * Called with qd_python_lock held - */ -qd_error_t qd_register_display_name_service(qd_dispatch_t *qd, void *displaynameservice) -{ - if (displaynameservice) { - qd->server->py_displayname_obj = displaynameservice; - Py_XINCREF((PyObject *)qd->server->py_displayname_obj); - return QD_ERROR_NONE; - } - else { - return qd_error(QD_ERROR_VALUE, "displaynameservice is not set"); - } -} - -QD_EXPORT qd_error_t qd_entity_refresh_sslProfile(qd_entity_t* entity, void *impl) -{ - return QD_ERROR_NONE; -} - // // Proactor event handlers // @@ -230,7 +208,6 @@ qd_server_t *qd_server(qd_dispatch_t *qd, int thread_count, const char *containe qd_server->pause_next_sequence = 0; qd_server->pause_now_serving = 0; qd_server->next_connection_id = 1; - qd_server->py_displayname_obj = 0; if (qd_server->sasl_config_path) pn_sasl_config_path(0, qd_server->sasl_config_path); @@ -258,9 +235,6 @@ void qd_server_free(qd_server_t *qd_server) sys_mutex_free(&qd_server->lock); sys_mutex_free(&qd_server->conn_activation_lock); sys_cond_free(&qd_server->cond); - qd_python_lock_state_t ls = qd_python_lock(); - Py_XDECREF((PyObject *)qd_server->py_displayname_obj); - qd_python_unlock(ls); free(qd_server); } @@ -336,32 +310,6 @@ sys_mutex_t *qd_server_get_activation_lock(qd_server_t * server) return &server->conn_activation_lock; } -/** - * Query DisplayNameServer for user name. - * - * Returns string containing user name if query succeeds else 0. - * Caller must free() returned user name string when no longer used. - */ -char *qd_server_query_user_name(const qd_server_t *server, const char *ssl_profile, const char *user_id) -{ - char *user_name = 0; - - // Translate extracted id into display name - qd_python_lock_state_t lock_state = qd_python_lock(); - PyObject *result = PyObject_CallMethod((PyObject *)server->py_displayname_obj, "query", "(ss)", ssl_profile, user_id ); - if (result) { - user_name = py_string_2_c(result); - Py_XDECREF(result); - } else { - qd_log(LOG_SERVER, QD_LOG_DEBUG, - "Internal: failed to read displaynameservice query result (%s)", - user_id); - } - qd_python_unlock(lock_state); - - return user_name; -} - const char *qd_server_get_container_name(const qd_server_t *server) { return server->container_name; diff --git a/src/server_private.h b/src/server_private.h index 9a2beca3e..85135490c 100644 --- a/src/server_private.h +++ b/src/server_private.h @@ -34,7 +34,6 @@ #include #include -#include qd_dispatch_t* qd_server_dispatch(qd_server_t *server); diff --git a/src/tls/README.txt b/src/tls/README.txt new file mode 100644 index 000000000..478d2ddf8 --- /dev/null +++ b/src/tls/README.txt @@ -0,0 +1,211 @@ += Transport Layer Security Management = + +This directory contains code for the management of TLS configuration +and use. + +Note: The terms "Connector" and "Listener" are used in this document +as generic names for protocol specific connector and listener +records. For example the term "Connector" implies both AMQP connectors +(record type "connector") as well as TCP connectors (record type +"tcpConnector"). + +== Configuration == + +The sslProfile management entity specifies a set of certificates, +keys, and password information that is used to instantiate one or more +TLS configuration instances. sslProfiles may be created/read/updated +and deleted via the router's management agent. + +Listener and Connector management entities will include the name of a +particular sslProfile when TLS is required on connections associated +with the Listener/Connector. TLS parameters that are specific to a +Listener/Connector are also included (e.g. authenticate peer or +validate peer hostname). + +See the router management schema for more details. + +== Primary Data Structures == + +There are five primary data structures used for TLS support: + +- qd_tls_context_t (private) +- qd_ssl2_profile_t +- qd_tls_config_t +- qd_tls_session_t +- qd_proton_config_t + +The root structure is the qd_tls_context_t. There is a one-to-one +relationship between a qd_tls_context_t and an sslProfile management +entity instance. In other words each sslProfile that is configured has +a qd_tls_context_t instance representing it. + +The qd_ssl2_profile_t structure contains the values from a given +sslProfile entity. A qd_tls_context_t has an embedded +qd_ssl2_profile_t instance for holding the current values from the +sslProfile record. + +A qd_tls_config_t represents an instance of an Openssl configuration +context. A qd_tls_config_t is created and owned by the +Listener/Connector which is configured to use an sslProfile. In order +to support management updates to an sslProfile record all +qd_tls_config_ts are held in a list in the qd_tls_context_t associated +with the qd_tls_config_t's parent sslProfile. The qd_tls_config_t +creates a qd_proton_config_t using the sslProfile and configuration +settings from its parent Listener/Connector. + +A qd_proton_config_t is created by a qd_tls_config_t and wraps the +underlying Proton SSL/TLS configuration instance. This is either an +instance of a pn_ssl_domain_t (for AMQP) or a pn_tls_config_t (for raw +connection). This wrapper fufills two requirements: 1) creation and +deletion of Proton TLS sessions using the same configuration MUST be +single threaded, and 2) the Proton configuration MUST NOT be freed +until all sessions using the configuration have been freed. The +qd_proton_config_t contains a mutex and an atomic reference count to +satisfy these requirements. qd_proton_config_ts are immutable. In +order to update a configuration a new qd_proton_config_t is created to +replace the old. + +A qd_tls_session_t is the per-connection TLS state. It is created +using the qd_tls_config_t and qd_proton_config_t associated with the +connection's parent Listener/Connector. + +=== Object Lifecycle === + +A qd_tls_context_t is created when a new sslProfile record is created +by the management agent. It is destroyed after the management agent +has deleted the corresponding sslProfile record and all child +qd_tls_config_t instances have been freed. + +A qd_tls_config_t instance is reference-counted. The +Listener/Connector that created the qd_tls_config_t holds a reference +to it. A reference is also held by the parent qd_tls_context_t. A +qd_tls_config_t instance is created when a Listener/Connector is +instantiated via the management agent. It is destroyed after the +parent Listener/Connector is deleted. + +A qd_proton_config_t is reference counted by the parent +qd_tls_config_t and all qd_tls_session_ts using it. + +A qd_tls_session_t is created when a new protocol connection is +opened. It is released when the associated connection is terminated. + +=== Object Relationship Diagram === + + +--- qd_tls_context_t ----+ + | | + | +- qd_ssl2_profile_t -+ | + | | "Profile1" | | + | +---------------------+ | + | | + +---+---------------------+ + | + | + | +- qd_tls_config_t-+ +- qd_tls_config_t -+ + +-->| | <--> | | <--> ... + +-----------------++ +-------------------+ + ^ | ^ + | | | + | | | + +-+- Listener -+ | +-+- Listener -+ + | "Foo" | | | "Bar" | + +--------------+ | +--------------+ + | + | + +------------------------+ + | + V + +- qd_tls_session_t -+ +- qd_proton_config_t -+ + | "Conn1" | | | + | +-------->+ | + | | | | + +--------------------+ +--+-------------------+ + ^ + | + +- qd_tls_session_t -+ | + | "Conn2" | | + | +------------+ + | | + +--------------------+ + +In the above diagram there are two Listener instances both sharing the +sslProfile record "Profile1". Each listener maintains its own +qd_tls_config_t. The qd_tls_config_t's are held in a list on the +parent qd_tls_context_t. Listener "Foo" has two active TLS +connections. These connections have a dedicated qd_tls_session_t +instance. The sessions reference the qd_proton_config_t used to create +them. + + +== Threading + +The management operations - create/update/delete - for sslProfile, +Listener, and Connector management entities occur on the management +agent thread. Therefore these operations are not multithreaded. This +allows the implementation to avoid using locks to manage +qd_tls_context_t and qd_tls_config_t/qd_proton_config_t +instances. Debug code is present to assert that these operations are +only called via the management agent thread. + +Per-connection qd_tls_session_t operations may occur on any I/O thread +and therefore can run simultaineously with the management agent +thread. This means sslProfile updates (and deletions) may occur while +I/O threads are using the qd_tls_sesson_t that are dependent on the +sslProfile configuration. There are mutexes in the qd_tls_config_t and +the qd_proton_config_t that prevent races between the I/O threads and +management. See below. + +== sslProfile Management Update + +When an sslProfile is created or updated it is necessary to read the +associated certficate files. Accessing the filesystem takes an +indeterminate amount of time during which the calling thread is +blocked. Therefore it is important not to do this operation on an I/O +thread as it blocks other I/O work for a potentially long time +(hundreds of milliseconds). Doing so would result in long latency +times. + +This implementation avoids this problem by loading the certificates +during the create/update operation which occurs on the management +thread. Therefore the management thread assumes the cost of the +operation and I/O threads are not impacted. + +However updating an sslProfile requires all child qd_tls_config_t +instances update their run-time configuration. This means that all +qd_tls_config_ts have to re-load their certificate files and generate +new qd_proton_config_t instances. While this is being done (on the +management thread) new qd_tls_session_t instances using that +qd_tls_config_t and its current qd_proton_config_t may be created by +the I/O threads (e.g. a new connection arrives). + +Two mutexes are used to prevent race conditions between the management +thread and the I/O threads. + +First there is a mutex in the qd_tls_config_t that protects its +pointer to the qd_proton_config_t instance. Keep in mind that a +qd_proton_config_t is immutable and must be replaced in order to +update the TLS configuration. When an sslProfile management operation +occurs this lock is held long enough to swap out the old +qd_proton_config_t instance with a new qd_proton_config_t instance +containing the updated management configuration. This same lock must +be held by an I/O thread when the qd_proton_config_t pointer is copied +into a new qd_tls_session_t instance and the qd_proton_config_t's +reference count is incremented. This prevents the qd_proton_config_t +from being deleted during the qd_tls_session_t creation process. + +Second there is a mutex in the qd_proton_config_t that must be held +when a Proton session is created or destroyed. This is necessary because +these operations are not thread safe. + +Since qd_proton_config_t are reference counted and immutable there is +no need to nest these locks. + +== Files + +- tls.c: main codebase for sslProfile management, TLS configuration + and session lifecycle logic. +- tls_amqp.c: those parts of the API that are specific to using TLS + with Proton AMQP connections. +- tls_raw.c: those parts of the API that are specific to use the + buffer-based TLS implementation. These APIs are used by the Proton + Raw connection transports. + diff --git a/src/tls/display_name.c b/src/tls/display_name.c new file mode 100644 index 000000000..67d6e5285 --- /dev/null +++ b/src/tls/display_name.c @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_common.h" +#include "private.h" + +#include "qpid/dispatch/internal/export.h" +#include "qpid/dispatch/python_embedded.h" + +/* + * API for accessing the Python DisplayNameService instance. The DisplayNameService is used to access the contents of + * the uidNameMappingFile configured in the sslProfile. + */ + +static void *py_displayname_obj; // Reference to the Python DisplayNameService singleton + + +/** + * Store address of display name service Python object for C code use. + * Called by Python during the router configuration file load. The qd_python_lock() is held during this call. + * + * @param display_name_service address of python object + */ +QD_EXPORT qd_error_t qd_tls_register_display_name_service(void *display_name_service) +{ + if (display_name_service) { + py_displayname_obj = display_name_service; + Py_XINCREF((PyObject *)py_displayname_obj); + return QD_ERROR_NONE; + } + else { + return qd_error(QD_ERROR_VALUE, "Display Name Service is not set"); + } +} + + +void tls_private_release_display_name_service(void) +{ + qd_python_lock_state_t ls = qd_python_lock(); + Py_XDECREF((PyObject *)py_displayname_obj); + qd_python_unlock(ls); + py_displayname_obj = 0; +} + + +/** + * Look up the display name corresponding to user_id in the given sslProfile's uidNameMappingFile + * @param ssl_profile_name name of the sslProfile record instance + * @param user_id user identifier used as lookup key + * @return a null-terminated user name string on success else 0. The caller must free() the user name string when done + * using it. + */ +char *tls_private_lookup_display_name(const char *ssl_profile_name, const char *user_id) +{ + char *user_name = 0; + + assert(py_displayname_obj); + + // Translate extracted id into display name + qd_python_lock_state_t lock_state = qd_python_lock(); + PyObject *result = PyObject_CallMethod((PyObject *)py_displayname_obj, "query", "(ss)", ssl_profile_name, user_id ); + if (result) { + user_name = py_string_2_c(result); + Py_XDECREF(result); + } + qd_python_unlock(lock_state); + + return user_name; +} diff --git a/src/tls/private.h b/src/tls/private.h new file mode 100644 index 000000000..cb2832215 --- /dev/null +++ b/src/tls/private.h @@ -0,0 +1,136 @@ +#ifndef __tls_private_h__ +#define __tls_private__ 1 +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_raw.h" +#include "qpid/dispatch/tls_amqp.h" + +#include "qpid/dispatch/ctools.h" +#include "qpid/dispatch/atomic.h" +#include "qpid/dispatch/threading.h" +#include "qpid/dispatch/log.h" + +typedef struct qd_tls_context_t qd_tls_context_t; +typedef struct qd_proton_config_t qd_proton_config_t; +typedef struct pn_tls_config_t pn_tls_config_t; +typedef struct pn_tls_t pn_tls_t; +typedef struct pn_ssl_domain_t pn_ssl_domain_t; +typedef struct pn_ssl_t pn_ssl_t; + + +/** + * Handle for Proton TLS configuration. + * + * A Proton TLS configuration cannot be destroyed until all related TLS sessions have closed and the parent TLS + * configuration has been deleted. A reference count is used to ensure this. Additionally a mutex is provided to + * single-thread the creation and deletion of proton sessions. This is necessary since proton sessions are + * multi-threaded. + */ +struct qd_proton_config_t { + // only one of the following Proton pointers will be set based on whether this configuration is for a raw connection + // or an AMQP connection + pn_tls_config_t *pn_raw; + pn_ssl_domain_t *pn_amqp; + sys_mutex_t lock; // held when creating and deleting sessions + sys_atomic_t ref_count; // for parent qd_tls_config_t and all children qd_tls_session_t +}; + +/** + * Context for a single per-connection TLS data stream + */ +struct qd_tls_session_t { + qd_proton_config_t *proton_tls_cfg; // TLS Proton configuration used by session + + // only one of the following Proton session pointers will be set based on whether this session is for a raw + // connection or an AMQP connection + pn_tls_t *pn_raw; + pn_ssl_t *pn_amqp; + + void *user_context; + qd_tls_session_on_secure_cb_t *on_secure_cb; + + // copies from parent qd_tls_config_t to avoid locking during I/O: + char *ssl_profile_name; + char *uid_format; + long version; + + bool tls_error; + bool output_eos; // pn_tls_close_output() called + bool raw_read_drained; // raw conn read closed and all buffer read + bool input_drained; // no more decrypted output, raw conn read closed + bool output_flushed; // encrypt done, raw conn write closed + + uint64_t encrypted_output_bytes; + uint64_t encrypted_input_bytes; +}; + +/** + * Context for a TLS configuration object. + * + * Factory for creating qd_tls_session_t instances. + * + * Note that this differs from the qd_ssl2_profile_t object in that this object holds the run-time configuration + * state. It is created by reading the certificate configuration provided by the sslProfile via the qd_ssl2_profile_t. + */ +struct qd_tls_config_t { + DEQ_LINKS(qd_tls_config_t); // for parent qd_tls_context_t list + sys_mutex_t lock; + char *ssl_profile_name; + char *uid_format; // lock must be held + qd_proton_config_t *proton_tls_cfg; // lock must be held + qd_tls_type_t p_type; + sys_atomic_t ref_count; + long version; // lock must be held + + bool authenticate_peer; + bool verify_hostname; + bool is_listener; +}; + +DEQ_DECLARE(qd_tls_config_t, qd_tls_config_list_t); + +/** + * Top-level TLS context + * + * Maintains all TLS state associated with an sslProfile record. Also includes the currently active qd_tls_config_t + * instances generated from that sslProfile record. + */ + +struct qd_tls_context_t { + DEQ_LINKS(qd_tls_context_t); + char *ssl_profile_name; + qd_ssl2_profile_t profile; + qd_tls_config_list_t tls_configs; +}; + +DEQ_DECLARE(qd_tls_context_t, qd_tls_context_list_t); + +// Internal use only! +void tls_private_release_display_name_service(void); +bool tls_private_validate_uid_format(const char *format); +char *tls_private_lookup_display_name(const char *ssl_profile_name, const char *user_id); +pn_tls_config_t *tls_private_allocate_raw_config(const char *ssl_profile_name, const qd_ssl2_profile_t *config, + bool is_listener, bool verify_hostname, bool authenticate_peer); +pn_ssl_domain_t *tls_private_allocate_amqp_config(const char *ssl_profile_name, const qd_ssl2_profile_t *config, + bool is_listener, bool verify_hostname, bool authenticate_peer); +qd_proton_config_t *qd_proton_config(pn_tls_config_t *tls_cfg, pn_ssl_domain_t *ssl_cfg); +void qd_proton_config_decref(qd_proton_config_t *p_cfg); +#endif + diff --git a/src/tls/tls.c b/src/tls/tls.c new file mode 100644 index 000000000..12291371c --- /dev/null +++ b/src/tls/tls.c @@ -0,0 +1,868 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_common.h" +#include "private.h" + +#include "qpid/dispatch/alloc_pool.h" +#include "qpid/dispatch/error.h" +#include "qpid/dispatch/threading.h" +#include "qpid/dispatch/buffer.h" +#include "entity.h" + +#include +#include +#include + +#include + +/* + * Manages TLS configuration and stream lifecycle + */ + +#define CHECKED_STRDUP(S) (!!(S) ? qd_strdup(S) : 0) + + +ALLOC_DECLARE(qd_proton_config_t); +ALLOC_DEFINE(qd_proton_config_t); + +ALLOC_DECLARE(qd_tls_session_t); +ALLOC_DEFINE(qd_tls_session_t); + +ALLOC_DECLARE(qd_tls_config_t); +ALLOC_DEFINE(qd_tls_config_t); + +ALLOC_DECLARE(qd_tls_context_t); +ALLOC_DEFINE(qd_tls_context_t); + + +/** + * Master list of all active TLS context instances. Only accessed by the management thread so no locking necessary. + */ +static qd_tls_context_list_t context_list; + + +// Internals: +static qd_error_t _read_tls_profile(qd_entity_t *entity, qd_ssl2_profile_t *profile); +static void _cleanup_tls_profile(qd_ssl2_profile_t *profile); +static qd_tls_context_t *_find_tls_context(const char *profile_name); +static void _tls_context_free(qd_tls_context_t *ctxt); +static qd_error_t _update_tls_config(qd_tls_config_t *tls_config, const qd_ssl2_profile_t *profile); +static qd_error_t _validate_config(const qd_ssl2_profile_t *profile, const char *profile_name, bool is_listener, + bool authenticate_peer); + +// TODO: these should be moved somewhere public as they are called from multiple places +extern void qd_server_config_process_password(char **actual_val, char *pw, bool *is_file, bool allow_literal_prefix); +extern void qd_set_password_from_file(const char *password_file, char **password_field); + + + +/* Thread verification + * + * We can avoid locking of the context_list IF all the functions that access the list are running on the same + * thread. Initial configuration file load occurs on main thread, management updates runs via a zero timer. + */ +#define ASSERT_MGMT_THREAD assert(sys_thread_role(0) == SYS_THREAD_MAIN || sys_thread_proactor_mode() == SYS_THREAD_PROACTOR_MODE_TIMER) + +void qd_tls_initialize(void) +{ + DEQ_INIT(context_list); +} + + +void qd_tls_finalize(void) +{ + qd_tls_context_t *ctxt = DEQ_HEAD(context_list); + while (ctxt) { + DEQ_REMOVE_HEAD(context_list); + _tls_context_free(ctxt); + ctxt = DEQ_HEAD(context_list); + } + tls_private_release_display_name_service(); +} + + +/** + * Handle sslProfile record create request from management + */ +QD_EXPORT void *qd_tls_configure_ssl_profile(qd_dispatch_t *qd, qd_entity_t *entity) +{ + ASSERT_MGMT_THREAD; + + qd_error_clear(); + char *name = qd_entity_opt_string(entity, "name", 0); + if (!name || qd_error_code()) { + free(name); + qd_log(LOG_AGENT, QD_LOG_ERROR, "Unable to create sslProfile: %s", qd_error_message()); + return 0; + } + + qd_tls_context_t *tls_context = new_qd_tls_context_t(); + ZERO(tls_context); + DEQ_ITEM_INIT(tls_context); + DEQ_INIT(tls_context->tls_configs); + tls_context->ssl_profile_name = name; + + if (_read_tls_profile(entity, &tls_context->profile) != QD_ERROR_NONE) { + qd_log(LOG_AGENT, QD_LOG_ERROR, "Unable to create sslProfile '%s': %s", name, qd_error_message()); + _tls_context_free(tls_context); + return 0; + } + + DEQ_INSERT_TAIL(context_list, tls_context); + qd_log(LOG_AGENT, QD_LOG_INFO, "Created sslProfile %s", tls_context->ssl_profile_name); + return tls_context; +} + + +/** + * Handle sslProfile record delete request from management. + */ +QD_EXPORT void qd_tls_delete_ssl_profile(qd_dispatch_t *qd, void *impl) +{ + ASSERT_MGMT_THREAD; + + qd_tls_context_t *tls_context = (qd_tls_context_t *) impl; + assert(tls_context); + + DEQ_REMOVE(context_list, tls_context); + + qd_log(LOG_AGENT, QD_LOG_INFO, "Deleted sslProfile %s", tls_context->ssl_profile_name); + + _tls_context_free(tls_context); +} + + +/** + * Handle sslProfile record update request from management. + */ +QD_EXPORT void *qd_tls_update_ssl_profile(qd_dispatch_t *qd, qd_entity_t *entity, void *impl) +{ + ASSERT_MGMT_THREAD; + + qd_tls_context_t *tls_context = (qd_tls_context_t *) impl; + qd_ssl2_profile_t new_profile; + + qd_error_clear(); + + assert(tls_context); + if (_read_tls_profile(entity, &new_profile) != QD_ERROR_NONE) { + qd_log(LOG_AGENT, QD_LOG_ERROR, "Unable to update sslProfile '%s': %s", tls_context->ssl_profile_name, qd_error_message()); + return 0; + } + + qd_tls_config_t *config = DEQ_HEAD(tls_context->tls_configs); + while (config) { + if (_update_tls_config(config, &new_profile) != QD_ERROR_NONE) { + // There is a problem with the new configuration. Discard the change and return 0 to force the management + // operation to fail + qd_log(LOG_AGENT, QD_LOG_ERROR, "Failed to update sslProfile '%s': %s", tls_context->ssl_profile_name, qd_error_message()); + _cleanup_tls_profile(&new_profile); + return 0; + } + config = DEQ_NEXT(config); + } + + _cleanup_tls_profile(&tls_context->profile); + tls_context->profile = new_profile; + qd_log(LOG_AGENT, QD_LOG_INFO, "Updated sslProfile %s ", tls_context->ssl_profile_name); + return impl; +} + + +/** + * Dummy stub since the Python agent expects a "qd_entity_refresh_BLAH" for every + * entity that has a C implementation (see CImplementation in agent.py) + */ +QD_EXPORT qd_error_t qd_entity_refresh_sslProfile(qd_entity_t* entity, void *impl) +{ + return QD_ERROR_NONE; +} + + +qd_proton_config_t *qd_proton_config(pn_tls_config_t *tls_cfg, pn_ssl_domain_t *ssl_cfg) +{ + qd_proton_config_t *p_cfg = new_qd_proton_config_t(); + ZERO(p_cfg); + p_cfg->pn_raw = tls_cfg; + p_cfg->pn_amqp = ssl_cfg; + sys_mutex_init(&p_cfg->lock); + sys_atomic_init(&p_cfg->ref_count, 1); + return p_cfg; +} + + +void qd_proton_config_decref(qd_proton_config_t *p_cfg) +{ + if (p_cfg) { + uint32_t rc = sys_atomic_dec(&p_cfg->ref_count); + assert(rc != 0); // underflow! + if (rc == 1) { + if (p_cfg->pn_raw) { + pn_tls_config_free(p_cfg->pn_raw); + } + if (p_cfg->pn_amqp) { + pn_ssl_domain_free(p_cfg->pn_amqp); + } + sys_mutex_free(&p_cfg->lock); + sys_atomic_destroy(&p_cfg->ref_count); + free_qd_proton_config_t(p_cfg); + } + } +} + + +qd_tls_config_t *qd_tls_config(const char *ssl_profile_name, + qd_tls_type_t p_type, + qd_tls_config_mode_t mode, + bool verify_hostname, + bool authenticate_peer) +{ + ASSERT_MGMT_THREAD; // called from listener/connector create callback + + pn_tls_config_t *pn_raw_config = 0; + pn_ssl_domain_t *pn_amqp_config = 0; + const bool is_listener = mode == QD_TLS_CONFIG_SERVER_MODE; + + qd_error_clear(); + + qd_tls_context_t *tls_context = _find_tls_context(ssl_profile_name); + if (!tls_context) { + qd_error(QD_ERROR_NOT_FOUND, "sslProfile '%s' not found", ssl_profile_name); + return 0; + } + + if (_validate_config(&tls_context->profile, ssl_profile_name, is_listener, authenticate_peer) != QD_ERROR_NONE) { + return 0; // validate_config sets qd_error() + } + + switch (p_type) { + case QD_TLS_TYPE_PROTON_AMQP: + pn_amqp_config = tls_private_allocate_amqp_config(ssl_profile_name, &tls_context->profile, is_listener, + verify_hostname, authenticate_peer); + if (!pn_amqp_config) { + // allocation function set qd_error() + return 0; + } + break; + case QD_TLS_TYPE_PROTON_RAW: + pn_raw_config = tls_private_allocate_raw_config(ssl_profile_name, &tls_context->profile, is_listener, + verify_hostname, authenticate_peer); + if (!pn_raw_config) { + // allocation function set qd_error() + return 0; + } + break; + default: + assert(false); + break; + } + + qd_tls_config_t *tls_config = new_qd_tls_config_t(); + ZERO(tls_config); + sys_mutex_init(&tls_config->lock); + + // 1 reference for caller and another for the tls_configs list + sys_atomic_init(&tls_config->ref_count, 2); + + tls_config->ssl_profile_name = qd_strdup(ssl_profile_name); + tls_config->uid_format = CHECKED_STRDUP(tls_context->profile.uid_format); + tls_config->version = tls_context->profile.version; + tls_config->authenticate_peer = authenticate_peer; + tls_config->verify_hostname = verify_hostname; + tls_config->is_listener = is_listener; + tls_config->p_type = p_type; + tls_config->proton_tls_cfg = qd_proton_config(pn_raw_config, pn_amqp_config); + DEQ_INSERT_TAIL(tls_context->tls_configs, tls_config); + + return tls_config; +} + + +void qd_tls_config_decref(qd_tls_config_t *tls_config) +{ + if (tls_config) { + uint32_t rc = sys_atomic_dec(&tls_config->ref_count); + assert(rc != 0); // underflow! + if (rc == 1) { + // Last reference: can assume it has already been removed from parent tls_context config list and no + // other threads are accessing it + qd_proton_config_decref(tls_config->proton_tls_cfg); + free(tls_config->ssl_profile_name); + free(tls_config->uid_format); + sys_atomic_destroy(&tls_config->ref_count); + sys_mutex_free(&tls_config->lock); + free_qd_tls_config_t(tls_config); + } + } +} + + +qd_tls_session_t *qd_tls_session_raw(qd_tls_config_t *tls_config, const char *peer_hostname, + const char **alpn_protocols, size_t alpn_protocol_count, + void *context, qd_tls_session_on_secure_cb_t *on_secure) +{ + assert(tls_config->p_type == QD_TLS_TYPE_PROTON_RAW); + + qd_error_clear(); + + qd_tls_session_t *tls_session = new_qd_tls_session_t(); + ZERO(tls_session); + + tls_session->user_context = context; + tls_session->on_secure_cb = on_secure; + tls_session->ssl_profile_name = qd_strdup(tls_config->ssl_profile_name); + + // Copy out those config elements that can be modified by the management thread. Do this under lock to prevent the + // management thread update handler from modifying those config elements during this copy + + qd_proton_config_t *p_cfg = 0; + + sys_mutex_lock(&tls_config->lock); + p_cfg = tls_config->proton_tls_cfg; + assert(p_cfg); + sys_atomic_inc(&p_cfg->ref_count); // prevents free after we drop lock + tls_session->uid_format = CHECKED_STRDUP(tls_config->uid_format); // may be changed by mgmt thread + tls_session->version = tls_config->version; // may be changed by mgmt thread + sys_mutex_unlock(&tls_config->lock); + + tls_session->proton_tls_cfg = p_cfg; + + // Must hold the proton config lock during the session initialization. Initialization is not thread safe since the + // proton config is modified during this process. + + sys_mutex_lock(&p_cfg->lock); + assert(p_cfg->pn_raw); + + // if ALPN needs to be configured it must be done on the proton config. Since the config lock is held this will not + // effect other sessions. + + if (alpn_protocol_count) { + assert(alpn_protocols); + int rc = pn_tls_config_set_alpn_protocols(p_cfg->pn_raw, alpn_protocols, alpn_protocol_count); + if (rc != 0) { + sys_mutex_unlock(&p_cfg->lock); + qd_error(QD_ERROR_CONFIG, "Failed to configure ALPN settings for new TLS session with sslProfile %s (%d)", + tls_session->ssl_profile_name, rc); + goto error; + } + } + + bool ok = true; + tls_session->pn_raw = pn_tls(p_cfg->pn_raw); + if (!tls_session->pn_raw) { + ok = false; + qd_error(QD_ERROR_CONFIG, "Failed to create new TLS session with sslProfile %s", tls_session->ssl_profile_name); + + } else { + if (peer_hostname) { + int rc = pn_tls_set_peer_hostname(tls_session->pn_raw, peer_hostname); + if (rc != 0) { + ok = false; + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS peer hostname '%s' for sslProfile %s (%d)", + peer_hostname, tls_session->ssl_profile_name, rc); + } + } + + if (ok) { + int rc = pn_tls_start(tls_session->pn_raw); + if (rc != 0) { + ok = false; + qd_error(QD_ERROR_CONFIG, "Failed to start TLS session for sslProfile %s (%d)", tls_session->ssl_profile_name, rc); + } + } + } + + // must restore the ALPN settings even if the setup of the session failed + + if (alpn_protocol_count) { + int rc = pn_tls_config_set_alpn_protocols(p_cfg->pn_raw, 0, 0); + if (rc != 0) { + // I have no idea how to recover from this: + ok = false; + qd_error(QD_ERROR_CONFIG, "Failed to clear ALPN settings for new TLS session with sslProfile %s (%d)", + tls_session->ssl_profile_name, rc); + } + } + + sys_mutex_unlock(&p_cfg->lock); + + if (ok) + return tls_session; + // else fall through + +error: + qd_tls_session_free(tls_session); + return 0; +} + + +qd_tls_session_t *qd_tls_session_amqp(qd_tls_config_t *tls_config, pn_transport_t *tport, bool allow_unencrypted) +{ + assert(tls_config->p_type == QD_TLS_TYPE_PROTON_AMQP); + + qd_error_clear(); + + qd_tls_session_t *tls_session = new_qd_tls_session_t(); + ZERO(tls_session); + + tls_session->ssl_profile_name = qd_strdup(tls_config->ssl_profile_name); + + // Copy out those config elements that can be modified by the management thread. Do this under lock to prevent the + // management thread update handler from modifying those config elements during this copy + + qd_proton_config_t *p_cfg = 0; + + sys_mutex_lock(&tls_config->lock); + p_cfg = tls_config->proton_tls_cfg; + assert(p_cfg); + sys_atomic_inc(&p_cfg->ref_count); // prevents free after we drop lock + tls_session->uid_format = CHECKED_STRDUP(tls_config->uid_format); // may be changed by mgmt thread + tls_session->version = tls_config->version; // may be changed by mgmt thread + sys_mutex_unlock(&tls_config->lock); + + tls_session->proton_tls_cfg = p_cfg; + + // Must hold the proton config lock during the session initialization. Initialization is not thread safe since the + // proton config is modified during this process. + + sys_mutex_lock(&p_cfg->lock); + assert(p_cfg->pn_amqp); + + tls_session->pn_amqp = pn_ssl(tport); + if (!tls_session->pn_amqp) { + sys_mutex_unlock(&p_cfg->lock); + qd_error(QD_ERROR_RUNTIME, "Failed to create an AMQP TLS session"); + goto error; + } + + int rc = pn_ssl_init(tls_session->pn_amqp, p_cfg->pn_amqp, 0); + if (rc) { + sys_mutex_unlock(&p_cfg->lock); + qd_error(QD_ERROR_RUNTIME, "Failed to initialize AMQP TLS session (%d)", rc); + goto error; + } + + sys_mutex_unlock(&p_cfg->lock); + + // By default adding tls to a transport forces encryption to be required, so if it's not set that here + if (allow_unencrypted) { + pn_transport_require_encryption(tport, false); + } + + return tls_session; + +error: + qd_tls_session_free(tls_session); + return 0; +} + + +void qd_tls_session_free(qd_tls_session_t *tls_session) +{ + pn_raw_buffer_t buf_desc; + + if (tls_session) { + if (tls_session->pn_raw) { + pn_tls_stop(tls_session->pn_raw); + + while (pn_tls_take_encrypt_output_buffers(tls_session->pn_raw, &buf_desc, 1) == 1) { + qd_buffer_free((qd_buffer_t *) buf_desc.context); + } + while (pn_tls_take_encrypt_input_buffers(tls_session->pn_raw, &buf_desc, 1) == 1) { + qd_buffer_free((qd_buffer_t *) buf_desc.context); + } + while (pn_tls_take_decrypt_output_buffers(tls_session->pn_raw, &buf_desc, 1) == 1) { + qd_buffer_free((qd_buffer_t *) buf_desc.context); + } + while (pn_tls_take_decrypt_input_buffers(tls_session->pn_raw, &buf_desc, 1) == 1) { + qd_buffer_free((qd_buffer_t *) buf_desc.context); + } + } + + // Need to lock the proton config when releasing the proton stream since the proton config is not thread safe + // due to use of a non-atomic reference counter. + + sys_mutex_lock(&tls_session->proton_tls_cfg->lock); + if (tls_session->pn_raw) { + pn_tls_free(tls_session->pn_raw); + } else { + // Proton does not provide a way to explicitly free the AMQP TLS session. It is owned by the parent + // pn_transport_t and will be released when the transport is closed (I think). + } + sys_mutex_unlock(&tls_session->proton_tls_cfg->lock); + + qd_proton_config_decref(tls_session->proton_tls_cfg); + free(tls_session->ssl_profile_name); + free(tls_session->uid_format); + free_qd_tls_session_t(tls_session); + } +} + + +char *qd_tls_session_get_alpn_protocol(const qd_tls_session_t *tls_session) +{ + char *protocol = 0; + const char *protocol_name; + size_t protocol_name_length; + + assert(tls_session->pn_raw); + if (pn_tls_get_alpn_protocol(tls_session->pn_raw, &protocol_name, &protocol_name_length)) { + protocol = (char *) qd_calloc(protocol_name_length + 1, sizeof(char)); + memmove(protocol, protocol_name, protocol_name_length); + protocol[protocol_name_length] = '\0'; + } + return protocol; +} + + +char *qd_tls_session_get_protocol_version(const qd_tls_session_t *tls_session) +{ + char *version = 0; + size_t version_len; + + if (tls_session->pn_raw) { + const char *protocol_version; + if (pn_tls_get_protocol_version(tls_session->pn_raw, &protocol_version, &version_len)) { + version = (char *) qd_calloc(version_len + 1, sizeof(char)); + memmove(version, protocol_version, version_len); + version[version_len] = '\0'; + } + } else { + version_len = 50; // 50 was used in the old code, so whatever... + version = (char *) qd_calloc(version_len, sizeof(char)); + if (!pn_ssl_get_protocol_name(tls_session->pn_amqp, version, version_len)) { + free(version); + version = 0; + } + } + return version; +} + +char *qd_tls_session_get_protocol_ciphers(const qd_tls_session_t *tls_session) +{ + char *ciphers = 0; + size_t ciphers_len; + + if (tls_session->pn_raw) { + const char *protocol_ciphers; + if (pn_tls_get_cipher(tls_session->pn_raw, &protocol_ciphers, &ciphers_len)) { + ciphers = (char *) qd_calloc(ciphers_len + 1, sizeof(char)); + memmove(ciphers, protocol_ciphers, ciphers_len); + ciphers[ciphers_len] = '\0'; + } + } else { + ciphers_len = 50; // 50 was used in the old code, so whatever... + ciphers = (char *) qd_calloc(ciphers_len, sizeof(char)); + if (!pn_ssl_get_cipher_name(tls_session->pn_amqp, ciphers, ciphers_len)) { + free(ciphers); + ciphers = 0; + } + } + return ciphers; +} + + +int qd_tls_session_get_ssf(const qd_tls_session_t *tls_session) +{ + if (tls_session->pn_raw) { + return pn_tls_get_ssf(tls_session->pn_raw); + } else { + return pn_ssl_get_ssf(tls_session->pn_amqp); + } +} + + +qd_ssl2_profile_t *qd_tls_read_ssl_profile(const char *ssl_profile_name, qd_ssl2_profile_t *profile) +{ + ASSERT_MGMT_THREAD; + + qd_tls_context_t *tls_context = _find_tls_context(ssl_profile_name); + if (!tls_context) { + ZERO(profile); + return 0; + } + + profile->ciphers = CHECKED_STRDUP(tls_context->profile.ciphers); + profile->protocols = CHECKED_STRDUP(tls_context->profile.protocols); + profile->password = CHECKED_STRDUP(tls_context->profile.password); + profile->uid_format = CHECKED_STRDUP(tls_context->profile.uid_format); + profile->certificate_file = CHECKED_STRDUP(tls_context->profile.certificate_file); + profile->private_key_file = CHECKED_STRDUP(tls_context->profile.private_key_file); + profile->uid_name_mapping_file = CHECKED_STRDUP(tls_context->profile.uid_name_mapping_file); + profile->trusted_certificate_db = CHECKED_STRDUP(tls_context->profile.trusted_certificate_db); + profile->version = tls_context->profile.version; + profile->oldest_valid_version = tls_context->profile.oldest_valid_version; + + return profile; +} + + +void qd_tls_cleanup_ssl_profile(qd_ssl2_profile_t *profile) +{ + if (profile) { + free(profile->ciphers); + free(profile->protocols); + free(profile->trusted_certificate_db); + free(profile->certificate_file); + free(profile->private_key_file); + free(profile->password); + free(profile->uid_format); + free(profile->uid_name_mapping_file); + ZERO(profile); + } +} + + +/** + * Read the sslProfile configuration record from entity and copy it into config + */ +static qd_error_t _read_tls_profile(qd_entity_t *entity, qd_ssl2_profile_t *profile) +{ + ZERO(profile); + + char *name = 0; + name = qd_entity_opt_string(entity, "name", ""); + if (qd_error_code()) goto error; + + profile->ciphers = qd_entity_opt_string(entity, "ciphers", 0); + if (qd_error_code()) goto error; + profile->protocols = qd_entity_opt_string(entity, "protocols", 0); + if (qd_error_code()) goto error; + profile->trusted_certificate_db = qd_entity_opt_string(entity, "caCertFile", 0); + if (qd_error_code()) goto error; + profile->certificate_file = qd_entity_opt_string(entity, "certFile", 0); + if (qd_error_code()) goto error; + profile->private_key_file = qd_entity_opt_string(entity, "privateKeyFile", 0); + if (qd_error_code()) goto error; + profile->password = qd_entity_opt_string(entity, "password", 0); + if (qd_error_code()) goto error; + profile->uid_format = qd_entity_opt_string(entity, "uidFormat", 0); + if (qd_error_code()) goto error; + profile->uid_name_mapping_file = qd_entity_opt_string(entity, "uidNameMappingFile", 0); + if (qd_error_code()) goto error; + profile->version = qd_entity_opt_long(entity, "version", 0); + if (qd_error_code()) goto error; + profile->oldest_valid_version = qd_entity_opt_long(entity, "oldestValidVersion", 0); + if (qd_error_code()) goto error; + + if (profile->uid_format) { + if (!tls_private_validate_uid_format(profile->uid_format)) { + // backward compatibility: this isn't treated as a hard error - the fallback behavior is to use the user + // name from the transport. I have no idea why that is the case but changing it to a hard error results in + // CI test failures so for now I go along to get along: + qd_log(LOG_AGENT, QD_LOG_ERROR, "Invalid format for uidFormat field in sslProfile '%s': %s", + name, profile->uid_format); + free(profile->uid_format); + profile->uid_format = 0; + } + } + + if (profile->password) { + // + // Process the password to handle any modifications or lookups needed + // + char *actual_pass = 0; + bool is_file_path = 0; + qd_server_config_process_password(&actual_pass, profile->password, &is_file_path, true); + if (qd_error_code()) goto error; + + if (actual_pass) { + if (is_file_path) { + qd_set_password_from_file(actual_pass, &profile->password); + free(actual_pass); + } + else { + free(profile->password); + profile->password = actual_pass; + } + } + } + + // simple validation of version fields: + if (profile->version < 0 || profile->oldest_valid_version < 0) { + qd_error(QD_ERROR_CONFIG, "Negative version field values are invalid (sslProfile '%s')", name); + goto error; + } + if (profile->version < profile->oldest_valid_version) { + qd_error(QD_ERROR_CONFIG, "version must be >= oldestValidVersion (sslProfile '%s')", name); + goto error; + } + + free(name); + return QD_ERROR_NONE; + +error: + free(name); + _cleanup_tls_profile(profile); + return qd_error_code(); +} + + +/** Release the contents of a configuration instance + */ +static void _cleanup_tls_profile(qd_ssl2_profile_t *profile) +{ + free(profile->ciphers); + free(profile->protocols); + free(profile->trusted_certificate_db); + free(profile->certificate_file); + free(profile->private_key_file); + free(profile->password); + free(profile->uid_format); + free(profile->uid_name_mapping_file); + ZERO(profile); +} + + +/** Instantiate a new Proton Raw TLS config + */ + + +/** + * Reload the sslProfile configuration for the given tls_config + * + * Note: this function blocks the caller while loading certificate files from the filesystem. It is *SLOW* - assume it + * will block for at least hundreds of milliseconds! + */ +static qd_error_t _update_tls_config(qd_tls_config_t *tls_config, const qd_ssl2_profile_t *profile) +{ + ASSERT_MGMT_THREAD; // runs too slow to block an I/O thread (see ISSUE-1572) + + pn_tls_config_t *pn_raw_config = 0; + pn_ssl_domain_t *pn_amqp_config = 0; + + if (_validate_config(profile, tls_config->ssl_profile_name, tls_config->is_listener, tls_config->authenticate_peer) != QD_ERROR_NONE) { + return qd_error_code(); // validate_config sets qd_error() + } + + // + // Generate a new proton configuration using the updated configuration from the parent tls_context and the existing + // tls_config. Do not hold the tls_config lock because loading certs takes a loooong time and we do not want to block + // new connections. + // + + switch (tls_config->p_type) { + case QD_TLS_TYPE_PROTON_AMQP: + pn_amqp_config = tls_private_allocate_amqp_config(tls_config->ssl_profile_name, profile, tls_config->is_listener, + tls_config->verify_hostname, tls_config->authenticate_peer); + if (!pn_amqp_config) { + return qd_error_code(); // allocation function set qd_error() + } + break; + case QD_TLS_TYPE_PROTON_RAW: + pn_raw_config = tls_private_allocate_raw_config(tls_config->ssl_profile_name, profile, tls_config->is_listener, + tls_config->verify_hostname, tls_config->authenticate_peer); + if (!pn_raw_config) { + return qd_error_code(); // allocation function set qd_error() + } + break; + default: + assert(false); + break; + } + + qd_proton_config_t *new_cfg = qd_proton_config(pn_raw_config, pn_amqp_config); + qd_proton_config_t *old_cfg = 0; + + // Pull the old switcheroo on the old proton configuration. Do this under lock to prevent I/O threads from creating + // new sessions while we change pointers. + + sys_mutex_lock(&tls_config->lock); + old_cfg = tls_config->proton_tls_cfg; + tls_config->proton_tls_cfg = new_cfg; + // And refresh any parameters that must be used when creating new sessions: + tls_config->version = profile->version; + free(tls_config->uid_format); + tls_config->uid_format = CHECKED_STRDUP(profile->uid_format); + sys_mutex_unlock(&tls_config->lock); + + // no need to hold the lock here because the decref is atomic and if this + // is the last reference then by definition there are no other threads involved. + qd_proton_config_decref(old_cfg); + + return QD_ERROR_NONE; +} + + +/** Find the TLS context associated with the given sslProfile name + */ +static qd_tls_context_t *_find_tls_context(const char *profile_name) +{ + ASSERT_MGMT_THREAD; + + qd_tls_context_t *ctxt = DEQ_HEAD(context_list); + while (ctxt) { + if (strcmp(ctxt->ssl_profile_name, profile_name) == 0) + return ctxt; + ctxt = DEQ_NEXT(ctxt); + } + return 0; +} + + +/** Free the TLS context. Assumes context is no longer on context_list + */ +static void _tls_context_free(qd_tls_context_t *ctxt) +{ + if (ctxt) { + qd_tls_config_t *tls_config = DEQ_HEAD(ctxt->tls_configs); + while (tls_config) { + DEQ_REMOVE_HEAD(ctxt->tls_configs); + qd_tls_config_decref(tls_config); + tls_config = DEQ_HEAD(ctxt->tls_configs); + } + free(ctxt->ssl_profile_name); + _cleanup_tls_profile(&ctxt->profile); + free_qd_tls_context_t(ctxt); + } +} + + +/*** + * Basic sanity checking that the sslProfile is valid + */ +static qd_error_t _validate_config(const qd_ssl2_profile_t *profile, const char *ssl_profile_name, bool is_listener, bool authenticate_peer) +{ + if (is_listener) { + // self identifying certificate is required for a listener: + if (!profile->certificate_file) { + qd_error(QD_ERROR_CONFIG, "Listener requires a self-identifying certificate (sslProfile: %s)", ssl_profile_name); + return QD_ERROR_CONFIG; + } + if (authenticate_peer) { + if (!profile->trusted_certificate_db) { + qd_error(QD_ERROR_CONFIG, "Listener requires a CA for peer authentication (sslProfile: %s)", ssl_profile_name); + return QD_ERROR_CONFIG; + } + } + } else if (!profile->trusted_certificate_db) { + // CA must be provided for a connector: + qd_error(QD_ERROR_CONFIG, "Connector requires a CA certificate (sslProfile: %s)", ssl_profile_name); + return QD_ERROR_CONFIG; + } + + if (profile->certificate_file && !profile->private_key_file) { + // missing private key file + qd_error(QD_ERROR_CONFIG, "Missing Private Keyfile (sslProfile: %s)", ssl_profile_name); + return QD_ERROR_CONFIG; + } + + return QD_ERROR_NONE; +} diff --git a/src/tls/tls_amqp.c b/src/tls/tls_amqp.c new file mode 100644 index 000000000..bd2416fb3 --- /dev/null +++ b/src/tls/tls_amqp.c @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_amqp.h" +#include "private.h" + +#include "qpid/dispatch/error.h" + +#include "proton/ssl.h" +#include "proton/transport.h" + + +// Allowed sslProfile uidFormat fields. +// +static const char CERT_COUNTRY_CODE = 'c'; +static const char CERT_STATE = 's'; +static const char CERT_CITY_LOCALITY = 'l'; +static const char CERT_ORGANIZATION_NAME = 'o'; +static const char CERT_ORGANIZATION_UNIT = 'u'; +static const char CERT_COMMON_NAME = 'n'; +static const char CERT_FINGERPRINT_SHA1 = '1'; +static const char CERT_FINGERPRINT_SHA256 = '2'; +static const char CERT_FINGERPRINT_SHA512 = '5'; + +static const char *valid_uid_fields = "csloun125"; // for validation + +static const char *COMPONENT_SEPARATOR = ";"; +#define UID_FORMAT_MAX_LEN 7 + + +bool tls_private_validate_uid_format(const char *format) +{ + // valid format allows any combination of the uidFormat characters + // "csloun" and any one of [125] + + if (strlen(format) > UID_FORMAT_MAX_LEN) { + return false; + } + + int fingerprints = 0; + for (const char *ptr = format; *ptr; ++ptr) { + if (strchr(valid_uid_fields, *ptr) == NULL) { + return false; // bad format specifier + } + if (*ptr == CERT_FINGERPRINT_SHA1 || + *ptr == CERT_FINGERPRINT_SHA256 || + *ptr == CERT_FINGERPRINT_SHA512) { + if (++fingerprints > 1) { + return false; // only one fingerprint allowed + } + } + } + + return true; +} + + +// Construct a user id from the various certificate subfields/fingerprints as specified in the uid_format string. See +// the sslProfile description in the schema file. +// +static char *_get_uid_name(pn_ssl_t *pn_session, const char *uid_format) +{ + assert(uid_format); + + const char *country_code = 0; + const char *state = 0; + const char *locality_city = 0; + const char *organization = 0; + const char *org_unit = 0; + const char *common_name = 0; + + // + // SHA1 is 20 octets (40 hex characters); SHA256 is 32 octets (64 hex characters). + // SHA512 is 64 octets (128 hex characters) + // + char fingerprint[129] = "\0"; + + // accumulate the length into uid_length on each pass so we definitively know the number of octets to malloc. + int uid_length = 0; + int semi_colon_count = -1; + + char *user_id = 0; + + for (const char *component = uid_format; *component; ++component) { + if (*component == CERT_COUNTRY_CODE) { + country_code = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_COUNTRY_NAME); + if (country_code) { + uid_length += strlen((const char *)country_code); + semi_colon_count++; + } + } else if (*component == CERT_STATE) { + state = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_STATE_OR_PROVINCE); + if (state) { + uid_length += strlen((const char *)state); + semi_colon_count++; + } + } else if (*component == CERT_CITY_LOCALITY) { + locality_city = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_CITY_OR_LOCALITY); + if (locality_city) { + uid_length += strlen((const char *)locality_city); + semi_colon_count++; + } + } else if (*component == CERT_ORGANIZATION_NAME) { + organization = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_ORGANIZATION_NAME); + if (organization) { + uid_length += strlen((const char *)organization); + semi_colon_count++; + } + } else if (*component == CERT_ORGANIZATION_UNIT) { + org_unit = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_ORGANIZATION_UNIT); + if (org_unit) { + uid_length += strlen((const char *)org_unit); + semi_colon_count++; + } + } else if (*component == CERT_COMMON_NAME) { + common_name = pn_ssl_get_remote_subject_subfield(pn_session, PN_SSL_CERT_SUBJECT_COMMON_NAME); + if (common_name) { + uid_length += strlen((const char *)common_name); + semi_colon_count++; + } + } else if (*component == CERT_FINGERPRINT_SHA1 || *component == CERT_FINGERPRINT_SHA256 || *component == CERT_FINGERPRINT_SHA512) { + // Allocate the memory for message digest + int out = 0; + + int fingerprint_length = 0; + if (*component == CERT_FINGERPRINT_SHA1) { + fingerprint_length = 40; + out = pn_ssl_get_cert_fingerprint(pn_session, fingerprint, fingerprint_length + 1, PN_SSL_SHA1); + } + else if (*component == CERT_FINGERPRINT_SHA256) { + fingerprint_length = 64; + out = pn_ssl_get_cert_fingerprint(pn_session, fingerprint, fingerprint_length + 1, PN_SSL_SHA256); + } + else if (*component == CERT_FINGERPRINT_SHA512) { + fingerprint_length = 128; + out = pn_ssl_get_cert_fingerprint(pn_session, fingerprint, fingerprint_length + 1, PN_SSL_SHA512); + } + (void) out; // avoid 'out unused' compiler warnings if NDEBUG undef'ed + assert (out == 0); + + uid_length += fingerprint_length; + semi_colon_count++; + } else { + assert(false); // should not be hit since uid_format has been validated + } + } + + if (uid_length > 0) { + user_id = (char *) qd_malloc((uid_length + semi_colon_count + 1) * sizeof(char)); // the +1 is for the '\0' character + *user_id = '\0'; + // The components in the user id string must appear in the same order as it appears in the component string. that is + // why we have this loop + + for (const char *component = uid_format; *component; ++component) { + if (*component == CERT_COUNTRY_CODE) { + if (country_code) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) country_code); + } + } else if (*component == CERT_STATE) { + if (state) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) state); + } + } else if (*component == CERT_CITY_LOCALITY) { + if (locality_city) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) locality_city); + } + } else if (*component == CERT_ORGANIZATION_NAME) { + if (organization) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) organization); + } + } else if (*component == CERT_ORGANIZATION_UNIT) { + if (org_unit) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) org_unit); + } + } else if (*component == CERT_COMMON_NAME) { + if (common_name) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) common_name); + } + } else { // fingerprints + if (strlen((char *) fingerprint) > 0) { + if (*user_id != '\0') + strcat(user_id, COMPONENT_SEPARATOR); + strcat(user_id, (char *) fingerprint); + } + } + } + } + + return user_id; +} + + +char *qd_tls_session_get_user_id(qd_tls_session_t *session) +{ + // only valid for Proton AMQP TLS sessions + assert(session->pn_amqp); + + char *uid = 0; + if (session->uid_format) { + uid = _get_uid_name(session->pn_amqp, session->uid_format); + if (uid) { + // Check for display name override + char *override = tls_private_lookup_display_name(session->ssl_profile_name, uid); + if (override) { + free(uid); + uid = override; + } + } + } + + return uid; +} + + +/** + * Allocate a Proton AMQP TLS configuration. + * + * @param ssl_profile_name name of the sslProfile configuration record to use + * @param profile the sslProfile configuration values + * @param is_listener true if the configuration is to be used with an AMQP listener, false for a connector + * @param verify_hostname if true verify the hostname returned in the peer's certificate (connector only). + * @param authenticate_peer if true verify the peer's certificate (listener only). + * @return a pointer to the new pn_ssl_domain_t on success, zero on failure. qd_error() is set on error. + */ +pn_ssl_domain_t *tls_private_allocate_amqp_config(const char *ssl_profile_name, const qd_ssl2_profile_t *profile, + bool is_listener, bool verify_hostname, bool authenticate_peer) +{ + pn_ssl_domain_t *domain = 0; + + qd_error_clear(); + + do { + domain = pn_ssl_domain(is_listener ? PN_SSL_MODE_SERVER : PN_SSL_MODE_CLIENT); + if (!domain) { + qd_error(QD_ERROR_CONFIG, "Failed to create TLS domain from sslProfile '%s' (TLS not available)", + ssl_profile_name); + break; + } + + // + // Configure the CA certificate for verifying the peer: + // + if (profile->trusted_certificate_db) { + if (pn_ssl_domain_set_trusted_ca_db(domain, profile->trusted_certificate_db)) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS caCertFile '%s' from sslProfile '%s'", + profile->trusted_certificate_db, ssl_profile_name); + break; + } + } + + // + // Configure my self-identifying cert: + // + + if (profile->certificate_file) { + if (pn_ssl_domain_set_credentials(domain, + profile->certificate_file, + profile->private_key_file, + profile->password)) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS certFile '%s' from sslProfile '%s'", + profile->certificate_file, ssl_profile_name); + break; + } + } + + // + // Configure the peer verification mode: + // + + int rc = 0; + if (is_listener) { + // do we force the peer to send a cert? + if (authenticate_peer) { + assert(profile->trusted_certificate_db); + rc = pn_ssl_domain_set_peer_authentication(domain, PN_SSL_VERIFY_PEER, profile->trusted_certificate_db); + } else { + rc = pn_ssl_domain_set_peer_authentication(domain, PN_SSL_ANONYMOUS_PEER, 0); + } + if (rc) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS peer authentication for sslProfile '%s'", ssl_profile_name); + break; + } + } else { // Connector + if (verify_hostname) { + rc = pn_ssl_domain_set_peer_authentication(domain, + PN_SSL_VERIFY_PEER_NAME, + profile->trusted_certificate_db); + } else { // verify cert but ignore hostname + rc = pn_ssl_domain_set_peer_authentication(domain, + PN_SSL_VERIFY_PEER, + profile->trusted_certificate_db); + } + if (rc) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS peer hostname verification for sslProfile '%s'", ssl_profile_name); + break; + } + } + + if (profile->protocols) { + if (pn_ssl_domain_set_protocols(domain, profile->protocols)) { + qd_error(QD_ERROR_CONFIG, + "Failed to configure TLS Protocols '%s' for sslProfile '%s')", + profile->protocols, ssl_profile_name); + break; + } + } + + if (profile->ciphers) { + if (pn_ssl_domain_set_ciphers(domain, profile->ciphers)) { + qd_error(QD_ERROR_CONFIG, + "Failed to configure TLS Ciphers '%s' for sslProfile '%s'. Use openssl ciphers -v to validate", + profile->ciphers, ssl_profile_name); + break; + } + } + + return domain; + + } while (0); + + // If we get here, the configuration setup failed + + if (domain) { + pn_ssl_domain_free(domain); + } + return 0; +} + diff --git a/src/tls/tls_raw.c b/src/tls/tls_raw.c new file mode 100644 index 000000000..f0e3cf064 --- /dev/null +++ b/src/tls/tls_raw.c @@ -0,0 +1,531 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/dispatch/tls_raw.h" +#include "private.h" + +#include "qpid/dispatch/error.h" + +#include + +#include + + +/* + * API for working with Proton Raw Connections running TLS + */ + + +pn_tls_config_t *tls_private_allocate_raw_config(const char *ssl_profile_name, const qd_ssl2_profile_t *profile, + bool is_listener, bool verify_hostname, bool authenticate_peer) +{ + pn_tls_config_t *tls_config = 0; + + qd_error_clear(); + + do { + int res = -1; // assume failure + tls_config = pn_tls_config(is_listener ? PN_TLS_MODE_SERVER : PN_TLS_MODE_CLIENT); + if (!tls_config) { + qd_error(QD_ERROR_CONFIG, "Failed to create TLS configuration using sslProfile '%s'", ssl_profile_name); + break; + } + + if (profile->trusted_certificate_db) { + res = pn_tls_config_set_trusted_certs(tls_config, profile->trusted_certificate_db); + if (res != 0) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS caCertFile %s for sslProfile '%s': (%d)", + profile->trusted_certificate_db, ssl_profile_name, res); + break; + } + } + + // + // Configure my self-identifying cert: + // + + if (profile->certificate_file) { + res = pn_tls_config_set_credentials(tls_config, + profile->certificate_file, + profile->private_key_file, + profile->password); + if (res != 0) { + qd_error(QD_ERROR_CONFIG, + "Failed to set TLS certFile '%s' for sslProfile '%s': (%d)", + profile->certificate_file, ssl_profile_name, res); + break; + } + } + + if (is_listener) { + if (authenticate_peer) { + assert(profile->trusted_certificate_db); + res = pn_tls_config_set_peer_authentication(tls_config, PN_TLS_VERIFY_PEER, profile->trusted_certificate_db); + } else { + res = pn_tls_config_set_peer_authentication(tls_config, PN_TLS_ANONYMOUS_PEER, 0); + } + if (res != 0) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS peer authentication for sslProfile '%s'", ssl_profile_name); + break; + } + } else { // Connector + if (verify_hostname) { + res = pn_tls_config_set_peer_authentication(tls_config, PN_TLS_VERIFY_PEER_NAME, profile->trusted_certificate_db); + } else { + res = pn_tls_config_set_peer_authentication(tls_config, PN_TLS_VERIFY_PEER, profile->trusted_certificate_db); + } + if (res != 0) { + qd_error(QD_ERROR_CONFIG, "Failed to configure TLS peer host name verification for sslProfile '%s'", ssl_profile_name); + break; + } + } + + // Note: Proton Raw TLS does not support setting the protocol version! + + if (!!profile->ciphers) { + res = pn_tls_config_set_impl_ciphers(tls_config, profile->ciphers); + if (res != 0) { + qd_error(QD_ERROR_CONFIG, + "Failed to configure TLS Ciphers '%s' for sslProfile '%s'. Use openssl ciphers -v to validate", + profile->ciphers, ssl_profile_name); + break; + } + } + + return tls_config; + + } while (0); + + // If we get here, the configuration setup failed + + if (tls_config) { + pn_tls_config_free(tls_config); + } + return 0; +} + + + + +// initialize a proton buffer descriptor to hold the given qd_buffer_t +// +static inline void _pn_buf_desc_give_buffer(pn_raw_buffer_t *pn_desc, const qd_buffer_t *buffer) +{ + pn_desc->size = qd_buffer_size(buffer); + pn_desc->capacity = qd_buffer_capacity(buffer); + pn_desc->offset = 0; + pn_desc->bytes = (char *) qd_buffer_base(buffer); + pn_desc->context = (uintptr_t) buffer; +} + + +// take a used buffer from the given proton buffer descriptor +// +static inline qd_buffer_t *_pn_buf_desc_take_buffer(pn_raw_buffer_t *pn_desc) +{ + qd_buffer_t *buffer = (qd_buffer_t *) pn_desc->context; + assert(buffer); + // do not use qd_adaptor_buffer_insert() since it *increments* the size, we need to set it: + buffer->size = pn_desc->size; + assert(buffer->size <= QD_BUFFER_SIZE); + return buffer; +} + + +int qd_tls_session_do_io(qd_tls_session_t *session, + pn_raw_connection_t *raw_conn, + qd_tls_take_output_buffers_cb_t *take_output_cb, + void *take_output_context, + qd_buffer_list_t *input_data, + uint64_t *input_data_count, + qd_log_module_t log_module, + uint64_t conn_id) +{ + assert(session); + assert(raw_conn); + + CHECK_PROACTOR_RAW_CONNECTION(raw_conn); + + bool work; + pn_raw_buffer_t pn_buf_desc; + const bool debug = qd_log_enabled(log_module, QD_LOG_DEBUG); + + if (input_data_count) + *input_data_count = 0; + + if (session->tls_error) + return -1; + if (session->input_drained && session->output_flushed) + return QD_TLS_DONE; + + do { + size_t capacity; + size_t taken; + size_t given; + uint64_t total_octets; + + // Loop until no more work can be done. "work" is considered true whenever the TLS layer produces output or + // opens up capacity for more input + + work = false; + + // + // give empty buffers for holding encrypted and decrypted output from the TLS layer + // + + capacity = pn_tls_get_encrypt_output_buffer_capacity(session->pn_raw); + if (capacity > 0) { + if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] giving %zu encrypt output buffers", conn_id, capacity); + } + while (capacity > 0) { + _pn_buf_desc_give_buffer(&pn_buf_desc, qd_buffer()); + given = pn_tls_give_encrypt_output_buffers(session->pn_raw, &pn_buf_desc, 1); + (void) given; + assert(given == 1); + capacity -= 1; + } + } + capacity = pn_tls_get_decrypt_output_buffer_capacity(session->pn_raw); + if (capacity > 0) { + if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] giving %zu decrypt output buffers", conn_id, capacity); + } + while (capacity > 0) { + _pn_buf_desc_give_buffer(&pn_buf_desc, qd_buffer()); + given = pn_tls_give_decrypt_output_buffers(session->pn_raw, &pn_buf_desc, 1); + (void) given; + assert(given == 1); + capacity -= 1; + } + } + + // + // discard written output buffers to make room for new output + // + + while (pn_raw_connection_take_written_buffers(raw_conn, &pn_buf_desc, 1) == 1) { + qd_buffer_t *buf = (qd_buffer_t *) pn_buf_desc.context; + qd_buffer_free(buf); + } + + // + // Push any unencrypted output data from the protocol adaptor into the TLS layer for encryption. + // + + if (pn_tls_is_secure(session->pn_raw)) { + if (take_output_cb) { + capacity = pn_tls_get_encrypt_input_buffer_capacity(session->pn_raw); + if (capacity > 0) { + qd_buffer_list_t ubufs = DEQ_EMPTY; + if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] encrypt input capacity = %zu bufs", conn_id, capacity); + } + int64_t out_octets = take_output_cb(take_output_context, &ubufs, capacity); + if (out_octets > 0) { + assert(!DEQ_IS_EMPTY(ubufs) && DEQ_SIZE(ubufs) <= capacity); + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] %" PRIi64 " unencrypted bytes taken by TLS for encryption (%zu buffers)", + conn_id, out_octets, DEQ_SIZE(ubufs)); + qd_buffer_t *abuf = DEQ_HEAD(ubufs); + while (abuf) { + DEQ_REMOVE_HEAD(ubufs); + _pn_buf_desc_give_buffer(&pn_buf_desc, abuf); + given = pn_tls_give_encrypt_input_buffers(session->pn_raw, &pn_buf_desc, 1); + assert(given == 1); + abuf = DEQ_HEAD(ubufs); + } + } else if (out_octets == 0) { + // currently no output, try again later + assert(DEQ_IS_EMPTY(ubufs)); + } else if (session->output_eos == false) { + // no further output, flush and close the connection + assert(DEQ_IS_EMPTY(ubufs)); + if (out_octets == QD_IO_EOS) { + // clean end-of-output, append TLS close notify: + pn_tls_close_output(session->pn_raw); + } + session->output_eos = true; + qd_log(log_module, QD_LOG_DEBUG, "[C%" PRIu64 "] outgoing stream EOS signalled: closing TLS output", + conn_id); + } + } + } else if (debug) { + // take_output_cb is not set + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] output data blocked", conn_id); + } + } + + // + // Push incoming encrypted data from raw conn into TLS. *Important*: once the TLS handshake is completed do not + // take raw connection incoming buffers if the calling thread is blocked from taking the resulting decrypted + // incoming buffers (input_data == 0). Why? Because if we happen to take the last read buffer from the raw + // connection then proactor will generate a PN_RAW_CONNECTION_DISCONNECTED event. Once this happens we can no + // longer call pn_raw_connection_wake() on it. This means we can no longer activate this thread when it is + // eventually unblocked. This results in unencrypted incoming data being stuck in the TLS session and eventually + // discarded when the session is deallocated (data truncation). + // + + capacity = pn_tls_get_decrypt_input_buffer_capacity(session->pn_raw); + if (capacity > 0) { + if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] decrypt input capacity = %zu bufs", conn_id, capacity); + } + if (!pn_tls_is_secure(session->pn_raw) || input_data != 0) { + size_t pushed = 0; + total_octets = 0; + while (pushed < capacity) { + size_t took = pn_raw_connection_take_read_buffers(raw_conn, &pn_buf_desc, 1); + if (took != 1) { + // No more read buffers available. Now it is safe to check if the raw connection has closed + session->raw_read_drained = pn_raw_connection_is_read_closed(raw_conn); + break; + } else if (pn_buf_desc.size) { + total_octets += pn_buf_desc.size; + given = pn_tls_give_decrypt_input_buffers(session->pn_raw, &pn_buf_desc, 1); + assert(given == 1); + ++pushed; + } else { + qd_buffer_free((qd_buffer_t *) pn_buf_desc.context); + } + } + if (pushed > 0) { + session->encrypted_input_bytes += total_octets; + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] %" PRIu64 + " encrypted bytes read from the raw connection passed to TLS for decryption (%zu buffers)", + conn_id, total_octets, pushed); + } + } + } + + // run the tls state machine (not considered "work"). + // + // If there is an error the TLS layer may have generated new encrypted output that contains the details about + // the failure that needs to be sent to the remote. That is why this code does not immediately cease processing + // if pn_tls_process returns an error: there may be more outgoing buffers that have to be written to the raw + // connection before it can be closed. This code assumes that the proton TLS library will stop giving capacity + // for other work once the error has occurred so it is safe to continue running this work loop. + + if (!session->tls_error) { + const bool check_if_secure = session->on_secure_cb && !pn_tls_is_secure(session->pn_raw); + int err = pn_tls_process(session->pn_raw); + if (err) { + session->tls_error = true; + qd_log(log_module, QD_LOG_DEBUG, "[C%" PRIu64 "] pn_tls_process failed: error=%d", conn_id, err); + } else if (check_if_secure && pn_tls_is_secure(session->pn_raw)) { + session->on_secure_cb(session, session->user_context); + session->on_secure_cb = 0; // one shot + } + } + + // + // Take encrypted TLS output and write it to the raw connection + // + + capacity = pn_raw_connection_write_buffers_capacity(raw_conn); + if (capacity > 0) { + size_t pushed = 0; + total_octets = 0; + + if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] raw write capacity=%zu bufs", conn_id, capacity); + } + while (pushed < capacity && pn_tls_take_encrypt_output_buffers(session->pn_raw, &pn_buf_desc, 1) == 1) { + total_octets += pn_buf_desc.size; + given = pn_raw_connection_write_buffers(raw_conn, &pn_buf_desc, 1); + assert(given == 1); + ++pushed; + } + if (pushed > 0) { + work = true; + session->encrypted_output_bytes += total_octets; + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] %" PRIu64 " encrypted bytes written to the raw connection by TLS (%zu buffers)", + conn_id, total_octets, pushed); + } + } else if (pn_raw_connection_is_write_closed(raw_conn)) { + // drain the TLS buffers - there is no place to send them! + taken = 0; + while (pn_tls_take_encrypt_output_buffers(session->pn_raw, &pn_buf_desc, 1) == 1) { + assert(pn_buf_desc.context); + qd_buffer_free(_pn_buf_desc_take_buffer(&pn_buf_desc)); + taken += 1; + } + if (taken) { + work = true; + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] discarded %zu outgoing encrypted buffers due to raw conn write closed", + conn_id, taken); + } + } + + // + // take decrypted output and give it to the adaptor + // + + if (input_data) { + assert(input_data_count); + total_octets = 0; + taken = 0; + while (pn_tls_take_decrypt_output_buffers(session->pn_raw, &pn_buf_desc, 1) == 1) { + qd_buffer_t *abuf = _pn_buf_desc_take_buffer(&pn_buf_desc); + if (qd_buffer_size(abuf)) { + total_octets += qd_buffer_size(abuf); + DEQ_INSERT_TAIL(*input_data, abuf); + ++taken; + } else { + qd_buffer_free(abuf); + } + } + if (taken) { + work = true; // more capacity for decrypted output + *input_data_count += total_octets; + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] %" PRIu64 " decrypted bytes taken from TLS for adaptor input (%zu buffers)", + conn_id, total_octets, taken); + } + } else if (debug) { + qd_log_impl(log_module, QD_LOG_DEBUG, __FILE__, __LINE__, "[C%" PRIu64 "] input_data blocked", conn_id); + } + + // + // Release all used TLS input buffers - they are no longer needed + // + + taken = 0; + while (pn_tls_take_encrypt_input_buffers(session->pn_raw, &pn_buf_desc, 1) == 1) { + qd_buffer_t *abuf = (qd_buffer_t *) pn_buf_desc.context; + assert(abuf); + qd_buffer_free(abuf); + ++taken; + } + while (pn_tls_take_decrypt_input_buffers(session->pn_raw, &pn_buf_desc, 1) == 1) { + qd_buffer_t *abuf = (qd_buffer_t *) pn_buf_desc.context; + assert(abuf); + qd_buffer_free(abuf); + ++taken; + } + if (taken) + work = true; // more capacity for encrypt/decrypt input buffers + + } while (work); + + // Cannot return an error until all pending outgoing encrypted data have been written to the raw conn so error info + // can be sent to peer. See comment above. + // + if (session->tls_error && !pn_tls_is_decrypt_output_pending(session->pn_raw)) { + char buf[1024] = "Unknown protocol error"; // default error msg + int err = pn_tls_get_session_error(session->pn_raw); + if (err) { + int rc = snprintf(buf, sizeof(buf), "Code: %d ", err); + if (rc > 0 && rc < sizeof(buf)) { + pn_tls_get_session_error_string(session->pn_raw, &buf[rc], sizeof(buf) - rc); + } + } + pn_condition_t *cond = pn_raw_connection_condition(raw_conn); + if (!!cond && !pn_condition_is_set(cond)) { + (void) pn_condition_set_name(cond, "TLS-connection-failed"); + (void) pn_condition_set_description(cond, buf); + } + pn_raw_connection_close(raw_conn); + qd_log(log_module, QD_LOG_WARNING, "[C%" PRIu64 "] TLS connection failed: %s", conn_id, buf); + return -1; + } + + // check if the output (encrypt) side is done + // + if (session->output_eos && !pn_tls_is_encrypt_output_pending(session->pn_raw)) { + // We closed the encrypt side of the TLS connection and we've sent all output + session->output_flushed = true; + if (!pn_raw_connection_is_write_closed(raw_conn)) { + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] TLS output closed - closing write side of raw connection", conn_id); + pn_raw_connection_write_close(raw_conn); + } + } + + // check for end of input (decrypt done) + // + if (pn_tls_is_input_closed(session->pn_raw)) { + // TLS clean close signalled by remote. Do not read any more data (prevent truncation attack). + // + session->input_drained = true; + if (!pn_raw_connection_is_read_closed(raw_conn)) { + qd_log(log_module, QD_LOG_DEBUG, + "[C%" PRIu64 "] TLS input closed - closing read side of raw connection", conn_id); + pn_raw_connection_read_close(raw_conn); + } + while (pn_raw_connection_take_read_buffers(raw_conn, &pn_buf_desc, 1) == 1) { + qd_buffer_free(_pn_buf_desc_take_buffer(&pn_buf_desc)); + } + } else if (session->raw_read_drained && !pn_tls_is_decrypt_output_pending(session->pn_raw)) { + // Unclean close: remote simply dropped the connection. Done reading remaining decrypted output. + // + session->input_drained = true; + qd_log(log_module, QD_LOG_DEBUG, "[C%" PRIu64 "] TLS input closed", conn_id); + } + + return session->input_drained && session->output_flushed ? QD_TLS_DONE : 0; +} + + +bool qd_tls_session_is_error(const qd_tls_session_t *session) +{ + if (!session || !session->pn_raw) + return false; + return session->tls_error; +} + + +bool qd_tls_session_is_secure(const qd_tls_session_t *session) +{ + if (!session || !session->pn_raw) + return false; + return pn_tls_is_secure(session->pn_raw); +} + + +bool qd_tls_session_is_input_drained(const qd_tls_session_t *session, bool *close_notify) +{ + assert(session); + *close_notify = pn_tls_is_input_closed(session->pn_raw); + return session->input_drained; +} + + +bool qd_tls_session_is_output_flushed(const qd_tls_session_t *session) +{ + assert(session); + return session->output_flushed; +} + + +uint64_t qd_tls_session_encrypted_output_octet_count(const qd_tls_session_t *session) +{ + assert(session); + return session->encrypted_output_bytes; +} + + +uint64_t qd_tls_session_encrypted_input_octet_count(const qd_tls_session_t *session) +{ + assert(session); + return session->encrypted_input_bytes; +} diff --git a/tests/TCP_echo_server.py b/tests/TCP_echo_server.py index 36728e117..1a0a1dea0 100755 --- a/tests/TCP_echo_server.py +++ b/tests/TCP_echo_server.py @@ -183,7 +183,8 @@ def run(self): self.error = ('%s configuring ssl_context for %s:%s exception: %s' % (self.prefix, self.HOST, self.port, traceback.format_exc())) self.logger.log(self.error) - return 1 + self.is_running = False + return # run the client socket event loop until it is cancelled try: @@ -219,7 +220,9 @@ async def event_loop(self, ssl_context, conn_stall, close_on_conn, close_on_data await self.server.serve_forever() except Exception: - self.error = "ERROR: exception : '%s'" % traceback.format_exc() + if self.error is None: + self.error = "ERROR: exception : '%s'" % traceback.format_exc() + self.logger.log(self.error) def wait(self, timeout=TIMEOUT): self.logger.log(" %s Server is shutting down" % self.prefix) @@ -232,6 +235,8 @@ def _cancel_server(): self.loop.call_soon_threadsafe(_cancel_server) self._thread.join(timeout) + if self.error is not None: + raise Exception(self.error) self.logger.log(" %s Server shutdown completed" % self.prefix) diff --git a/tests/cpp/cpp_system/test_connection_manager_static.cpp b/tests/cpp/cpp_system/test_connection_manager_static.cpp index d87e052f2..be62cc019 100644 --- a/tests/cpp/cpp_system/test_connection_manager_static.cpp +++ b/tests/cpp/cpp_system/test_connection_manager_static.cpp @@ -24,8 +24,8 @@ extern "C" { #include "entity.h" - -#include "qpid/dispatch/connection_manager.h" +#include "qpid/dispatch/tls_common.h" +#include "qpid/dispatch/threading.h" } #include @@ -38,8 +38,8 @@ extern "C" { using std::string_literals::operator""s; extern "C" { -QD_EXPORT qd_config_ssl_profile_t *qd_dispatch_configure_ssl_profile(qd_dispatch_t *qd, qd_entity_t *entity); -QD_EXPORT void qd_connection_manager_delete_ssl_profile(qd_dispatch_t *qd, void *impl); +QD_EXPORT void *qd_tls_configure_ssl_profile(qd_dispatch_t *qd, qd_entity_t *entity); +QD_EXPORT void qd_tls_delete_ssl_profile(qd_dispatch_t *qd, void *impl); } // uses the python-exposed api to run the code; hopefully this is robust enough @@ -47,18 +47,30 @@ static void check_password(qd_dispatch_t *qd, const char *password, const char * { PyObject *pyObject = PyDict_New(); PyObject *item = PyUnicode_FromString(password); + PyObject *name = PyUnicode_FromString("profileName"); + + PyDict_SetItemString(pyObject, "name", name); PyDict_SetItemString(pyObject, "password", item); - qd_entity_t *entity = reinterpret_cast(pyObject); - qd_config_ssl_profile_t *profile = qd_dispatch_configure_ssl_profile(qd, entity); + + sys_thread_proactor_mode_t old_mode = sys_thread_proactor_set_mode(SYS_THREAD_PROACTOR_MODE_TIMER, 0); + + qd_entity_t *entity = reinterpret_cast(pyObject); + void *phandle = qd_tls_configure_ssl_profile(qd, entity); if (expect_success) { - REQUIRE(profile != nullptr); - CHECK(profile->ssl_password == std::string{expected}); - qd_connection_manager_delete_ssl_profile(qd, profile); + qd_ssl2_profile_t profile; + REQUIRE(phandle != nullptr); + CHECK(qd_tls_read_ssl_profile("profileName", &profile) != nullptr); + CHECK(profile.password == std::string{expected}); + qd_tls_cleanup_ssl_profile(&profile); + qd_tls_delete_ssl_profile(qd, phandle); } else { - REQUIRE(profile == nullptr); + REQUIRE(phandle == nullptr); } + Py_DECREF(name); Py_DECREF(item); Py_DECREF(pyObject); + + (void) sys_thread_proactor_set_mode(old_mode, 0); } TEST_CASE("qd_dispatch_configure_ssl_profile") @@ -130,4 +142,4 @@ TEST_CASE("qd_dispatch_configure_ssl_profile") } qdr.deinitialize(); }).join(); -} \ No newline at end of file +} diff --git a/tests/displayname_files/profile_names1.json b/tests/displayname_files/profile_names1.json index afc0d0cab..29e7e8be2 100644 --- a/tests/displayname_files/profile_names1.json +++ b/tests/displayname_files/profile_names1.json @@ -2,6 +2,6 @@ "14845961c5646ee0129536b3a9ef1eea0d8d2f26f8c3ed08ece4f8f3027ba9d5": "gmurthy", "94745961c5646ee0129536b3acef1eea0d8d2f26f8c3ed08ece4f8f3027bcd47": "janedoe", "94745961c5646ee0129536b3acef1eea0d8d2f26f8c3ed08ece4f8f3027bcd48": "johndoe", - "3eccbf1a2f3e46da823c63a9da9158983cb495a3": "user13", + "789d81777d7426286656747bb21310d5cb54e91e": "user13", "82244216b6d02ffdfb886c8da3c803e0f7a7b330a7b665dccabd30bd25d0f35e2a4fff5f0a2a01d56eb7dbae085c108e71a32b84bab16c9ec243a1f6d014900d" : "user14" } diff --git a/tests/displayname_files/profile_names2.json b/tests/displayname_files/profile_names2.json index 1fe9e34fa..1ea33cabe 100644 --- a/tests/displayname_files/profile_names2.json +++ b/tests/displayname_files/profile_names2.json @@ -2,5 +2,5 @@ "14845961c5646ee0129536b3a9ef1eea0d8d2f26f8c3ed08ece4f5546546": "jerry", "94745961c5646ee0129536b3acef1eea0d8d2f26f8c353455233027bcd47": "elaine", "94745961c5646ee0129536b3acef1eea0d8d2f26f8c3ed08ece3027bcd48": "george", - "72d543690cb0a8fc2d0f4c704c65411b9ee8ad53839fced4c720d73e58e4f0d7": "user12" + "6cea7256e7d65c16a5c9d34036ede844e45e77a3271bcc7ec43badeb0550fd46": "user12" } diff --git a/tests/ssl_certs/README b/tests/ssl_certs/README index 3be8be496..6b82f26a6 100644 --- a/tests/ssl_certs/README +++ b/tests/ssl_certs/README @@ -1,2 +1,6 @@ -The certificates in this folder have been created using gencerts.sh which uses a combination of java keytool and openssl commands -gencerts_openssl.sh uses all openssl commands and no keytool commands. Do not use this file to generate the certs in this folder. It is only meant to give serve as an example of how to use openssl to generate certs. \ No newline at end of file +The certificates in this folder are for testing the TLS functionality +of the router and its tools. The certificates were generated by the +gencerts.sh script. + +These certificates are not intended to be used in production! They are +insecure and meant for testing only! diff --git a/tests/ssl_certs/bad-ca-certificate.pem b/tests/ssl_certs/bad-ca-certificate.pem index 1f01a23c2..a8a868690 100644 --- a/tests/ssl_certs/bad-ca-certificate.pem +++ b/tests/ssl_certs/bad-ca-certificate.pem @@ -1,24 +1,33 @@ -Bag Attributes - friendlyName: bad-ca - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 38 39 39 39 31 39 34 -subject=/CN=Trusted.CA.com/O=Trust Me Inc. -issuer=/CN=Trusted.CA.com/O=Trust Me Inc. -----BEGIN CERTIFICATE----- -MIIDAzCCAeugAwIBAgIEZAarkDANBgkqhkiG9w0BAQwFADAxMRcwFQYDVQQDEw5U -cnVzdGVkLkNBLmNvbTEWMBQGA1UEChMNVHJ1c3QgTWUgSW5jLjAgFw0xNjEyMTky -MzA5NTlaGA8yMjkwMTAwMzIzMDk1OVowMTEXMBUGA1UEAxMOVHJ1c3RlZC5DQS5j -b20xFjAUBgNVBAoTDVRydXN0IE1lIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCBZ5OceONxsN9T+wVTjJQlns9h9S6tls1X5wfCP71rFVxm66Ks -qt/SiYOp7n/1CKrvYtkKEFXvYypazzIJtyZhL8mrJA7r7RxXk9vTMPH/GUrmOYGs -K4Gi9hIlro13M53TMqJyCWJGDHwBfTKXRriAZcyDq8xCZ/hEQcC1MhOtFpod73Wi -kW0y7ihW+6K9fkZ+ui8hIyc6D6z5HdJjLDRiU33Gw9UwCBAm9EC9vsX0pGOYwPCN -VQnXLY+CFPlbfiiYgTczqDVEaRjnpJYtkx5/TWqWpXxYF6fyQ/Mu7uSk72C2Clh7 -6vn+VBi3nIswtLRS3RefljFBxpsatsNlcvMlAgMBAAGjITAfMB0GA1UdDgQWBBQ9 -LoDO7aJQa0Jkkblsa34LtsAy1TANBgkqhkiG9w0BAQwFAAOCAQEAYI4g416Go17P -ZInPljI/80Dw9d3SIIQ8ipONcbrD2IWnynhqXZAUF/9UnBFwGReOUV0OGrRXlQVG -TdVuJ3E1dwB/DfSr/EMXv8j3D5n+h0jLORL8ax+7APa9sL+xyL7mt3Nry57aQB79 -1T4aNbJSrqoDA/CTE+8qB25/Y1m/J4xEsep0LKKoKZd/aLCAmINNrmOf766pu5c7 -goPfkmP2wigs1cTS50v1+h/P3HX0NrbFKntR3v0J0yR8DssrHfXrHLD9Qi1cprke -qnokZ3NG0z8DiLt/cOZyI3aJPbEcM5wN6/XnY96C4RiTA4TstIGmPJS3+muWcK5o -9fMEBI6qKA== +MIIFsTCCA5mgAwIBAgIUJAYrNw6A18buKHm6lmELwDxRFkAwDQYJKoZIhvcNAQEL +BQAwZzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEdMBsGA1UECgwURG8gTm90IFRydXN0IE1lIEluYy4xEzARBgNVBAMM +CkJhZC5DQS5jb20wIBcNMjQwODEzMTgwMzUyWhgPMjI5ODA1MjgxODAzNTJaMGcx +CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwIQnJvb2ts +eW4xHTAbBgNVBAoMFERvIE5vdCBUcnVzdCBNZSBJbmMuMRMwEQYDVQQDDApCYWQu +Q0EuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjk7Tpj+lc0DU +b9s8gQl2BU+LeQplH7jvwagij+UDMfwVnfGKZXXbU6vSujA0Gs25wKargaq9cObN +kSeiIvXjAKYX63c3TL6FhqJ1TAFZnxwZsykrRHoGe8sbkrMQA+kZ1/ZogYMMq/Gh +CMzLbpW2nsKAumQDA1JpkHOpr1LLqmUqPce9LJrNPfDCzRVgre371gBrJze+d5R0 +n4yFNV2OWyOo+8eyqGD103AYt2PdDF5NgE6UIjS6xTkZK3w19Zrv3moA6UWFdDbk +X/tCOKQLvntcRw/fOX0egkIPx49i8In4KZE0DFiare2mxzKa4xFkdbhGxgJ1I8CH +BDIcav60zayFirI1CATFmnEuqnhiYCNwXPxZpyLruEaDzNru0SfVqA2erirw/dr5 +6PyvLm9AySP+M2l/kb9Gcm66aZIOc+KR+osOnBjrtN40ZyiJW0scUpk2jj00SoYQ +RFAkc+oL/gfaRVdiQnSn654gYAwrMF0Vy1If9Yq5qz5c7k+FUoOwTztCVdC3ZiGs +lJdO6b4cv/MiIAUGVSf68KQIRD/6MkDQSmYuNwlXWCfX+F4ZZQP7i6Wy55sOQI1/ +1LbgkAUCU1jzxW4i+idVtOSTgQxjT5JDjnOSBcvsZjKJMRFuZ167sDdUnDfA/7i+ +/OFSWRULEhGMC9WVrlHMp/EnUUubXd8CAwEAAaNTMFEwHQYDVR0OBBYEFB9uFfIn +vTgXwTNVHaFJN/rsRLSiMB8GA1UdIwQYMBaAFB9uFfInvTgXwTNVHaFJN/rsRLSi +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBAD+ltOtm24AR5wqd +IrOEgdCIJin2mfrDZIV1umWj9NV/osOyvyYbzCKOyzCrJYprLjaOhVWD5LP/cZGb +ciKecQagxXAaiV6Xg9EZwVqEhW/GROwe9Y6UW8nAVdlehIZq3b2Gc2DY7X6V6zmG +rBuYAD6zy7q9GCCbAVipreCaH1ELlangvEWlZU5mLgBXkf8r/gq9AUaUghXVZxID +HluArBVs6jsP5wuRaYuVKJmWqOXcTCoPV+lGrVsothKeyNY6gL7xmQojwihPOhnI +ncFlGuxD802jHc5l5iKEPghgs1creAQxog0cxfTidTKea0XvcnyIwtDiHRSxHUxD +K1NKqkQAivWRPicULn+ynxgwHNMx0IIWfwBziDZBPDTrS9s/a+TnVaQcZc42o407 +lA8MabkG5XZuEQZWVmsIbnCRm0i93+2nHIWrITZaUpo1Ld6A/SkXieQEyb1z7QXE +QISN6Ewx3sow36VrcAv3apjTaaD/IH86GwoibJYaPzjnndMzPFpF+W+19wA4ltdB +h64osAKkTUwnrOkFyip49k8w82zR8S3vVw5MSURlLbZt6yMKGMAJjWICcD6GZvjo +cOAO7XXNoLTHrM4ctF/SyzyTqqV2BsJKXn4HksAt5dpeWdSShtbFOdMy98MVD318 +KhRN83L0SDj4CRwVYvO2fBVA8VKS -----END CERTIFICATE----- diff --git a/tests/ssl_certs/bad-ca-private-key.pem b/tests/ssl_certs/bad-ca-private-key.pem new file mode 100644 index 000000000..771a75973 --- /dev/null +++ b/tests/ssl_certs/bad-ca-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/ca-certificate.pem b/tests/ssl_certs/ca-certificate.pem index e1dcfb42d..77fb2cd0c 100644 --- a/tests/ssl_certs/ca-certificate.pem +++ b/tests/ssl_certs/ca-certificate.pem @@ -1,24 +1,33 @@ -Bag Attributes - friendlyName: ca - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 38 39 39 38 35 36 36 -subject=/CN=Trusted.CA.com/O=Trust Me Inc. -issuer=/CN=Trusted.CA.com/O=Trust Me Inc. -----BEGIN CERTIFICATE----- -MIIDFzCCAf+gAwIBAgIEGcOakjANBgkqhkiG9w0BAQwFADAxMRcwFQYDVQQDEw5U -cnVzdGVkLkNBLmNvbTEWMBQGA1UEChMNVHJ1c3QgTWUgSW5jLjAgFw0xNjExMTky -MzA5NThaGA8yMjkwMDkwMzIzMDk1OFowMTEXMBUGA1UEAxMOVHJ1c3RlZC5DQS5j -b20xFjAUBgNVBAoTDVRydXN0IE1lIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCPVrDGiPlRPnptJSqKE7XEUTkKbNaUBOgyUlvvPekOO02/xsgY -oBFOp25V/fTZFV1KdRUz+iD1z0FxIxYe124PsdeLLb2ZxnOVoqfLRWwU8paVi1KK -jG3zDcymevCQvnR6q1+yrSMJMakAAkhKQHGTgSvzGlwV+/VB+aj1tak+kWmdqemI -RCj9/0aM4Mv/Rfm9pEKG5CyvCkb/370kSjy1S+2DBhwoNkPnbJfimod22IqUT0Cd -1ybRKyMe34CM70PkqlDLiQcBWEbegKbc2JzCxZbJd9qiZ1aqTgq6MOmofODvwclF -Q2qoNeUmltLdMw4qcTFk7M8x0lsqX1w2+g4nAgMBAAGjNTAzMBIGA1UdEwEB/wQI -MAYBAf8CAQAwHQYDVR0OBBYEFIKNKmxyN9+AYDjwu0i8pcJqWpGnMA0GCSqGSIb3 -DQEBDAUAA4IBAQBK0QqrlGVpARcibuYuW0BIvyxjsim/LClxgmwwRBWkMzBp2QZt -tAqZmAuUDjYefbb9pf400Nt9A8HWj2ecWPAp3QgAdcE4ZMPtZbf+d4c1hhE4NWQZ -efV8WulfYY1ZaZHFJtSiq+dSS6KqVH7jqjqQgwflwbVf5q2UD3pwU0a1Da/3f4F9 -bsxwmOnan8olg5Hku8eRbOglvKhXqf97XNIgiut5K6/jZLyNYI6CjY7vVJSZN37B -c2AZyeM9Gu3nkvyKfzC5aiLO5pP+UUHYtLtX1NyEBidZmtduvCr33HD9D17Zq0De -j+kdBLLb4AZR7Pm6wEpLl1UDMi1Gg+6airvE +MIIFqzCCA5OgAwIBAgIUBInZxf2/OnzpoYf/dPMVSFJtrRgwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEWMBQGA1UECgwNVHJ1c3QgTWUgSW5jLjEXMBUGA1UEAwwOVHJ1c3Rl +ZC5DQS5jb20wIBcNMjQwODEzMTgwMzUxWhgPMjI5ODA1MjgxODAzNTFaMGQxCzAJ +BgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwIQnJvb2tseW4x +FjAUBgNVBAoMDVRydXN0IE1lIEluYy4xFzAVBgNVBAMMDlRydXN0ZWQuQ0EuY29t +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoImfHxzQDT+pT9MlLLY7 +QOacS+QmY54JJ+rVlQvfg2ySAtpCkqBlTMC4E6pZwj+Bj0t58panQAA5WiPQMy5+ +LETKrwyamaumRQ8hOSw35UrWQHSKOjhuy5BMhheABCDm71ouGN9879olB3FKPgTH +69DgEL761GuGopV+upQiE8rU1DcQq5xyHaPyUdO+Vv2KgS9B2tuWl7U6TusF+IgH +gokSkqTo1r7bRzb5G0ikCJ4GggjZPbttxxJVUTWEYKQCJVnnz9RFWM0m4yFj3Vxs +qqgzLCe+09CEJ2RgI8gL4sf0skz8bLJBsUBeX3jV2xuysg4sZqwF4A/aDSpgB+Kk +NV6W+RjOl8GxUdoBE3Lh7GKoPgp3zF9Hc+xZ7eNi2FfG5JTY7sB035XXrId+jaJu +mfaelid+32NljlWTTOBONR1wi7+IMdcAD9ZgLARERLY8xYmJYEFm2WDCUCLqBqR2 +CcxZLJlcdZxXwHmrt7kY42Eqa6MdHbxdLFV3jEunS41heT0Cm85DCFj2qPNTcFbu +VLo2T3sN6u6Ij6mSSHUWhyrxzE0VDH48bFMXCIAaJs5gya9fKyJK+7dF3Mr5Est5 +Y8G+oiMUPvvUKlZwBN/jcOEVVYrbAy5kbtoO9/Vco3dv628xSU48X9zpe1T7tvRB +OsJzds/rjnPO3t5K0g8DBP0CAwEAAaNTMFEwHQYDVR0OBBYEFMupBnS8vXwNpd6T +hh8LQE2GoDTgMB8GA1UdIwQYMBaAFMupBnS8vXwNpd6Thh8LQE2GoDTgMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADDgOwLKPcCEl6iX5rmybo4K +ZmrvlKl8nfQMtSMWdcUnPCzlh4tFvhz2GBO7zh1NXxfMoFdOUDSQYF62lM0GEcSZ +0b0xBrRjrQAEEApoaEws7Ggv+Ppvh/OqhvuvXKgGnjlQt+a31ldk9WiyLLFIB7FS +Jjj+Huf0jLhiG/oYVnK8sRcKthX8enO04RmplZRQf1OiCfK+zS3ZWf1fZK7I28E8 +qAw/x1x7Ix4aDY79C3sargca3XfjCHI54F5VJTZy7gD66NQzhtUZyey5gXwZoDOm +0AtCrNlC5h62yjTEnmxqow50ZjffxN3LBX6vsscxwIt4pifCz3fqvEp05e4H+FAe +wlrJbfdGGh2LoJXjxUmaypSGK5W6LgCvS1vR0zbPQWtzO/gc2SP4wRnV50MgJmB/ +DsMfkefMRHUZdgx0W+V4syIA3kSaBz4255emX8BF/uWviyaenxJG+IdRojrIXpDt +n5zZbvoAhw7EhQ8moN2evkX1baNwsM7C7GugNtyZ37nUl5Wr8iwQJc5UkQiGvaB6 +Hgr9vF1OpwhK/KGKiuXRuiqnCVrfj8UYeSbf+1jho0G1dymEzMxMk7yUfZiUMio5 +ix2oy9qY+B6tTaB9PZ8IMYw4xZjVWpi1SXZmv77IfeQ+LdL/gpsTllmS8rqzS8ly +wd8Ltfjp5NREdhnE4hho -----END CERTIFICATE----- diff --git a/tests/ssl_certs/ca-private-key.pem b/tests/ssl_certs/ca-private-key.pem new file mode 100644 index 000000000..bc721b84b --- /dev/null +++ b/tests/ssl_certs/ca-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/ca2-certificate.pem b/tests/ssl_certs/ca2-certificate.pem new file mode 100644 index 000000000..7f7f72176 --- /dev/null +++ b/tests/ssl_certs/ca2-certificate.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIUfpA3ESdcoAg8IVRtKAtG1heyqXwwDQYJKoZIhvcNAQEL +BQAwaTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEaMBgGA1UECgwRVHJ1c3QgTWUgVG9vIEluYy4xGDAWBgNVBAMMD1Ry +dXN0ZWQuQ0EyLmNvbTAgFw0yNDA4MTMxODAzNTJaGA8yMjk4MDUyODE4MDM1Mlow +aTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhCcm9v +a2x5bjEaMBgGA1UECgwRVHJ1c3QgTWUgVG9vIEluYy4xGDAWBgNVBAMMD1RydXN0 +ZWQuQ0EyLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCCcx84 +LqsnnufA746XsB33lOXEm3x3D3Mtpfjw/3uD5Txo+djtFH0LbBfxyyjvAyhD/H+u +w8Ed/qe5Lj+lSBOw+CXpeiy0FAB7MMSoz74mEoFG0dxwd5V0R6JvGCimtidEesmA +ze6Cldsk1n1Uqn2ecmMdUXq8cDAdJVMh8iEoKsnb0mJiZfstwTD1ZmzNT05g60XJ ++cUQdYh+kw15+TbFpHEPHRXt2wUIklHS1wil2kfmJE4gyY28sNYySbihMOh1LbEW +IQRl0f3llnCjqaWBF/t2ykKn9PESsH5MERXGcot0z+P8mkmoTX+WR8TWMm2wV+9w +ztR9Ox9nKchF4J4wz2Hu6RckvqYHvu7kODiE4m+ws5e01/ILiRY/4QoaOI+Fa2zN +83kRRv0pVZj03V2mM/xYXNQndEwvj04td8J2NnbLNAE0BjpiAkqoDv8Jb3vlGeuu +UMVC5vZSkMU+CMih+zsSY95XHpc/7KAwxX9IW+j3PxvoWm+28CPEW3lCEvLbE4B9 +Fx46IkHY5IwnEvMOrWMkkaeM2oCyJZrfWoz9yr2WiRUb/MasltyoF9YStq6T2lcR +R3qQSbCCblpWyEIMbyPplRWRr9ORHvy0xTfMwmblcuJmBqksGe7KTIomG1Q5SZSM +p4ZooMno4Fdkx2XmvhdYXaCQAkHIZUD188DdAgMBAAGjUzBRMB0GA1UdDgQWBBQH +6inOS5Lmd0qizefwZA92rqZNfjAfBgNVHSMEGDAWgBQH6inOS5Lmd0qizefwZA92 +rqZNfjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQC2lXkO/mPS +9rNkBGORwSeIgotYLfLgD9UmUUszNmPsRXZTEK5U/sBq+fptDPo8uNZGJx7UHeHJ +5qnmtq+YAlFMpg7MEkDngjyf7gu46CI3DgwXDIIIT8etNebdeZRCf459HNXWyF6F +nqcF7whP2ZKlviX8MCbbCYKw0GQvtSxOhNNsYI2QAATH8XtG3hSMsWsjArA465ho +vPUYK5wFQEJmkcqJQg6NJCbnTwImqNTU5CAzOyUFbyJezjUIcjuoAxahdLE9CaD0 +Vym5unaMbUzqkf9Ter80QRLKjUEueHQgVKXR2Ov0H+CDjH6BUJkKPpHHnIScX9SK +iuv75rx7egVGFcQYa7Sn+MZsLwzK20yp7L0RoP3kK0iVMmCeMnmDyaA9rfEQVDRg +BEOV8T25uYu1HrQuJWlcSYHSxSuzsOrSPD55y6SzrmgbsZuPXHibcFEBOPa+0YvH +wvPOshHozvso72d8BBIQpAf21pllfaQLidO1uWY3997UK5r4wnsmI23wCrJfURlf +AtgKRYAtejStPX+4wfahnZnD24OfKeZl603qCVTNVTZyaYtChstaWAgy4GMXfGu9 +O6hd0Y6ySh/bYLSpO5E9F7jo6IwqTw8NxVu7I9STMQlBlz92l9wueoPIw9/jO69k +7Xe//jzvE2RXvlhoFDchBudZ44bJw29ipg== +-----END CERTIFICATE----- diff --git a/tests/ssl_certs/ca2-private-key.pem b/tests/ssl_certs/ca2-private-key.pem new file mode 100644 index 000000000..b99f6a2ca --- /dev/null +++ b/tests/ssl_certs/ca2-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/chained.pem b/tests/ssl_certs/chained.pem index fde6a0787..4da6c281c 100644 --- a/tests/ssl_certs/chained.pem +++ b/tests/ssl_certs/chained.pem @@ -1,43 +1,65 @@ -----BEGIN CERTIFICATE----- -MIIDGDCCAgCgAwIBAgIEGlalRzANBgkqhkiG9w0BAQwFADAxMRcwFQYDVQQDEw5U -cnVzdGVkLkNBLmNvbTEWMBQGA1UEChMNVHJ1c3QgTWUgSW5jLjAgFw0xNjEyMTky -MzEwMDBaGA8yMjkwMTAwMzIzMTAwMFowJTESMBAGA1UEAxMJbG9jYWxob3N0MQ8w -DQYDVQQKEwZTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS -XksPE70eYVVl/WK0H+8gdn/3GPdkj0c4psG2q0o1EVWxGYxczC94CleXYg9tA2jl -fzz4/hyAwmHfbC+eHx+v2CauGam8bQlu3fsthV47b3if4E0RjJA1DJLb36xMoqV6 -w7tgjye0HymTVDLQX/thG8oBHtspQ7Gorn3h6b5mGMULaqe6Ygu+easySRimKFh4 -7ORfpuUesWffbjrD+gsfXVPbOxLgRalXX+VBe2fuWS5Otv2omqrz0iMWbyhwRtV1 -U1rcSWieNUQ323n0mKon15vGAy6i8CFT0hw7SJXf0E0i40ZByb9SDdmes0fbE+hu -uIabC95h16RqqOHw8fpBAgMBAAGjQjBAMB8GA1UdIwQYMBaAFIKNKmxyN9+AYDjw -u0i8pcJqWpGnMB0GA1UdDgQWBBTG9hDzVI5ihubr6gtgB+DqWBDzOzANBgkqhkiG -9w0BAQwFAAOCAQEAHKqpEBTjDgRLXoUkShYreqbzf8jFwyLFbhC48svxXwRNdZuE -LPXwHFwvdayoOKQeckLav2mDzmyWBF6Lt60Z80HB09SH1HkFH5s+X4R+srBMUtSa -KsDk5MhoEpLxZNLgQPDEfYOZKUSBBfsx9Pwr10b5vskdPB1X6qE+Iu1UDuZHw/2P -QnemC+lyDrBJ6SUiewwPQOJfGlSuTEyziQ/NOsZyb9EXEm8wGgodh5JCmb6T2FmF -ayW5Vu7p3z9BoxEFP0RFLQdtZhjk0rSw4/jWpPVC85eD5tYRGCqW3zEez6aLwB27 -VJ/lpA5PyZN2uN83yeyGFo4VjzNGBLG6f/cJoA== +MIIFjTCCA3WgAwIBAgIUUDzFw9TxWRcFoyQjiz6mUtohp2wwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEWMBQGA1UECgwNVHJ1c3QgTWUgSW5jLjEXMBUGA1UEAwwOVHJ1c3Rl +ZC5DQS5jb20wIBcNMjQwODEzMTgwMzUxWhgPMjI5ODA1MjgxODAzNTFaMFcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEP +MA0GA1UECgwGU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDIpXkvfopGYcJoZ8GpxAFxQ2M/gb+bY0MJlw16 +gp0H4/vRBd73FdGwiQvnxcVqID5aK9S9fLDQR8rejc0r1Zcf6dqsqbga7C81M4dr +b6I29InctoX4/p5gqdLgL9XetbD2LECg30Ymp0q8HAYZF6gydo0pATG1yH5WfZlH +IOB1+cCFCCs8yAb8T8YOA0W2zn+tfviiTKQp7m6ZY/eX4R0rLiv5I5wpQlzDa3Ut +L3CzPnbySpXO2/hFZ5cXeYSYOdNmCJ9E7X3MsioAhOhSLai8zILhtKFIuakv9ZQV +Vp4erUxISxeKggfgq93iYHlOLEEzkQ+BDlIKc1Jv1i7rT3l+TGjitPYv9ysEyA9O +g6/42nb4HHr/Yp1RbleVnMfBasAh3e9S+mJAHmHczYVKxSVCSUCSnGDe/x359kiI +XLjI3qlhLIqAhNuia+v30KxBe2Wb2+g+UjCczBDHfXIAro152f3d2X8b/XVNHjO6 +wgOzalRVdr/r07hFI4M6XQQxKbYpBMYRNcgJg9yEQFerjCI5hqQKm+xbA9O3GHN3 +i2HATD/9vdPNoW+Bp5qJyggdaXQPe7GwVE5SsVzg6Wa+oj6aGJabea7hmxIMPXrp +0y76BSx0Z3xAVf1sAUiNd5ZiXICDFWuybpoMCYCiCkL8838D3tIBOBgZYUayLNcn +AkbbnwIDAQABo0IwQDAdBgNVHQ4EFgQUaJcCk7c6O0h7XngO+UWj/q5wmVIwHwYD +VR0jBBgwFoAUy6kGdLy9fA2l3pOGHwtATYagNOAwDQYJKoZIhvcNAQELBQADggIB +AIk1YBQQ23gd6YerKFIxjVb38W+XO8C/b/VW2F1KbVxeKNhhs6mdCl8FO15b09RM +zNDYqIqEuqEwv7ES4nO8RjKhCZU8vBKFWpqkPirh9cskgrtj4kt4iZBdUbsI07iO +KoW0YFTt/aWz2JDrQy1wrKDK0ueeC672Ak+CnzxM6Dc9FsexAgEvrEJ30on0jFRt +AwoZtc7a5K1OXoOIxQfXfXZjN4vJJplFPVTHeUnP4dCOPOiVzDJzWfAMtNFrM1h5 +EPk6hVE+YXMPyYK02KYde6AStxU5V3HbzE/VKSTHzFVCePA6eOuA/+PQksA3Uvto +36/p0ug5Ym4LvI2VBFxrgeAFgLnnMrpayHR6byOxzerQwltVA+ZqKdhxxcIZGbOZ +tOVJX+iXQ3gpPnG/iQ9m+YecbDAC+QPhnVRM3aEXIgjZ++OAm4H+aYC8gqb47hHA +W0Vxh6lTu62rP/bhjyOnqMc/R2YWdIfovmLEiw6lwswha9hnVpxHijLdmSUYQ4CR +f9myzSvQ632NyL4glWtRjFDcJLkEh7zbrr9eXsN/25UJoJPURYcq99FtifEEf85H +lVXLqWLELKRwnD9zfavqkHdc6Wpeh/WHmfUQgxnbdbKHWvI5KwXls98uVg/6AqtC +D5CqHX26sNXam4ocx2dNEpWheSgrWMrdIIqWOk+JS0SW -----END CERTIFICATE----- -Bag Attributes - friendlyName: ca - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 38 39 39 38 35 36 36 -subject=/CN=Trusted.CA.com/O=Trust Me Inc. -issuer=/CN=Trusted.CA.com/O=Trust Me Inc. -----BEGIN CERTIFICATE----- -MIIDFzCCAf+gAwIBAgIEGcOakjANBgkqhkiG9w0BAQwFADAxMRcwFQYDVQQDEw5U -cnVzdGVkLkNBLmNvbTEWMBQGA1UEChMNVHJ1c3QgTWUgSW5jLjAgFw0xNjExMTky -MzA5NThaGA8yMjkwMDkwMzIzMDk1OFowMTEXMBUGA1UEAxMOVHJ1c3RlZC5DQS5j -b20xFjAUBgNVBAoTDVRydXN0IE1lIEluYy4wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCPVrDGiPlRPnptJSqKE7XEUTkKbNaUBOgyUlvvPekOO02/xsgY -oBFOp25V/fTZFV1KdRUz+iD1z0FxIxYe124PsdeLLb2ZxnOVoqfLRWwU8paVi1KK -jG3zDcymevCQvnR6q1+yrSMJMakAAkhKQHGTgSvzGlwV+/VB+aj1tak+kWmdqemI -RCj9/0aM4Mv/Rfm9pEKG5CyvCkb/370kSjy1S+2DBhwoNkPnbJfimod22IqUT0Cd -1ybRKyMe34CM70PkqlDLiQcBWEbegKbc2JzCxZbJd9qiZ1aqTgq6MOmofODvwclF -Q2qoNeUmltLdMw4qcTFk7M8x0lsqX1w2+g4nAgMBAAGjNTAzMBIGA1UdEwEB/wQI -MAYBAf8CAQAwHQYDVR0OBBYEFIKNKmxyN9+AYDjwu0i8pcJqWpGnMA0GCSqGSIb3 -DQEBDAUAA4IBAQBK0QqrlGVpARcibuYuW0BIvyxjsim/LClxgmwwRBWkMzBp2QZt -tAqZmAuUDjYefbb9pf400Nt9A8HWj2ecWPAp3QgAdcE4ZMPtZbf+d4c1hhE4NWQZ -efV8WulfYY1ZaZHFJtSiq+dSS6KqVH7jqjqQgwflwbVf5q2UD3pwU0a1Da/3f4F9 -bsxwmOnan8olg5Hku8eRbOglvKhXqf97XNIgiut5K6/jZLyNYI6CjY7vVJSZN37B -c2AZyeM9Gu3nkvyKfzC5aiLO5pP+UUHYtLtX1NyEBidZmtduvCr33HD9D17Zq0De -j+kdBLLb4AZR7Pm6wEpLl1UDMi1Gg+6airvE +MIIFqzCCA5OgAwIBAgIUBInZxf2/OnzpoYf/dPMVSFJtrRgwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEWMBQGA1UECgwNVHJ1c3QgTWUgSW5jLjEXMBUGA1UEAwwOVHJ1c3Rl +ZC5DQS5jb20wIBcNMjQwODEzMTgwMzUxWhgPMjI5ODA1MjgxODAzNTFaMGQxCzAJ +BgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazERMA8GA1UEBwwIQnJvb2tseW4x +FjAUBgNVBAoMDVRydXN0IE1lIEluYy4xFzAVBgNVBAMMDlRydXN0ZWQuQ0EuY29t +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoImfHxzQDT+pT9MlLLY7 +QOacS+QmY54JJ+rVlQvfg2ySAtpCkqBlTMC4E6pZwj+Bj0t58panQAA5WiPQMy5+ +LETKrwyamaumRQ8hOSw35UrWQHSKOjhuy5BMhheABCDm71ouGN9879olB3FKPgTH +69DgEL761GuGopV+upQiE8rU1DcQq5xyHaPyUdO+Vv2KgS9B2tuWl7U6TusF+IgH +gokSkqTo1r7bRzb5G0ikCJ4GggjZPbttxxJVUTWEYKQCJVnnz9RFWM0m4yFj3Vxs +qqgzLCe+09CEJ2RgI8gL4sf0skz8bLJBsUBeX3jV2xuysg4sZqwF4A/aDSpgB+Kk +NV6W+RjOl8GxUdoBE3Lh7GKoPgp3zF9Hc+xZ7eNi2FfG5JTY7sB035XXrId+jaJu +mfaelid+32NljlWTTOBONR1wi7+IMdcAD9ZgLARERLY8xYmJYEFm2WDCUCLqBqR2 +CcxZLJlcdZxXwHmrt7kY42Eqa6MdHbxdLFV3jEunS41heT0Cm85DCFj2qPNTcFbu +VLo2T3sN6u6Ij6mSSHUWhyrxzE0VDH48bFMXCIAaJs5gya9fKyJK+7dF3Mr5Est5 +Y8G+oiMUPvvUKlZwBN/jcOEVVYrbAy5kbtoO9/Vco3dv628xSU48X9zpe1T7tvRB +OsJzds/rjnPO3t5K0g8DBP0CAwEAAaNTMFEwHQYDVR0OBBYEFMupBnS8vXwNpd6T +hh8LQE2GoDTgMB8GA1UdIwQYMBaAFMupBnS8vXwNpd6Thh8LQE2GoDTgMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADDgOwLKPcCEl6iX5rmybo4K +ZmrvlKl8nfQMtSMWdcUnPCzlh4tFvhz2GBO7zh1NXxfMoFdOUDSQYF62lM0GEcSZ +0b0xBrRjrQAEEApoaEws7Ggv+Ppvh/OqhvuvXKgGnjlQt+a31ldk9WiyLLFIB7FS +Jjj+Huf0jLhiG/oYVnK8sRcKthX8enO04RmplZRQf1OiCfK+zS3ZWf1fZK7I28E8 +qAw/x1x7Ix4aDY79C3sargca3XfjCHI54F5VJTZy7gD66NQzhtUZyey5gXwZoDOm +0AtCrNlC5h62yjTEnmxqow50ZjffxN3LBX6vsscxwIt4pifCz3fqvEp05e4H+FAe +wlrJbfdGGh2LoJXjxUmaypSGK5W6LgCvS1vR0zbPQWtzO/gc2SP4wRnV50MgJmB/ +DsMfkefMRHUZdgx0W+V4syIA3kSaBz4255emX8BF/uWviyaenxJG+IdRojrIXpDt +n5zZbvoAhw7EhQ8moN2evkX1baNwsM7C7GugNtyZ37nUl5Wr8iwQJc5UkQiGvaB6 +Hgr9vF1OpwhK/KGKiuXRuiqnCVrfj8UYeSbf+1jho0G1dymEzMxMk7yUfZiUMio5 +ix2oy9qY+B6tTaB9PZ8IMYw4xZjVWpi1SXZmv77IfeQ+LdL/gpsTllmS8rqzS8ly +wd8Ltfjp5NREdhnE4hho -----END CERTIFICATE----- diff --git a/tests/ssl_certs/client-certificate.pem b/tests/ssl_certs/client-certificate.pem index eead32f3d..f3564f5e7 100644 --- a/tests/ssl_certs/client-certificate.pem +++ b/tests/ssl_certs/client-certificate.pem @@ -1,20 +1,32 @@ -----BEGIN CERTIFICATE----- -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 -----END CERTIFICATE----- diff --git a/tests/ssl_certs/client-private-key-no-pass.pem b/tests/ssl_certs/client-private-key-no-pass.pem index a45a7cd29..868b8672a 100644 --- a/tests/ssl_certs/client-private-key-no-pass.pem +++ b/tests/ssl_certs/client-private-key-no-pass.pem @@ -1,32 +1,52 @@ -Bag Attributes - friendlyName: client-certificate - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 39 30 30 30 38 30 37 -Key Attributes: -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9snYob/F5+4l9 -XZc0yIU72UsRIwouuhZoVoLb6kUn9RRly1SPmMm0oApawKr68ooy2cY9zavS2Q9+ -pKt1v5mntCOxaaVsuKJoU4XXrkPk8TNrPraILuXwX08Y8Ek61JNrmX0B+dDwcbNM -1Ni8ntgN7J9+4F9d52xOLWBP6SMyzbKNMNpV4mi0VFiP7A6ifhariKbxDnSuaD9H -7zjfQNTXi2bb85w01U7jenH0F2rxhx+vhfLjwlUbr2ef9bwlyz04+klkyOvQ6CMM -hlbQCzQWTvYVBP0Z6YUwZNRwFQXNiD1zqr3x8tCNE+COMLj9yoHfJr8HA2Y/pRcx -/8WR+S23AgMBAAECggEAOUp5txK/+lhGmO84KH/bBU1GB6HuhFg9RjvCf8qMIlo5 -U9kJ6+h4Gq4vEmOX//xaUqtE3QuNtjtKG7R9+CzqqiSUlxNSG7jTi/CVvqLp7G4Q -WnZxSEXpuiP+zohbhYIIF/oeMAeT29prDWxHHQhYNjRdo0L+amznX+ECcn/12nSS -MHibRqN2R7jnjydUNmPvRCUlSCuCzEXXKLZyGW21ZnEu4OHSfNW4ePWSfDBumuwZ -rJBQfgrg0RNPwKOWnsqsxv06NKd3aqAje+10WwaaEsUD31UQb6sOc/M/X1mBmx/q -VsGJP5A/MIu5z/Gu+qwPwax4lWq8yZGWUfSW2zdv4QKBgQDhZEY6sdLkuV5Z15Bw -0TlM9ypwVVG+KQS2+2TL0flchBkW3ziiWqLlxf8PJvS0UT71XiYYzJkGo70rgyNI -cOeOTjc5UMihrKhmZca403Gdka+00bXGGMo07NKCmlfiNiKduvLh0yRS3B3LW4UE -0CbfZm9CDvdadTfWzwVtu+HQUQKBgQDXdUSLCLcgZm0Pfp9fTHzcVAtQjLKf/ji9 -2dkrd0MCkvha2dAUSyiaToof8wHMooyT5r9utXrh2bzOp2dvDvT+jiEqxnDkFEtZ -oPsVWN7mNjPygQ54joJLenQt+XoGmTNyjD2BY1Ll7Y65QV6/kYOUOMttyTjpNuyS -7G1hHMVjhwKBgBmJI8/Ij80sFz10h7/QZH6MzjkV4YH64bbdeokBDBBoQvNtbNww -Cp6ftQsBbd30Cn8AIK7Ul03CRU9ltxyg9gsQX39V4LU4e4fGZiJQimHAvLhuvfpo -mpgucJctZ2GQopFeOeBN7KKan6GHh+WIs67Byz8ftPSBrUbgA4xwKeBRAoGAM+eA -N65Fz3xdUZpDcoXOnpVwlFsQvRJVQQc2Fg6JChm10s0eH4VrpqC9Da1RBaczDn5M -J7N4gugdX/tcI5kK1DgRLWjY0VlCPblWQ3JBYFw2b3k/rzNFmEFLs7eh+ez2Q+Yc -+wqecd2kwGRLGwNHG3IDL9OF7kjY/RwPzZFMzAkCgYEAmjY7s6nNYqseP/kwUZQ5 -tTWJHuGkmmGUnAGK9X0bEDsiPhQFzEFHt3zyZHk7VaZb1vKZLrqGrYubf1xJ7Ubx -qNHbhBb1Ps7drq4dOY5BOF5VHUdGLX6XxJNeN7ShtdRwO3h0zfwKKfI9OTsS7iEu -DZmZCuAx+ixkLypnQ0ACPpw= +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 +in+RNxd6B4kUip4Lb2iWOtgItXFSzA== -----END PRIVATE KEY----- diff --git a/tests/ssl_certs/client-private-key.pem b/tests/ssl_certs/client-private-key.pem index abb7d5264..1a7fa5772 100644 --- a/tests/ssl_certs/client-private-key.pem +++ b/tests/ssl_certs/client-private-key.pem @@ -1,34 +1,54 @@ -Bag Attributes - friendlyName: client-certificate - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 39 30 30 30 38 30 37 -Key Attributes: -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIpVB1qwawUjgCAggA -MBQGCCqGSIb3DQMHBAgLOPZ2viV04wSCBMjqZqIv7+O93gZgucmuk9xahBFZYPXU -p/rVW5czKjw29mJ55lWEc3MfQNMJq/GFaQ9W+uqvJVsi1Ed5vak27eRvy2ojcp/M -k1M25TFaRxIu9mdbpFCzE51Gv5X7SPU9H64r3AYSCjm32Zb8uVs7lZ9VuHv48c5W -E3IN1AW61L0sMwDDK/LajG5QOn8eUKaOqYn5CJqVgn7JuRyXwUzCW6vrmidV4Avh -pdy3TurPeCAClfJz/kN3pLbguvwFoW8vmwAxrhN9qsIsbDyHfK5xoS8LOArTuzEi -yJ+OOSZ12ZqoV3IEQQ2qbCncyp0uoaqlpCdSSJdiO0RRNeVSnPbRboCK50RrURaf -Y7xcAayxpFszKGsOhcAHkRQ/z7JWw4ACFN2S4AEEuC0Y0A1mC/gfha5Elcwiw3u/ -P7LN3r0Y03KjQdK6EgkgBZSsJM2HXwonFOvKxDFTATPC1DLMiPI6ZUHqx0ISKlxh -uho/WLVV5yttyxQlePSPmX+ska71IEYT7On5qupCAZvaPVpm2NiKRmOKwbZY9nHo -3aVkOxYbDLyEMM24gzNq6whs86ms7DwZrMxA99kz7sZ6xoTehcb/uOq1EDErEryR -fmChqy+JIzvVaE8ANhPkZmgQKNDWNA9cvCPbV7tCYBw0jvucoV/6GG1QSezxP9uT -XWg6NTu0+fapIMNuQ8eVE2/RvGGOV7ylvjXXfRcY5vjTgg0GaiYcT/Qy6w90Bl7H -ObRklDyUC8UCt/vmRU3/fgxQOgVTaIzxIaOs+5WPXcvSTqCL/sMbE1VgMtRnezoD -C/bt9sqi9eVj3jUnnMgsWtmCzPZFpKDI5Rm6atQzeJVXoa/hllBV5oJjPyzGzmv9 -ocO9JgagtRcFRW4Ea4Y1sW14Lf+bweHzpBY0oIyqwwKBtHKcft+j8X0mWtoiyarb -h6O4z/79AqoT7PfoezZHMItyPE3CSGwEUK2F294TICEWHU5owvucgdRZ9IHa5li6 -VPq/jcAj67iUSPqq1y4WAmn770vGwq0c386DJgQ6eGPPEa/raNHhIrJ0V5uYmVla -bCa1+f6t2vz9Og5R1JrJclPAV/yEIJiormpkRN9unCQOPI2HrbiPwU7d9Pk3iL43 -LhiIGqweGhUAlEUXY8VkfUmsdfTzh/nYP1NwPRDTVg4dKjxmexHYvrqiB8Ilxigc -hkpGQZXHoY5lWVJDoXhZ4NsRXakGh+KU7G+VifJXah8PfN3RbR/nwjSbcMFZQ2HR -lk+LYwxdvqTMLPb5mHUqfJMWV1XYePGykVDfC5caSVB1FxF/C6lBMU4qOgVZQB36 -ryetJaQOP6oTLfTnM11Mf00MphOA/LXscyQGCYmT+17GpswavdkAGoWrWrxiXe0F -UOoWeJmTX0TRFnPIYbMtmnfTApMFItEAcMPloI4lg+k37WT6mywl+/oDx5NwPR9g -MUzKeipIYgSi5DYCUn+Am4Hc4vOtLpluzNql4dXDhQA4ngMRlhGOQUkQ7nqwH6H/ -iK0lDv2r4+Nvgs1n8S9JS+T63iAI02Wiq6w4ErRHrevjo87vinAAw0BosxPtBwbs -T+CJqgNfGateHYwx3cNI67pNJwjFOrzjbg6Y4H5xbNv56wSRs91nP42tgHiOMYDz -UfY= +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 -----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/client-request.pem b/tests/ssl_certs/client-request.pem deleted file mode 100644 index 6f4d0333a..000000000 --- a/tests/ssl_certs/client-request.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN NEW CERTIFICATE REQUEST----- -MIIC1DCCAbwCAQAwXzESMBAGA1UEAxMJMTI3LjAuMC4xMQ8wDQYDVQQKEwZDbGll -bnQxDDAKBgNVBAsTA0RldjEQMA4GA1UEBxMHUmFsZWlnaDELMAkGA1UECBMCTkMx -CzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvbJ2 -KG/xefuJfV2XNMiFO9lLESMKLroWaFaC2+pFJ/UUZctUj5jJtKAKWsCq+vKKMtnG -Pc2r0tkPfqSrdb+Zp7QjsWmlbLiiaFOF165D5PEzaz62iC7l8F9PGPBJOtSTa5l9 -AfnQ8HGzTNTYvJ7YDeyffuBfXedsTi1gT+kjMs2yjTDaVeJotFRYj+wOon4Wq4im -8Q50rmg/R+8430DU14tm2/OcNNVO43px9Bdq8Ycfr4Xy48JVG69nn/W8Jcs9OPpJ -ZMjr0OgjDIZW0As0Fk72FQT9GemFMGTUcBUFzYg9c6q98fLQjRPgjjC4/cqB3ya/ -BwNmP6UXMf/FkfkttwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQU -XKNOdXMsyERFcAt9v7jeUjj9xBEwDQYJKoZIhvcNAQEMBQADggEBAE96yo9Glnb7 -CfEouTboUFxjDhv0FsFaU18nmLd2JHmpJz+aL5z3P0IBhzbeoP1gar9AeZe5XFMZ -FE07p5vttTnVxaULv+YFJVPhrik4HE5eDOzYUiFmPAM8yPWvDBJsxoXPUNOXyFac -gDNsTsrByAyLemIdrceJg2xZRtszb3krLzTBnrS9M0rFlJZqUg5nnoB4O7NkvqBt -3Hi9b3GnBbNlZazQbia+1fEcpDIX00QxFJYhVVMCAtGPUVx8/u99ayLmHOy4zGpM -BoH3jD2hWkI9OIxPIV7wYK7nUJm6ei5lSzkgrKWAOeOA82f2661u8hngjI/Q3Egt -ih4PIKhwhS0= ------END NEW CERTIFICATE REQUEST----- diff --git a/tests/ssl_certs/client2-certificate.pem b/tests/ssl_certs/client2-certificate.pem new file mode 100644 index 000000000..b992fb3e9 --- /dev/null +++ b/tests/ssl_certs/client2-certificate.pem @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFoTCCA4mgAwIBAgIUeMJsAv6dqkNN9/Sk/ADeiz87neQwDQYJKoZIhvcNAQEL +BQAwaTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEaMBgGA1UECgwRVHJ1c3QgTWUgVG9vIEluYy4xGDAWBgNVBAMMD1Ry +dXN0ZWQuQ0EyLmNvbTAgFw0yNDA4MTMxODAzNTNaGA8yMjk4MDUyODE4MDM1M1ow +ZjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMQwwCgYDVQQLDANEZXYxEDAOBgNVBAoMB0NsaWVudDIxEjAQBgNVBAMMCTEy +Ny4wLjAuMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO9AbmQLIbot +sJKRqDU3iCH0ag1Ca7etpdoeq4JULx8iE4i8aYjwmLvj5x5IpGS/brs5ulhUm26N +wcx4+QGEbcsAKTMLd6d5CkE+O2ErzAFQdOHiPnkoS8YrEKjVlJrmBkInn4PU9SdO +mr7qfJYG23tJ5Q90oPrm7BllKqPSj+pWoAz6OYXrfBbqSGcAgUr41A2Vmplm34XE +1Hdmfa9dhWH6HQQyC0ZgH3LWxIuOV1yknA5mI7/utnjnfrL5TeJ8G0jKYABgvj2m +Euvktr/5q2bRHud6hpGpz54y9UO35K42gJd7FS3tTmlaMCSXLWCBhSOS89d81EvC +cBQ1cEr4fbhinzufPrIzi4xAE+LnSOu4/UbtEsSjJcIfzBoEEK2rHGBx6emcdOwC +JAG4+zCDxBK9ekqPr7grNH2c5xPDicPefy5/zkKfQBk3Awcv1TWjYBdCSbGJUw0a +v582MB/o2zS90sfPZl3BlrjaeWU0WFz40ws/UMcuaA8jokEX9aUoO0/5G6HZ83TH +ZAZXUbSaN74EzXoL4fRpE0AEDyrBqGE6HhAkTi1d6QuFRDgCaj1r3q8x241FJ8LA +58wisSmIE7ag93mxtBkhDKeaDkTH1psSIh70rieCSrodeeE7hAYrRIpiIZgdQuWA +NnRHa5CL621teo9/9fTY/vNvOgkrZozpAgMBAAGjQjBAMB0GA1UdDgQWBBRgS9cc +SmwPq0RURcaeoKcz/MPB2zAfBgNVHSMEGDAWgBQH6inOS5Lmd0qizefwZA92rqZN +fjANBgkqhkiG9w0BAQsFAAOCAgEAGQf5ltksmiLJhoZikznxcH/3qRTNAP/rjgUx +L/sT/qiWnXMZ5snj1rxflQ0vaariVnkE2FfYC8j9LhUVAT37+h0r+2hEmoS6peoL ++uW3m/iP+IhQz7Ph+1jg59Ds4MfGwgvnhpEAYd738Xik3SP6mFS8v/CFE9K4eWr1 +6HHAOD11oNNIMHMQmECvEszU2/pH7JU8WKbG4sGqrL5jlAo8fM2IbcypRU/MDm// +jz2BEK8q0apn/ezToISllztqU6t+UYE9+HCQrj4EToDAgUCKyG2uHDRps2cWwL8q +derqBNGtfJeO1xjGEGC8+pgEHQwYal0NSbe8Yo52dgE2Ry48uXTB8GVMHh0Vb/OV +HgOc+n0LUwct/HnkQRsotNZI+WXLdNNuAUzLnEOJ5sEqqkaVUpzi/AKGTUliJ7Xo +pL4nUHwabUwh3xrhdxD31qeizB1ElriPLw94OCQvbYdaLpfcQpgAN//abxIQq/wG +uZBrqJXHtEazQ+2pRNNBK3mUVxLblfAZqikHaATfrKNKQG4K0ZWL6jr6WxiYx4iT +TWX/vD2mgdsyBRhfa2MCa/dZFCa6xJaFoyqFN5VtR7LTl468Rxvw2HXnfkTujwgn +Xezgs4lT9Qw+iBGHcHo3DWi1Vu7qM3Cl0rzqlrjZA8S0a7WWgMaS89g7eZuF55rG +2IryOKs= +-----END CERTIFICATE----- diff --git a/tests/ssl_certs/client2-private-key-no-pass.pem b/tests/ssl_certs/client2-private-key-no-pass.pem new file mode 100644 index 000000000..a9836766c --- /dev/null +++ b/tests/ssl_certs/client2-private-key-no-pass.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +IgdEiFHL5TbmhCx49VAvdku/V4N2Aw== +-----END PRIVATE KEY----- diff --git a/tests/ssl_certs/client2-private-key.pem b/tests/ssl_certs/client2-private-key.pem new file mode 100644 index 000000000..a26629826 --- /dev/null +++ b/tests/ssl_certs/client2-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/gencerts.sh b/tests/ssl_certs/gencerts.sh index 0d2e70f53..d9eae8467 100755 --- a/tests/ssl_certs/gencerts.sh +++ b/tests/ssl_certs/gencerts.sh @@ -19,36 +19,62 @@ # under the License # -rm -f *.pem *.pkcs12 *.p12 +# Creates TLS certificate files for use by the system tests. export SERVER=localhost export CLIENT=127.0.0.1 -export KEYARGS="-storetype pkcs12 -keyalg RSA -keysize 2048 -sigalg sha384WithRSA" - -keytool $KEYARGS -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -genkey -dname "O=Trust Me Inc.,CN=Trusted.CA.com" -validity 99999 -ext bc:c=ca:true,pathlen:0 -startdate -1m -openssl pkcs12 -nokeys -passin pass:ca-password -in ca.pkcs12 -passout pass:ca-password -out ca-certificate.pem - -keytool $KEYARGS -keystore bad-ca.pkcs12 -storepass bad-ca-password -alias bad-ca -keypass bad-ca-password -genkey -dname "O=Trust Me Inc.,CN=Trusted.CA.com" -validity 99999 -openssl pkcs12 -nokeys -passin pass:bad-ca-password -in bad-ca.pkcs12 -passout pass:bad-ca-password -out bad-ca-certificate.pem - -keytool $KEYARGS -keystore server.pkcs12 -storepass server-password -alias server-certificate -keypass server-password -genkey -dname "O=Server,CN=$SERVER" -validity 99999 -keytool $KEYARGS -keystore server.pkcs12 -storepass server-password -alias server-certificate -keypass server-password -certreq -file server-request.pem -keytool $KEYARGS -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -gencert -rfc -validity 99999 -infile server-request.pem -outfile server-certificate.pem - -# Create a server private key file. -openssl pkcs12 -nocerts -passin pass:server-password -in server.pkcs12 -passout pass:server-password -out server-private-key.pem -# Create a server private key without a password -openssl pkcs12 -nocerts -passin pass:server-password -in server.pkcs12 -nodes -out server-private-key-no-pass.pem - -# Generate a PKCS12 key which will be used for client side cert -keytool $KEYARGS -keystore client.pkcs12 -storepass client-password -alias client-certificate -keypass client-password -genkey -dname "C=US,ST=NC,L=Raleigh,OU=Dev,O=Client,CN=$CLIENT" -validity 99999 -# Create a certificate request file -keytool $KEYARGS -keystore client.pkcs12 -storepass client-password -alias client-certificate -keypass client-password -certreq -file client-request.pem -# Create a client certificate -keytool $KEYARGS -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -gencert -rfc -validity 99999 -infile client-request.pem -outfile client-certificate.pem -# Create a client private key file. -openssl pkcs12 -nocerts -passin pass:client-password -in client.pkcs12 -passout pass:client-password -out client-private-key.pem -# Create a client private key without a password -openssl pkcs12 -nocerts -passin pass:client-password -in client.pkcs12 -nodes -out client-private-key-no-pass.pem + +# Create a self-signed CA +openssl genrsa -aes256 -passout pass:ca-password -out ca-private-key.pem 4096 +openssl req -key ca-private-key.pem -new -x509 -days 99999 -out ca-certificate.pem -passin pass:ca-password -subj "/C=US/ST=New York/L=Brooklyn/O=Trust Me Inc./CN=Trusted.CA.com" + +# Create a server certificate signed by the CA +openssl genrsa -aes256 -passout pass:server-password -out server-private-key.pem 4096 +openssl req -new -key server-private-key.pem -passin pass:server-password -out server.csr -subj "/C=US/ST=CA/L=San Francisco/O=Server/CN=$SERVER" +openssl x509 -req -in server.csr -CA ca-certificate.pem -CAkey ca-private-key.pem -CAcreateserial -days 99999 -out server-certificate.pem -passin pass:ca-password +# strip the password to make a passwordless key for testing +openssl rsa -in server-private-key.pem -passin pass:server-password -out server-private-key-no-pass.pem + +# Create a client certificate signed by the CA +openssl genrsa -aes256 -passout pass:client-password -out client-private-key.pem 4096 +openssl req -new -key client-private-key.pem -passin pass:client-password -out client.csr -subj "/C=US/ST=CA/L=San Francisco/OU=Dev/O=Client/CN=$CLIENT" +openssl x509 -req -in client.csr -CA ca-certificate.pem -CAkey ca-private-key.pem -CAcreateserial -days 99999 -out client-certificate.pem -passin pass:ca-password +# strip the password to make a passwordless key for testing +openssl rsa -in client-private-key.pem -passin pass:client-password -out client-private-key-no-pass.pem + +# Verify the certs: +openssl verify -verbose -CAfile ca-certificate.pem server-certificate.pem +openssl verify -verbose -CAfile ca-certificate.pem client-certificate.pem + +# +# Create a "bad" CA for negative testing +# + +openssl genrsa -aes256 -passout pass:bad-ca-password -out bad-ca-private-key.pem 4096 +openssl req -key bad-ca-private-key.pem -new -x509 -days 99999 -out bad-ca-certificate.pem -passin pass:bad-ca-password -subj "/C=US/ST=New York/L=Brooklyn/O=Do Not Trust Me Inc./CN=Bad.CA.com" cat server-certificate.pem ca-certificate.pem > chained.pem + +# +# Generate an alternative set of certificats for testing certificate update +# + +openssl genrsa -aes256 -passout pass:ca2-password -out ca2-private-key.pem 4096 +openssl req -key ca2-private-key.pem -new -x509 -days 99999 -out ca2-certificate.pem -passin pass:ca2-password -subj "/C=US/ST=New York/L=Brooklyn/O=Trust Me Too Inc./CN=Trusted.CA2.com" + +openssl genrsa -aes256 -passout pass:server2-password -out server2-private-key.pem 4096 +openssl req -new -key server2-private-key.pem -passin pass:server2-password -out server2.csr -subj "/C=US/ST=CA/L=San Francisco/O=Server2/CN=$SERVER" +openssl x509 -req -in server2.csr -CA ca2-certificate.pem -CAkey ca2-private-key.pem -CAcreateserial -days 99999 -out server2-certificate.pem -passin pass:ca2-password +openssl rsa -in server2-private-key.pem -passin pass:server2-password -out server2-private-key-no-pass.pem + +openssl genrsa -aes256 -passout pass:client2-password -out client2-private-key.pem 4096 +openssl req -new -key client2-private-key.pem -passin pass:client2-password -out client2.csr -subj "/C=US/ST=CA/L=San Francisco/OU=Dev/O=Client2/CN=$CLIENT" +openssl x509 -req -in client2.csr -CA ca2-certificate.pem -CAkey ca2-private-key.pem -CAcreateserial -days 99999 -out client2-certificate.pem -passin pass:ca2-password +openssl rsa -in client2-private-key.pem -passin pass:client2-password -out client2-private-key-no-pass.pem + +# Verify the certs: +openssl verify -verbose -CAfile ca2-certificate.pem server2-certificate.pem +openssl verify -verbose -CAfile ca2-certificate.pem client2-certificate.pem + + + diff --git a/tests/ssl_certs/gencerts_openssl.sh b/tests/ssl_certs/gencerts_openssl.sh deleted file mode 100755 index fedb3a648..000000000 --- a/tests/ssl_certs/gencerts_openssl.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -ex - -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License -# - -# Creates a root CA and intermediate CA and creates password protected server and client certificates using openssl commands - -##### Create root CA ##### -# Create a password protected private key for root CA -openssl genrsa -aes256 -passout pass:ca-password -out ca-private-key.pem 4096 - -# Use the private key to create a root CA cert -openssl req -key ca-private-key.pem -new -x509 -days 99999 -sha256 -out ca-certificate.pem -passin pass:ca-password -subj "/C=US/ST=New York/L=Brooklyn/O=Trust Me Inc./CN=Trusted.CA.com" - - - -##### Create an intermediate CA ##### -# Create a password protected private key for the intermediate CA -openssl genrsa -aes256 -passout pass:intermediate-ca-password -out intermediate-ca-private-key.pem 4096 - -# Create a CSR using the private key created from the previous step -openssl req -new -key intermediate-ca-private-key.pem -passin pass:intermediate-ca-password -out intermediate.csr -subj "/C=US/ST=FL/L=Miami/O=Server/CN=Trusted.IntermediateCA.com" - -# Create the intermediate signed certificate signed by the root CA -# Note here that the v3_ca.ext file sets basicConstraints=critical, CA:true which means that the issued certificate is for a Certificate Authority, in this case an intermediate CA -# and this certificate must not be used to create further CA certificates -openssl x509 -req -in intermediate.csr -CA ca-certificate.pem -CAkey ca-private-key.pem -CAcreateserial -days 9999 -out intermediate-ca-certificate.pem -passin pass:ca-password -extfile v3_ca.ext - -# Concatenate the intermediate-ca-certificate.pem and ca-certificate.pem to form the ca-chain-cert.pem -cat ca-certificate.pem intermediate-ca-certificate.pem > ca-chain-cert.pem - - - -##### Create a server certificate signed by the intermediate CA ##### -# Create a password protected server private key which will be used to create the server certificate -openssl genrsa -aes256 -passout pass:server-password -out server-private-key.pem 4096 - -# Create a CSR using the private key created from the previous step -openssl req -new -key server-private-key.pem -passin pass:server-password -out server.csr -subj "/C=US/ST=CA/L=San Francisco/O=Server/CN=server.com" - -# Now the CSR has been created and must be sent to the CA. -# The intermediate CA receives the CSR and runs this command to create a server certificate (server-certificate.pem) -openssl x509 -req -in server.csr -CA intermediate-ca-certificate.pem -CAkey intermediate-ca-private-key.pem -CAcreateserial -days 9999 -out server-certificate.pem -passin pass:intermediate-ca-password - - - -##### Create a client certificate signed by the root CA ##### -# Create a password protected client private key which will be used to create the client certificate -openssl genrsa -aes256 -passout pass:client-password -out client-private-key.pem 4096 - -# Create a CSR using the client private key created from the previous step -openssl req -new -key client-private-key.pem -passin pass:client-password -out client.csr -subj "/C=US/ST=CA/L=San Francisco/O=Client/CN=client.com" - -# Now the CSR has been created and must be sent to the CA. -# The root CA receives the CSR and runs this command to create a client certificate (client_certificate.pem) -openssl x509 -req -in client.csr -CA intermediate-ca-certificate.pem -CAkey intermediate-ca-private-key.pem -CAcreateserial -days 9999 -out client-certificate.pem -passin pass:intermediate-ca-password - - -# Verify the certs with the cert chain -openssl verify -verbose -CAfile ca-chain-cert.pem server-certificate.pem -openssl verify -verbose -CAfile ca-chain-cert.pem client-certificate.pem -openssl verify -verbose -CAfile ca-chain-cert.pem intermediate-ca-certificate.pem \ No newline at end of file diff --git a/tests/ssl_certs/server-certificate.pem b/tests/ssl_certs/server-certificate.pem index 9cefa21d1..305bff618 100644 --- a/tests/ssl_certs/server-certificate.pem +++ b/tests/ssl_certs/server-certificate.pem @@ -1,19 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIDGDCCAgCgAwIBAgIEGlalRzANBgkqhkiG9w0BAQwFADAxMRcwFQYDVQQDEw5U -cnVzdGVkLkNBLmNvbTEWMBQGA1UEChMNVHJ1c3QgTWUgSW5jLjAgFw0xNjEyMTky -MzEwMDBaGA8yMjkwMTAwMzIzMTAwMFowJTESMBAGA1UEAxMJbG9jYWxob3N0MQ8w -DQYDVQQKEwZTZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCS -XksPE70eYVVl/WK0H+8gdn/3GPdkj0c4psG2q0o1EVWxGYxczC94CleXYg9tA2jl -fzz4/hyAwmHfbC+eHx+v2CauGam8bQlu3fsthV47b3if4E0RjJA1DJLb36xMoqV6 -w7tgjye0HymTVDLQX/thG8oBHtspQ7Gorn3h6b5mGMULaqe6Ygu+easySRimKFh4 -7ORfpuUesWffbjrD+gsfXVPbOxLgRalXX+VBe2fuWS5Otv2omqrz0iMWbyhwRtV1 -U1rcSWieNUQ323n0mKon15vGAy6i8CFT0hw7SJXf0E0i40ZByb9SDdmes0fbE+hu -uIabC95h16RqqOHw8fpBAgMBAAGjQjBAMB8GA1UdIwQYMBaAFIKNKmxyN9+AYDjw -u0i8pcJqWpGnMB0GA1UdDgQWBBTG9hDzVI5ihubr6gtgB+DqWBDzOzANBgkqhkiG -9w0BAQwFAAOCAQEAHKqpEBTjDgRLXoUkShYreqbzf8jFwyLFbhC48svxXwRNdZuE -LPXwHFwvdayoOKQeckLav2mDzmyWBF6Lt60Z80HB09SH1HkFH5s+X4R+srBMUtSa -KsDk5MhoEpLxZNLgQPDEfYOZKUSBBfsx9Pwr10b5vskdPB1X6qE+Iu1UDuZHw/2P -QnemC+lyDrBJ6SUiewwPQOJfGlSuTEyziQ/NOsZyb9EXEm8wGgodh5JCmb6T2FmF -ayW5Vu7p3z9BoxEFP0RFLQdtZhjk0rSw4/jWpPVC85eD5tYRGCqW3zEez6aLwB27 -VJ/lpA5PyZN2uN83yeyGFo4VjzNGBLG6f/cJoA== +MIIFjTCCA3WgAwIBAgIUUDzFw9TxWRcFoyQjiz6mUtohp2wwDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEWMBQGA1UECgwNVHJ1c3QgTWUgSW5jLjEXMBUGA1UEAwwOVHJ1c3Rl +ZC5DQS5jb20wIBcNMjQwODEzMTgwMzUxWhgPMjI5ODA1MjgxODAzNTFaMFcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEP +MA0GA1UECgwGU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3 +DQEBAQUAA4ICDwAwggIKAoICAQDIpXkvfopGYcJoZ8GpxAFxQ2M/gb+bY0MJlw16 +gp0H4/vRBd73FdGwiQvnxcVqID5aK9S9fLDQR8rejc0r1Zcf6dqsqbga7C81M4dr +b6I29InctoX4/p5gqdLgL9XetbD2LECg30Ymp0q8HAYZF6gydo0pATG1yH5WfZlH +IOB1+cCFCCs8yAb8T8YOA0W2zn+tfviiTKQp7m6ZY/eX4R0rLiv5I5wpQlzDa3Ut +L3CzPnbySpXO2/hFZ5cXeYSYOdNmCJ9E7X3MsioAhOhSLai8zILhtKFIuakv9ZQV +Vp4erUxISxeKggfgq93iYHlOLEEzkQ+BDlIKc1Jv1i7rT3l+TGjitPYv9ysEyA9O +g6/42nb4HHr/Yp1RbleVnMfBasAh3e9S+mJAHmHczYVKxSVCSUCSnGDe/x359kiI +XLjI3qlhLIqAhNuia+v30KxBe2Wb2+g+UjCczBDHfXIAro152f3d2X8b/XVNHjO6 +wgOzalRVdr/r07hFI4M6XQQxKbYpBMYRNcgJg9yEQFerjCI5hqQKm+xbA9O3GHN3 +i2HATD/9vdPNoW+Bp5qJyggdaXQPe7GwVE5SsVzg6Wa+oj6aGJabea7hmxIMPXrp +0y76BSx0Z3xAVf1sAUiNd5ZiXICDFWuybpoMCYCiCkL8838D3tIBOBgZYUayLNcn +AkbbnwIDAQABo0IwQDAdBgNVHQ4EFgQUaJcCk7c6O0h7XngO+UWj/q5wmVIwHwYD +VR0jBBgwFoAUy6kGdLy9fA2l3pOGHwtATYagNOAwDQYJKoZIhvcNAQELBQADggIB +AIk1YBQQ23gd6YerKFIxjVb38W+XO8C/b/VW2F1KbVxeKNhhs6mdCl8FO15b09RM +zNDYqIqEuqEwv7ES4nO8RjKhCZU8vBKFWpqkPirh9cskgrtj4kt4iZBdUbsI07iO +KoW0YFTt/aWz2JDrQy1wrKDK0ueeC672Ak+CnzxM6Dc9FsexAgEvrEJ30on0jFRt +AwoZtc7a5K1OXoOIxQfXfXZjN4vJJplFPVTHeUnP4dCOPOiVzDJzWfAMtNFrM1h5 +EPk6hVE+YXMPyYK02KYde6AStxU5V3HbzE/VKSTHzFVCePA6eOuA/+PQksA3Uvto +36/p0ug5Ym4LvI2VBFxrgeAFgLnnMrpayHR6byOxzerQwltVA+ZqKdhxxcIZGbOZ +tOVJX+iXQ3gpPnG/iQ9m+YecbDAC+QPhnVRM3aEXIgjZ++OAm4H+aYC8gqb47hHA +W0Vxh6lTu62rP/bhjyOnqMc/R2YWdIfovmLEiw6lwswha9hnVpxHijLdmSUYQ4CR +f9myzSvQ632NyL4glWtRjFDcJLkEh7zbrr9eXsN/25UJoJPURYcq99FtifEEf85H +lVXLqWLELKRwnD9zfavqkHdc6Wpeh/WHmfUQgxnbdbKHWvI5KwXls98uVg/6AqtC +D5CqHX26sNXam4ocx2dNEpWheSgrWMrdIIqWOk+JS0SW -----END CERTIFICATE----- diff --git a/tests/ssl_certs/server-private-key-no-pass.pem b/tests/ssl_certs/server-private-key-no-pass.pem index c29de7275..4f8d3727d 100644 --- a/tests/ssl_certs/server-private-key-no-pass.pem +++ b/tests/ssl_certs/server-private-key-no-pass.pem @@ -1,32 +1,52 @@ -Bag Attributes - friendlyName: server-certificate - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 38 39 39 39 35 33 38 -Key Attributes: -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSXksPE70eYVVl -/WK0H+8gdn/3GPdkj0c4psG2q0o1EVWxGYxczC94CleXYg9tA2jlfzz4/hyAwmHf -bC+eHx+v2CauGam8bQlu3fsthV47b3if4E0RjJA1DJLb36xMoqV6w7tgjye0HymT -VDLQX/thG8oBHtspQ7Gorn3h6b5mGMULaqe6Ygu+easySRimKFh47ORfpuUesWff -bjrD+gsfXVPbOxLgRalXX+VBe2fuWS5Otv2omqrz0iMWbyhwRtV1U1rcSWieNUQ3 -23n0mKon15vGAy6i8CFT0hw7SJXf0E0i40ZByb9SDdmes0fbE+huuIabC95h16Rq -qOHw8fpBAgMBAAECggEAUJEKq8/np5NYGH2zGQcC71GK+o6Jjpa3U12m/0+l1zQl -gQhZFLNn5aF7uV5Gs3DPCpRREZV+Rvm4Oe0pANaM8JQAJpEFtywlpxsVKWdiCUFA -mnAyu9gtgKXdKJ4kD0Lv53VZ7Lqf1ivZDLmqBrzud/N968P6SupyHjF+JR4uqa9Z -NrXACB9nrDOTWsD8lgk0A7vMfggoMuTilJMGietRwQREZXqwLxm0d+/w0uk/GSYD -hnwGa9q8lDM5S4v3QE8GgtTQ861JbOOiTlpzpAMqHHtJ6mIIQfItOHaR4XJMqG6n -aQdseJ20xOFgVSuxLS/irSJGU0vFcQeztOopbqMQyQKBgQD3LVEECRb09ovsABgc -8Im4FzZ+tDtAdlYXGkLczcsmloKleQ7bdGK75nbdBChk/JJ+sa9xEc0uTK5knFiv -cZvNK1rdMKhSVk4slq/xEp52qc4vj+X2XvPJ71LSQyn5Sj1pqjr5hlVrHE54J49F -xmwehbcpK57k4gSMG2DXixi0MwKBgQCXl8uO5pBWnCJguYxKs2Dtz/OCxD1QwUzm -K4hPNVfez/m2GzbHTEA8VBwUROrDdMUiWLFyndzTl8Ubv4aBw5+AdmZReENORWzz -4JuL2IThapxcSgNepMscknENBi8A/rE/pKoFSL60cueGNRJkYCBjVGz+pJeXUgBW -UJLrsU9DuwKBgQDySvepjwdz5OHP21/HrnoxfH+swBt82wMU7mcwboJ/NvamIf7Q -XEwUiJtnl5XJOFSZ4K9rlNmeUwh4E1JT2HezLJaLUBEPdQ54YtXg6DFxfI5izRnM -4dLz1adH+RKCDaYZmAbrZiUBM+QvoAevjAub99NNhjbhgjSW6peJcg4rRQKBgDLA -Yij8bfRhH21MkXhs55WWmkr8Uh7YKFui1WbXUAzLiil6oPNGRvcrnZ73l7VKQbmH -AhmCWm0IkbgBfhGxoZPLj+PUpIvu2FQprUm7enlRTPnk2Y31E/8D1A9tM41ct5C8 -GyPbv+T/1x1qZTbvq8g1eqwiBJJnpK9BGH8KBKSDAoGAaHl5KLcL5jlDZCPtVbs+ -Af4RjHG18VTNFRmlg6vS2sd2lbpteNBnRbAeENlorgQOZCTtVkW7sdmtdobXXqhK -t3gKezkc0HEEkJ7cJXj133ApTkOJKJ7HAmPxM44+VW2OQ34F9S0liooPUxOjkDbT -HfdyvMcS4ZtAE2UjeYMXIfU= +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 +B1MQlGNxdd/AwIeAQMtFFLJYsHmGKcs= -----END PRIVATE KEY----- diff --git a/tests/ssl_certs/server-private-key.pem b/tests/ssl_certs/server-private-key.pem index 83ea74d07..5fb0c99fe 100644 --- a/tests/ssl_certs/server-private-key.pem +++ b/tests/ssl_certs/server-private-key.pem @@ -1,34 +1,54 @@ -Bag Attributes - friendlyName: server-certificate - localKeyID: 54 69 6D 65 20 31 34 38 32 31 38 38 39 39 39 35 33 38 -Key Attributes: -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIZaEZ9J0MiLgCAggA -MBQGCCqGSIb3DQMHBAiDdI6zqmzMsASCBMjEm3M31BwmYOHIwN2Pl7AX+6/2r2a8 -BuzCixorpA4y+OVhLVgNGverxveG+ulthHZB9lUxG3HWfz01cViXbUIxLKjcIdAs -jpn1zn05oFGxNuGOzJz/sWBHcXytbgaNSapCKYEpsx44x81K8Sn0nj+qphKp0IiR -fR1KKjtgqL/gTSTxz8X2K/7PfuvCJwEaMsSXsfmK+skQmae4JtrQ8IuVTkuzmEGT -sTGieSzfdNajrE5CUE6y82QD26TkOEvAnVnH+W3UG1JI+uRymJN3z+PBpfudcZ7C -N36NlCaE/VyLmNWtNuOhfvm3+jAejRxODKasdtTZewsHK2O/k0e+wXCCjAQ6JIY0 -xXQ3w4tl9EKT6tXaU9CWxi1H9C+JRzY5XkWtCE9upSjcLuKnETrCBcQna70WxBRd -j7yfox4vZ0XtWkQrPSGZgpmXk6l/hPaJCKJWGfGRkQLklQwIu0f3pD/JEJf4ZrBR -PIx2EF7GpTLWW0PUua/fyDSHKE3UmBt+tbBvyQ0O5POKHTQ2Po1aoiLfxZE1q/FE -U41LlWP3qSHjM7ozOthcQeJg3eBshWcWnmGsMl3mehTB7oUL8RMncArl2+rnzz+B -KX0CwBQFkHHrM/GT+f6FRxBsNHVgTbS/hHqM5jWXnR9guJQnZ1/9HvoFotdOPooq -/kZNN5Q78S7XplpscuuQLhOApzkUeIDMcjjEaDbfTusv4YLadfBHyTbZ3gmwFwFP -ZYuk3ecd9/ZUee/L55hynol9wsefITjy/XM7gk/U7Teeo2xt29NreOFQlKy6L5Mh -b+ArxZMeWdZMqrd/2v1ILJFJvhOH7IFwTLoW/mV8a9nXsi8tt9u12XGn6hCLzVA9 -35WzgyhT4KDwXy2F4BuAgfq/JpDepnoOkX6HocG9+SCOU+MmUCMAxG4HVd/3VJM6 -RbwAm5f01RwjENcrHiWjVyAs/7cjD8XO+OgCFA3XyOr/tKmdlC6r9o/DEwSoGVJS -a3X41MiVz++A5bdvbXe+ESE8NNefwV8CyOPEVdp/8h+MalV9H5UcTI4jyQ/xK183 -mSrRGjt8rJSWk9RQVT+kWfTignEgbml2yptqUPAfjp4IMhQ9F4U2VE5HOMHkje0L -PV8pzaUv9KANLTEAkLAQ1kubyxiMD/ikyOOm7WDFdpkpL91Vsm3y0ORfWA6RtwCP -RqOJVCeW/oUhfSNKkUL9wqGg4mk7FA36Y6vW4iJXqJ3Ferf7DluWAQCi0s33cy8I -4xBQsOQp2C13VUFsxoNG98V/UwGavAO4lVEKRtvsCyd0HtA5CvexEkAHNrJpluLN -Fyx5wUb25f1Hv4ESs3tLN5COBMZM/6dx+U8dP0eqm+6ThHxTyh+Stqv9z46+tgFa -9rovoxBIrlYG+z3iW1aNk4wocKJ69rNlUI0cKWDuxiV8ZFvR+7rgqMv/IoDWHi6+ -KEULAQJAMfVE6HmxkNY+G8w0tzOuqAn+GfXrnNnQNvH0JgmYFypnRtLLK+vFMCeN -Wn5olygNXI7g6hRKe31jM2aCzlnMtQNrGEw5AfoGOE4hpqihVZC5GdjHA0Z0TxUG -gtCQhkhWiJcvtyyHKaJgWo52/po4xCWgVGPUfnxI0yuDYIMAtIqprNTHBaNFtKES -d/M= +MIIJtTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQreBY171KTj0+1Ugr +EjmPhAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEK3tisUCXeGuT0dg +EKUSHGEEgglQEs+2dKuj+z1wCpclPshRkyNVo86K9RXklI6ij6aPRs55BkWRd+yM +JLxqsx7GM+GoG2coNASd7KkrQ/azTg34gGezhoeFnz+FcazvwSNofxJk1n3tnwUW +QCUWdwGeT63j2U6r0thH+K5X6uHCmxUKIC2SHWeR/Bwbfk7KqcnbhRttqFTJbmb/ +nmpE09aniA+zK8nrFzQ6c6Uit9WBTBVhLZVucZlEMEWaTv2dbtGGnmuzcse3b0d4 +8r+2Vpu1aElVEL8oddxFuoB2oNOQvEQPSOLD/ff7ELmrg5a4fDkziaMJ+PSXXgTv +ivJnsXpJztEDBELfUa3yQWa/bigQuz272+6GZdEDaxHSEleCamCNHuuaWthT1AUD +yDXd6TONxLsUTKPMltw841ciu5jS9xQWABRvcdcmk2PjWpe3EElR+Z6LmXt+xMgF +S+eTM27GICDfmdkvb77TxF/q4+dObnOWoSc4FDiePKB7scftOG80axq43eZfee4x +8Fe0VXkw6jE8fGmNpUOuUUC4Wi6RaoxiRUOG/4oOv9r0OokHJo4jG6bOmwc/tffj +SoTkTLpXJ9XVNIgKsFoel5UQuqRKh9BNrxhylzBvZKyaWLG5Fn8Rb+6TmASaYxIq +L2vkwtkCByt670HAz/XOnM+BgA2ZeTOar8DUVPm1Z9XvsKKt4gvi6IE7+FQXCQIT +rDlyPe9j6i/QIwkbLMT0sV/cag42RMTMu1duhPuEsCEzkc85bAEsvReGo5LZKm3W +sb8FjK/yZR6PSjitcmR95u6cZFrcuXg3+NHvCbyOD2LJw1gK6+QAH5MWz23Kcu58 +bwd38pV0/Jgp+knVsa1SVnJq16BOBA6iGsK83i8uDwqhyon9cs7G6LiFORn30Blm +HyS2cEsbeehhZ0l6cY275dgUFGfkqLxLc1W/ChI7gJaiSuXaCS1WWRK1XfW+HOxR +oNGVhH57toSeaY7fliRpIsDmKUZGGd6ONcBV3cy59+D+aNeF5NhpopiWBH+Wdm3i +z1Jy3b931JZsB3XvWaLBB1w2v9xHJDsgBw2vp+QGb2h6dloqvHqECW9vQtFQGFnz +N2dkWKM83XwcC/wSWd/qmnuiEXVr1YniclGKw/R8amLlpWnh5vZaVPpnT6i/BaQ2 +YETPYaPEfJn0k7VDT+d4zlWCdTwMw3Zlejf3i3pzg38IBj/ob/RPX/YgswOnHKeT +A0fu/IBks25k+L/vzQin2eY6RXC+aimDsk95UOdCHeLBLxyUSL+H6lZ8bTeNT/6Q +HaxpDQusaLfQxuAKmEsN6exaGoPzinW1XODWMazybT46e/gt9pAldxx4Ug4s0MrB +lsv7CPIu0CsJlcZHZfxrvkdJfpfpkXfuV+0+c61UtdSw959mIyjNvu4Jx7kMx0Yz +VFapcGkkriDUzn5EVN458xmzFIqf8l+RQWj0pCeUDITMJQ/kFHuJ+9TUzTCnoFeV +aMeksXG4tDp+z407vYH7EvT+KizoOTcNuCMjvOPYo16CkrnCgZGSRHUBqZ3CvQNf +VlyyZqZbX8e4uWGgbbt5C9Tn0HEsHccewM2dp89W+1rMljQiFtTHcnD5xcFYI8Re ++kglGeL5A4E74PEUpiNOleryOd5iP9PU3DvCRubIi/JtpedRSzClTHlIAO2yAurZ +IOi6bGYDHJUhbqu5W7pWBtLWxXjGHABnIcR2oms003lznajt5+AJ0C7XDFdkedAl +q7aYJINjcE3aqy1ZOtXcvxrWLPc7QKTNdEnhHlZsqDn135LPxWuSdTFzYwLkNQCY +NF5LP7KC5h2fkOd+6S7wYXAxpRM9y02b4m3LP6jD+jpSRCJMU3pmXN+5zQSRYTo6 +ufJOKQJF6aqqpf9gJnvfTqHCIesEAXCE0ZvR9CY6Gc21Nf+GBNEcUFSH3q900SzB +1zVexwym7mP9LUR3EsdPwHdTeL9vJou7wk3rcJo41JijtfgW5ctolO9lu8v18Bt1 ++OvG8TWvKcRKxCFkFSA6TYUCt5vBhJXEcRMC165RGHHYvT4zpkDfv+kdv6vX/lS9 +rqz0CKem04kOnGTLKyWSQ1FFU7xfKBiHpdOudIRXlKV2ul7fqOGfuuK5ELSvTPZ2 +sBi60803IDQIO24gxfMTKpJVl0eLx9lkynkNPy5zZ5eoq756sRUfWClR1vdXOEvR +bomLD+SKwChXVeH5KiDxQlB9ErUuDTbru5/f7pgvEj3PJpcWczWtu20ZdCXAa6v3 +dspasATtO9+KyV3tBQnn6PFYo6enXW0e1jK8TTlaskHOvEworjov5NSiNfOIL+E4 +1EOIe+iLJ7sWAA9WHrYOfsiVg9by11r1PpuzJ9shT+rCX6zZfKildv1utTNk+Mw7 +vSYc0lLdwik9XXIzQDTHv+fzasVgQCOqpYLuPZK5x2qhdTaNID13AV/ZFa30FouY +4If2DSdTVaOMYZQDRhsoi33seyEb0k2WeIdznNF+iSbdpnTHb8N86YQlIeLBX6Io +lNaDRTT9sY5sCKOeb/9wZykrkZHJqOeGFYG7/NCMtARhHsit3Sku2ro0wMxrIJdb +TPgzqJrd1x1a4udITR2ss7Y26pBFC7E7Hp/ypYVU8EuXFd5sHuvKAHmTuynA+Nly +uuvooXi7/1e+uvv8F5yMA3jmNywwG/FnVDQ2a8ZJqmiUmY0NEwebhseit4/gUUzj +4MjrQDQudUxbQA20ZBgMovk5aioF9tSrMNQd8H0ke98g+gm+cYztp/RjDC0t0aDt +Br87VwhzeIi7VXsUZlmC8TONZnybtCBT1Xno18U4VtcaSPKc24ZuokdkvVqHjCwv +M03jf/wwZfTlwOioEZuD+o/J8Nxtw127OIc/m0JuDHGGb8kH+lTbq85XTE9FvWat +yVLcmgbzds88ZpYlP5g/+H8Nehbm2E5FtuIuyNwNe7Gg5pn+02a2NdBqlrL/sLFQ +ojofbY8PPT+tOVN8pOdcdhW7olbqKZYFhXqXhwHD74nXs8PJITlMMWA6/RDXr71j +JdwjtfPJJcs1rDaFWc+eq7QLmfC4VyoPuLHguq0Dzdugtv4Q3SMzofHOFe5Msn7F +OAqfg1fmfY81OpzcKYXtVtSUO0KlziDjqCoE27YhUYQmuTnxr1mMF3b7kxZKdI9Y +QRoWc5b3wU++Y1e6qPCDbPZvq+85GYyVmf3toWt40fudwychz0F497E= -----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/ssl_certs/server-request.pem b/tests/ssl_certs/server-request.pem deleted file mode 100644 index f051971c4..000000000 --- a/tests/ssl_certs/server-request.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN NEW CERTIFICATE REQUEST----- -MIICmjCCAYICAQAwJTESMBAGA1UEAxMJbG9jYWxob3N0MQ8wDQYDVQQKEwZTZXJ2 -ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCSXksPE70eYVVl/WK0 -H+8gdn/3GPdkj0c4psG2q0o1EVWxGYxczC94CleXYg9tA2jlfzz4/hyAwmHfbC+e -Hx+v2CauGam8bQlu3fsthV47b3if4E0RjJA1DJLb36xMoqV6w7tgjye0HymTVDLQ -X/thG8oBHtspQ7Gorn3h6b5mGMULaqe6Ygu+easySRimKFh47ORfpuUesWffbjrD -+gsfXVPbOxLgRalXX+VBe2fuWS5Otv2omqrz0iMWbyhwRtV1U1rcSWieNUQ323n0 -mKon15vGAy6i8CFT0hw7SJXf0E0i40ZByb9SDdmes0fbE+huuIabC95h16RqqOHw -8fpBAgMBAAGgMDAuBgkqhkiG9w0BCQ4xITAfMB0GA1UdDgQWBBTG9hDzVI5ihubr -6gtgB+DqWBDzOzANBgkqhkiG9w0BAQwFAAOCAQEAXUxI8QCZpaWC+ysJw8kmLTtV -szM+Xnt29fA1kgh8BVaMx2xXZpgcBJX/Afd5c5Vamvyqd35LxQrMaD/z2A9dmPsX -YFKvbTf1IKJv/bQE9hM4c57TR0Gg2CNggzr7y22MsMsM78eGmOZcgEgD7ro3s3x0 -ttL4PYYJCbgo0cmhDaPdS3fKR3U1NhwR9Q6s2e9D5/YcJ6ZMLEhh+b4GhPICigQy -fC4iLIdD67+k6R2F3TeyVyG9y9y0S/qfQbt4Xc3gHF0YI/2VH9dN7+gbAvDL9xPc -AmBDNUEKXQKv6TyvojCxC8VZJrlIY9h5c+pXQq4TtvezNEpb+0c6T2Q/gbxe5g== ------END NEW CERTIFICATE REQUEST----- diff --git a/tests/ssl_certs/server2-certificate.pem b/tests/ssl_certs/server2-certificate.pem new file mode 100644 index 000000000..35ecec696 --- /dev/null +++ b/tests/ssl_certs/server2-certificate.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUeMJsAv6dqkNN9/Sk/ADeiz87neMwDQYJKoZIhvcNAQEL +BQAwaTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3JrMREwDwYDVQQHDAhC +cm9va2x5bjEaMBgGA1UECgwRVHJ1c3QgTWUgVG9vIEluYy4xGDAWBgNVBAMMD1Ry +dXN0ZWQuQ0EyLmNvbTAgFw0yNDA4MTMxODAzNTNaGA8yMjk4MDUyODE4MDM1M1ow +WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNp +c2NvMRAwDgYDVQQKDAdTZXJ2ZXIyMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4+ws71tRS+/COpQuceDTkrSxr1gy2 +MBR+d8kpY207MuyhL1ezMBxOLrq6bnukUiU5eN/zDbxpzqPLZUuCccHQoVtDK8GC +UKGYtpsPIoF5WoFw+HZmxEMLC31NHK3iDJn9D1hTluLCiwmq85/qRDqV2jvuAFdl +RBHMi1EUWsIwwcQ+6mlHEU3mYnHdQQtJlsTtos0lurVoxdQWeKrz5G8OHqEjxwjJ +2RjfNO+et69s7AXUKduGGPF3VJWmf173DQLloBCAk8SnLs1N+VPB+Fx8dstGDTCa +pQOkJHttvSwc80KvfdaGYRgv6GDWg9q3K1pDdF06ucbd/NAVHM+r73Ol4EhgdQic +H8K4jOjqXlWcZ9jRs1QtPCeO5MNjCpqQv9f+ozaFpBRWtLnAFMoc3aOe9gpOQPf7 +cD64m751TG6AS3ZT7bsD4UcBv0lZj9jUC000j0ufeIJ3itaTFwJETexv7DBQLeDP +2yu2AYcoSoCi3qL4ltf3RjOzXcHBvs93rYl+f7iIPqAdPrRtbLdiqd28BxaUmBoK +cMecTb6dPOmtMlPAmT97Dat6fTIMBpFdOs6LNxkT+cUN/FLcUd2ufsIam/lBLXkL +mtpRtNsofmr5aQJ71vypa6ZnnQGapKtOrgjOo6AFHkzitw/j6hHjXQgNNXHL9UTK +345ogSxai47N4QIDAQABo0IwQDAdBgNVHQ4EFgQUXEu9Wbx0fuepIofaQcHhCj1d +XDgwHwYDVR0jBBgwFoAUB+opzkuS5ndKos3n8GQPdq6mTX4wDQYJKoZIhvcNAQEL +BQADggIBAEQZJMcCg7GjZb9bqYSSXXWNNs9IIgOiz4RJ7ZDIEC6CgNeEr2ODKF+8 +JNzL1UygESypLBqrgSeeiMPnhP3x+LxVW65v2J3Lr95t/9Hawi15rYjPnfxkeU8u +maVFWfW3zavMOlGTBI0fSftD7fobvD31AFHBu37IgTNKGCmxB605k4mlySh2DnqI +NkImbV7os80rwE5u8K223FAVm7weGlxuH16diPAN3X6urM5MJEES7Wu0qjjVjsiq +7H5954VyNIoGA/s7MYpz/jQJLUwVtb1zPPBKRiQj5VjsKjdWpBpC8mM7TDg5s8bA +MNfjB5DOWizDAaaOiKbgoNO2XN8EKvH0tm8kyxh2aOX4hL93rN5ZLwzozneTsiEK +uguKWpWykFYVi/aB1+8WdMUKD90oEHwQCsssbg/PKuvqA97yVHLOmP3fqc8OJhl5 +hWGsVdvCmXcyfNfFTxHyTKGN+VybuDLAP6OF7WB0bKxe7g2tElYhAHldP46JRavq +Ow1kYNE3HXU086z8ojURM9+s9T65BK4eOoINCh0oRt+PaKbhSSbTl48MEIGf+RTQ +W3RX7/ESctcCDrghgiDvli7QB+BcBlbxPn9XKfAgBelJdCweCylKQoWH0HjrIN+u +pmHz5KzQiFvQFR6Mud7tySNmG7yrBxN4zX9NOsYgNyGYaKExEzxP +-----END CERTIFICATE----- diff --git a/tests/ssl_certs/server2-private-key-no-pass.pem b/tests/ssl_certs/server2-private-key-no-pass.pem new file mode 100644 index 000000000..92131a9a0 --- /dev/null +++ b/tests/ssl_certs/server2-private-key-no-pass.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +hZMY2Eyu4AkDf6Xvhv6j3eMuiWUdQw== +-----END PRIVATE KEY----- diff --git a/tests/ssl_certs/server2-private-key.pem b/tests/ssl_certs/server2-private-key.pem new file mode 100644 index 000000000..ec44aee53 --- /dev/null +++ b/tests/ssl_certs/server2-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/system_test.py b/tests/system_test.py index 5b57696c2..ede880018 100755 --- a/tests/system_test.py +++ b/tests/system_test.py @@ -123,6 +123,17 @@ def ssl_file(name): SERVER_PASSWORD_FILE = ssl_file('server-password-file.txt') CHAINED_CERT = ssl_file('chained.pem') +# Alternate CA and certs +CA2_CERT = ssl_file('ca2-certificate.pem') +SERVER2_CERTIFICATE = ssl_file('server2-certificate.pem') +SERVER2_PRIVATE_KEY = ssl_file('server2-private-key.pem') +SERVER2_PRIVATE_KEY_NO_PASS = ssl_file('server2-private-key-no-pass.pem') +SERVER2_PRIVATE_KEY_PASSWORD = 'server2-password' +CLIENT2_CERTIFICATE = ssl_file('client2-certificate.pem') +CLIENT2_PRIVATE_KEY = ssl_file('client2-private-key.pem') +CLIENT2_PRIVATE_KEY_NO_PASS = ssl_file('client2-private-key-no-pass.pem') +CLIENT2_PRIVATE_KEY_PASSWORD = 'client2-password' + try: import qpidtoollibs # pylint: disable=unused-import @@ -653,7 +664,12 @@ def __init__(self, self.openssl_server_cmd.append("pass:" + server_private_key_password) if cl_args: self.openssl_server_cmd += cl_args - super(OpenSSLServer, self).__init__(self.openssl_server_cmd, name=name, expect=expect, **kwargs) + super(OpenSSLServer, self).__init__(self.openssl_server_cmd, name=name, + expect=expect, **kwargs) + # avoid startup races: wait until the server prints "ACCEPT" to stdout + # before returning in order to ensure the server is ready for + # inbound connections + wait_message(r'ACCEPT', file_path=self.outfile_path) class Qdrouterd(Process): @@ -1480,6 +1496,10 @@ class AsyncTestReceiver(MessagingHandler): """ Empty = Queue.Empty + class TestReceiverException(Exception): + def __init__(self, error=None): + super(AsyncTestReceiver.TestReceiverException, self).__init__(error) + class MyQueue(Queue.Queue): def __init__(self, receiver): self._async_receiver = receiver @@ -1521,8 +1541,13 @@ def __init__(self, address, source, conn_args=None, container_id=None, self._thread.start() self.num_queue_puts = 0 self.num_queue_gets = 0 - if wait and self._ready.wait(timeout=TIMEOUT) is False: - raise Exception("Timed out waiting for receiver start") + self._error = None + if wait: + ready = self._ready.wait(timeout=TIMEOUT) + if ready is False: + raise AsyncTestReceiver.TestReceiverException("Timed out waiting for receiver start") + elif self._error is not None: + raise AsyncTestReceiver.TestReceiverException(self._error) self.queue_stats = "self.num_queue_puts=%d, self.num_queue_gets=%d" def get_queue_stats(self): @@ -1537,23 +1562,34 @@ def _main(self): if self._conn: self._conn.close() self._conn = None + self._ready.set() self._logger.log("AsyncTestReceiver reactor thread done") + def on_transport_error(self, event): + self._logger.log("AsyncTestReceiver on_transport_error=%s" % event.transport.condition.description) + self._error = f"Connection Error: {event.transport.condition.description}" + self._stop_thread = True + def on_connection_error(self, event): self._logger.log("AsyncTestReceiver on_connection_error=%s" % event.connection.remote_condition.description) + self._error = f"Connection Error: {event.connection.remote_condition.description}" + self._stop_thread = True def on_link_error(self, event): self._logger.log("AsyncTestReceiver on_link_error=%s" % event.link.remote_condition.description) + self._error = f"Link Error: {event.link.remote_condition.description}" + self._stop_thread = True def stop(self, timeout=TIMEOUT): self._stop_thread = True self._container.wakeup() self._thread.join(timeout=TIMEOUT) - self._logger.log("thread done") if self._thread.is_alive(): - raise Exception("AsyncTestReceiver did not exit") + raise AsyncTestReceiver.TestReceiverException("AsyncTestReceiver did not exit") del self._conn del self._container + if self._error is not None: + raise AsyncTestReceiver.TestReceiverException(self._error) def on_start(self, event): kwargs = {'url': self.address} @@ -1607,7 +1643,8 @@ def __init__(self, error=None): super(AsyncTestSender.TestSenderException, self).__init__(error) def __init__(self, address, target, count=1, message=None, - container_id=None, presettle=False, print_to_console=False): + container_id=None, presettle=False, print_to_console=False, + conn_args=None, get_link_info=True): super(AsyncTestSender, self).__init__(auto_accept=False, auto_settle=False) self.address = address @@ -1622,6 +1659,8 @@ def __init__(self, address, target, count=1, message=None, self.error = None self.link_stats = None self._conn = None + self.conn_args = conn_args + self._get_link_info = get_link_info self._sender = None self._message = message or Message(body="test") self._container = Container(self) @@ -1651,7 +1690,7 @@ def wait(self): self._thread.join(timeout=TIMEOUT) self._logger.log("AsyncTestSender wait: thread done") assert not self._thread.is_alive(), "sender did not complete" - if self.error: + if self.error is not None: raise AsyncTestSender.TestSenderException(self.error) del self._sender del self._conn @@ -1659,7 +1698,22 @@ def wait(self): self._logger.log("AsyncTestSender wait: no errors in wait") def on_start(self, event): - self._conn = self._container.connect(self.address) + kwargs = {'url': self.address} + if self.conn_args is not None: + kwargs.update(self.conn_args) + self._conn = self._container.connect(**kwargs) + + def on_transport_error(self, event): + self._logger.log("AsyncTestSender on_transport_error=%s" % event.transport.condition.description) + self.error = f"Connection Error: {event.transport.condition.description}" + + def on_connection_error(self, event): + self._logger.log("AsyncTestSender on_connection_error=%s" % event.connection.remote_condition.description) + self.error = f"Connection Error: {event.connection.remote_condition.description}" + + def on_link_error(self, event): + self._logger.log("AsyncTestSender on_link_error=%s" % event.link.remote_condition.description) + self.error = f"Link Error: {event.link.remote_condition.description}" def on_connection_opened(self, event): self._logger.log("Connection opened") @@ -1680,9 +1734,11 @@ def _check_if_done(self): and (self.presettle or (self.accepted + self.released + self.modified + self.rejected == self.sent))) + done = done or self.error is not None if done and self._conn: - self.link_stats = get_link_info(self._link_name, - self.address) + if self._get_link_info: + self.link_stats = get_link_info(self._link_name, + self.address) self._conn.close() self._conn = None self._logger.log("Connection closed") @@ -1711,13 +1767,6 @@ def on_rejected(self, event): event.delivery.settle() self._logger.log("message %d rejected" % self.rejected) - def on_link_error(self, event): - self.error = "link error:%s" % str(event.link.remote_condition) - self._logger.log(self.error) - if self._conn: - self._conn.close() - self._conn = None - def on_disconnected(self, event): # if remote terminates the connection kill the thread else it will spin # on the cpu diff --git a/tests/system_tests_one_router.py b/tests/system_tests_one_router.py index 02db22682..c25b97ac5 100644 --- a/tests/system_tests_one_router.py +++ b/tests/system_tests_one_router.py @@ -36,6 +36,7 @@ from system_test import CONNECTION_TYPE, ROUTER_ADDRESS_TYPE, ROUTER_LINK_TYPE from system_test import ROUTER_TYPE, ROUTER_METRICS_TYPE from system_test import CA_CERT, CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY +from system_test import SERVER_CERTIFICATE, SERVER_PRIVATE_KEY CONNECTION_PROPERTIES_UNICODE_STRING = {'connection': 'properties', 'int_property': 6451} CONNECTION_PROPERTIES_SYMBOL = dict() @@ -402,7 +403,10 @@ def setUpClass(cls): 'port': 9999, 'sslProfile': "BrokenProfile"}), ('sslProfile', {'name': "BrokenProfile", - 'caCertFile': "/does/not/exist.pem"}), + 'caCertFile': "/does/not/exist.pem", + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password"}) ]) cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) @@ -431,7 +435,10 @@ def setUpClass(cls): 'port': 9999, 'sslProfile': "BrokenProfile"}), ('sslProfile', {'name': "BrokenProfile", - 'certFile': "/certfile/does/not/exist.pem"}), + 'caCertFile': CA_CERT, + 'certFile': "/does/not/exist.pem", + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': "client-password"}), ]) cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) @@ -451,7 +458,7 @@ def setUpClass(cls): cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) - # tcpConnector with a bad path for self identifying certificate file + # tcpConnector with invalid password name = "test-router-30" cfg = Qdrouterd.Config([ ('router', {'mode': 'interior', 'id': name}), @@ -467,7 +474,7 @@ def setUpClass(cls): ]) cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) - # httpConnector with a bad path for self identifying certificate file + # httpConnector with invalid password for index in [(31, "HTTP1"), (32, "HTTP2")]: name = f"test-router-{index[0]}" cfg = Qdrouterd.Config([ @@ -496,6 +503,9 @@ def setUpClass(cls): 'sslProfile': "BadCipherProfile"}), ('sslProfile', {'name': "BadCipherProfile", 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password", 'ciphers': "Blah-Blah-Blabbity-Blab"}), ]) cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) @@ -517,6 +527,125 @@ def setUpClass(cls): cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) + # tcpListener with an invalid private key file + name = "test-router-36" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('tcpListener', {'address': 'foo', + 'host': '0.0.0.0', + 'port': 9999, + 'sslProfile': "BadProfile"}), + ('sslProfile', {'name': "BadProfile", + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': "/file/not/found.pem", + 'password': "server-password"}), + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, + expect=Process.EXIT_FAIL)) + + # AMQP listener with bad path to CA Certificate file + name = "test-router-37" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('listener', {'host': '0.0.0.0', + 'port': 9999, + 'role': 'inter-router', + 'sslProfile': "BrokenProfile"}), + ('sslProfile', {'name': "BrokenProfile", + 'caCertFile': "/does/not/exist.pem", + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password"}) + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) + + # AMQP connector with a bad path for self identifying certificate file + name = "test-router-38" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('connector', {'host': '127.0.0.1', + 'port': 9999, + 'role': 'inter-router', + 'sslProfile': "BrokenProfile"}), + ('sslProfile', {'name': "BrokenProfile", + 'caCertFile': CA_CERT, + 'certFile': "/does/not/exist.pem", + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': "client-password"}), + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, + expect=Process.EXIT_FAIL)) + + # AMQP onnector with invalid password + name = "test-router-39" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('connector', {'host': '127.0.0.1', + 'port': 9999, + 'role': 'inter-router', + 'sslProfile': "BrokenProfile"}), + ('sslProfile', {'name': "BrokenProfile", + 'caCertFile': CA_CERT, + 'certFile': CLIENT_CERTIFICATE, + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': "invalid-password"}) + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) + + # AMQP listener with invalid ciphers + name = "test-router-40" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('listener', {'host': '0.0.0.0', + 'port': 9999, + 'role': 'inter-router', + 'sslProfile': "BadCipherProfile"}), + ('sslProfile', {'name': "BadCipherProfile", + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password", + 'ciphers': "Blah-Blah-Blabbity-Blab"}), + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, + expect=Process.EXIT_FAIL)) + + # tcpListener with invalid values for sslProfile versions + name = "test-router-41" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('tcpListener', {'address': 'foo', + 'host': '0.0.0.0', + 'port': 9999, + 'sslProfile': "BrokenProfile"}), + ('sslProfile', {'name': "BrokenProfile", + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password", + 'version': -1}) + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) + + # sslProfile with oldestValidVersion > version + name = "test-router-42" + cfg = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': name}), + ('tcpListener', {'address': 'foo', + 'host': '0.0.0.0', + 'port': 9999, + 'sslProfile': "BrokenProfile"}), + ('sslProfile', {'name': "BrokenProfile", + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': "server-password", + 'version': 1, + 'oldestValidVersion': 42}) + ]) + cls.routers.append(cls.tester.qdrouterd(name, cfg, wait=False, expect=Process.EXIT_FAIL)) + # Give some time for the test to write to the .out file. Without this, the tests execute too # fast and find that nothing has yet been written to the .out files. for router in cls.routers: @@ -617,10 +746,10 @@ def test_48_router_in_error(self): self.routers[12].wait_log_message(err, self.routers[12].outfile + '.out', timeout=1.0) self.routers[13].wait_log_message(err, self.routers[13].outfile + '.out', timeout=1.0) - err = "Adaptor connector tcpConnector/127.0.0.1:9999 configuration error: failed to find sslProfile 'DoesNotExist'" + err = "sslProfile 'DoesNotExist' not found" self.routers[18].wait_log_message(err, timeout=1.0) - err = "Adaptor listener tcpListener/0.0.0.0:9999 configuration error: failed to find sslProfile 'DoesNotExist'" + err = "sslProfile 'DoesNotExist' not found" self.routers[19].wait_log_message(err, timeout=1.0) err = "Adaptor connector httpConnector/127.0.0.1:9999 configuration error: failed to find sslProfile 'DoesNotExist'" @@ -631,18 +760,51 @@ def test_48_router_in_error(self): self.routers[22].wait_log_message(err, timeout=1.0) self.routers[23].wait_log_message(err, timeout=1.0) + err = r"Failed to configure TLS caCertFile /does/not/exist.pem for sslProfile 'BrokenProfile'" + self.routers[24].wait_log_message(err, timeout=1.0) + err = "sslProfile BrokenProfile: failed to set TLS caCertFile" - for index in [24, 25, 26]: + for index in [25, 26]: self.routers[index].wait_log_message(err, timeout=1.0) + err = "Failed to set TLS certFile '/does/not/exist.pem' for sslProfile 'BrokenProfile'" + self.routers[27].wait_log_message(err, timeout=1.0) + + err = f"Failed to set TLS certFile '{CLIENT_CERTIFICATE}' for sslProfile 'BrokenProfile'" + self.routers[30].wait_log_message(err, timeout=1.0) + err = "sslProfile BrokenProfile: failed to set TLS certificate configuration" - for index in [27, 28, 29, 30, 31, 32]: + for index in [28, 29, 31, 32]: self.routers[index].wait_log_message(err, timeout=1.0) + err = "Failed to configure TLS Ciphers 'Blah-Blah-Blabbity-Blab' for sslProfile 'BadCipherProfile'" + self.routers[33].wait_log_message(err, timeout=1.0) + err = "sslProfile BadCipherProfile: failed to configure ciphers Blah-Blah-Blabbity-Blab" - for index in [33, 34, 35]: + for index in [34, 35]: self.routers[index].wait_log_message(err, timeout=1.0) + err = f"Failed to set TLS certFile '{SERVER_CERTIFICATE}' for sslProfile 'BadProfile'" + self.routers[36].wait_log_message(err, timeout=1.0) + + err = "Failed to configure TLS caCertFile '/does/not/exist.pem' from sslProfile 'BrokenProfile'" + self.routers[37].wait_log_message(err, timeout=1.0) + + err = "Failed to configure TLS certFile '/does/not/exist.pem' from sslProfile 'BrokenProfile'" + self.routers[38].wait_log_message(err, timeout=1.0) + + err = f"Failed to configure TLS certFile '{CLIENT_CERTIFICATE}' from sslProfile 'BrokenProfile'" + self.routers[39].wait_log_message(err, timeout=1.0) + + err = "Failed to configure TLS Ciphers 'Blah-Blah-Blabbity-Blab' for sslProfile 'BadCipherProfile'" + self.routers[40].wait_log_message(err, timeout=1.0) + + err = "Negative version field values are invalid" + self.routers[41].wait_log_message(err, timeout=1.0) + + err = "version must be >= oldestValidVersion" + self.routers[42].wait_log_message(err, timeout=1.0) + class OneRouterTest(TestCase): """System tests involving a single router""" diff --git a/tests/system_tests_router_mesh.py b/tests/system_tests_router_mesh.py index f0242dc40..2fe9ed77a 100644 --- a/tests/system_tests_router_mesh.py +++ b/tests/system_tests_router_mesh.py @@ -166,7 +166,7 @@ def test_03_unavailable_link_attach(self): ats.wait() self.assertTrue(False) # expect exception except AsyncTestSender.TestSenderException as exc: - self.assertIn("link error", ats.error) + self.assertIn("Link Error", ats.error) def test_04_unavailable_anonymous_link_attach(self): """ diff --git a/tests/system_tests_skmanage.py b/tests/system_tests_skmanage.py index 3ba1bfef3..915f04729 100644 --- a/tests/system_tests_skmanage.py +++ b/tests/system_tests_skmanage.py @@ -221,7 +221,7 @@ def test_get_attributes(self): def test_get_operations(self): out = json.loads(self.run_skmanage("get-operations")) self.assertEqual(len(out), TOTAL_ENTITIES) - self.assertEqual(out[SSL_PROFILE_TYPE], ['CREATE', 'DELETE', 'READ']) + self.assertEqual(out[SSL_PROFILE_TYPE], ['CREATE', 'UPDATE', 'DELETE', 'READ']) def test_get_types_with_ssl_profile_type(self): out = json.loads(self.run_skmanage(f"get-types --type={SSL_PROFILE_TYPE}")) @@ -230,22 +230,52 @@ def test_get_types_with_ssl_profile_type(self): def test_get_ssl_profile_type_attributes(self): out = json.loads(self.run_skmanage(f'get-attributes --type={SSL_PROFILE_TYPE}')) self.assertEqual(len(out), 1) - self.assertEqual(len(out[SSL_PROFILE_TYPE]), 11) + self.assertEqual(len(out[SSL_PROFILE_TYPE]), 13) def test_get_ssl_profile_attributes(self): out = json.loads(self.run_skmanage(f'get-attributes {SSL_PROFILE_TYPE}')) self.assertEqual(len(out), 1) - self.assertEqual(len(out[SSL_PROFILE_TYPE]), 11) + self.assertEqual(len(out[SSL_PROFILE_TYPE]), 13) def test_get_ssl_profile_type_operations(self): out = json.loads(self.run_skmanage(f'get-operations --type={SSL_PROFILE_TYPE}')) self.assertEqual(len(out), 1) - self.assertEqual(len(out[SSL_PROFILE_TYPE]), 3) + self.assertEqual(len(out[SSL_PROFILE_TYPE]), 4) def test_get_ssl_profile_operations(self): out = json.loads(self.run_skmanage(f'get-operations {SSL_PROFILE_TYPE}')) self.assertEqual(len(out), 1) - self.assertEqual(len(out[SSL_PROFILE_TYPE]), 3) + self.assertEqual(len(out[SSL_PROFILE_TYPE]), 4) + + def test_create_update_delete_ssl_profile(self): + cmd = f'CREATE --type={SSL_PROFILE_TYPE} --name=testSslProfile' + cmd += f' caCertFile={CA_CERT}' + cmd += f' certFile={SERVER_CERTIFICATE}' + cmd += f' privateKeyFile={SERVER_PRIVATE_KEY}' + cmd += f' password={SERVER_PRIVATE_KEY_PASSWORD}' + out = json.loads(self.run_skmanage(cmd)) + self.assertEqual(CA_CERT, out['caCertFile']) + self.assertEqual(SERVER_CERTIFICATE, out['certFile']) + self.assertEqual(SERVER_PRIVATE_KEY, out['privateKeyFile']) + self.assertEqual(SERVER_PRIVATE_KEY_PASSWORD, out['password']) + identity = out['identity'] + + cmd = f'UPDATE --type={SSL_PROFILE_TYPE} --identity={identity}' + cmd += f' certFile={CLIENT_CERTIFICATE}' + cmd += f' privateKeyFile={CLIENT_PRIVATE_KEY}' + cmd += f' password={CLIENT_PASSWORD_FILE}' + out = json.loads(self.run_skmanage(cmd)) + self.assertEqual(CA_CERT, out['caCertFile']) + self.assertEqual(CLIENT_CERTIFICATE, out['certFile']) + self.assertEqual(CLIENT_PRIVATE_KEY, out['privateKeyFile']) + self.assertEqual(CLIENT_PASSWORD_FILE, out['password']) + + cmd = f'DELETE --type={SSL_PROFILE_TYPE} --identity={identity}' + self.run_skmanage(cmd) + + out = json.loads(self.run_skmanage(f'QUERY --type={SSL_PROFILE_TYPE}')) + self.assertEqual(1, len(out)) + self.assertNotEqual(out[0]['name'], 'testSslProfile') def test_get_log(self): logs = json.loads(self.run_skmanage("get-log limit=20")) diff --git a/tests/system_tests_ssl.py b/tests/system_tests_ssl.py index cdd030700..605ce8d1a 100644 --- a/tests/system_tests_ssl.py +++ b/tests/system_tests_ssl.py @@ -27,13 +27,17 @@ import cproton from proton import SASL, Url, SSLDomain, SSLUnavailable, ConnectionException +from proton import Message from proton.utils import BlockingConnection -from skupper_router.management.client import Node -from system_test import TIMEOUT, TestCase, main_module, Qdrouterd +from system_test import TIMEOUT, TestCase, main_module, Qdrouterd, Process from system_test import unittest, retry, CONNECTION_TYPE, ROUTER_NODE_TYPE, ssl_file -from system_test import CA_CERT, BAD_CA_CERT, CLIENT_CERTIFICATE, SERVER_CERTIFICATE, SERVER_PRIVATE_KEY, \ - CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD, SERVER_PRIVATE_KEY_PASSWORD +from system_test import CA_CERT, BAD_CA_CERT, CA2_CERT, SSL_PROFILE_TYPE +from system_test import CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD +from system_test import SERVER_CERTIFICATE, SERVER_PRIVATE_KEY, SERVER_PRIVATE_KEY_PASSWORD +from system_test import CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD +from system_test import SERVER2_CERTIFICATE, SERVER2_PRIVATE_KEY, SERVER2_PRIVATE_KEY_PASSWORD +from system_test import AsyncTestSender, AsyncTestReceiver def protocol_name(proto): @@ -44,6 +48,14 @@ def protocol_name(proto): return proto +def get_router_nodes(router): + """ + Retrieves the router-ids of all nodes connected to router. + """ + response = router.management.query(type=ROUTER_NODE_TYPE, attribute_names=["id"]) + return [resp['id'] for resp in response.get_dicts()] + + class RouterTestSslBase(TestCase): """ Base class to help with SSL related testing. @@ -204,7 +216,7 @@ def check_tls_protocol(self, mgmt, listener_port, tls_protocol, If sasl_enabled is true use client authentication via SASL. """ ATTR_NAMES = ['ssl', 'sslProto', 'sasl', 'isAuthenticated', - 'isEncrypted', 'user'] + 'isEncrypted', 'user', 'sslCipher', 'sslSsf'] # Management address to connect using the given TLS protocol url = Url("amqps://0.0.0.0:%d/$management" % listener_port) @@ -329,6 +341,10 @@ def test_tls_ssl_sasl_client(self): self.assertTrue(result['isAuthenticated'], "SASL not authenticated properly") self.assertEqual('test@domain.com', result['user'], "Unexpected SASL user") + self.assertNotEqual(0, result['sslSsf'], f"expected non-zero ssf: {result}") + self.assertIsNotNone(result['sslCipher'], f"expected a cipher: {result}") + self.assertNotEqual(0, len(result['sslCipher']), + f"Expected non-empty cipher: {result}") # ensure that the connection fails if client attempts non-SSL connection @@ -516,23 +532,6 @@ def setUpClass(cls): cls.bad_router = cls.tester.qdrouterd("BAD-ROUTER", conf, wait=False) cls.bad_router.wait_ports() - def get_router_nodes(self): - """ - Retrieves connected router nodes. - :return: - """ - if not SASL.extended(): - self.skipTest("Cyrus library not available. skipping test") - - url = Url("amqp://0.0.0.0:%d/$management" % self.PORT_NO_SSL) - node = Node.connect(url) - response = node.query(type=ROUTER_NODE_TYPE, attribute_names=["id"]) - router_nodes = [] - for resp in response.get_dicts(): - router_nodes.append(resp['id']) - node.close() - return router_nodes - def test_connected_tls_sasl_routers(self): """ Validates if all expected routers are connected in the network with the @@ -551,6 +550,8 @@ def _get_ssl_conns(mgmt): attribute_names=['role', 'ssl', 'sslProto', + 'sslCipher', + 'sslSsf', 'sasl', 'isAuthenticated', 'isEncrypted', @@ -570,6 +571,10 @@ def _get_ssl_conns(mgmt): self.assertTrue(c['isAuthenticated'], f"Not authed {c}") self.assertEqual('PLAIN', c['sasl'], f"bad mech {c}") self.assertEqual('test@domain.com', c['user'], f"bad user {c}") + self.assertNotEqual(0, c['sslSsf'], f"expected non-zero ssf: {c}") + self.assertIsNotNone(c['sslCipher'], f"expected a cipher: {c}") + self.assertNotEqual(0, len(c['sslCipher']), + f"Expected non-empty cipher: {c}") router = None for version, router in self.routers_any.items(): @@ -582,6 +587,10 @@ def _get_ssl_conns(mgmt): self.assertEqual('PLAIN', c['sasl'], f"bad mech {c}") self.assertEqual('test@domain.com', c['user'], f"bad user {c}") self.assertEqual(version, c['sslProto'], f"wrong proto {c}") + self.assertNotEqual(0, c['sslSsf'], f"expected non-zero ssf: {c}") + self.assertIsNotNone(c['sslCipher'], f"expected a cipher: {c}") + self.assertNotEqual(0, len(c['sslCipher']), + f"Expected non-empty cipher: {c}") for version, router in self.routers_only.items(): router.wait_router_connected("QDR.A") @@ -593,6 +602,10 @@ def _get_ssl_conns(mgmt): self.assertEqual('PLAIN', c['sasl'], f"bad mech {c}") self.assertEqual('test@domain.com', c['user'], f"bad user {c}") self.assertEqual(version, c['sslProto'], f"wrong proto {c}") + self.assertNotEqual(0, c['sslSsf'], f"expected non-zero ssf: {c}") + self.assertIsNotNone(c['sslCipher'], f"expected a cipher: {c}") + self.assertNotEqual(0, len(c['sslCipher']), + f"Expected non-empty cipher: {c}") # wait for the bad router to log that the connection to QDR.A has # failed @@ -601,16 +614,13 @@ def _get_ssl_conns(mgmt): self.router_a.wait_log_message("Connection from .* failed: amqp:connection:policy-error Client connection unencrypted") -class RouterTestSslInterRouterWithInvalidPathToCA(RouterTestSslBase): +class RouterTestSslInterRouterWithInvalidCertPaths(RouterTestSslBase): """ DISPATCH-1762 - Starts 2 routers: - Router A two listeners serve a normal, good certificate - Router B two connectors configured with an invalid CA file path in its profile - - one sets verifyHostname true, the other false. - Test proves: - Router B must not connect to A with mis-configured CA file path regardless of - verifyHostname setting. + Attempt to start two routers with invalid SSL configurations: one with an + invalid self-identifying certificate and another with an invalid CA file. + + Expect neither router to start successfully. """ # Listener ports for each TLS protocol definition PORT_NO_SSL = 0 @@ -621,7 +631,7 @@ def setUpClass(cls): """ Prepares 2 routers to form a network. """ - super(RouterTestSslInterRouterWithInvalidPathToCA, cls).setUpClass() + super(RouterTestSslInterRouterWithInvalidCertPaths, cls).setUpClass() if not SASL.extended(): return @@ -629,7 +639,7 @@ def setUpClass(cls): os.environ["ENV_SASL_PASSWORD"] = "password" # Generate authentication DB - super(RouterTestSslInterRouterWithInvalidPathToCA, cls).create_sasl_files() + super(RouterTestSslInterRouterWithInvalidCertPaths, cls).create_sasl_files() # Router expected to be connected cls.connected_tls_sasl_routers = [] @@ -638,9 +648,7 @@ def setUpClass(cls): cls.routers = [] # Saving listener ports for each TLS definition - cls.PORT_NO_SSL = cls.tester.get_port() - cls.PORT_TLS_ALL_1 = cls.tester.get_port() - cls.PORT_TLS_ALL_2 = cls.tester.get_port() + inter_router_port = cls.tester.get_port() # Configured connector host cls.CONNECTOR_HOST = "localhost" @@ -651,129 +659,64 @@ def setUpClass(cls): 'saslConfigName': 'tests-mech-PLAIN', 'saslConfigDir': os.getcwd()}), # No auth and no SSL for management access - ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.PORT_NO_SSL}), - # All TLS versions and normal, good sslProfile config - ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': cls.PORT_TLS_ALL_1, - 'saslMechanisms': 'PLAIN', - 'requireEncryption': 'yes', 'requireSsl': 'yes', - 'sslProfile': 'ssl-profile-tls-all'}), - # All TLS versions and normal, good sslProfile config - ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': cls.PORT_TLS_ALL_2, - 'saslMechanisms': 'PLAIN', - 'requireEncryption': 'yes', 'requireSsl': 'yes', - 'sslProfile': 'ssl-profile-tls-all'}), - # SSL Profile for all TLS versions (protocols element not defined) - ('sslProfile', {'name': 'ssl-profile-tls-all', + ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port()}), + + # SSL Profile with non-existing certFile + ('sslProfile', {'name': 'bogus-certfile', 'caCertFile': CA_CERT, - 'certFile': SERVER_CERTIFICATE, + 'certFile': ssl_file('nonexisting_certfile.pem'), 'privateKeyFile': SERVER_PRIVATE_KEY, 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS', - 'password': SERVER_PRIVATE_KEY_PASSWORD}) + 'password': SERVER_PRIVATE_KEY_PASSWORD}), + ('listener', {'host': '0.0.0.0', 'role': 'inter-router', 'port': inter_router_port, + 'saslMechanisms': 'PLAIN', + 'requireEncryption': 'yes', 'requireSsl': 'yes', + 'sslProfile': 'bogus-certfile'}), ]) - # Router B has a connector to listener that allows all protocols but will not verify hostname. - # The sslProfile has a bad caCertFile name and this router should not connect. config_b = Qdrouterd.Config([ ('router', {'id': 'QDR.B', 'mode': 'interior', 'dataConnectionCount': '2'}), - # Connector to All TLS versions allowed listener + # No auth and no SSL for management access + ('listener', {'host': '0.0.0.0', 'role': 'normal', 'port': cls.tester.get_port()}), + + # SSL Profile with an invalid caCertFile file path. + ('sslProfile', {'name': 'bogus-caCertFile', + 'caCertFile': ssl_file('nonexisting_cacertfile.pem'), + 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' + 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS'}), ('connector', {'name': 'connector1', 'host': cls.CONNECTOR_HOST, 'role': 'inter-router', - 'port': cls.PORT_TLS_ALL_1, + 'port': inter_router_port, 'verifyHostname': 'no', 'saslMechanisms': 'PLAIN', 'saslUsername': 'test@domain.com', 'saslPassword': 'pass:password', - 'sslProfile': 'ssl-profile-tls-all'}), - # Connector to All TLS versions allowed listener - ('connector', {'name': 'connector2', - 'host': cls.CONNECTOR_HOST, 'role': 'inter-router', - 'port': cls.PORT_TLS_ALL_2, - 'verifyHostname': 'yes', 'saslMechanisms': 'PLAIN', - 'saslUsername': 'test@domain.com', 'saslPassword': 'pass:password', - 'sslProfile': 'ssl-profile-tls-all'}), - - # SSL Profile with an invalid caCertFile file path. The correct file path here would allow this - # router to connect. The object is to trigger a specific failure in the ssl - # setup chain of calls to pn_ssl_domain_* functions. - ('sslProfile', {'name': 'ssl-profile-tls-all', - 'caCertFile': ssl_file('ca-certificate-INVALID-FILENAME.pem'), - 'ciphers': 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:' - 'DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS'}) + 'sslProfile': 'bogus-caCertFile'}), ]) - cls.routers.append(cls.tester.qdrouterd("A", config_a, wait=False)) - cls.routers.append(cls.tester.qdrouterd("B", config_b, wait=False)) - - # Wait until A is running - cls.routers[0].wait_ports() - - # Can't wait until B is connected because it's not supposed to connect. - - def get_router_nodes(self): - """ - Retrieves connected router nodes from QDR.A - :return: list of connected router id's - """ - if not SASL.extended(): - self.skipTest("Cyrus library not available. skipping test") - - url = Url("amqp://0.0.0.0:%d/$management" % self.PORT_NO_SSL) - node = Node.connect(url) - response = node.query(type=ROUTER_NODE_TYPE, attribute_names=["id"]) - router_nodes = [] - for resp in response.get_dicts(): - router_nodes.append(resp['id']) - node.close() - return router_nodes + cls.routers.append(cls.tester.qdrouterd("A", config_a, + expect=Process.EXIT_FAIL, wait=False)) + cls.routers.append(cls.tester.qdrouterd("B", config_b, + expect=Process.EXIT_FAIL, wait=False)) - def test_invalid_ca_path(self): + def test_invalid_cert_paths(self): """ - Prove sslProfile with invalid path to CA prevents the router from joining the network + Verify the routers have indeed exited due to the invalid configurations """ if not SASL.extended(): self.skipTest("Cyrus library not available. skipping test") - # Poll for a while until the connector error shows up in router B's log - pattern = " SERVER (error) SSL CA configuration failed" - host_port_1 = self.CONNECTOR_HOST + ":" + str(self.PORT_TLS_ALL_1) - host_port_2 = self.CONNECTOR_HOST + ":" + str(self.PORT_TLS_ALL_2) - sleep_time = 0.1 # seconds - poll_duration = 60.0 # seconds - verified = False - for tries in range(int(poll_duration / sleep_time)): - logfile = os.path.join(self.routers[1].outdir, self.routers[1].logfile) - if os.path.exists(logfile): - with open(logfile, 'r') as router_log: - log_lines = router_log.read().split("\n") - e1_lines = [s for s in log_lines if pattern in s and host_port_1 in s] - e2_lines = [s for s in log_lines if pattern in s and host_port_2 in s] - verified = len(e1_lines) > 0 and len(e2_lines) > 0 - if verified: - break - time.sleep(sleep_time) - self.assertTrue(verified, "Log line containing '%s' not seen for both connectors in QDR.B log" % pattern) - - verified = False - pattern1 = "Connection to %s failed:" % host_port_1 - pattern2 = "Connection to %s failed:" % host_port_2 - for tries in range(int(poll_duration / sleep_time)): - logfile = os.path.join(self.routers[1].outdir, self.routers[1].logfile) - if os.path.exists(logfile): - with open(logfile, 'r') as router_log: - log_lines = router_log.read().split("\n") - e1_lines = [s for s in log_lines if pattern1 in s] - e2_lines = [s for s in log_lines if pattern2 in s] - verified = len(e1_lines) > 0 and len(e2_lines) > 0 - if verified: - break - time.sleep(sleep_time) - self.assertTrue(verified, "Log line containing '%s' or '%s' not seen in QDR.B log" % (pattern1, pattern2)) + self.routers[0].wait_log_message(r"\(critical\) Router start-up failed:") + self.routers[0].wait_log_message("Failed to configure TLS certFile") + self.routers[1].wait_log_message(r"\(critical\) Router start-up failed:") + self.routers[1].wait_log_message("Failed to configure TLS caCertFile") - # Show that router A does not have router B in its network - router_nodes = self.get_router_nodes() - self.assertTrue(router_nodes) - node = "QDR.B" - self.assertNotIn(node, router_nodes, msg="%s should not be connected" % node) + # race fix: on slow CI systems the test will exit before the routers + # have cleanly shutdown - this will cause the test to fail. Manually + # wait for the router processes to compile (raise Timeout error if they + # do not) + self.routers[0].wait(TIMEOUT) + self.routers[1].wait(TIMEOUT) class RouterTestSslInterRouterWithoutHostnameVerificationAndMismatchedCA(RouterTestSslBase): @@ -862,23 +805,6 @@ def setUpClass(cls): # Can't wait until B is connected because it's not supposed to connect. - def get_router_nodes(self): - """ - Retrieves connected router nodes from QDR.A - :return: list of connected router id's - """ - if not SASL.extended(): - self.skipTest("Cyrus library not available. skipping test") - - url = Url("amqp://0.0.0.0:%d/$management" % self.PORT_NO_SSL) - node = Node.connect(url) - response = node.query(type=ROUTER_NODE_TYPE, attribute_names=["id"]) - router_nodes = [] - for resp in response.get_dicts(): - router_nodes.append(resp['id']) - node.close() - return router_nodes - def test_mismatched_ca_and_no_hostname_verification(self): """ Prove that improperly configured ssl-enabled connector prevents the router @@ -905,10 +831,609 @@ def test_mismatched_ca_and_no_hostname_verification(self): self.assertTrue(verified, "Log line containing '%s' not seen in QDR.B log" % pattern) # Show that router A does not have router B in its network - router_nodes = self.get_router_nodes() - self.assertTrue(router_nodes) - node = "QDR.B" - self.assertNotIn(node, router_nodes, msg="%s should not be connected" % node) + router_nodes = get_router_nodes(self.routers[0]) + self.assertNotIn("QDR.B", router_nodes, msg="QDR.B should not be connected") + + +class RouterTestSslProfileUpdate(RouterTestSslBase): + """ + Verify updates to the sslProfile configurations for inter-router connections. + """ + @classmethod + def setUpClass(cls): + super(RouterTestSslProfileUpdate, cls).setUpClass() + if cls.DISABLE_SSL_TESTING: + cls.skipTest(cls.DISABLE_REASON) + if not SASL.extended(): + cls.skipTest("Cyrus library not available. skipping test") + + cls.main_listener1_port = cls.tester.get_port() + cls.main_listener2_port = cls.tester.get_port() + + main_cfg = Qdrouterd.Config([ + ('router', {'id': 'main', + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': cls.tester.get_port()}), + + # SSL profile for two inter-router listeners. + # This will be updated by test case + ('sslProfile', {'name': 'ssl-profile', + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD}), + ('listener', {'name': 'Listener1', + 'host': 'localhost', + 'role': 'inter-router', + 'port': cls.main_listener1_port, + 'requireSsl': 'true', + 'authenticatePeer': 'true', + 'saslMechanisms': 'EXTERNAL', + 'sslProfile': 'ssl-profile'}), + ('listener', {'name': 'Listener2', + 'host': 'localhost', + 'role': 'inter-router', + 'port': cls.main_listener2_port, + 'requireSsl': 'true', + 'authenticatePeer': 'false', + 'saslMechanisms': 'ANONYMOUS', + 'sslProfile': 'ssl-profile'}) + ]) + cls.main_router = cls.tester.qdrouterd("main", main_cfg, wait=True) + + # two test routers that should remain connected to the main router + # after the sslProfile has been modified (existing connections are not + # dropped by design). + + a_cfg = Qdrouterd.Config([ + ('router', {'id': 'QDR.A', + 'dataConnectionCount': 2, + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': cls.tester.get_port()}), + ('sslProfile', {'name': 'ssl-profile', + 'caCertFile': CA_CERT, + 'certFile': CLIENT_CERTIFICATE, + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': CLIENT_PRIVATE_KEY_PASSWORD}), + ('connector', {'name': 'AConn1', + 'host': 'localhost', + 'role': 'inter-router', + 'port': cls.main_listener1_port, + 'verifyHostname': 'true', + 'saslMechanisms': 'EXTERNAL', + 'sslProfile': 'ssl-profile'}) + ]) + cls.a_router = cls.tester.qdrouterd("QDR.A", a_cfg, wait=True) + + b_cfg = Qdrouterd.Config([ + ('router', {'id': 'QDR.B', + 'dataConnectionCount': 2, + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': cls.tester.get_port()}), + ('sslProfile', {'name': 'ssl-profile', + 'caCertFile': CA_CERT, + # listener2 does not request a client cert + # so no self identifying cert is necessary + }), + ('connector', {'name': 'BConn1', + 'host': 'localhost', + 'role': 'inter-router', + 'port': cls.main_listener2_port, + 'verifyHostname': 'true', + 'saslMechanisms': 'ANONYMOUS', + 'sslProfile': 'ssl-profile'}) + ]) + cls.b_router = cls.tester.qdrouterd("QDR.B", b_cfg, wait=True) + + cls.main_router.wait_router_connected("QDR.A") + cls.main_router.wait_router_connected("QDR.B") + cls.a_router.wait_router_connected("QDR.B") + cls.b_router.wait_router_connected("QDR.A") + + def test_ssl_profile_update(self): + # update sslProfile on main router to use new certs. These certs are + # incompatible with the existing configuration + + new_cfg = {'caCertFile': CA2_CERT, + 'certFile': SERVER2_CERTIFICATE, + 'privateKeyFile': SERVER2_PRIVATE_KEY, + 'password': SERVER2_PRIVATE_KEY_PASSWORD} + self.main_router.sk_manager.update(SSL_PROFILE_TYPE, new_cfg, name='ssl-profile') + + # Attempt to attach a router using the old client configuration, expect + # it to never connect + + bad_cfg = Qdrouterd.Config([ + ('router', {'id': 'QDR.BAD', + 'dataConnectionCount': 2, + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': self.tester.get_port()}), + ('sslProfile', {'name': 'ssl-profile', + 'caCertFile': CA_CERT, + 'certFile': CLIENT_CERTIFICATE, + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': CLIENT_PRIVATE_KEY_PASSWORD}), + ('connector', {'name': 'BadConn1', + 'host': 'localhost', + 'role': 'inter-router', + 'port': self.main_listener1_port, + 'verifyHostname': 'true', + 'saslMechanisms': 'EXTERNAL', + 'sslProfile': 'ssl-profile'}) + ]) + bad_router = self.tester.qdrouterd("QDR.BAD", bad_cfg, wait=False) + bad_router.wait_ports() # wait for mgmt interface port to activate + bad_router.wait_log_message("SSL Failure") + bad_router.wait_log_message("certificate verify failed") + + # Attempt to attach a router using the new client configuration. It + # should succeed. + + good_cfg = Qdrouterd.Config([ + ('router', {'id': 'QDR.GOOD', + 'dataConnectionCount': 2, + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': self.tester.get_port()}), + ('sslProfile', {'name': 'ssl-profile', + 'caCertFile': CA2_CERT, + 'certFile': CLIENT2_CERTIFICATE, + 'privateKeyFile': CLIENT2_PRIVATE_KEY, + 'password': CLIENT2_PRIVATE_KEY_PASSWORD}), + ('connector', {'name': 'GoodConn1', + 'host': 'localhost', + 'role': 'inter-router', + 'port': self.main_listener1_port, + 'verifyHostname': 'true', + 'saslMechanisms': 'EXTERNAL', + 'sslProfile': 'ssl-profile'}) + ]) + good_router = self.tester.qdrouterd("QDR.GOOD", good_cfg, wait=True) + self.main_router.wait_router_connected("QDR.GOOD") + + # Verify that only A, B, and Good are present in main's routing table + + def check_nodes(): + routers = get_router_nodes(self.main_router) + if 'QDR.A' in routers \ + and 'QDR.B' in routers \ + and 'QDR.GOOD' in routers \ + and 'QDR.BAD' not in routers: + return True + return False + + ok = retry(check_nodes) + self.assertTrue(ok, f"Unexpected routers found: {get_router_nodes(self.main_router)}") + + +class RouterTestSslProfileUpdateClients(RouterTestSslBase): + """ + Verify updates to the sslProfile configurations for client connections + """ + @classmethod + def setUpClass(cls): + super(RouterTestSslProfileUpdateClients, cls).setUpClass() + if cls.DISABLE_SSL_TESTING: + cls.skipTest(cls.DISABLE_REASON) + if not SASL.extended(): + cls.skipTest("Cyrus library not available. skipping test") + + cls.listener1_port = cls.tester.get_port() + cls.listener2_port = cls.tester.get_port() + + # default TLS configurations + cls.profile_L1_cfg = { + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD + } + cls.profile_L2_cfg = { + 'caCertFile': CA2_CERT, + 'certFile': SERVER2_CERTIFICATE, + 'privateKeyFile': SERVER2_PRIVATE_KEY, + 'password': SERVER2_PRIVATE_KEY_PASSWORD + } + + ssl_profile_L1 = {'name': 'ssl-profile-L1'} + ssl_profile_L1.update(cls.profile_L1_cfg) + ssl_profile_L2 = {'name': 'ssl-profile-L2'} + ssl_profile_L2.update(cls.profile_L2_cfg) + + router_cfg = Qdrouterd.Config([ + ('router', {'id': 'Router1', + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': cls.tester.get_port()}), + + # Listener1 + ('sslProfile', ssl_profile_L1), + ('listener', {'name': 'Listener1', + 'host': 'localhost', + 'role': 'normal', + 'port': cls.listener1_port, + 'requireSsl': 'true', + 'authenticatePeer': 'true', + 'saslMechanisms': 'EXTERNAL', + 'requireEncryption': 'yes', + 'sslProfile': 'ssl-profile-L1'}), + + # Listener2 + ('sslProfile', ssl_profile_L2), + ('listener', {'name': 'Listener2', + 'host': 'localhost', + 'role': 'normal', + 'port': cls.listener2_port, + 'requireSsl': 'true', + 'authenticatePeer': 'true', + 'saslMechanisms': 'EXTERNAL', + 'requireEncryption': 'yes', + 'sslProfile': 'ssl-profile-L2'}) + ]) + cls.router1 = cls.tester.qdrouterd("router1", router_cfg, wait=True) + + def test_ssl_client_profile_update(self): + """ + Verify updates to the sslProfiles for client connections + """ + + payload = "?" * 1024 * 65 + payload += "TLS Message!" + message = Message(body=payload) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_rx = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr", + container_id="FooRx", + conn_args=conn_args) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr", + message=message, + container_id="FooTx", + conn_args=conn_args, + get_link_info=False) + test_tx.wait() + test_rx.stop() + self.assertEqual(1, test_rx.num_queue_puts, "expected 1 message") + msg = test_rx.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") + + # + # Now update the listeners certificates and test that clients using the + # old certs fail with verification errors + # + + new_cfg = {'caCertFile': CA2_CERT, + 'certFile': SERVER2_CERTIFICATE, + 'privateKeyFile': SERVER2_PRIVATE_KEY, + 'password': SERVER2_PRIVATE_KEY_PASSWORD} + self.router1.sk_manager.update(SSL_PROFILE_TYPE, new_cfg, name='ssl-profile-L1') + + new_cfg = {'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD} + self.router1.sk_manager.update(SSL_PROFILE_TYPE, new_cfg, name='ssl-profile-L2') + + # + # Expect TLS connection failures: + # + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + with self.assertRaises(AsyncTestReceiver.TestReceiverException) as exc: + AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr", + container_id="FooRx2", + conn_args=conn_args) + self.assertIn("certificate verify failed", str(exc.exception), f"{exc.exception}") + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + with self.assertRaises(AsyncTestReceiver.TestReceiverException) as exc: + AsyncTestReceiver(f"amqps://localhost:{self.listener2_port}", + source="test/addr", + container_id="FooRx3", + conn_args=conn_args) + self.assertIn("certificate verify failed", str(exc.exception), f"{exc.exception}") + + # + # Now verify clients can connect with the proper certifcates + # + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_rx = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr", + container_id="FooRxOk", + conn_args=conn_args) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr", + message=message, + container_id="FooTxOk", + conn_args=conn_args, + get_link_info=False) + test_tx.wait() + test_rx.stop() + self.assertEqual(1, test_rx.num_queue_puts, "expected 1 message") + msg = test_rx.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") + + # restore original sslProfile configurations + self.router1.sk_manager.update(SSL_PROFILE_TYPE, self.profile_L1_cfg, name='ssl-profile-L1') + self.router1.sk_manager.update(SSL_PROFILE_TYPE, self.profile_L2_cfg, name='ssl-profile-L2') + + def test_ssl_client_profile_update_load(self): + """ + Test sslProfile updates while under client load + """ + + payload = "?" * 1024 * 65 + payload += "TLS Message!" + message = Message(body=payload) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_rx = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr", + container_id="FooRx", + conn_args=conn_args) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + + clients = [] + for test in range(10): + + # Expect failure + bad_ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + bad_ssl_domain.set_trusted_ca_db(CA_CERT) + bad_ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + bad_ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + bad_conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': bad_ssl_domain} + + with self.assertRaises(Exception) as exc: + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr", + message=message, + container_id=f"BADTX{test}", + conn_args=bad_conn_args, + get_link_info=False) + test_tx.wait() + + for c_index in range(4): + c_id = f"FooTx-{c_index}" + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr", + message=message, + container_id=c_id, + conn_args=conn_args, + get_link_info=False) + clients.append(test_tx) + + self.router1.sk_manager.update(SSL_PROFILE_TYPE, self.profile_L2_cfg, name='ssl-profile-L2') + + for client in clients: + client.wait() + + test_rx.stop() + self.assertEqual(40, test_rx.num_queue_puts, "expected 40 messages") + for count in range(40): + msg = test_rx.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") + + +class RouterTestSslProfileDeleteClients(RouterTestSslBase): + """ + Verify deleting an sslProfile does not effect existing clients + """ + @classmethod + def setUpClass(cls): + super(RouterTestSslProfileDeleteClients, cls).setUpClass() + if cls.DISABLE_SSL_TESTING: + cls.skipTest(cls.DISABLE_REASON) + if not SASL.extended(): + cls.skipTest("Cyrus library not available. skipping test") + + cls.listener1_port = cls.tester.get_port() + cls.listener2_port = cls.tester.get_port() + + router_cfg = Qdrouterd.Config([ + ('router', {'id': 'Router1', + 'mode': 'interior'}), + ('listener', {'host': '0.0.0.0', + 'role': 'normal', + 'port': cls.tester.get_port()}), + + # Listener1 - for receiver clients + ('sslProfile', {'name': 'ssl-profile-L1', + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD}), + ('listener', {'name': 'Listener1', + 'host': 'localhost', + 'role': 'normal', + 'port': cls.listener1_port, + 'requireSsl': 'true', + 'authenticatePeer': 'true', + 'saslMechanisms': 'EXTERNAL', + 'requireEncryption': 'yes', + 'sslProfile': 'ssl-profile-L1'}), + + # Listener2 - for sender client + ('sslProfile', {'name': 'ssl-profile-L2', + 'caCertFile': CA2_CERT, + 'certFile': SERVER2_CERTIFICATE, + 'privateKeyFile': SERVER2_PRIVATE_KEY, + 'password': SERVER2_PRIVATE_KEY_PASSWORD}), + ('listener', {'name': 'Listener2', + 'host': 'localhost', + 'role': 'normal', + 'port': cls.listener2_port, + 'requireSsl': 'true', + 'authenticatePeer': 'true', + 'saslMechanisms': 'EXTERNAL', + 'requireEncryption': 'yes', + 'sslProfile': 'ssl-profile-L2'}) + ]) + cls.router1 = cls.tester.qdrouterd("router1", router_cfg, wait=True) + + def test_ssl_client_profile_delete(self): + """ + Attach two receivers then delete the associated sslProfile + """ + + payload = "?" * 1024 * 65 + payload += "TLS Message!" + message = Message(body=payload) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_rx1 = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr1", + container_id="FooRx1", + conn_args=conn_args) + test_rx2 = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr2", + container_id="FooRx2", + conn_args=conn_args) + + # the receiver connections have been established, delete the sslProfile + + mgmt = self.router1.sk_manager + mgmt.delete(SSL_PROFILE_TYPE, name='ssl-profile-L1') + + # now verify the existing connections are not affected + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr1", + message=message, + container_id="FooTx1", + conn_args=conn_args, + get_link_info=False) + test_tx.wait() + test_rx1.stop() + self.assertEqual(1, test_rx1.num_queue_puts, "expected 1 message") + msg = test_rx1.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") + + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr2", + message=message, + container_id="FooTx2", + conn_args=conn_args, + get_link_info=False) + test_tx.wait() + test_rx2.stop() + self.assertEqual(1, test_rx2.num_queue_puts, "expected 1 message") + msg = test_rx2.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") + + # Create a new connection. Since the listener still exists and has a + # copy of the now deleted sslProfile config this should succeed + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA_CERT) + ssl_domain.set_credentials(CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_rx3 = AsyncTestReceiver(f"amqps://localhost:{self.listener1_port}", + source="test/addr3", + container_id="FooRx3", + conn_args=conn_args) + + ssl_domain = SSLDomain(SSLDomain.MODE_CLIENT) + ssl_domain.set_trusted_ca_db(CA2_CERT) + ssl_domain.set_peer_authentication(SSLDomain.VERIFY_PEER_NAME, CA2_CERT) + ssl_domain.set_credentials(CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD) + conn_args = {'sasl_enabled': True, + 'allowed_mechs': "EXTERNAL", + 'ssl_domain': ssl_domain} + test_tx = AsyncTestSender(f"amqps://localhost:{self.listener2_port}", + target="test/addr3", + message=message, + container_id="FooTx3", + conn_args=conn_args, + get_link_info=False) + test_tx.wait() + test_rx3.stop() + self.assertEqual(1, test_rx3.num_queue_puts, "expected 1 message") + msg = test_rx3.queue.get() + self.assertIn("TLS Message!", msg.body, "missing payload") if __name__ == '__main__': diff --git a/tests/system_tests_tcp_adaptor_tls.py b/tests/system_tests_tcp_adaptor_tls.py index 4e7f4b600..0a136450e 100644 --- a/tests/system_tests_tcp_adaptor_tls.py +++ b/tests/system_tests_tcp_adaptor_tls.py @@ -17,17 +17,22 @@ # under the License. # import os -import shutil from system_test import unittest, TestCase, Qdrouterd, NcatException, Logger, Process, run_curl, \ CA_CERT, CLIENT_CERTIFICATE, CLIENT_PRIVATE_KEY, CLIENT_PRIVATE_KEY_PASSWORD, \ SERVER_CERTIFICATE, SERVER_PRIVATE_KEY, SERVER_PRIVATE_KEY_PASSWORD, SERVER_PRIVATE_KEY_NO_PASS, BAD_CA_CERT, \ CHAINED_CERT +from system_test import CA2_CERT +from system_test import CLIENT2_CERTIFICATE, CLIENT2_PRIVATE_KEY, CLIENT2_PRIVATE_KEY_PASSWORD +from system_test import SERVER2_CERTIFICATE, SERVER2_PRIVATE_KEY, SERVER2_PRIVATE_KEY_PASSWORD +from system_test import SSL_PROFILE_TYPE +from system_test import is_pattern_present from system_tests_ssl import RouterTestSslBase from system_tests_tcp_adaptor import TcpAdaptorBase, CommonTcpTests, ncat_available from http1_tests import wait_tcp_listeners_up from system_tests_http2 import get_address, get_digest, image_file from system_tests_http2 import skip_nginx_test from TCP_echo_server import TcpEchoServer +from TCP_echo_client import TcpEchoClient class TcpTlsAdaptor(TcpAdaptorBase, CommonTcpTests): @@ -112,7 +117,7 @@ def test_connector_mgmt_missing_ssl_profile(self): 'port': port, 'sslProfile': "NotFound"}) self.assertEqual(1, mgmt.returncode, "Unexpected returncode from skmanage") - self.assertIn("Invalid tcpConnector configuration", mgmt.stdout) + self.assertIn("sslProfile 'NotFound' not found", mgmt.stdout) def test_connector_mgmt_missing_ca_file(self): """Attempt to create a connector with an invalid CA file""" @@ -127,7 +132,7 @@ def test_connector_mgmt_missing_ca_file(self): 'port': port, 'sslProfile': "BadCAFile"}) self.assertEqual(1, mgmt.returncode, "Unexpected returncode from skmanage") - self.assertIn("Invalid tcpConnector configuration", mgmt.stdout) + self.assertIn("Failed to configure TLS caCertFile", mgmt.stdout) mgmt.delete("sslProfile", name='BadCAFile') def test_listener_mgmt_missing_ssl_profile(self): @@ -140,7 +145,7 @@ def test_listener_mgmt_missing_ssl_profile(self): 'port': port, 'sslProfile': "NotFound"}) self.assertEqual(1, mgmt.returncode, "Unexpected returncode from skmanage") - self.assertIn("Invalid tcpListener configuration", mgmt.stdout) + self.assertIn("sslProfile 'NotFound' not found", mgmt.stdout) def test_listener_mgmt_missing_ca_file(self): """Attempt to create a listener with an invalid CA file""" @@ -148,14 +153,35 @@ def test_listener_mgmt_missing_ca_file(self): mgmt = self.router.sk_manager mgmt.create("sslProfile", {'name': 'BadCAFile', - 'caCertFile': '/bad/path/CA.pem'}) + 'caCertFile': '/bad/path/CA.pem', + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': 'server-password'}) self.assertRaises(Exception, mgmt.create, "tcpListener", {'address': 'foo', 'host': '0.0.0.0', 'port': port, 'sslProfile': "BadCAFile"}) self.assertEqual(1, mgmt.returncode, "Unexpected returncode from skmanage") - self.assertIn("Invalid tcpListener configuration", mgmt.stdout) + self.assertIn("Failed to configure TLS caCertFile", mgmt.stdout) + mgmt.delete("sslProfile", name='BadCAFile') + + def test_listener_mgmt_missing_private_key(self): + """Attempt to create a listener without a proper private key""" + port = self.tester.get_port() + mgmt = self.router.sk_manager + mgmt.create("sslProfile", + {'name': 'BadCAFile', + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'password': 'server-password'}) + self.assertRaises(Exception, mgmt.create, "tcpListener", + {'address': 'foo', + 'host': '0.0.0.0', + 'port': port, + 'sslProfile': "BadCAFile"}) + self.assertEqual(1, mgmt.returncode, "Unexpected returncode from skmanage") + self.assertIn("Missing Private Keyfile (sslProfile: BadCAFile)", mgmt.stdout) mgmt.delete("sslProfile", name='BadCAFile') @@ -735,59 +761,64 @@ def test_get_image_jpg_http11(self): self.assertEqual(digest_of_server_file, digest_of_response_file) -class TcpAdaptorCertCorruptionTests(TestCase): +class TcpAdaptorSslProfileUpdateTests(TestCase): """ - The TCP adaptor re-loads the certificate files every time a new client or - server TCP connection is created. This test verifies that the router can - handle the case where the certificate files are removed or corrupted while - new connections are created. + Verify that management updates to a tcpListener/Connector's sslProfile are + adopted for new connections. """ @classmethod def setUpClass(cls): - super(TcpAdaptorCertCorruptionTests, cls).setUpClass() + super(TcpAdaptorSslProfileUpdateTests, cls).setUpClass() cls.openssl_server_listening_port = cls.tester.get_port() cls.router_listener_port = cls.tester.get_port() - # make a local copy of the client and server certificates. This test - # destroys these files, so we do not want to destroy the originals! + cls.listener_profile_cfg = { + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD + } + + cls.connector_profile_cfg = { + 'caCertFile': CA_CERT, + 'certFile': CLIENT_CERTIFICATE, + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': CLIENT_PRIVATE_KEY_PASSWORD + } + + # default extra arguments for the openssl test client and server - cls.server_cert_file = shutil.copy(SERVER_CERTIFICATE, - os.path.dirname(os.getcwd())) - cls.client_cert_file = shutil.copy(CLIENT_CERTIFICATE, - os.path.dirname(os.getcwd())) + cls.s_server_args = ['-Verify', '1', '-verify_return_error'] + cls.s_client_args = ['-verify', '10', '-verify_return_error', '-nocommands'] # Set up two routers, TCP listener on one, connector on another inter_router_port = cls.tester.get_port() + ssl_profile = {'name': 'listener-ssl-profile'} + ssl_profile.update(cls.listener_profile_cfg) config_qdra = Qdrouterd.Config([ ('router', {'mode': 'interior', 'id': 'QDR.A'}), ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}), ('listener', {'role': 'inter-router', 'port': inter_router_port}), - ('sslProfile', {'name': 'listener-ssl-profile', - 'caCertFile': CA_CERT, - 'certFile': cls.server_cert_file, - 'privateKeyFile': SERVER_PRIVATE_KEY, - 'password': SERVER_PRIVATE_KEY_PASSWORD}), + ('sslProfile', ssl_profile,), ('tcpListener', {'port': cls.router_listener_port, - 'address': 'corrupt/me', + 'address': 'update/me', 'host': 'localhost', 'authenticatePeer': 'yes', 'sslProfile': 'listener-ssl-profile'}) ]) + ssl_profile = {'name': 'connector-ssl-profile'} + ssl_profile.update(cls.connector_profile_cfg) config_qdrb = Qdrouterd.Config([ ('router', {'mode': 'interior', 'id': 'QDR.B'}), ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}), ('connector', {'name': 'connectorToA', 'role': 'inter-router', 'port': inter_router_port}), - ('sslProfile', {'name': 'connector-ssl-profile', - 'caCertFile': CA_CERT, - 'certFile': cls.client_cert_file, - 'privateKeyFile': CLIENT_PRIVATE_KEY, - 'password': CLIENT_PRIVATE_KEY_PASSWORD}), + ('sslProfile', ssl_profile), ('tcpConnector', {'port': cls.openssl_server_listening_port, - 'address': 'corrupt/me', + 'address': 'update/me', 'host': 'localhost', 'verifyHostname': 'yes', 'sslProfile': 'connector-ssl-profile'}), @@ -799,22 +830,23 @@ def setUpClass(cls): cls.router_qdrb.wait_router_connected('QDR.A') wait_tcp_listeners_up(cls.router_qdra.addresses[0]) - def test_cert_corruption(self): - # Note we do not want to corrupt the certs held by the ssl server or - # client. We need them to be valid so the test client/server do not - # fail themselves. We only want the router to be affected! - + def test_ssl_profile_update(self): + """ + Test management updates to the listener and connector sslProfile + configurations + """ + payload = b'?' * 1024 * 65 server_ssl_info = dict() server_ssl_info['CA_CERT'] = CA_CERT server_ssl_info['SERVER_CERTIFICATE'] = SERVER_CERTIFICATE server_ssl_info['SERVER_PRIVATE_KEY'] = SERVER_PRIVATE_KEY server_ssl_info['SERVER_PRIVATE_KEY_PASSWORD'] = SERVER_PRIVATE_KEY_PASSWORD - openssl_server = self.tester.openssl_server - self.openssl_server = openssl_server(listening_port=self.openssl_server_listening_port, - ssl_info=server_ssl_info, - name="OpenSSLServerAuthPeer", - cl_args=['-Verify', '1']) + server_create = self.tester.openssl_server + openssl_server = server_create(listening_port=self.openssl_server_listening_port, + ssl_info=server_ssl_info, + name="OpenSSLServerAuthPeer", + cl_args=self.s_server_args) client_ssl_info = dict() client_ssl_info['CA_CERT'] = CA_CERT @@ -824,50 +856,344 @@ def test_cert_corruption(self): out, error = self.opensslclient(port=self.router_listener_port, ssl_info=client_ssl_info, - data=b"Sanity Check the Configuration!") - self.assertIn(b"Verification: OK", out) - self.assertIn(b"Verify return code: 0 (ok)", out) + data=b"Sanity Check the Configuration!" + payload, + cl_args=self.s_client_args) + self.assertIn(b"Verification: OK", out, f"{error}") + self.assertIn(b"Verify return code: 0 (ok)", out, f"{error}") - self.openssl_server.wait_out_message("Sanity Check the Configuration!") + openssl_server.wait_out_message("Sanity Check the Configuration!") # - # Phase 1: corrupt the server-facing certificate + # Attempt to update the listener-side sslProfile with a bad + # CA cert file. Verify that the management operation fails + # the configuration error. # - with open(self.client_cert_file, mode="w") as clientf: - clientf.write("Oh mercy! I'm corrupt!") + skmgr_a = self.router_qdra.sk_manager + + with self.assertRaises(Exception) as emgr: + skmgr_a.update(SSL_PROFILE_TYPE, + {'caCertFile': '/this/file/does/not/exist.pem'}, + name='listener-ssl-profile') - _, _ = self.opensslclient(port=self.router_listener_port, - ssl_info=client_ssl_info, - data=b"Phase 1") - self.router_qdrb.wait_log_message(r'failed to set TLS certificate') + self.assertIn('Failed to configure TLS caCertFile', str(emgr.exception)) # - # Phase 2: corrupt the client-facing certificate + # Restore the proper password and verify clients can connect # - with open(self.server_cert_file, mode="w") as serverf: - serverf.write("Alas! I too have been rendered corrupt!") + skmgr_a.update(SSL_PROFILE_TYPE, {'caCertFile': CA_CERT}, + name='listener-ssl-profile') - _, _ = self.opensslclient(port=self.router_listener_port, - ssl_info=client_ssl_info, - data=b"Phase 2", - expect=Process.EXIT_FAIL) - self.router_qdra.wait_log_message(r'failed to set TLS certificate') + out = skmgr_a.read(name='listener-ssl-profile') + self.assertEqual(SERVER_PRIVATE_KEY_PASSWORD, out['password']) + + out, error = self.opensslclient(port=self.router_listener_port, + ssl_info=client_ssl_info, + data=b"Hey password is good!" + payload, + cl_args=self.s_client_args) + self.assertIn(b"Verification: OK", out, f"{error}") + self.assertIn(b"Verify return code: 0 (ok)", out, f"{error}") + + openssl_server.wait_out_message("Hey password is good!") # - # Phase 3: Restore the files and verify all is well + # Now update the listener sslProfile with a valid config, but one that + # will not allow the client to connect # + new_cfg = {'caCertFile': CA2_CERT, + 'certFile': SERVER2_CERTIFICATE, + 'privateKeyFile': SERVER2_PRIVATE_KEY, + 'password': SERVER2_PRIVATE_KEY_PASSWORD} + skmgr_a.update(SSL_PROFILE_TYPE, new_cfg, name='listener-ssl-profile') - self.server_cert_file = shutil.copy(SERVER_CERTIFICATE, - os.path.dirname(os.getcwd())) - self.client_cert_file = shutil.copy(CLIENT_CERTIFICATE, - os.path.dirname(os.getcwd())) + out, error = self.opensslclient(port=self.router_listener_port, + ssl_info=client_ssl_info, + data=b"The CA will not allow this!" + payload, + expect=Process.EXIT_FAIL, + cl_args=self.s_client_args) + self.router_qdra.wait_log_message(r'TLS connection failed') + # + # Update the client ssl_info to use a compatible client cert and verify + # all is well: + # + + client_ssl_info = dict() + client_ssl_info['CA_CERT'] = CA2_CERT + client_ssl_info['CLIENT_CERTIFICATE'] = CLIENT2_CERTIFICATE + client_ssl_info['CLIENT_PRIVATE_KEY'] = CLIENT2_PRIVATE_KEY + client_ssl_info['CLIENT_PRIVATE_KEY_PASSWORD'] = CLIENT2_PRIVATE_KEY_PASSWORD out, error = self.opensslclient(port=self.router_listener_port, ssl_info=client_ssl_info, - data=b"Hey we recovered!") - self.assertIn(b"Verification: OK", out) - self.assertIn(b"Verify return code: 0 (ok)", out) + data=b"Hey we recovered!" + payload, + cl_args=self.s_client_args) + self.assertIn(b"Verification: OK", out, f"{error}") + self.assertIn(b"Verify return code: 0 (ok)", out, f"{error}") + + openssl_server.wait_out_message("Hey we recovered!") - self.openssl_server.wait_out_message("Hey we recovered!") + # + # Test updates on the connector sslProfile + # + + # + # start a new ssl server that uses an incompatible TLS configuration + # The connection should fail when router QDR.B attempts to connect to + # it + # + + openssl_server.teardown() + server_ssl_info = dict() + server_ssl_info['CA_CERT'] = CA2_CERT + server_ssl_info['SERVER_CERTIFICATE'] = SERVER2_CERTIFICATE + server_ssl_info['SERVER_PRIVATE_KEY'] = SERVER2_PRIVATE_KEY + server_ssl_info['SERVER_PRIVATE_KEY_PASSWORD'] = SERVER2_PRIVATE_KEY_PASSWORD + openssl_server = server_create(listening_port=self.openssl_server_listening_port, + ssl_info=server_ssl_info, + name="OpenSSLServerAuthPeer2", + cl_args=self.s_server_args) + + out, error = self.opensslclient(port=self.router_listener_port, + ssl_info=client_ssl_info, + data=b"The server conn must fail" + payload, + cl_args=self.s_client_args) + self.router_qdrb.wait_log_message(r'TLS connection failed') + with open(openssl_server.outfile_path, 'rt') as out_file: + self.assertFalse(is_pattern_present(out_file, + "The server conn must fail"), + "TLS connection did not fail") + + # Now update the connectors sslProfile with a compatible client cert + # and verify a new connection succeeds + + new_cfg = {'caCertFile': CA2_CERT, + 'certFile': CLIENT2_CERTIFICATE, + 'privateKeyFile': CLIENT2_PRIVATE_KEY, + 'password': CLIENT2_PRIVATE_KEY_PASSWORD} + skmgr_b = self.router_qdrb.sk_manager + skmgr_b.update(SSL_PROFILE_TYPE, new_cfg, name='connector-ssl-profile') + out, error = self.opensslclient(port=self.router_listener_port, + ssl_info=client_ssl_info, + data=b"The server conn must succeed!" + payload, + cl_args=self.s_client_args) + self.assertIn(b"Verification: OK", out, f"{error}") + self.assertIn(b"Verify return code: 0 (ok)", out, f"{error}") + openssl_server.wait_out_message("The server conn must succeed!") + openssl_server.teardown() + + # Restore the original sslProfile configuration + skmgr_a.update(SSL_PROFILE_TYPE, self.listener_profile_cfg, name='listener-ssl-profile') + skmgr_b.update(SSL_PROFILE_TYPE, self.connector_profile_cfg, name='connector-ssl-profile') + + def test_ssl_profile_update_load(self): + """ + Test sslProfile updates while under client load + """ + + skmgr_a = self.router_qdra.sk_manager + skmgr_a.update(SSL_PROFILE_TYPE, self.listener_profile_cfg, name='listener-ssl-profile') + + skmgr_b = self.router_qdrb.sk_manager + skmgr_b.update(SSL_PROFILE_TYPE, self.connector_profile_cfg, name='connector-ssl-profile') + + server_ssl_info = {'SERVER_CERTIFICATE': SERVER_CERTIFICATE, + 'SERVER_PRIVATE_KEY': SERVER_PRIVATE_KEY_NO_PASS, + 'CA_CERT': CA_CERT} + client_ssl_info = {'CLIENT_CERTIFICATE': CLIENT_CERTIFICATE, + 'CLIENT_PRIVATE_KEY': CLIENT_PRIVATE_KEY, + 'CLIENT_PRIVATE_KEY_PASSWORD': CLIENT_PRIVATE_KEY_PASSWORD, + 'CA_CERT': CA_CERT} + + server_name = "SslProfileEchoLoadServer" + server_logger = Logger(title=server_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{server_name}.log")) + echo_server = TcpEchoServer(prefix="SslProfileEchoServer", + port=self.openssl_server_listening_port, + logger=server_logger, + ssl_info=server_ssl_info) + if echo_server.is_running is False: + echo_server.logger.dump() + self.assertTrue(echo_server.is_running, "Echo Server failed to start") + + # Negative test - expect failure + + bad_ssl_info = {'CLIENT_CERTIFICATE': CLIENT2_CERTIFICATE, + 'CLIENT_PRIVATE_KEY': CLIENT2_PRIVATE_KEY, + 'CLIENT_PRIVATE_KEY_PASSWORD': CLIENT2_PRIVATE_KEY_PASSWORD, + 'CA_CERT': CA2_CERT} + + client_name = "SslProfileEchoClientBAD" + client_logger = Logger(title=client_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{client_name}.log")) + with self.assertRaises(Exception) as exc: + bad_client = TcpEchoClient(prefix=client_name, + host='localhost', + port=self.router_listener_port, + size=5000, + count=10, + logger=client_logger, + ssl_info=bad_ssl_info) + bad_client.wait() + + # now test multiple simultaineous clients while updating the sslProfile + + clients = [] + + for test in range(10): + for c_index in range(4): + client_name = f"SslProfileEchoClient-{test}-{c_index}-A" + client_logger = Logger(title=client_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{client_name}.log")) + echo_client = TcpEchoClient(prefix=client_name, + host='localhost', + port=self.router_listener_port, + size=5000, + count=10, + logger=client_logger, + ssl_info=client_ssl_info) + clients.append(echo_client) + + skmgr_a.update(SSL_PROFILE_TYPE, self.listener_profile_cfg, name='listener-ssl-profile') + skmgr_b.update(SSL_PROFILE_TYPE, self.connector_profile_cfg, name='connector-ssl-profile') + + for client in clients: + try: + # expect no failures + client.wait() + except Exception: + client.logger.dump() + raise + + try: + echo_server.wait() + except Exception: + echo_server.logger.dump() + raise + + +class TcpAdaptorSslProfileDeleteTests(TestCase): + """ + Verify deleting an sslProfile does not effect existing clients + """ + @classmethod + def setUpClass(cls): + super(TcpAdaptorSslProfileDeleteTests, cls).setUpClass() + cls.server_listening_port = cls.tester.get_port() + cls.router_listener_port = cls.tester.get_port() + + cls.listener_profile_cfg = { + 'caCertFile': CA_CERT, + 'certFile': SERVER_CERTIFICATE, + 'privateKeyFile': SERVER_PRIVATE_KEY, + 'password': SERVER_PRIVATE_KEY_PASSWORD + } + + cls.connector_profile_cfg = { + 'caCertFile': CA_CERT, + 'certFile': CLIENT_CERTIFICATE, + 'privateKeyFile': CLIENT_PRIVATE_KEY, + 'password': CLIENT_PRIVATE_KEY_PASSWORD + } + + ssl_listener_profile = {'name': 'listener-ssl-profile'} + ssl_listener_profile.update(cls.listener_profile_cfg) + + ssl_connector_profile = {'name': 'connector-ssl-profile'} + ssl_connector_profile.update(cls.connector_profile_cfg) + + config_qdra = Qdrouterd.Config([ + ('router', {'mode': 'interior', 'id': 'QDR.A'}), + ('listener', {'port': cls.tester.get_port(), 'role': 'normal', 'host': '0.0.0.0'}), + ('sslProfile', ssl_listener_profile,), + ('sslProfile', ssl_connector_profile,), + ('tcpListener', {'port': cls.router_listener_port, + 'address': 'update/me', + 'host': 'localhost', + 'authenticatePeer': 'yes', + 'sslProfile': 'listener-ssl-profile'}), + ('tcpConnector', {'port': cls.server_listening_port, + 'address': 'update/me', + 'host': 'localhost', + 'verifyHostname': 'yes', + 'sslProfile': 'connector-ssl-profile'}), + ]) + + cls.router_qdra = cls.tester.qdrouterd("QDR.A", config_qdra, wait=True) + wait_tcp_listeners_up(cls.router_qdra.addresses[0]) + + def test_ssl_profile_delete(self): + """ + Test sslProfile delete while under client load + """ + + skmgr_a = self.router_qdra.sk_manager + + server_ssl_info = {'SERVER_CERTIFICATE': SERVER_CERTIFICATE, + 'SERVER_PRIVATE_KEY': SERVER_PRIVATE_KEY_NO_PASS, + 'CA_CERT': CA_CERT} + client_ssl_info = {'CLIENT_CERTIFICATE': CLIENT_CERTIFICATE, + 'CLIENT_PRIVATE_KEY': CLIENT_PRIVATE_KEY, + 'CLIENT_PRIVATE_KEY_PASSWORD': CLIENT_PRIVATE_KEY_PASSWORD, + 'CA_CERT': CA_CERT} + + server_name = "SslProfileDeleteEchoServer" + server_logger = Logger(title=server_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{server_name}.log")) + echo_server = TcpEchoServer(prefix=server_name, + port=self.server_listening_port, + logger=server_logger, + ssl_info=server_ssl_info) + if echo_server.is_running is False: + echo_server.logger.dump() + self.assertTrue(echo_server.is_running, "Echo Server failed to start") + + # Test the configuration + + client_name = "SslProfileEchoClient-1" + client_logger = Logger(title=client_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{client_name}.log")) + echo_client = TcpEchoClient(prefix=client_name, + host='localhost', + port=self.router_listener_port, + size=5000, + count=10, + logger=client_logger, + ssl_info=client_ssl_info) + echo_client.wait() + + # now delete the sslProfile. Since the connector stores the sslProfile + # configuration new connections should not be affected + + skmgr_a.delete(SSL_PROFILE_TYPE, name='connector-ssl-profile') + + # Expect this to pass + + client_name = "SslProfileEchoClient-2" + client_logger = Logger(title=client_name, + print_to_console=False, + ofilename=os.path.join(os.path.dirname(os.getcwd()), + f"{client_name}.log")) + echo_client = TcpEchoClient(prefix=client_name, + host='localhost', + port=self.router_listener_port, + size=5000, + count=10, + logger=client_logger, + ssl_info=client_ssl_info) + try: + # expect no failures + echo_client.wait() + except Exception: + echo_client.logger.dump() + raise diff --git a/tests/system_tests_topology.py b/tests/system_tests_topology.py index 89ead526e..aa43b2fcf 100644 --- a/tests/system_tests_topology.py +++ b/tests/system_tests_topology.py @@ -764,7 +764,10 @@ def test_01_reboot_INT_A(self): # stop consumers so INT_A's route table will be different when it comes # back online so it will require an immediate sync for c in consumers: - c.stop() + with self.assertRaises(AsyncTestReceiver.TestReceiverException) as exc: + c.stop() + # expect that the connection failed due to the teardown of INT_A + self.assertIn("connection aborted", str(exc.exception), f"{exc.exception}") time.sleep(1.0) INT_A = self._create_router('INT.A', @@ -808,7 +811,10 @@ def test_02_shutdown_INT_A(self): # propagated to INT_C. Now remove INT_A INT_A.teardown() for c in consumers: - c.stop() + with self.assertRaises(AsyncTestReceiver.TestReceiverException) as exc: + c.stop() + # expect that the connection failed due to the teardown of INT_A + self.assertIn("connection aborted", str(exc.exception), f"{exc.exception}") start = time.time() diff --git a/tests/system_tests_user_id.py b/tests/system_tests_user_id.py index 1ba79701f..ff360d7fe 100644 --- a/tests/system_tests_user_id.py +++ b/tests/system_tests_user_id.py @@ -241,64 +241,70 @@ def test_ssl_user_id(self): ssl_opts['ssl-key'] = CLIENT_PRIVATE_KEY ssl_opts['ssl-password'] = CLIENT_PRIVATE_KEY_PASSWORD + # from pn_transport_get_user() if no uidFormat given + DEFAULT_UID = 'CN=127.0.0.1,O=Client,OU=Dev,L=San Francisco,ST=CA,C=US' + # create the SSL domain object domain = self.create_ssl_domain(ssl_opts) + # SHA1 fingerprint of client-certificate.pem: addr = self.address(0).replace("amqp", "amqps") - node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) user_id = node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[0][0] - self.assertEqual("3eccbf1a2f3e46da823c63a9da9158983cb495a3", user_id) + self.assertEqual("789d81777d7426286656747bb21310d5cb54e91e", user_id) + # sha256 fingerprint of client-certificate.pem: addr = self.address(1).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("72d543690cb0a8fc2d0f4c704c65411b9ee8ad53839fced4c720d73e58e4f0d7", + self.assertEqual("6cea7256e7d65c16a5c9d34036ede844e45e77a3271bcc7ec43badeb0550fd46", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[1][0]) + # sha512 fingerprint of client-certificate.pem: addr = self.address(2).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("c6de3a340014b0f8a1d2b41d22e414fc5756494ffa3c8760bbff56f3aa9f179a5a6eae09413fd7a6afbf36b5fb4bad8795c2836774acfe00a701797cc2a3a9ab", - node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[2][0]) + fp = "9d7be15953e46b55b9c132156bc4d1dac9463382600f12002ad62f5b71207c4445cf907421d8ac8aba00a9316496d5b3b902655ba6ec0cdc0410ffa4c8de89fe" + self.assertEqual(fp, node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[2][0]) + # uidFormat 2noucs addr = self.address(3).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("72d543690cb0a8fc2d0f4c704c65411b9ee8ad53839fced4c720d73e58e4f0d7;127.0.0.1;Client;Dev;US;NC", + self.assertEqual("6cea7256e7d65c16a5c9d34036ede844e45e77a3271bcc7ec43badeb0550fd46;127.0.0.1;Client;Dev;US;CA", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[3][0]) addr = self.address(4).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("3eccbf1a2f3e46da823c63a9da9158983cb495a3;US;NC", + self.assertEqual("789d81777d7426286656747bb21310d5cb54e91e;US;CA", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[4][0]) addr = self.address(5).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("US;NC;c6de3a340014b0f8a1d2b41d22e414fc5756494ffa3c8760bbff56f3aa9f179a5a6eae09413fd7a6afbf36b5fb4bad8795c2836774acfe00a701797cc2a3a9ab", + self.assertEqual("US;CA;9d7be15953e46b55b9c132156bc4d1dac9463382600f12002ad62f5b71207c4445cf907421d8ac8aba00a9316496d5b3b902655ba6ec0cdc0410ffa4c8de89fe", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[5][0]) addr = self.address(6).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("127.0.0.1;NC;Dev;US;Client", + self.assertEqual("127.0.0.1;CA;Dev;US;Client", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[6][0]) addr = self.address(7).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("NC;US;Client;Dev;127.0.0.1;Raleigh", + self.assertEqual("CA;US;Client;Dev;127.0.0.1;San Francisco", node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[7][0]) addr = self.address(8).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("C=US,ST=NC,L=Raleigh,OU=Dev,O=Client,CN=127.0.0.1", + self.assertEqual(DEFAULT_UID, node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[8][0]) addr = self.address(9).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) - self.assertEqual("C=US,ST=NC,L=Raleigh,OU=Dev,O=Client,CN=127.0.0.1", + self.assertEqual(DEFAULT_UID, node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[9][0]) addr = self.address(10).replace("amqp", "amqps") node = Node.connect(addr, timeout=TIMEOUT, ssl_domain=domain) user = node.query(type=CONNECTION_TYPE, attribute_names=['user']).results[10][0] - self.assertEqual("C=US,ST=NC,L=Raleigh,OU=Dev,O=Client,CN=127.0.0.1", str(user)) + self.assertEqual(DEFAULT_UID, str(user)) # authenticatePeer is set to 'no' in this listener, the user should anonymous on the connection. addr = self.address(11).replace("amqp", "amqps") @@ -318,6 +324,13 @@ def test_ssl_user_id(self): node.close() + # verify that the two invalid uidFormat fields were detected: + + m1 = r"Invalid format for uidFormat field in sslProfile 'server-ssl10': 1x" + m2 = r"Invalid format for uidFormat field in sslProfile 'server-ssl11': abxd" + self.router.wait_log_message(m1) + self.router.wait_log_message(m2) + if __name__ == '__main__': unittest.main(main_module())