in c/src/ssl/schannel.cpp [1110:1249]
static void client_handshake( pn_transport_t* transport) {
pni_ssl_t *ssl = transport->ssl;
// Feed SChannel ongoing responses from the server until the handshake is complete.
SEC_CHAR *host = (SEC_CHAR *)(ssl->peer_hostname);
ULONG ctxt_requested = ISC_REQ_STREAM | ISC_REQ_USE_SUPPLIED_CREDS;
ULONG ctxt_attrs;
size_t max = 0;
// token_buffs describe the buffer that's coming in. It should have
// a token from the SSL server, or empty if sending final shutdown alert.
bool shutdown = ssl->state == SHUTTING_DOWN;
SecBuffer token_buffs[2];
token_buffs[0].cbBuffer = shutdown ? 0 : ssl->sc_in_count;
token_buffs[0].BufferType = SECBUFFER_TOKEN;
token_buffs[0].pvBuffer = shutdown ? 0 : ssl->sc_inbuf;
token_buffs[1].cbBuffer = 0;
token_buffs[1].BufferType = SECBUFFER_EMPTY;
token_buffs[1].pvBuffer = 0;
SecBufferDesc token_buff_desc;
token_buff_desc.ulVersion = SECBUFFER_VERSION;
token_buff_desc.cBuffers = 2;
token_buff_desc.pBuffers = token_buffs;
// send_buffs will hold information to forward to the peer.
SecBuffer send_buffs[2];
send_buffs[0].cbBuffer = ssl->sc_out_size;
send_buffs[0].BufferType = SECBUFFER_TOKEN;
send_buffs[0].pvBuffer = ssl->sc_outbuf;
send_buffs[1].cbBuffer = 0;
send_buffs[1].BufferType = SECBUFFER_EMPTY;
send_buffs[1].pvBuffer = 0;
SecBufferDesc send_buff_desc;
send_buff_desc.ulVersion = SECBUFFER_VERSION;
send_buff_desc.cBuffers = 2;
send_buff_desc.pBuffers = send_buffs;
SECURITY_STATUS status;
{
csguard g(&ssl->cred->cslock);
status = InitializeSecurityContext(&ssl->cred_handle,
&ssl->ctxt_handle, host, ctxt_requested, 0, 0,
&token_buff_desc, 0, NULL, &send_buff_desc,
&ctxt_attrs, NULL);
}
switch (status) {
case SEC_E_INCOMPLETE_MESSAGE:
// Not enough - get more data from the server then try again.
// Leave input buffers untouched.
ssl_log(transport, PN_LEVEL_TRACE, "client handshake: incomplete record");
ssl->sc_in_incomplete = true;
return;
case SEC_I_CONTINUE_NEEDED:
// Successful handshake step, requiring data to be sent to peer.
ssl->sc_out_count = send_buffs[0].cbBuffer;
// the token is the whole quantity to send
ssl->network_out_pending = ssl->sc_out_count;
ssl->network_outp = ssl->sc_outbuf;
ssl_log(transport, PN_LEVEL_DEBUG, "client handshake token %zu bytes", ssl->network_out_pending);
break;
case SEC_E_OK:
// Handshake complete.
if (shutdown) {
if (send_buffs[0].cbBuffer > 0) {
ssl->sc_out_count = send_buffs[0].cbBuffer;
// the token is the whole quantity to send
ssl->network_out_pending = ssl->sc_out_count;
ssl->network_outp = ssl->sc_outbuf;
ssl_log(transport, PN_LEVEL_DEBUG, "client shutdown token %zu bytes", ssl->network_out_pending);
} else {
ssl->state = SSL_CLOSED;
}
// we didn't touch sc_inbuf, no need to reset
return;
}
if (send_buffs[0].cbBuffer != 0) {
ssl_failed(transport, "unexpected final server token");
break;
}
if (const char *err = tls_version_check(ssl)) {
ssl_failed(transport, err);
break;
}
if (ssl->verify_mode == PN_SSL_VERIFY_PEER || ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME) {
bool tracing = PN_SHOULD_LOG(&transport->logger, PN_SUBSYSTEM_SSL, PN_LEVEL_TRACE);
HRESULT ec = verify_peer(ssl, ssl->cred->trust_store, ssl->peer_hostname, tracing);
if (ec) {
if (ssl->peer_hostname)
ssl_log_error_status(ec, "certificate verification failed for host %s", ssl->peer_hostname);
else
ssl_log_error_status(ec, "certificate verification failed");
ssl_failed(transport, "TLS certificate verification error");
break;
}
}
if (token_buffs[1].BufferType == SECBUFFER_EXTRA && token_buffs[1].cbBuffer > 0) {
// This seems to work but not documented, plus logic differs from decrypt message
// since the pvBuffer value is not set. Grrr.
ssl->extra_count = token_buffs[1].cbBuffer;
ssl->inbuf_extra = ssl->sc_inbuf + (ssl->sc_in_count - ssl->extra_count);
}
QueryContextAttributes(&ssl->ctxt_handle,
SECPKG_ATTR_STREAM_SIZES, &ssl->sc_sizes);
max = ssl->sc_sizes.cbMaximumMessage + ssl->sc_sizes.cbHeader + ssl->sc_sizes.cbTrailer;
if (max > ssl->sc_out_size) {
ssl_log_error("Buffer size mismatch have %d, need %d", (int) ssl->sc_out_size, (int) max);
ssl->state = SHUTTING_DOWN;
ssl->app_input_closed = ssl->app_output_closed = PN_ERR;
start_ssl_shutdown(transport);
pn_do_error(transport, "amqp:connection:framing-error", "SSL Failure: buffer size");
break;
}
ssl->state = RUNNING;
ssl->max_data_size = max - ssl->sc_sizes.cbHeader - ssl->sc_sizes.cbTrailer;
ssl_log(transport, PN_LEVEL_DEBUG, "client handshake successful %zu max record size", max);
break;
case SEC_I_CONTEXT_EXPIRED:
// ended before we got going
default:
ssl_log(transport, PN_LEVEL_ERROR, "client handshake failed %d", (int) status);
ssl_failed(transport, 0);
break;
}
if (token_buffs[1].BufferType == SECBUFFER_EXTRA && token_buffs[1].cbBuffer > 0 &&
!ssl->ssl_closed) {
// remaining data after the consumed TLS record(s)
ssl->extra_count = token_buffs[1].cbBuffer;
ssl->inbuf_extra = ssl->sc_inbuf + (ssl->sc_in_count - ssl->extra_count);
}
ssl->decrypting = false;
rewind_sc_inbuf(ssl);
}