int pn_ssl_init(pn_ssl_t *ssl0, pn_ssl_domain_t *domain, const char *session_id) { pn_transport_t *transport = get_transport_internal(ssl0); pni_ssl_t *ssl = transport->ssl; if (!ssl || !domain || ssl->domain) return -1; ssl->domain = domain; domain->ref_count++; if (session_id && domain->mode == PN_SSL_MODE_CLIENT) ssl->session_id = pn_strdup(session_id); // If SSL doesn't specifically allow skipping encryption, require SSL // TODO: This is a probably a stop-gap until allow_unsecured is removed if (!domain->allow_unsecured) transport->encryption_required = true; return init_ssl_socket(transport, ssl); }
int pn_ssl_init( pn_ssl_t *ssl, pn_ssl_domain_t *domain, const char *session_id) { if (!ssl || !domain || ssl->domain) return -1; ssl->domain = domain; domain->ref_count++; if (domain->allow_unsecured) { ssl->io_layer->process_input = process_input_unknown; ssl->io_layer->process_output = process_output_unknown; } else { ssl->io_layer->process_input = process_input_ssl; ssl->io_layer->process_output = process_output_ssl; } if (session_id && domain->mode == PN_SSL_MODE_CLIENT) ssl->session_id = pn_strdup(session_id); return init_ssl_socket(ssl); }
// take data from the network, and pass it into SSL. Attempt to read decrypted data from // SSL socket and pass it to the application. static ssize_t process_input_ssl( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t available) { pni_ssl_t *ssl = transport->ssl; if (ssl->ssl == NULL && init_ssl_socket(transport, ssl)) return PN_EOS; ssl_log( transport, "process_input_ssl( data size=%d )",available ); ssize_t consumed = 0; bool work_pending; bool shutdown_input = (available == 0); // caller is closed do { work_pending = false; // Write to network bio as much as possible, consuming bytes/available if (available > 0) { int written = BIO_write( ssl->bio_net_io, input_data, available ); if (written > 0) { input_data += written; available -= written; consumed += written; ssl->read_blocked = false; work_pending = (available > 0); ssl_log( transport, "Wrote %d bytes to BIO Layer, %d left over", written, available ); } } else if (shutdown_input) { // lower layer (caller) has closed. Close the WRITE side of the BIO. This will cause // an EOF to be passed to SSL once all pending inbound data has been consumed. ssl_log( transport, "Lower layer closed - shutting down BIO write side"); (void)BIO_shutdown_wr( ssl->bio_net_io ); shutdown_input = false; } // Read all available data from the SSL socket if (!ssl->ssl_closed && ssl->in_count < ssl->in_size) { int read = BIO_read( ssl->bio_ssl, &ssl->inbuf[ssl->in_count], ssl->in_size - ssl->in_count ); if (read > 0) { ssl_log( transport, "Read %d bytes from SSL socket for app", read ); ssl_log_clear_data(transport, &ssl->inbuf[ssl->in_count], read ); ssl->in_count += read; work_pending = true; } else { if (!BIO_should_retry(ssl->bio_ssl)) { int reason = SSL_get_error( ssl->ssl, read ); switch (reason) { case SSL_ERROR_ZERO_RETURN: // SSL closed cleanly ssl_log(transport, "SSL connection has closed"); start_ssl_shutdown(transport); // KAG: not sure - this may not be necessary ssl->ssl_closed = true; break; default: // unexpected error return (ssize_t)ssl_failed(transport); } } else { if (BIO_should_write( ssl->bio_ssl )) { ssl->write_blocked = true; ssl_log(transport, "Detected write-blocked"); } if (BIO_should_read( ssl->bio_ssl )) { ssl->read_blocked = true; ssl_log(transport, "Detected read-blocked"); } } } } // write incoming data to app layer if (!ssl->app_input_closed) { if (ssl->in_count > 0 || ssl->ssl_closed) { /* if ssl_closed, send 0 count */ ssize_t consumed = transport->io_layers[layer+1]->process_input(transport, layer+1, ssl->inbuf, ssl->in_count); if (consumed > 0) { ssl->in_count -= consumed; if (ssl->in_count) memmove( ssl->inbuf, ssl->inbuf + consumed, ssl->in_count ); work_pending = true; ssl_log( transport, "Application consumed %d bytes from peer", (int) consumed ); } else if (consumed < 0) { ssl_log(transport, "Application layer closed its input, error=%d (discarding %d bytes)", (int) consumed, (int)ssl->in_count); ssl->in_count = 0; // discard any pending input ssl->app_input_closed = consumed; if (ssl->app_output_closed && ssl->out_count == 0) { // both sides of app closed, and no more app output pending: start_ssl_shutdown(transport); } } else { // app did not consume any bytes, must be waiting for a full frame if (ssl->in_count == ssl->in_size) { // but the buffer is full, not enough room for a full frame. // can we grow the buffer? uint32_t max_frame = pn_transport_get_max_frame(transport); if (!max_frame) max_frame = ssl->in_size * 2; // no limit if (ssl->in_size < max_frame) { // no max frame limit - grow it. size_t newsize = pn_min(max_frame, ssl->in_size * 2); char *newbuf = (char *)realloc( ssl->inbuf, newsize ); if (newbuf) { ssl->in_size = newsize; ssl->inbuf = newbuf; work_pending = true; // can we get more input? } } else { // can't gather any more input, but app needs more? // This is a bug - since SSL can buffer up to max-frame, // the application _must_ have enough data to process. If // this is an oversized frame, the app _must_ handle it // by returning an error code to SSL. pn_transport_log(transport, "Error: application unable to consume input."); } } } } } } while (work_pending); //_log(ssl, "ssl_closed=%d in_count=%d app_input_closed=%d app_output_closed=%d", // ssl->ssl_closed, ssl->in_count, ssl->app_input_closed, ssl->app_output_closed ); // PROTON-82: Instead, close the input side as soon as we've completed enough of the SSL // shutdown handshake to send the close_notify. We're not requiring the response, as // some implementations never reply. // --- // tell transport our input side is closed if the SSL socket cannot be read from any // longer, AND any pending input has been written up to the application (or the // application is closed) //if (ssl->ssl_closed && ssl->app_input_closed) { // consumed = ssl->app_input_closed; //} if (ssl->app_input_closed && (SSL_get_shutdown(ssl->ssl) & SSL_SENT_SHUTDOWN) ) { consumed = ssl->app_input_closed; if (transport->io_layers[layer]==&ssl_output_closed_layer) { transport->io_layers[layer] = &ssl_closed_layer; } else { transport->io_layers[layer] = &ssl_input_closed_layer; } } ssl_log(transport, "process_input_ssl() returning %d", (int) consumed); return consumed; }
static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_len) { pni_ssl_t *ssl = transport->ssl; if (!ssl) return PN_EOS; if (ssl->ssl == NULL && init_ssl_socket(transport, ssl)) return PN_EOS; ssize_t written = 0; bool work_pending; do { work_pending = false; // first, get any pending application output, if possible if (!ssl->app_output_closed && ssl->out_count < ssl->out_size) { ssize_t app_bytes = transport->io_layers[layer+1]->process_output(transport, layer+1, &ssl->outbuf[ssl->out_count], ssl->out_size - ssl->out_count); if (app_bytes > 0) { ssl->out_count += app_bytes; work_pending = true; ssl_log(transport, "Gathered %d bytes from app to send to peer", app_bytes ); } else { if (app_bytes < 0) { ssl_log(transport, "Application layer closed its output, error=%d (%d bytes pending send)", (int) app_bytes, (int) ssl->out_count); ssl->app_output_closed = app_bytes; } } } // now push any pending app data into the socket if (!ssl->ssl_closed) { char *data = ssl->outbuf; if (ssl->out_count > 0) { int wrote = BIO_write( ssl->bio_ssl, data, ssl->out_count ); if (wrote > 0) { data += wrote; ssl->out_count -= wrote; work_pending = true; ssl_log( transport, "Wrote %d bytes from app to socket", wrote ); } else { if (!BIO_should_retry(ssl->bio_ssl)) { int reason = SSL_get_error( ssl->ssl, wrote ); switch (reason) { case SSL_ERROR_ZERO_RETURN: // SSL closed cleanly ssl_log(transport, "SSL connection has closed"); start_ssl_shutdown(transport); // KAG: not sure - this may not be necessary ssl->out_count = 0; // can no longer write to socket, so erase app output data ssl->ssl_closed = true; break; default: // unexpected error return (ssize_t)ssl_failed(transport); } } else { if (BIO_should_read( ssl->bio_ssl )) { ssl->read_blocked = true; ssl_log(transport, "Detected read-blocked"); } if (BIO_should_write( ssl->bio_ssl )) { ssl->write_blocked = true; ssl_log(transport, "Detected write-blocked"); } } } } if (ssl->out_count == 0) { if (ssl->app_input_closed && ssl->app_output_closed) { // application is done sending/receiving data, and all buffered output data has // been written to the SSL socket start_ssl_shutdown(transport); } } else if (data != ssl->outbuf) { memmove( ssl->outbuf, data, ssl->out_count ); } } // read from the network bio as much as possible, filling the buffer if (max_len) { int available = BIO_read( ssl->bio_net_io, buffer, max_len ); if (available > 0) { max_len -= available; buffer += available; written += available; ssl->write_blocked = false; work_pending = work_pending || max_len > 0; ssl_log(transport, "Read %d bytes from BIO Layer", available ); } } } while (work_pending); //_log(ssl, "written=%d ssl_closed=%d in_count=%d app_input_closed=%d app_output_closed=%d bio_pend=%d", // written, ssl->ssl_closed, ssl->in_count, ssl->app_input_closed, ssl->app_output_closed, BIO_pending(ssl->bio_net_io) ); // PROTON-82: close the output side as soon as we've sent the SSL close_notify. // We're not requiring the response, as some implementations never reply. // ---- // Once no more data is available "below" the SSL socket, tell the transport we are // done. //if (written == 0 && ssl->ssl_closed && BIO_pending(ssl->bio_net_io) == 0) { // written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS; //} if (written == 0 && (SSL_get_shutdown(ssl->ssl) & SSL_SENT_SHUTDOWN) && BIO_pending(ssl->bio_net_io) == 0) { written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS; if (transport->io_layers[layer]==&ssl_input_closed_layer) { transport->io_layers[layer] = &ssl_closed_layer; } else { transport->io_layers[layer] = &ssl_output_closed_layer; } } ssl_log(transport, "process_output_ssl() returning %d", (int) written); return written; }