InitializeSecurityContext (Schannel) returns SEC_I_INCOMPLETE_CREDENTIALS 意外
InitializeSecurityContext (Schannel) returns SEC_I_INCOMPLETE_CREDENTIALS unexpectedly
我已经实现了使用 SSPI 创建安全连接的代码。我用它通过 SMTP 发送邮件。它适用于 smtp.gmail.com
和我尝试过的其他一些服务器。但它不适用于 smtp.live.com
。 InitializeSecurityContext()
returns我的第二个电话SEC_I_INCOMPLETE_CREDENTIALS
。据我了解,这意味着我需要提供客户证书。但这是一个 public 免费邮件服务器。它需要什么客户端证书?或者我可能使用了一些错误的参数或标志?
我连接到 smtp.live.com:587 最初连接是未加密的纯文本。首先,我发送 EHLO 命令,然后发送 STARTTLS 命令。之后我使用下面的代码执行 SSL 握手
这是代码:
SCHANNEL_CRED schCred;
memset(&schCred,0,sizeof(schCred));
schCred.dwVersion=SCHANNEL_CRED_VERSION; // == 4
schCred.dwFlags=SCH_CRED_NO_DEFAULT_CREDS;
// acquire credentials
SECURITY_STATUS status=
AcquireCredentialsHandle(NULL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NULL,
&schCred,NULL,NULL,&Creds,NULL);
// then I create a context
const DWORD dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
SECURITY_STATUS scRet =
InitializeSecurityContextW(&Creds, NULL, targetName, dwSSPIFlags,
0, 0 , NULL, 0, &Context,&outBuffer, &dwSSPIOutFlags, &tsExpiry);
// scRet==SEC_I_CONTINUE_NEEDED
SendOutBuffer(outBuffers);
ReceiveInputBuffers(inBuffers);
// Then I call InitializeSecurityContext for the second time
scRet = InitializeSecurityContext(&Creds, &Context, NULL, dwSSPIFlags,
0, 0, &inBuffer, 0, NULL, &outBuffer, &dwSSPIOutFlags, &tsExpiry);
if( ( SEC_E_OK == scRet || SEC_I_CONTINUE_NEEDED == scRet ||
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR) )
&& BufferNotEmpty(outBuffers)) {
SendOutBuffer(outBuffers);
}
if(SEC_I_INCOMPLETE_CREDENTIALS == scRet) {
// This is where I am when connecting to smtp.live.com
}
回答我自己的问题。我使用 Wireshark 捕获了流量,是的,smtp.live.com 在 TLS 握手期间确实要求提供客户端证书。但这足以向它发送一个空的(零长度)证书来继续握手。为此,在获得 SEC_I_INCOMPLETE_CREDENTIALS
之后,我只需使用相同的参数再次调用 InitializeSecurityContext()
。换句话说,在获得 SEC_I_INCOMPLETE_CREDENTIALS
之后,我继续握手循环,但跳过接收下一次迭代的输入缓冲区。如果我第二次得到 SEC_I_INCOMPLETE_CREDENTIALS
,我会报错退出,因为我的客户端不支持使用证书进行身份验证。 (其实我不确定有没有可能得到SEC_I_INCOMPLETE_CREDENTIALS
两次)
我已经实现了使用 SSPI 创建安全连接的代码。我用它通过 SMTP 发送邮件。它适用于 smtp.gmail.com
和我尝试过的其他一些服务器。但它不适用于 smtp.live.com
。 InitializeSecurityContext()
returns我的第二个电话SEC_I_INCOMPLETE_CREDENTIALS
。据我了解,这意味着我需要提供客户证书。但这是一个 public 免费邮件服务器。它需要什么客户端证书?或者我可能使用了一些错误的参数或标志?
我连接到 smtp.live.com:587 最初连接是未加密的纯文本。首先,我发送 EHLO 命令,然后发送 STARTTLS 命令。之后我使用下面的代码执行 SSL 握手
这是代码:
SCHANNEL_CRED schCred;
memset(&schCred,0,sizeof(schCred));
schCred.dwVersion=SCHANNEL_CRED_VERSION; // == 4
schCred.dwFlags=SCH_CRED_NO_DEFAULT_CREDS;
// acquire credentials
SECURITY_STATUS status=
AcquireCredentialsHandle(NULL,UNISP_NAME,SECPKG_CRED_OUTBOUND,NULL,
&schCred,NULL,NULL,&Creds,NULL);
// then I create a context
const DWORD dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
SECURITY_STATUS scRet =
InitializeSecurityContextW(&Creds, NULL, targetName, dwSSPIFlags,
0, 0 , NULL, 0, &Context,&outBuffer, &dwSSPIOutFlags, &tsExpiry);
// scRet==SEC_I_CONTINUE_NEEDED
SendOutBuffer(outBuffers);
ReceiveInputBuffers(inBuffers);
// Then I call InitializeSecurityContext for the second time
scRet = InitializeSecurityContext(&Creds, &Context, NULL, dwSSPIFlags,
0, 0, &inBuffer, 0, NULL, &outBuffer, &dwSSPIOutFlags, &tsExpiry);
if( ( SEC_E_OK == scRet || SEC_I_CONTINUE_NEEDED == scRet ||
FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR) )
&& BufferNotEmpty(outBuffers)) {
SendOutBuffer(outBuffers);
}
if(SEC_I_INCOMPLETE_CREDENTIALS == scRet) {
// This is where I am when connecting to smtp.live.com
}
回答我自己的问题。我使用 Wireshark 捕获了流量,是的,smtp.live.com 在 TLS 握手期间确实要求提供客户端证书。但这足以向它发送一个空的(零长度)证书来继续握手。为此,在获得 SEC_I_INCOMPLETE_CREDENTIALS
之后,我只需使用相同的参数再次调用 InitializeSecurityContext()
。换句话说,在获得 SEC_I_INCOMPLETE_CREDENTIALS
之后,我继续握手循环,但跳过接收下一次迭代的输入缓冲区。如果我第二次得到 SEC_I_INCOMPLETE_CREDENTIALS
,我会报错退出,因为我的客户端不支持使用证书进行身份验证。 (其实我不确定有没有可能得到SEC_I_INCOMPLETE_CREDENTIALS
两次)