Custom CNG KSP and Logon domain

血红的双手。 提交于 2020-07-23 07:37:11

问题


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. This is step how I Issue certificates and set up logons. Is there something missing?

  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. At SampleKSPSignHash(), My signing data is a buffer that contains the encoded certificate ( pbCertEncoded) and I signed the certificate with the private key and also verified the signature with the public key successfully. I think the final handling is always in the SampleKSPSignHash () function, and I know that function can be called multiple times so I tried force code to always return the pbSignature signature, but still could not logon the domain.The event log always returns the error code ID 4625. About CAPI2 and Crypto-NCrypt log, Although I have enabled but when deploy logon with custom KSP and my credential provider doesn't output. It seems that has not been called yet, then I tried to logon with the default password window, the log was exported. Currently I only have a Log in Custom KSP developing. Is code handling logic wrong?
    Below is the code of SampleKSPSignHash():

    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)
     {
         SECURITY_STATUS     Status = NTE_INTERNAL_ERROR;
         NTSTATUS            ntStatus = STATUS_INTERNAL_ERROR;
         SAMPLEKSP_KEY* pKey = NULL;
         DWORD               cbTmpSig = 0;
         DWORD               cbTmp = 0;
         UNREFERENCED_PARAMETER(hProvider);
         DebugPrint("Call function ");
    
         //Start workround
         //Add handling to hash data and sign certificate with private key.
         char text[4096];
         DWORD dwBufferLen = 0, cbKeyBlob = 0;
         PBYTE pbBuffer = NULL, pbKeyBlob = NULL;
         LPBYTE   lpHashData;
         DWORD    dwHashDataSize;
         NTSTATUS status;
         BCRYPT_ALG_HANDLE  hAlg;
         DWORD    dwSignatureSize;
         PBYTE   lpSignature;
         BCRYPT_PKCS1_PADDING_INFO padding_PKCS1;
         padding_PKCS1.pszAlgId = BCRYPT_SHA1_ALGORITHM;
    
         //Start Regardless of which calls to take, force always signs with the current certificate
         //Alway force input pbHashValue = aCertContext->pbCertEncoded
         HCERTSTORE hMyCertStore = NULL;
         PCCERT_CONTEXT aCertContext = NULL;
         LPBYTE pbData = NULL;
         DWORD cbData = 0;
         DWORD dwKeySpec;
         hMyCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
         X509_ASN_ENCODING,
         0,
         CERT_SYSTEM_STORE_LOCAL_MACHINE,
         L"MY");
    
         if (hMyCertStore == NULL)
         {
             DebugPrint("Call function -> hMyCertStore is NULL");
         }
         aCertContext = CertFindCertificateInStore(hMyCertStore,
         X509_ASN_ENCODING,
         0,
         CERT_FIND_SUBJECT_STR_A,
         L"test01", // use appropriate subject name
         NULL);
         if (aCertContext == NULL)
         {
             DebugPrint("Call function -> Error: aCertContext is NULL");
         }
    
         pbHashValue = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, aCertContext->cbCertEncoded);
         CopyMemory(pbHashValue, aCertContext->pbCertEncoded, aCertContext->cbCertEncoded);
         cbHashValue = aCertContext->cbCertEncoded;
         // End force
    
         //Debug printout
         DebugPrint("Call function - cbHashValue= %ld", cbHashValue);
         DebugPrint("Call function - cbSignaturee= %ld", cbSignaturee);
         DebugPrint("Call function - dwFlags= %ld", dwFlags);
         for (int i = 0; i < cbHashValue; i++)
         {
             sprintf((char*)text + (i * 2), "%02X", pbHashValue[i]);
         }
         DebugPrint("Call function -> pbHashValue: %s", text);
    
         // ------- HARCODE PRIVATE KEY ------ //
         //Import the previously exported private key using the pfx file.Use the command below to     
         //export the private key.
         //Command :#openssl pkcs12 -in sample.pfx -nocerts -nodes -out sample.key
         //         #openssl rsa -in sample.key -out sample_private.key
         const char* szPemPrivKeyPass =
         "-----BEGIN RSA PRIVATE KEY-----"
         "MIIEowIBAAKCAQEA1MtKkDL5RuY7lYwCZy38x1w9kisJLhyb7VkIlodJPLyqkQUZ"
         "eDjEvSyKl75ucgB4gzyO4MyYbH/lrttXH2sR830gG40MKz6EnsxyzCsCgYEA4AUC"
         "fz5l+q7lW9Fm+hhM9duDFO5EME6RDJp6MIEWH9C0khv2wWhNJCdWNPwwlPgCWIpL"
         "7ueaVfhErJkJLzH8V8gIPpb5Hot4YUycTNvZffSeS+RE5AF9kWgzlxcd31fGHgZZ"
         "f9W0xn7ieQS3fFnVWlK900drOQ+qkQ8jMKvxDdcCgYEAtUJnGoSFq6undecimpVI"
         "qwzzfr+MKpt7Ym+cdDrJ3qts+kYCD35O80lNM6TqqSJqCB76EwV3VmyzKQ+1/bZ9"
         "wrb2FPOTew+ytzDh20dOHpAaVt3krCRQ4gBWzjgsWq4NP5cQParfSbvYBlBTkcJX"
         "........................................................."
         "-----END RSA PRIVATE KEY-----";
    
         DebugPrint("Process Start import private key");
         if (!CryptStringToBinaryA(szPemPrivKeyPass, 0, CRYPT_STRING_BASE64HEADER, NULL, &dwBufferLen, 
         NULL, NULL))
         {
             DebugPrint("Failed to convert BASE64 private key. Error 0x%.8X\n", GetLastError());
         }
         pbBuffer = (PBYTE)LocalAlloc(0, dwBufferLen);
         if (!CryptStringToBinaryA(szPemPrivKeyPass, 0, CRYPT_STRING_BASE64HEADER, pbBuffer, 
         &dwBufferLen, NULL, NULL))
         {
             DebugPrint("Failed to convert BASE64 private key. Error 0x%.8X\n", GetLastError());
         }
         if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, 
         pbBuffer, dwBufferLen, 0, NULL, NULL, &cbKeyBlob))
         {
             DebugPrint("Failed to parse private key. Error 0x%.8X\n", GetLastError());
         }
         pbKeyBlob = (PBYTE)LocalAlloc(0, cbKeyBlob);
         if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, 
         pbBuffer, 
         dwBufferLen, 0, NULL, pbKeyBlob, &cbKeyBlob))
         {
             DebugPrint("Failed to parse private key. Error 0x%.8X\n", GetLastError());
         }
    
         // ---------START  HASH DATA ------------//
         DebugPrint("Start Hash the data");
         status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
         if (!NT_SUCCESS(status)) {
             DebugPrint("Error: BCryptOpenAlgorithmProvider");
             return 0;
         }
    
         //Import key pair
         status = BCryptImportKeyPair(hAlg, NULL, LEGACY_RSAPRIVATE_BLOB, &hKey, (PUCHAR)pbKeyBlob, 
         cbKeyBlob, 0);
    
         if (!NT_SUCCESS(status)) {
             DebugPrint("Error: BCryptImportKeyPair : 0x%.8X\n", GetLastError());
             return FALSE;
         }
    
         // Hash Data certificate 
         if (!GetHashData((LPBYTE)pbHashValue, cbHashValue, &lpHashData, &dwHashDataSize)) {
             DebugPrint("Error: GetHashData");
             return FALSE;
         }
    
         //Sign hash certificate
         BCryptSignHash(hKey, &padding_PKCS1, (LPBYTE)lpHashData, dwHashDataSize, NULL, 0,    
         &dwSignatureSize, BCRYPT_PAD_PKCS1);
    
         pbSignature = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSignatureSize);
         status = BCryptSignHash(hKey, &padding_PKCS1, (LPBYTE)lpHashData, dwHashDataSize, 
         pbSignature, dwSignatureSize, pcbResult, BCRYPT_PAD_PKCS1);
    
         //Debug print 
         DebugPrint("Call function - dwHashDataSize= %ld", dwHashDataSize);
         DebugPrint("Call function - pcbResult= %ld", *pcbResult);
         DebugPrint("Call function - dwSignatureSize= %ld", dwSignatureSize);
    
         if (!NT_SUCCESS(status)) {
             DebugPrint("Error: BCryptSignHash= %X", status);
             HeapFree(GetProcessHeap(), 0, lpHashData);
             HeapFree(GetProcessHeap(), 0, pbSignature);
             return FALSE;
         }
    
         // Print the Signature data.
         char textPn[4096];
         for (int i = 0; i < dwSignatureSize; i++){
             sprintf((char*)textPn + (i * 2), "%02X", pbSignature[i]);
         }
         DebugPrint("pbSignature: %s", textPn);
    
         // Verify the signature with the public key
         if (!VerifySign(pbSignature, dwSignatureSize)) {
             DebugPrint("Error signature");
             return FALSE;
         }
    
         DebugPrint("Verify the signature success", );
         Status = ERROR_SUCCESS;
         //End workround
    
         cleanup:
             return Status;
         }
    
     BOOL VerifySign(
     __in_bcount(cbSignaturee) PBYTE pbSignature,
     __in    DWORD   cbSignaturee)
     {
         HCERTSTORE hMyCertStore = NULL;
         PCCERT_CONTEXT aCertContext = NULL;
         LPBYTE pbData = NULL;
         DWORD cbData = 0;
         DWORD dwKeySpec;
         PBYTE pbOutput = NULL;
         PBYTE   vHashData;
         DWORD   vHashDataSize;
         NTSTATUS status;
         BCRYPT_PKCS1_PADDING_INFO padding_PKCS1;
         padding_PKCS1.pszAlgId = BCRYPT_SHA1_ALGORITHM;
    
         DebugPrint("Start VerifySign");
         hMyCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
         X509_ASN_ENCODING,
         0,
         CERT_SYSTEM_STORE_LOCAL_MACHINE,
         L"MY");
    
         if (hMyCertStore == NULL)
         {
             DebugPrint("Call function -> hMyCertStore is NULL");
             return FALSE;
         }
         aCertContext = CertFindCertificateInStore(hMyCertStore,
         X509_ASN_ENCODING,
         0,
         CERT_FIND_SUBJECT_STR_A,
         L"test01", // use appropriate subject name 
         NULL
         );
    
         if (aCertContext == NULL)
         {
             DebugPrint("Call function -> Error: aCertContext is NULL");
             return FALSE;
         }
    
         PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, aCertContext- 
         >pbCertEncoded, aCertContext->cbCertEncoded);
    
         if (!pcCertContext)
         {
             DebugPrint("ERROR: pcCertContext");
             return FALSE;
         }
         BCRYPT_KEY_HANDLE publicKeyHandle = NULL;
         if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &pcCertContext->pCertInfo- 
         >SubjectPublicKeyInfo, 0, NULL, &publicKeyHandle))
         {
             DebugPrint("CryptImportPublicKeyInfoEx2 failed");
             return FALSE;
         }
    
         pbOutput = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, aCertContext->cbCertEncoded);
         CopyMemory(pbOutput, aCertContext->pbCertEncoded, aCertContext->cbCertEncoded);
    
         if (!GetHashData((LPBYTE)pbOutput, aCertContext->cbCertEncoded, &vHashData, &vHashDataSize))         
         {
             DebugPrint("GetHashData failed.");
             return FALSE;
         }
    
         status = BCryptVerifySignature(publicKeyHandle, &padding_PKCS1, vHashData, vHashDataSize, 
         pbSignature, cbSignaturee, BCRYPT_PAD_PKCS1);
    
         if (!NT_SUCCESS(status)) {
             DebugPrint("Error: BCryptSignHash= %X", status);
             return FALSE;
         }
             DebugPrint("Verify Sign OK");
             return TRUE;
         }
    
     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;
    
         DebugPrint("Call function ");
         status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA1_ALGORITHM, NULL, 0);
         if (!NT_SUCCESS(status)) {
             DebugPrint("Error: BCryptOpenAlgorithmProvider 0x%.8X\n", GetLastError());
             return FALSE;
         }
    
         DebugPrint("Call function ");
         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)) {
             DebugPrint("Error: BCryptCreateHash 0x%.8X\n", GetLastError());
             DebugPrint("Error: status= %X\n", status);
             HeapFree(GetProcessHeap(), 0, lpHashObject);
             BCryptCloseAlgorithmProvider(hAlg, 0);
             return FALSE;
         }
    
         DebugPrint("Call function ");
         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);
         DebugPrint("Call function ");
         return TRUE;
     }
    

I also wonder if this Issue has anything to do with the my Credential Provider?
Below is my current Credential Provider code:

   //Build the authentication data used by LsaLogonUser
   void ConstructAuthInfo(LPBYTE* ppbAuthInfo, ULONG* pulAuthInfoLen)
   {
       DebugPrint("call function");
       WCHAR szCardName[] = L""; 
       WCHAR szContainerName[] = L"";
       WCHAR szReaderName[] = L"";
       WCHAR szCspName[] = L"Microsoft Sample Key Storage Provider";
       WCHAR szPin[] = L"1234";
       ULONG ulPinByteLen = wcslen(szPin) * sizeof(WCHAR);
       WCHAR szUserName[] = L"test01";
       ULONG ulUserByteLen = wcslen(szUserName) * sizeof(WCHAR);
       WCHAR szDomainName[] = L"xyz.co"; 
       ULONG ulDomainByteLen = wcslen(szDomainName) * sizeof(WCHAR);
       LPBYTE pbAuthInfo = NULL;
       ULONG  ulAuthInfoLen = 0;
       KERB_CERTIFICATE_LOGON* pKerbCertLogon;
       KERB_SMARTCARD_CSP_INFO* pKerbCspInfo;
       LPBYTE pbDomainBuffer, pbUserBuffer, pbPinBuffer;
       LPBYTE pbCspData;
       LPBYTE pbCspDataContent;

       ULONG ulCspDataLen = sizeof(KERB_SMARTCARD_CSP_INFO) - 
       sizeof(TCHAR) + (wcslen(szCardName) + 1) * sizeof(WCHAR) +
       (wcslen(szCspName) + 1) * sizeof(WCHAR) +
       (wcslen(szContainerName) + 1) * sizeof(WCHAR) +
       (wcslen(szReaderName) + 1) * sizeof(WCHAR);

       ulAuthInfoLen = sizeof(KERB_CERTIFICATE_LOGON) +
       ulDomainByteLen + sizeof(WCHAR) + ulUserByteLen + sizeof(WCHAR) +
       ulPinByteLen + sizeof(WCHAR) + ulCspDataLen;

       pbAuthInfo = (LPBYTE)CoTaskMemAlloc(ulAuthInfoLen);
       ZeroMemory(pbAuthInfo, ulAuthInfoLen);

       pbDomainBuffer = pbAuthInfo + sizeof(KERB_CERTIFICATE_LOGON);
       pbUserBuffer = pbDomainBuffer + ulDomainByteLen + sizeof(WCHAR);
       pbPinBuffer = pbUserBuffer + ulUserByteLen + sizeof(WCHAR);
       pbCspData = pbPinBuffer + ulPinByteLen + sizeof(WCHAR);

       memcpy(pbDomainBuffer, szDomainName, ulDomainByteLen);
       memcpy(pbUserBuffer, szUserName, ulUserByteLen);
       memcpy(pbPinBuffer, szPin, ulPinByteLen);

       pKerbCertLogon = (KERB_CERTIFICATE_LOGON*)pbAuthInfo;

       pKerbCertLogon->MessageType = KerbCertificateLogon;
       pKerbCertLogon->DomainName.Length = (USHORT)ulDomainByteLen;
       pKerbCertLogon->DomainName.MaximumLength = (USHORT)(ulDomainByteLen 
       + sizeof(WCHAR));
       pKerbCertLogon->DomainName.Buffer = (PWSTR)(pbDomainBuffer - pbAuthInfo);
       pKerbCertLogon->UserName.Length = (USHORT)ulUserByteLen;
       pKerbCertLogon->UserName.MaximumLength = (USHORT)(ulUserByteLen + sizeof(WCHAR));
       pKerbCertLogon->UserName.Buffer = (PWSTR)(pbUserBuffer - pbAuthInfo);
       pKerbCertLogon->Pin.Length = (USHORT)ulPinByteLen;
       pKerbCertLogon->Pin.MaximumLength = (USHORT)(ulPinByteLen + sizeof(WCHAR));
       pKerbCertLogon->Pin.Buffer = (PWSTR)(pbPinBuffer - pbAuthInfo);

       pKerbCertLogon->CspDataLength = ulCspDataLen;
       pKerbCertLogon->CspData = (PUCHAR)(pbCspData - pbAuthInfo);

       pKerbCspInfo = (KERB_SMARTCARD_CSP_INFO*)pbCspData;
       pKerbCspInfo->dwCspInfoLen = ulCspDataLen;
       pKerbCspInfo->MessageType = 1;
       pKerbCspInfo->KeySpec = CERT_NCRYPT_KEY_SPEC;
       //pKerbCspInfo->KeySpec = AT_KEYEXCHANGE;

       pKerbCspInfo->nCardNameOffset = 0;
       pKerbCspInfo->nReaderNameOffset = pKerbCspInfo->nCardNameOffset + wcslen(szCardName) + 1;
       pKerbCspInfo->nContainerNameOffset = pKerbCspInfo->nReaderNameOffset + wcslen(szReaderName) + 1;
       pKerbCspInfo->nCSPNameOffset = pKerbCspInfo->nContainerNameOffset + wcslen(szContainerName) + 1;

       pbCspDataContent = pbCspData + sizeof(KERB_SMARTCARD_CSP_INFO) - sizeof(TCHAR);
       memcpy(pbCspDataContent + (pKerbCspInfo->nCardNameOffset * sizeof(WCHAR)), szCardName, wcslen(szCardName) * sizeof(WCHAR));
       memcpy(pbCspDataContent + (pKerbCspInfo->nReaderNameOffset * sizeof(WCHAR)), szReaderName, wcslen(szReaderName) * sizeof(WCHAR));
       memcpy(pbCspDataContent + (pKerbCspInfo->nContainerNameOffset * sizeof(WCHAR)), szContainerName, wcslen(szContainerName) * sizeof(WCHAR));
       memcpy(pbCspDataContent + (pKerbCspInfo->nCSPNameOffset * sizeof(WCHAR)), szCspName, wcslen(szCspName) * sizeof(WCHAR));

       *ppbAuthInfo = pbAuthInfo;
       *pulAuthInfoLen = ulAuthInfoLen;
       DebugPrint("call function -> 12");
   }

   HRESULT CSampleCredential::GetSerialization(
   _Out_ CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpgsr,
   _Out_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs,
   _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText,
   _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon)
   {
       if (DEVELOPING) PrintLn("Credential::GetSerialization");
       HRESULT hr = E_UNEXPECTED;
       *pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED;
       *ppwszOptionalStatusText = nullptr;
       *pcpsiOptionalStatusIcon = CPSI_NONE;
       ZeroMemory(pcpcs, sizeof(*pcpcs));

       //Start add connect to KSP
       if (DEVELOPING) DebugPrint("Start test login");
       ULONG ulAuthPackage;
       hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
       ConstructAuthInfo(&pcpcs->rgbSerialization, &pcpcs->cbSerialization);

       if (SUCCEEDED(hr))
       {
            DebugPrint("Start test SUCCEEDED");
            pcpcs->ulAuthenticationPackage = ulAuthPackage;
    pcpcs->clsidCredentialProvider = CLSID_CSample;

           *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
            DebugPrint("End test SUCCEEDED");
        }

       return hr;
   }

   HRESULT RetrieveNegotiateAuthPackage(ULONG* pulAuthPackage)
   {
       HRESULT hr = S_OK;
       HANDLE hLsa = NULL;

       NTSTATUS status = LsaConnectUntrusted(&hLsa);
       if (SUCCEEDED(HRESULT_FROM_NT(status)))
       {
           ULONG ulAuthPackage;
           LSA_STRING lsaszKerberosName;
           _LsaInitString(&lsaszKerberosName, MICROSOFT_KERBEROS_NAME_A);//NEGOSSP_NAME_A or MICROSOFT_KERBEROS_NAME_A
           status = LsaLookupAuthenticationPackage(hLsa, &lsaszKerberosName, &ulAuthPackage);
           if (SUCCEEDED(HRESULT_FROM_NT(status)))
           {
               *pulAuthPackage = ulAuthPackage;
               hr = S_OK;
           }
           else
           {
               hr = HRESULT_FROM_NT(status);
           }
           LsaDeregisterLogonProcess(hLsa);
       }
       else
       {
           hr = HRESULT_FROM_NT(status);
       }

       return hr;
   }

This is all my code SampleKSP.c. Thank in advance.

来源:https://stackoverflow.com/questions/62982683/custom-cng-ksp-and-logon-domain

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!