Days of troubleshooting on this one, googling solutions & re-reading Microsoft documentation on the needed functions. Changing variables, retrying again and again. Help is very thoroughly appreciated, I'm sure it's not just me running into this.
I am working to implement networked client & server apps which communicate with a SSL/TLS layer using SChannel (and later will also get this working with OpenSSL for cross-compatibility). I have a client which is known to work, so we can focus on server-side.
For now the goal is to have it work without providing a certificate on either side (they should just generate on the fly as needed). I perform AcquireCredentialsHandle, load up the initial token from the client (running on the same host) and call AcceptSecurityContext.
It seems that no matter what variable I change I always end up with the same 0x80090331 error on the first call to AcceptSecurityContext. Tested on windows 7 & Windows Server 2012.
It seems to me there must be something outside my code, an OS setting that I need to fix. I'm finding contradictory information on the web. TLS 1.1 & TLS 1.2 have been added to the registry under SecurityProviders\SCHANNEL\Protocols* and set with a data DisabledByDefault=0. Also added ", schannel.dll" to 'SecurityProviders'.
Code is as follows:
... snip ... (Code to call AcquireCredentialsHandle)
PCCERT_CONTEXT serverCerts[1] = {0};
SCHANNEL_CRED sc = {0};
sc.dwVersion = SCHANNEL_CRED_VERSION;
//sc.grbitEnabledProtocols = SP_PROT_SSL3_SERVER | SP_PROT_TLS1_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER;
sc.grbitEnabledProtocols = SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_2_SERVER;
sc.dwFlags = 0;
sc.cCreds = 0; // Let Crypto API find the appropriate certificate for us
sc.paCred = serverCerts;
TimeStamp tsExpiry;
SECURITY_STATUS status = AcquireCredentialsHandle(
NULL,
UNISP_NAME,
SECPKG_CRED_INBOUND,
NULL,
&sc,
NULL,
NULL,
&hCredential,
&tsExpiry);
std::cout << "AcquireCredentialsHandle result = 0x" << std::hex << status << std::endl;
... snip ... (Code to call AcceptSecurityContext)
// TOKEN is received from client into m_receivedData
//std::vector<char> m_receivedData;
m_ctxtFlags = ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM;
SecBuffer inBuffers[2];
// Provide Schannel with the remote host's handshake data
inBuffers[0].pvBuffer = (char*)(&m_receivedData[0]);
inBuffers[0].cbBuffer = (unsigned long)m_receivedData.size();
inBuffers[0].BufferType = SECBUFFER_TOKEN;
inBuffers[1].pvBuffer = NULL;
inBuffers[1].cbBuffer = 0;
inBuffers[1].BufferType = SECBUFFER_EMPTY;
SecBufferDesc inBufferDesc = {0};
inBufferDesc.cBuffers = 2;
inBufferDesc.pBuffers = inBuffers;
inBufferDesc.ulVersion = SECBUFFER_VERSION;
SecBuffer outBuffers[2];
// We let Schannel allocate the output buffer for us
outBuffers[0].pvBuffer = NULL;
outBuffers[0].cbBuffer = 0;
outBuffers[0].BufferType = SECBUFFER_TOKEN;
// Contains alert data if an alert is generated
outBuffers[1].pvBuffer = NULL;
outBuffers[1].cbBuffer = 0;
outBuffers[1].BufferType = SECBUFFER_ALERT;
SecBufferDesc outBufferDesc = {0};
outBufferDesc.cBuffers = 2;
outBufferDesc.pBuffers = outBuffers;
outBufferDesc.ulVersion = SECBUFFER_VERSION;
unsigned long fContextAttr;
TimeStamp tsTimeStamp;
SECURITY_STATUS status = NULL;
if (isFirstCall) {
status = AcceptSecurityContext(
&hCredential,
NULL,
&inBufferDesc,
m_ctxtFlags,
0,
&hNewContext,
&outBufferDesc,
&fContextAttr,
NULL);
std::cout << "AcceptSecurityContext (isFirstCall) result = 0x" << std::hex << status << std::endl;
}
SSL/TLS requires a server-side certificate. A missing server-side certificate generates the 0x80090331 error. The next step is to create a self-signed certificate as needed. See CertCreateSelfSignCertificate. A C example using CryptoAPI can be found here.
Related
I have a certificate that I need to send in the header of an http request. This is how I acquired the cert:
PCCERT_CONTEXT cert = nullptr;
wstring store = // store name
wstring subjectName = // subject name
HCERTSTORE hStoreHandle = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
store.c_str());
cert = CertFindCertificateInStore(
hStoreHandle,
X509_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
subjectName.c_str(),
NULL);
I need to send it as a custom header, as the load balancer that sits in front of my service strips off the certificate header ["X-ARR-CLIENTCERT"] before forwarding the request. I believe I need to send the cert->pbCertEncoded, but on the server, I can't decode it and convert it back to an X509Certificate2.
This is what I tried on the client:
request.headers().add("client-cert", cert->pbCertEncoded);
On the server:
var headerCert = Request.Headers["client-cert"];
byte[] certdata = Convert.FromBase64String(headerCert);
X509Certificate2 cert = new X509Certificate2(certdata);
The request header on the server is non-null. But it cannot parse it back to an X509Certificate2.
I tried another thing on the client. After getting the cert, I converted it to a string
DWORD size = 0;
CryptBinaryToString(cert->pbCertEncoded, cert->cbCertEncoded, CRYPT_STRING_BASE64, NULL, &size);
LPWSTR outstring = new TCHAR[size];
CryptBinaryToString(cert->pbCertEncoded, cert->cbCertEncoded, CRYPT_STRING_BASE64, outstring, &size);
If I try to send outstring in the header, it complains:
WinHttpAddRequestHeaders: 87: The parameter is incorrect.
But when I take the contents of outstring and try to parse it on the server, it decodes back to the right certificate. This tells me that I'm not doing something right when passing cert->pbCertEncoded in the header. Maybe I need to re-encode it or transform it somehow so the server can correctly parse it? I'd appreciate any help. Thanks!
My client is in c++ and server in .NET. I'm using cpprestsdk to send the certificate in the http request.
The pbCertEncoded is the ASN.1 encoded representation of the certificate. Look for instance here.
So you must encode the bytes to base64 for instance like this:
#include <Wincrypt.h>
#pragma comment (lib, "Crypt32.lib")
int ToBase64Crypto(const BYTE* pSrc, int nLenSrc, char* pDst, int nLenDst )
{
DWORD nLenOut = nLenDst;
BOOL fRet = CryptBinaryToString(
(const BYTE*)pSrc,
nLenSrc,
CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF,
pDst, &nLenOut
);
if (!fRet) {
nLenOut=0; // failed
}
return (nLenOut);
}
Helps on commands within redis-cli are stored in redis/src/help.h.
I would like to provide my help for commands loaded via redis module (using loadmodule). I could find relevant information from Redis Modules: an introduction to the API
Do you have any suggestion?
I did a check on redis/src/redis-cli.c, the help is created during compile time. Currently there is no possibility to do that.
static void cliInitHelp(void) {
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
int groupslen = sizeof(commandGroups)/sizeof(char*);
int i, len, pos = 0;
helpEntry tmp;
helpEntriesLen = len = commandslen+groupslen;
helpEntries = zmalloc(sizeof(helpEntry)*len);
for (i = 0; i < groupslen; i++) {
tmp.argc = 1;
tmp.argv = zmalloc(sizeof(sds));
tmp.argv[0] = sdscatprintf(sdsempty(),"#%s",commandGroups[i]);
tmp.full = tmp.argv[0];
tmp.type = CLI_HELP_GROUP;
tmp.org = NULL;
helpEntries[pos++] = tmp;
}
for (i = 0; i < commandslen; i++) {
tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
tmp.full = sdsnew(commandHelp[i].name);
tmp.type = CLI_HELP_COMMAND;
tmp.org = &commandHelp[i];
helpEntries[pos++] = tmp;
}
}
Redis module developers should not write their module command document in redis/src/help/h. I would suggest the following:
Using a new Module API function, module developer register new command documentation (consisting of command syntax, summary, since, group) into a system hash.
redis-cli reads additional command documentation from the system hash, to populate the helpEntries[].
I am currently using HttpDeclarePushto exploit the Server Push feature in HTTP/2.
I am able to successfully create all the parameters that this function accepts. But the issue is when HttpDeclarePushexecutes it returns a value of 1229 (ERROR_CONNECTION_INVALID) - https://learn.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1000-1299-.
On further investigation I found that the HttpHeaderConnection in _HTTP_HEADER_ID (https://learn.microsoft.com/en-us/windows/desktop/api/http/ne-http-_http_header_id) is actually passed in the function as 'close'. That implies that on every request response the server closes the connection and that is also happening in my case, I checked it in the log.
Here is the code.
class http2_native_module : public CHttpModule
{
public:
REQUEST_NOTIFICATION_STATUS OnBeginRequest(IN IHttpContext * p_http_context, IN IHttpEventProvider * p_provider)
{
HTTP_REQUEST_ID request_id;
const HTTPAPI_VERSION version = HTTPAPI_VERSION_2;
auto pHttpRequest = p_http_context->GetRequest();
auto phttpRequestRaw = pHttpRequest->GetRawHttpRequest();
HANDLE p_req_queue_handle = nullptr;
auto isHttp2 = phttpRequestRaw->Flags;
try {
const auto request_queue_handle = HttpCreateRequestQueue(version, nullptr, nullptr, NULL, &p_req_queue_handle);
const auto verb = phttpRequestRaw->Verb;
const auto http_path = L"/polyfills.0d74a55d0dbab6b8c32c.js"; //ITEM that I want to PUSH to client
const auto query = nullptr;
request_id = phttpRequestRaw->RequestId;
auto headers = phttpRequestRaw->Headers;
auto connId = phttpRequestRaw->ConnectionId;
WriteEventViewerLog(L"OnBeginRequest - Entering HTTPDECLAREPUSH");
headers.KnownHeaders[1].pRawValue = NULL;
headers.KnownHeaders[1].RawValueLength = 0;
const auto is_success = HttpDeclarePush(p_req_queue_handle, request_id, verb, http_path, query, &headers);
sprintf_s(szBuffer, "%lu", is_success);
Log("is_success value", szBuffer); //ERROR CODE 1229 here
HttpCloseRequestQueue(p_req_queue_handle);
}
catch (std::bad_alloc & e)
{
auto something = e;
}
return RQ_NOTIFICATION_CONTINUE;
}
I even tried to update the header connection value as below but it still gives me 1229.
headers.KnownHeaders[1].pRawValue = NULL;
headers.KnownHeaders[1].RawValueLength = 0;
I understand from https://http2.github.io/http2-spec/ that HTTP/2 actually ignores the content in HTTP HEADERs and uses some other mechanism as part of its FRAME.
This brings us to the next question on how we can keep the connection OPEN and is it something related to the FRAME (similar to HEADER) that HTTP2 uses, if so, how C++ or rather Microsoft helps us to play and exploit with the FRAME in HTTP2?
I'm opening the certificate store using the "CertOpenStore" API and get the certificates using the "CertEnumCertificatesInStore" API.
The CERT_CONTEXT data returned by the API gives the issuer name in CERT_NAME_BLOB type.
How to get the CERT_RDN or CERT_NAME_INFO from the certificate.?
My requirement is to get the issuer name attributes (O, OU, etc.). I do not want to parse the string returned by the CertNameToStr API.
The above comment is correct, you do need to decode the ASN.1 encoded data in the CERT_NAME_BLOB. However, the CryptoAPI has a function to do this for you - CryptDecodeObject.
If you have a PCCERT_CONTEXT handle pCertContext, you can decode it to a CERT_NAME_INFO structure as follows:
BOOL success = CryptDecodeObject(
X509_ASN_ENCODING,
X509_NAME,
pCertContext->pCertInfo->Issuer.pbData,
pCertContext->pCertInfo->Issuer.cbData,
0,
NULL,
&dwNameInfoSize);
// (check that CryptDecodeObject succeeded)
PCERT_NAME_INFO pCertNameInfo = (PCERT_NAME_INFO) malloc(dwNameInfoSize);
// (check that malloc succeeded)
CryptDecodeObject(
X509_ASN_ENCODING,
X509_NAME,
pCertContext->pCertInfo->Issuer.pbData,
pCertContext->pCertInfo->Issuer.cbData,
0,
pCertNameInfo,
&dwNameInfoSize);
Now you can loop through the different components of the RDN like this:
for (DWORD i = 0; i < pCertNameInfo->cRDN; i++)
{
for (DWORD j = 0; j < pCertNameInfo->rgRDN[i].cRDNAttr; j++)
{
CERT_RDN_ATTR &rdnAttribute = pCertNameInfo->rgRDN[i].rgRDNAttr[j];
//
// Do stuff with the RDN attribute
//
}
}
With each iteration, rdnAttribute will be set to a different component of the issuer name like you want.
Finally, free the memory when you're done:
free(pCertNameInfo);
I would like to ask you a question about implementing mutual authentication with Kerberos, using SSPI and LDAP API.
I am using the guidelines described in: ldap_sasl_bind_s(GSSAPI) - What should be provided in the credentials BERVAL structure.
Here is the algorithm I am using:
//--------------------------------------------------------------------------------------------
// client side
AcquireCredentialsHandle(NULL, "Kerberos", SECPKG_CRED_BOTH, NULL, &secIdent, NULL, NULL, &kClientCredential, &kClientTimeOut);
// AcquireCredentialsHandle returns SEC_E_OK
// begin validation
unsigned long ulClientFlags = ISC_REQ_CONNECTION | ISC_REQ_MUTUAL_AUTH | ISC_REQ_DELEGATE;
int iCliStatus = InitializeSecurityContext(&kClientCredential, isContextNull(kClientContext) ? NULL : &kClientContext,
pacTargetName, ulClientFlags, 0, SECURITY_NATIVE_DREP, pkServerToken,
0, &kClientContext, &kClientToken, &ulContextAttr, NULL);
// InitializeSecurityContext returns SEC_I_CONTINUE_NEEDED
//--------------------------------------------------------------------------------------------
// server side
// ldap_init returns ok
ldap_set_option(ld, LDAP_OPT_SIGN, LDAP_OPT_OFF);
ldap_set_option(ld, LDAP_OPT_ENCRYPT, LDAP_OPT_OFF);
unsigned long ulVersion = LDAP_VERSION3;
ldap_set_option(ld, LDAP_OPT_VERSION, &ulVersion);
// ldap_connect returns LDAP_SUCCESS
// build the credentials based on what InitializeSecurityContext returned
BERVAL creds;
creds.bv_len = kClientToken.pBuffers[0].cbBuffer;
creds.bv_val = reinterpret_cast(kClientToken.pBuffers[0].pvBuffer);
BERVAL* pServerCreds = NULL;
int iError = ldap_sasl_bind_s(ld, "", "GSSAPI", &creds, NULL, NULL, &pServerCreds);
// ldap_sasl_bind_s returns LDAP_SUCCESS
unsigned long ulError = 0;
ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ulError);
// ulError is equal to LDAP_SASL_BIND_IN_PROGRESS
And here is the problem: both LDAP error codes are ok, but pServerCreds points to an empty BERVAL structure (not NULL, but bv_len equals to 0), and it should contain the server credential I have to pass to the next InitializeSecurityContext call. If I use that data to build the SecBufferDesc structure for the following call, it returns SEC_E_INVALID_TOKEN.
Is ldap_sasl_bind_s supposed to return an empty BERVAL or am I doing something wrong?
I have tested the authentication using full SSPI calls (AcceptSecurityContext for the server) and it works just as expected. The problem is that I need the server to be cross-platform, so I cannot use SSPI.
Thanks for taking the time to answer!
Juan
I found the problem.
According to this thread there is a bug with ldap_sasl_bind_s returning empty server credentials in Windows XP. I have tested my application under Windows 2008 Server and the credentials are properly returned.