我正在使用wincrypt api for C ++与从文件加载的预定根CA建立服务器端安全TLS / Schannel连接,但似乎当我尝试通过openssl客户端连接到我的服务器时,如下所示: openssl s_client -connect localhost:20715
它显示了系统存储中的一些CA证书以及我从接受的CA权限列表中的文件加载的CA证书。这是我的错误/预期行为/错误吗?
以下是重现问题的代码示例:
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <assert.h>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#define SECURITY_WIN32 1
#include <Security.h>
#include <WinCrypt.h>
#include <schannel.h>
#include <fstream>
#include <string.h>
#define DEFAULT_BUFLEN 2048
#define DEFAULT_PORT "27015"
static const char* BEGIN_CERTIFICATE_TAG = "-----BEGIN CERTIFICATE-----";
static const char* END_CERTIFICATE_TAG = "-----END CERTIFICATE-----";
static const size_t endTagLength = strlen(END_CERTIFICATE_TAG);
static const char* caCertPath = "C:\\client_ca.crt";
static const char* certPath = "C:\\server.p12";
ULONG getTokenMaxSize()
{
PSecPkgInfo pPackageInfo = NULL;
SECURITY_STATUS status = QuerySecurityPackageInfo("schannel", &pPackageInfo);
ULONG maxTokenSize = pPackageInfo->cbMaxToken;
FreeContextBuffer(pPackageInfo);
return maxTokenSize;
}
void DoHandshake(char* buffer, int bufferLength, char* outBuffer, int outBufferSize)
{
HCERTSTORE caStore = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
FILE *caCertFile = NULL;
fopen_s(&caCertFile, caCertPath, "rb");
fseek(caCertFile, 0, SEEK_END);
size_t caFileSize = ftell(caCertFile);
fclose(caCertFile);
BYTE* caData = new BYTE[caFileSize];
std::fstream caCert(caCertPath, std::ios_base::in | std::ios_base::binary);
caCert.read((char*)caData, caFileSize);
char* startPos = strstr((char*)caData, BEGIN_CERTIFICATE_TAG);
while (startPos != NULL)
{
char* endPos = strstr(startPos, END_CERTIFICATE_TAG) + endTagLength;
DWORD sizeNeeded = 0;
BOOL ok = CryptStringToBinary(startPos, endPos - startPos, CRYPT_STRING_BASE64HEADER, NULL, &sizeNeeded, NULL, NULL);
BYTE* buffer = new BYTE[sizeNeeded];
ok = CryptStringToBinary(startPos, endPos - startPos, CRYPT_STRING_BASE64HEADER, buffer, &sizeNeeded, NULL, NULL);
ok = CertAddEncodedCertificateToStore(caStore, X509_ASN_ENCODING, buffer, sizeNeeded, CERT_STORE_ADD_USE_EXISTING, NULL);
startPos = strstr(endPos, BEGIN_CERTIFICATE_TAG);
}
FILE *certFile = NULL;
fopen_s(&certFile, certPath, "rb");
fseek(certFile, 0, SEEK_END);
size_t fileSize = ftell(certFile);
fclose(certFile);
BYTE* spData = new BYTE[fileSize];
std::fstream cert(certPath, std::ios_base::in | std::ios_base::binary);
cert.read((char*)spData, fileSize);
CRYPT_DATA_BLOB blob;
blob.cbData = (DWORD)fileSize;
blob.pbData = spData;
// Importing the cert. The file cannot be password protected
HCERTSTORE certStore = PFXImportCertStore(&blob, L"", CRYPT_MACHINE_KEYSET);
PCCERT_CONTEXT certContext = CertFindCertificateInStore(certStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);
CertCloseStore(certStore, 0);
DWORD sspiFlags = ASC_REQ_SEQUENCE_DETECT |
ASC_REQ_REPLAY_DETECT |
ASC_REQ_CONFIDENTIALITY |
ASC_REQ_EXTENDED_ERROR |
ASC_REQ_STREAM |
ASC_REQ_MUTUAL_AUTH;
SecBuffer inBuffers[2];
inBuffers[0].BufferType = SECBUFFER_TOKEN;
inBuffers[0].cbBuffer = bufferLength;
inBuffers[0].pvBuffer = buffer;
inBuffers[1].BufferType = SECBUFFER_EMPTY;
inBuffers[1].cbBuffer = 0;
inBuffers[1].pvBuffer = NULL;
SecBufferDesc inBufferDesc;
inBufferDesc.cBuffers = 2;
inBufferDesc.pBuffers = inBuffers;
inBufferDesc.ulVersion = SECBUFFER_VERSION;
SecBuffer outputBuffer[1];
outputBuffer[0].BufferType = SECBUFFER_TOKEN;
outputBuffer[0].cbBuffer = outBufferSize;
outputBuffer[0].pvBuffer = outBuffer;
SecBufferDesc outBufferDesc;
outBufferDesc.cBuffers = 1;
outBufferDesc.pBuffers = outputBuffer;
outBufferDesc.ulVersion = SECBUFFER_VERSION;
CtxtHandle sslContext;
CredHandle credHandle;
SCHANNEL_CRED schannelCred;
memset(&schannelCred, 0, sizeof(schannelCred));
schannelCred.dwVersion = SCHANNEL_CRED_VERSION;
schannelCred.grbitEnabledProtocols = SP_PROT_TLS1;
schannelCred.cCreds = 1;
schannelCred.paCred = &certContext;
schannelCred.hRootStore = NULL;
schannelCred.dwFlags |= SCH_SEND_ROOT_CERT | SCH_CRED_MANUAL_CRED_VALIDATION;
schannelCred.hRootStore = caStore;
TimeStamp expiration;
SECURITY_STATUS status = AcquireCredentialsHandle(
NULL,
UNISP_NAME,
SECPKG_CRED_INBOUND,
NULL,
&schannelCred,
NULL,
NULL,
&credHandle,
&expiration);
TimeStamp expire;
status = AcceptSecurityContext(
(PCredHandle)(&credHandle),
NULL,
&inBufferDesc,
sspiFlags,
0,
&sslContext,
&outBufferDesc,
&sspiFlags,
&expire);
}
int __cdecl main(void)
{
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and port
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Create a SOCKET for connecting to server
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// Setup the TCP listening socket
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// No longer need server socket
closesocket(ListenSocket);
size_t outSize = getTokenMaxSize();
char* outBuf = new char[outSize];
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
DoHandshake(recvbuf, recvbuflen, outBuf, outSize);
iSendResult = send(ClientSocket, outBuf, outSize, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// cleanup
closesocket(ClientSocket);
WSACleanup();
delete outBuf;
return 0;
}