0

I'm implementing the program related to logon domain with certificate by custom KSP and my credential provider. I have successfully interacted from my credential provider to custom KSP. I'm in the process of implementing custom KSP. The steps I perform handling in custom KSP are as follows:

  1. Install the template certificate Kerberos that has been issued from ADCS to local machine store.
  2. Export the private key from the file (.pfx) that has been issued from ADCS via the command:
#openssl pkcs12 -in sample.pfx -nocerts -nodes -out sample.key.
#openssl rsa -in sample.key -out sample_private.key.
  1. The flow custom KSP looks like this:
SampleKSPOpenProvider -> SampleKSPOpenKey-> SampleKSPGetKeyProperty -> SampleKSPSignHash.
  1. In SampleKSPSignHash, I read the private key and imported the key, then implemented the functions BCryptCreateHash, BCryptHashData, BCryptFinishHash, and finally BCryptSignHash. The data hash will be taken from SampleKSPGetKeyProperty by reading the certificate from the local machine store(CertContext->pbCertEncoded).But I'm having trouble with the hash data and there was an error during BCryptSignHash.Below is the code of SampleKSPGetKeyProperty:
SECURITY_STATUS
WINAPI
SampleKSPGetKeyProperty(
__in NCRYPT_PROV_HANDLE hProvider,
__in NCRYPT_KEY_HANDLE hKey,
__in LPCWSTR pszProperty,
__out_bcount_part_opt(cbOutput, *pcbResult) PBYTE pbOutput,
__in DWORD cbOutput,
__out DWORD * pcbResult,
__in DWORD dwFlags)
{
    ....
    ....
    else if (wcscmp(pszProperty, NCRYPT_CERTIFICATE_PROPERTY) == 0) {
        if (pbOutput == NULL) // get the certificate size {
            *pcbResult = aCertContext->cbCertEncoded;
        }
        else
        {
            if (aCertContext->cbCertEncoded < *pcbResult)
            {
                DebugPrint("ERROR", "Buffer too small!");
                Status = NTE_BUFFER_TOO_SMALL;
                goto cleanup;
            }

            DebugPrint("INFO Returning certificate payload...");
            *pcbResult = aCertContext->cbCertEncoded;
        
            CopyMemory(pbOutput, aCertContext->pbCertEncoded, aCertContext-    
            >cbCertEncoded);
    
            //Debug print the output certEncoded
            char text[4096];
            for (int i = 0; i < aCertContext->cbCertEncoded; i++)
            {
                sprintf((char*)text + (i), "%02X", pbOutput[i]);
            }
            DebugPrint("Call function -> pbOutput: %s", text);

            // There should handle call SampleKSPSignHash directly here ?
            PBYTE pbSignature = NULL;
            DWORD cbSignaturee = 0;

            SampleKSPSignHash(hProvider,hKey,NULL, pbOutput, aCertContext-    
            >cbCertEncoded, pbSignature, pbSignature,0,0);

         }
    }
    ....

}

Next is the code of SampleKSPSignHash, When calling BCryptSignHash, it failed:

SECURITY_STATUS
WINAPI
SampleKSPSignHash(
__in NCRYPT_PROV_HANDLE hProvider,
__in NCRYPT_KEY_HANDLE hKey,
__in_opt VOID *pPaddingInfo,
__in_bcount(cbHashValue) PBYTE pbHashValue,
__in DWORD cbHashValue,
__out_bcount_part_opt(cbSignaturee, *pcbResult) PBYTE pbSignature,
__in DWORD cbSignaturee,
__out DWORD * pcbResult,
__in DWORD dwFlags)
{
    DWORD dwBufferLen = 0, cbKeyBlob = 0;
    PBYTE pbBuffer = NULL, pbKeyBlob = NULL;
    LPBYTE   lpHashData;
    DWORD    dwHashDataSize;
    NTSTATUS status;
    BCRYPT_ALG_HANDLE  hAlg;
    DWORD    dwSignatureSize;
    PBYTE   lpSignature;

    const char* szPemPrivKeyPass = 
    "-----BEGIN RSA PRIVATE KEY-----"
    "MIIEpAIBAAKCAQEAn5JrYEBEC8Yy3cbCzZnu89MyLNsFnuRlWQzKx2toE9xZCuUf"
    ".....
    "eSfelLMqp94Ia//VwTFTnj5jKJCcTkQ4L7M0I2tm3PAM7PUzCxKHgw=="
    "-----END RSA PRIVATE KEY-----";

    if (!CryptStringToBinaryA(szPemPrivKeyPass, 0, CRYPT_STRING_BASE64HEADER, 
        NULL, &dwBufferLen, NULL, NULL))
    {
        return FALSE;
    }
    
    pbBuffer = (PBYTE)LocalAlloc(0, dwBufferLen);
    if (!CryptStringToBinaryA(szPemPrivKeyPass, 0, CRYPT_STRING_BASE64HEADER, 
        pbBuffer, &dwBufferLen, NULL, NULL))
    {
         return FALSE;
    }
    if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,         
       PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, NULL, NULL, 
       &cbKeyBlob))
    {
         return FALSE;
    }

    pbKeyBlob = (PBYTE)LocalAlloc(0, cbKeyBlob);
    if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,     
        PKCS_RSA_PRIVATE_KEY, pbBuffer, dwBufferLen, 0, NULL, pbKeyBlob,     
        &cbKeyBlob))
    {
         return FALSE;
    }

    // -------------START HASH DATA ------------//
    status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 
    0);

    if (!NT_SUCCESS(status)) {
        return FALSE;
    }

    status = BCryptImportKeyPair(hAlg, NULL, LEGACY_RSAPRIVATE_BLOB, &hKey, 
        (PUCHAR)pbKeyBlob, cbKeyBlob, 0);
    if (!NT_SUCCESS(status)) {
        return FALSE;
    }
    if (!GetHashData((PBYTE)pbHashValue, cbHashValue, &lpHashData, 
        &dwHashDataSize)) {
        return FALSE;
    }
    BCryptSignHash(hKey, NULL, (PBYTE)lpHashData, dwHashDataSize, NULL, 0, 
    &dwSignatureSize, 0);

    pbSignature = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignatureSize);

    //----I have failed here---//
    status = BCryptSignHash(hKey, NULL, (PBYTE)lpHashData, dwHashDataSize, 
    pbSignature, dwSignatureSize, &dwSignatureSize, 0);
    if (!NT_SUCCESS(status)) {
        HeapFree(GetProcessHeap(), 0, lpHashData);
        HeapFree(GetProcessHeap(), 0, pbSignature);
        return FALSE; //I have failed here
    }    
}

BOOL GetHashData(PBYTE lpData, DWORD dwDataSize, PBYTE* lplpHashData, 
LPDWORD 
lpdwHashDataSize){
BCRYPT_ALG_HANDLE  hAlg;
BCRYPT_HASH_HANDLE hHash;
DWORD              dwResult;
DWORD              dwHashObjectSize;
PBYTE             lpHashObject;
NTSTATUS           status;
    status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA1_ALGORITHM, NULL, 
    0);
    if (!NT_SUCCESS(status)) {
        DebugPrint("Error: BCryptOpenAlgorithmProvider 0x%.8X\n", 
        GetLastError());
        return FALSE;
    }
    BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH, (PBYTE)&dwHashObjectSize,     
    sizeof(DWORD), &dwResult, 0);

    lpHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, dwHashObjectSize);

    status = BCryptCreateHash(hAlg, &hHash, lpHashObject, dwHashObjectSize, 
    NULL, 0, 0);
    if (!NT_SUCCESS(status)) {
        HeapFree(GetProcessHeap(), 0, lpHashObject);
        BCryptCloseAlgorithmProvider(hAlg, 0);
        return FALSE;
    }
    BCryptHashData(hHash, lpData, dwDataSize, 0);
    BCryptGetProperty(hAlg, BCRYPT_HASH_LENGTH, (PBYTE)lpdwHashDataSize, 
    sizeof(DWORD), &dwResult, 0);
   *lplpHashData = (PBYTE)HeapAlloc(GetProcessHeap(), 0, *lpdwHashDataSize);
    BCryptFinishHash(hHash, *lplpHashData, *lpdwHashDataSize, 0);
    HeapFree(GetProcessHeap(), 0, lpHashObject);
    BCryptDestroyHash(hHash);
    BCryptCloseAlgorithmProvider(hAlg, 0);
    return TRUE;
}   

I think after performing such a process and calling the credential provider will login to the domain. Do I understand that correctly? - Thanks in advance.

sliva
  • 11
  • 4
  • 1
    Code must be present in your question, not in an off-site link. And you must do the work to reduce your code to a [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example). – President James K. Polk Jul 08 '20 at 12:09
  • What is the error you get when calling BCryptSIgnHash? – Frank Jul 08 '20 at 13:28
  • @PresidentJamesK.Polk, Thanks for your suggestions, I have edited this post. – sliva Jul 08 '20 at 14:09
  • @Frank, Status out with Error: BCryptSignHash = C000000D. Although previously passed the funtion of BCryptCreateHash, BCryptHashData, BCryptFinishHash. I wonder if this is due to the import of the private key (BCryptImportKeyPair). But actually when importing, the status returned it passed this function. – sliva Jul 08 '20 at 14:12

1 Answers1

0

It's been a long time since I wrote something like that, but if I remember correctly you need to call BCryptSignHash two times. The first time to get the expected size of the signature and the second time to actually do the signing.

pbOutput

The address of a buffer to receive the signature produced by this function. The cbOutput parameter contains the size of this buffer.

If this parameter is NULL, this function will calculate the size required for the signature and return the size in the location pointed to by the pcbResult parameter.

Even when I already knew the size and handed it over to the function it still complained with STATUS_INVALID_PARAMETER which is the translation of 0xC000000D. Only after I called it twice things started to work. Be sure to read the documentation of the windows crypto API carefully as there are some catches in it. ;-)

EDIT

Looking closer at your example I see that you have 0 as the last parameter in your call to BCryptSignHash. According to the documentation this should be 0x00000002 (PKCS1) or 0x00000008 (PSS):

dwFlags

A set of flags that modify the behavior of this function. The allowed set of flags depends on the type of key specified by the hKey parameter.

This can be one of the following values.

BCRYPT_PAD_PKCS1 Use the PKCS1 padding scheme. The pPaddingInfo parameter is a pointer to a BCRYPT_PKCS1_PADDING_INFO structure.

BCRYPT_PAD_PSS Use the Probabilistic Signature Scheme (PSS) padding scheme. The pPaddingInfo parameter is a pointer to a BCRYPT_PSS_PADDING_INFO structure.

Frank
  • 2,036
  • 1
  • 20
  • 32
  • Thanks for your suggestions. I tried calling 2 times, but it is still seems impossible to Signhash. I'm thinking there seems to be a problem with BCRYPT_RSA_ALGORITHM. In this example http://eternalwindows.jp/crypto/cng/cng06.html. I have successfully signhash a string with the algorithm BCRYPT_ECDSA_P256_ALGORITHM. But when I changed it to BCRYPT_RSA_ALGORITHM, I still got the C000000D error. – sliva Jul 09 '20 at 13:01
  • Thanks Franks, I fixed this issue and was able to signhash the certificate. But does the logon domain handling with the certificate end in the BCryptSignHash function? does LSA will automatically get this pbSignature output sent to the domain and perform logon. I'm actually quite vague about this handling. - Thanks in advance – sliva Jul 10 '20 at 06:46
  • The process normally stops at calling the SignHash of your KSP. However, depending on the number of kerberos tickets your user needs this function may be called more than once. – Frank Jul 10 '20 at 10:57
  • Thanks Frank, Currently I still can't logon on domain. I'm thinking that not being able to logon a domain may be related to not using OTP according to microsoft instructions (https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/gg637807(v=ws.10)?redirectedfrom=MSDN). I wonder if my current custom method above is wrong? Does AD need to implement anything special? After signing the certificate at the SignHash function and stopping it here, how do I debug that my data is correctly packed and sent to the AD to perform logon - Thanks in advance. – sliva Jul 13 '20 at 03:54
  • If you want to use a certificate for logon you must have your DC (domain controller) to trust your certificate (the issuer should be in the list of trusted root CAs). You need to use a correct template with the correct key usage for the certificate and you need to have the UPN of the user as subject alternativ name. You might find more information what exactly goes wrong in the windows eventlog, especially CAPI2 and Crypto-NCrypt. Furthermore you might enable detailed logging of kerberos-events with some registry entries (I don't remeber which one, but you can google that ;-)). – Frank Jul 13 '20 at 16:30
  • Thanks Frank. The window server side, DC to trust certificate (the issuer should be in the list of trusted root CAs), and the UPN of the user as subject alternativ name is also available. But still cannot logon, The event log always returns the error code ID 4625. I wonder at KSP that handled signhash certificate as the code presented above that could this possibly be the reason? ( Data signhash is 【aCertContext-> pbCertEncoded】which is taken from the certificate in the local machine store). In addition, I have also verified the signature with the public key of the certificate successfully.. – sliva Jul 17 '20 at 10:01
  • As the original problem is solved you should open a new question with all the informations from CAPI2 and Crypto-NCrypt log. Not just an error code ID. – Frank Jul 17 '20 at 10:33
  • Thank Frank, I have created a new question, could you please help me? - https://stackoverflow.com/questions/62982683/custom-cng-ksp-and-logon-domain – sliva Jul 19 '20 at 16:18