Related
I'd like to implement data encryption and decryption in a C++ application running on Windows. I've spent considerable time looking around the Web and am thinking I should probably use the Windows Cryptography API: Next Generation (CNG) functions (although I'm open to better alternatives).
What I find everywhere are complex examples that do all sorts of stuff. I don't feel that confident in this area and so I'd like to find a simple example. In the end, I need a method that takes a string and encrypts, and another methods that decrypts the data back to the string. The user would supply a password for both operations.
This must have been done countless times already. Can anyone point me to a complete and competent example? Ultimately, I'll end up with an Encrypt() and Decrypt() method.
Something that is both secure and performant would be ideal.
Before encrypting (and decrypting) you need to derive key from password with key derivation functions (for example PBKDF2 with SHA256). To prevent pre-computed dictionary attacks
in additional to password you will also need random string (called salt).
Next pick cipher algorithm (AES with 256-bit key is good one) and cipher mode (ECB cipher mode considered weak, so use any other for example CBC). Also it will require one more random string (called initialization vector).
So encrypting algorithm will be:
Generate random salt
Derive key(password, salt) = key
Generate random IV
Encrypt(key, IV, plain text) = cipher text
Input parameters: plain text, password
Output parameters: cipher text, salt, IV
Decrypting algorithm will be:
Derive key(password, salt) = key
Decrypt(key, iv, cipher text) = plain text
Input parameters: cipher text, salt, iv, password
Output parameters: plain text
Sample code:
#include <Windows.h>
#include <iostream>
#include <vector>
#include <array>
#pragma comment(lib, "bcrypt")
static NTSTATUS gen_random(BYTE* buf, ULONG buf_len)
{
BCRYPT_ALG_HANDLE hAlg = nullptr;
NTSTATUS status = NTE_FAIL;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"RNG", nullptr, 0);
if (status != ERROR_SUCCESS) {
return status;
}
status = BCryptGenRandom(hAlg, buf, buf_len, 0);
} while (0);
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
static NTSTATUS derive_key(BYTE* pass, ULONG pass_len, BYTE* salt,
ULONG salt_len, const ULONG iteration, BYTE* derived_key, ULONG derived_key_len)
{
BCRYPT_ALG_HANDLE hPrf = nullptr;
NTSTATUS status = ERROR_SUCCESS;
do {
status = BCryptOpenAlgorithmProvider(&hPrf, L"SHA256", nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG);
if (status != ERROR_SUCCESS) {
break;
}
status = BCryptDeriveKeyPBKDF2(hPrf, pass, pass_len, salt, salt_len, iteration, derived_key, derived_key_len, 0);
} while (0);
if (hPrf) {
BCryptCloseAlgorithmProvider(hPrf, 0);
}
return status;
}
static NTSTATUS do_encrypt(BYTE* key, ULONG key_len, BYTE* plain_text, ULONG plain_text_len,
std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
NTSTATUS status = NTE_FAIL;
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* create key object */
status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* set chaining mode */
std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* generate iv */
ULONG block_len = 0;
ULONG res = 0;
status = BCryptGetProperty(hAlg, BCRYPT_BLOCK_LENGTH, reinterpret_cast<BYTE*>(&block_len), sizeof(block_len), &res, 0);
if (status != ERROR_SUCCESS) {
break;
}
iv.resize(block_len);
status = gen_random(iv.data(), static_cast<ULONG>(iv.size()));
if (status != ERROR_SUCCESS) {
break;
}
/* BCryptEncrypt modify iv parameter, so we need to make copy */
std::vector<BYTE> iv_copy = iv;
/* get cipher text length */
ULONG cipher_text_len = 0;
status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
nullptr, cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
if (status != ERROR_SUCCESS) {
break;
}
cipher_text.resize(static_cast<size_t>(cipher_text_len));
/* now encrypt */
status = BCryptEncrypt(hKey, plain_text, plain_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
cipher_text.data(), cipher_text_len, &cipher_text_len, BCRYPT_BLOCK_PADDING);
} while (0);
/* cleanup */
if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
static NTSTATUS do_decrypt(BYTE* key, ULONG key_len, BYTE* cipher_text, ULONG cipher_text_len,
const std::vector<BYTE>& iv, std::vector<BYTE>& plain_text)
{
NTSTATUS status = NTE_FAIL;
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
status = BCryptOpenAlgorithmProvider(&hAlg, L"AES", nullptr, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* create key object */
status = BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, key, key_len, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* set chaining mode */
std::wstring mode = BCRYPT_CHAIN_MODE_CBC;
BYTE* ptr = reinterpret_cast<BYTE*>(const_cast<wchar_t*>(mode.data()));
ULONG size = static_cast<ULONG>(sizeof(wchar_t) * (mode.size() + 1));
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, ptr, size, 0);
if (status != ERROR_SUCCESS) {
break;
}
/* BCryptEncrypt modify iv parameter, so we need to make copy */
std::vector<BYTE> iv_copy = iv;
/* get expected plain text length */
ULONG plain_text_len = 0;
status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
nullptr, plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
plain_text.resize(static_cast<size_t>(plain_text_len));
/* decrypt */
status = BCryptDecrypt(hKey, cipher_text, cipher_text_len, nullptr, iv_copy.data(), static_cast<ULONG>(iv_copy.size()),
plain_text.data(), plain_text_len, &plain_text_len, BCRYPT_BLOCK_PADDING);
/* actualize size */
plain_text.resize(static_cast<size_t>(plain_text_len));
} while (0);
/* cleanup */
if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return status;
}
NTSTATUS encrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& plain_text,
std::vector<BYTE>& salt, std::vector<BYTE>& iv, std::vector<BYTE>& cipher_text)
{
NTSTATUS status = NTE_FAIL;
salt.resize(8);
std::array<BYTE, 32> key{0x00};
do {
/* generate salt */
status = gen_random(salt.data(), static_cast<ULONG>(salt.size()));
if (status != ERROR_SUCCESS) {
break;
}
/* derive key from password using SHA256 algorithm and 20000 iteration */
status = derive_key(pass, pass_len, salt.data(), static_cast<ULONG>(salt.size()), 20000, key.data(), key.size());
if (status != ERROR_SUCCESS) {
break;
}
/* encrypt */
status = do_encrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(plain_text.data()),
static_cast<ULONG>(plain_text.size()), iv, cipher_text);
} while (0);
SecureZeroMemory(key.data(), key.size());
return status;
}
NTSTATUS decrypt(BYTE* pass, ULONG pass_len, const std::vector<BYTE>& salt, const std::vector<BYTE>& iv,
const std::vector<BYTE>& cipher_text, std::vector<BYTE>& plain_text)
{
NTSTATUS status = NTE_FAIL;
std::array<BYTE, 32> key{0x00};
do {
/* derive key from password using same algorithm, salt and iteraion count */
status = derive_key(pass, pass_len, const_cast<BYTE*>(salt.data()), static_cast<ULONG>(salt.size()),
20000, key.data(), key.size());
if (status != ERROR_SUCCESS) {
break;
}
/* decrypt */
status = do_decrypt(key.data(), static_cast<ULONG>(key.size()), const_cast<BYTE*>(cipher_text.data()),
static_cast<ULONG>(cipher_text.size()), const_cast<BYTE*>(iv.data()),
static_cast<ULONG>(iv.size()), plain_text);
} while (0);
SecureZeroMemory(key.data(), key.size());
return status;
}
How to use CryptoAPI Next Generation (CNG): Don't use it. Use OTP.
If your user is physically sent the key, then use the simple but unbreakable OTP or One Time Pad (See https://en.wikipedia.org/wiki/One-time_pad).
It is very easy to use.
It is very easy to understand.
Simply supply enough OTP keys to the user, which you or someone randomly types in via a keyboard (I told you it is easy), to last for what you think is a reasonable use by the user.
Do NOT derive the keys from passwords or from anything else that the previous posters listed. That is lazy and is NOT secure. Make the keys as I said and then they will NOT be linked to any code source at all.
Do NOT waste your time with "learn some of the fundamentals of encryption and cryptography" (if that learning is not directly in support of your using One Time Pads).
For you to read some discussions and definitions related to OTP's:
https://www.slideshare.net/AsadAli108/3-l4
https://www.slideshare.net/Jonlitan/one-time-pad-encryption-technique
There is no commonly known encryption that even comes close to OTPs. Example: OTPs can not be mathematically or computationally broken down into a mathematical or computational process.
I think that you have asked for simple, and easy, and not so complicated, but secure, and One Time Pads are all that. If you supply the keys to the user directly, then no human (other than Christians with enough faith) can break them.
Some links to help you with hopefully useful independent examples in C and C++:
A quick search gave me these examples which due to the limit on time for this bounty I have not tested:
https://www.sanfoundry.com/cpp-program-implement-one-time-pad-algorithm/
and
http://www.cplusplus.com/forum/beginner/179981/
and
https://github.com/DDomjosa/One-time-pad-encryption/blob/master/One%20time%20pad%20encryption.cpp
ps: In case you are wondering why I post but do not reply to comments: My browser does not seem to support the "add a comment" on these Stack Overflow pages, so I can post but I cannot (until SO makes their pages backward compatible sufficient for me) reply or comment beyond that.
Here is how I manage to create the key pair and store in files successfully. The problem comes when reading the private key protected with password where I always get NULL. Any tip to solve this issue? Thanks.
FILE *dsa_privatekey_file;
FILE *dsa_publickey_file;
const char *pkeykey = "password";
int result = 0;
DeleteFile("dsapub.pem");
DeleteFile("dsapriv.pem");
dsa_publickey_file = fopen("dsapub.pem", "r");
dsa_privatekey_file = fopen("dsapriv.pem", "r");
if (dsa_privatekey_file == NULL || dsa_publickey_file == NULL)
{
if (dsa_privatekey_file != NULL)
{
fclose(dsa_privatekey_file);
}
if (dsa_publickey_file != NULL)
{
fclose(dsa_publickey_file);
}
dsa_publickey_file = fopen("dsapub.pem", "w");
dsa_privatekey_file = fopen("dsapriv.pem", "w");
DSA* dsa = DSA_new();
result = DSA_generate_parameters_ex(dsa, 2048, NULL, 0, NULL, NULL, NULL);
result = DSA_generate_key(dsa);
result = PEM_write_DSAPrivateKey(dsa_privatekey_file, dsa, EVP_des_ede3_cbc(), NULL, 0, NULL, (void *)pkeykey);
//result = PEM_write_DSAPrivateKey(dsa_privatekey_file, dsa, EVP_des_ede3_cbc(), (unsigned char *) pkeykey, strlen(pkeykey), NULL, NULL);
//result = PEM_write_DSAPrivateKey(dsa_privatekey_file, dsa, NULL, NULL, 0, NULL, NULL);
result = PEM_write_DSA_PUBKEY(dsa_publickey_file, dsa);
fclose(dsa_privatekey_file);
fclose(dsa_publickey_file);
DSA_free(dsa);
dsa_publickey_file = fopen("dsapub.pem", "r");
dsa_privatekey_file = fopen("dsapriv.pem", "r");
}
DSA *dsa_sign = PEM_read_DSAPrivateKey(dsa_privatekey_file, NULL, default_set_password, (void *)pkeykey);
DSA *dsa_verify = PEM_read_DSA_PUBKEY(dsa_publickey_file, NULL, NULL, NULL);
Here's the simple function I use to hard insert the password, but anyway it seems not to be called.
static int default_set_password(char *buf, int size, int rwflag, void *descr){ memcpy(buf, descr, size); return size; }
A call to
OpenSSL_add_all_algorithms();
was needed to work as expected.
I am stuck on how to convert a BIO* to its EVP_PKEY counterparts so that i can later use it in encryption. Currently my keygen routine looks like this:
bool SecureCrypto::GenerateRSAKeys()
{
BIGNUM* BigNum = nullptr;
BIGNUM* BigNum2 = nullptr;
BigNum = BN_new();
if (BN_set_word(BigNum, RSA_F4) <= 0)
return false;
BigNum2 = BN_new();
if (BN_set_word(BigNum2, RSA_F4) <= 0)
return false;
m_ServerKeyPair = RSA_new();
m_ClientKeyPair = RSA_new();
RSA_generate_key_ex(m_ServerKeyPair,RSA_KeyLen, BigNum, NULL);
RSA_generate_key_ex(m_ClientKeyPair,RSA_KeyLen, BigNum2, NULL);
PEM_write_bio_RSAPublicKey(m_ServerPublicKeyBIO, m_ServerKeyPair);
PEM_write_bio_RSAPrivateKey(m_ServerPrivateKeyBIO,
m_ServerKeyPair,EVP_aes_256_cbc(), (unsigned char*)RSA_PrivKeyPassPhrase.c_str(),
RSA_PrivKeyPassPhrase.size(), NULL, NULL);
PEM_write_bio_RSAPublicKey(m_ClientPublicBIO, m_ClientKeyPair);
PEM_write_bio_RSAPrivateKey(m_ClientPrivateBIO, m_ClientKeyPair, EVP_aes_256_cbc(),
(unsigned char*)RSA_PrivKeyPassPhrase.c_str(), RSA_PrivKeyPassPhrase.size(), NULL,
NULL);
BN_free(BigNum);
BN_free(BigNum2);
return true;
}
Later on i need to convert m_ServerPrivateKeyBIO into a EVP_PKEY for use with EVP_SealInit.
I only know of PEM_read_bio_PrivateKey(), but everytime i call it i get a return value of 0 (fail).I call it as such:
PEM_read_bio_PrivateKey(m_ServerPrivateKeyBIO, NULL, NULL, "Enc Key");
I plan on the server doing encryption with its private key then sending data to client and client decrypting it with the servers public key, i would appreciate any help here, as the OpenSSL docs don't help at all.
P.S i have client and server keys being generated for a test, obviously this would not happen in final code.
I'm trying to get both of these certs into a X509_STORE_CTX, but when I go to read them out, they are both NULL. Any ideas?
The certs look like:
// Not the real certs. Just trying to illustrate that the certs are just a new line
// delimited string
const char *certA = "-----BEGIN CERTIFICATE-----\nMIIGWDCCBUCgAwI......\n.....\n"
SSL_library_init();
SSL_CTX * sslCtx = SSL_CTX_new(SSLv23_client_method());
X509_STORE *store = SSL_CTX_get_cert_store(sslCtx);
X509_STORE_CTX *store_ctx = X509_STORE_CTX_new();
BIO *bio;
X509 *certificate;
/*First cert*/
bio = BIO_new(BIO_s_mem());
BIO_write(bio,(const void*)certA ,sizeof(certA));
certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
X509_STORE_add_cert(store, certificate);
/*second cert*/
bio = BIO_new(BIO_s_mem());
BIO_write(bio,(const void*)certB ,sizeof(certB));
certificate = PEM_read_bio_X509(bio, NULL, NULL, NULL);
X509_STORE_add_cert(store, certificate);
X509_STORE_CTX_init(store_ctx, store, NULL, NULL);
sizeof(certA) here will provide just the size of that const char* variable, which is the size of a pointer (mostly 4 or 8).
Try declaring the certificate contents as static const char certA[] instead.
Also using BIO_puts() and avoiding the sizeof() completely might be easier.
After fighting with this for a week I have not really gotten anywhere in why it constantly fails in my code, but not in other examples. My code, which while it compiles, will not log into a user that I know has the correct login information. Where it fails is the following line:
wi = gcnew WindowsIdentity(token);
It fails here because the token is zero, meaning that it was never set to a user token. Here is my full code:
#ifndef UNCAPI_H
#define UNCAPI_H
#include <windows.h>
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Security::Principal;
using namespace System::Security::Permissions;
namespace UNCAPI
{
public ref class UNCAccess
{
public:
//bool Logon(String ^_srUsername, String ^_srDomain, String ^_srPassword);
[PermissionSetAttribute(SecurityAction::Demand, Name = "FullTrust")]
bool Logon(String ^_srUsername, String ^_srDomain, String ^_srPassword)
{
bool bSuccess = false;
token = IntPtr(0);
bSuccess = LogonUser(_srUsername, _srDomain, _srPassword, 8, 0, &tokenHandle);
if(bSuccess)
{
wi = gcnew WindowsIdentity(token);
wic = wi->Impersonate();
}
return bSuccess;
}
void UNCAccess::Logoff()
{
if (wic != nullptr )
{
wic->Undo();
}
CloseHandle((int*)token.ToPointer());
}
private:
[DllImport("advapi32.dll", SetLastError=true)]//[DllImport("advapi32.DLL", EntryPoint="LogonUserW", SetLastError=true, CharSet=CharSet::Unicode, ExactSpelling=true, CallingConvention=CallingConvention::StdCall)]
bool static LogonUser(String ^lpszUsername, String ^lpszDomain, String ^lpszPassword, int dwLogonType, int dwLogonProvider, IntPtr *phToken);
[DllImport("KERNEL32.DLL", EntryPoint="CloseHandle", SetLastError=true, CharSet=CharSet::Unicode, ExactSpelling=true, CallingConvention=CallingConvention::StdCall)]
bool static CloseHandle(int *handle);
IntPtr token;
WindowsIdentity ^wi;
WindowsImpersonationContext ^wic;
};// End of Class UNCAccess
}// End of Name Space
#endif UNCAPI_H
Now using this slightly modified example from Microsoft I was able to get a login and a token:
#using <mscorlib.dll>
#using <System.dll>
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Security::Principal;
using namespace System::Security::Permissions;
[assembly:SecurityPermissionAttribute(SecurityAction::RequestMinimum, UnmanagedCode=true)]
[assembly:PermissionSetAttribute(SecurityAction::RequestMinimum, Name = "FullTrust")];
[DllImport("advapi32.dll", SetLastError=true)]
bool LogonUser(String^ lpszUsername, String^ lpszDomain, String^ lpszPassword, int dwLogonType, int dwLogonProvider, IntPtr* phToken);
[DllImport("kernel32.dll", CharSet=System::Runtime::InteropServices::CharSet::Auto)]
int FormatMessage(int dwFlags, IntPtr* lpSource, int dwMessageId, int dwLanguageId, String^ lpBuffer, int nSize, IntPtr *Arguments);
[DllImport("kernel32.dll", CharSet=CharSet::Auto)]
bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", CharSet=CharSet::Auto, SetLastError=true)]
bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, IntPtr* DuplicateTokenHandle);
// GetErrorMessage formats and returns an error message
// corresponding to the input errorCode.
String^ GetErrorMessage(int errorCode)
{
int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
//int errorCode = 0x5; //ERROR_ACCESS_DENIED
//throw new System.ComponentModel.Win32Exception(errorCode);
int messageSize = 255;
String^ lpMsgBuf = "";
int dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
IntPtr ptrlpSource = IntPtr::Zero;
IntPtr prtArguments = IntPtr::Zero;
int retVal = FormatMessage(dwFlags, &ptrlpSource, errorCode, 0, lpMsgBuf, messageSize, &prtArguments);
if (0 == retVal)
{
throw gcnew Exception(String::Format( "Failed to format message for error code {0}. ", errorCode));
}
return lpMsgBuf;
}
// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[PermissionSetAttribute(SecurityAction::Demand, Name = "FullTrust")]
int main()
{
IntPtr tokenHandle = IntPtr(0);
IntPtr dupeTokenHandle = IntPtr(0);
try
{
String^ userName;
String^ domainName;
// Get the user token for the specified user, domain, and password using the
// unmanaged LogonUser method.
// The local machine name can be used for the domain name to impersonate a user on this machine.
Console::Write("Enter the name of the domain on which to log on: ");
domainName = Console::ReadLine();
Console::Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
userName = Console::ReadLine();
Console::Write("Enter the password for {0}: ", userName);
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
const int SecurityImpersonation = 2;
tokenHandle = IntPtr::Zero;
dupeTokenHandle = IntPtr::Zero;
// Call LogonUser to obtain a handle to an access token.
bool returnValue = LogonUser(userName, domainName, Console::ReadLine(),
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
&tokenHandle);
Console::WriteLine("LogonUser called.");
if (false == returnValue)
{
int ret = Marshal::GetLastWin32Error();
Console::WriteLine("LogonUser failed with error code : {0}", ret);
Console::WriteLine("\nError: [{0}] {1}\n", ret, GetErrorMessage(ret));
int errorCode = 0x5; //ERROR_ACCESS_DENIED
throw gcnew System::ComponentModel::Win32Exception(errorCode);
}
Console::WriteLine("Did LogonUser Succeed? {0}", (returnValue?"Yes":"No"));
Console::WriteLine("Value of Windows NT token: {0}", tokenHandle);
// Check the identity.
Console::WriteLine("Before impersonation: {0}", WindowsIdentity::GetCurrent()->Name);
bool retVal = DuplicateToken(tokenHandle, SecurityImpersonation, &dupeTokenHandle);
if (false == retVal)
{
CloseHandle(tokenHandle);
Console::WriteLine("Exception thrown in trying to duplicate token.");
return -1;
}
// The token that is passed to the following constructor must
// be a primary token in order to use it for impersonation.
WindowsIdentity^ newId = gcnew WindowsIdentity(dupeTokenHandle);
WindowsImpersonationContext^ impersonatedUser = newId->Impersonate();
// Check the identity.
Console::WriteLine("After impersonation: {0}", WindowsIdentity::GetCurrent()->Name);
// Stop impersonating the user.
impersonatedUser->Undo();
// Check the identity.
Console::WriteLine("After Undo: {0}", WindowsIdentity::GetCurrent()->Name);
// Free the tokens.
if (tokenHandle != IntPtr::Zero)
CloseHandle(tokenHandle);
if (dupeTokenHandle != IntPtr::Zero)
CloseHandle(dupeTokenHandle);
}
catch(Exception^ ex)
{
Console::WriteLine("Exception occurred. {0}", ex->Message);
}
Console::ReadLine();
}// end of function
Why should Microsoft's code succeed, where mine fails?
"Why should Microsoft's code succeed, where mine fails?"
Because your code does something different :-)
In this line:
bool returnValue = LogonUser(userName, domainName, Console::ReadLine(), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
&tokenHandle);
note that 'tokenHandle' is passed by reference, but in your version:
bSuccess = LogonUser(_srUsername, _srDomain, _srPassword, 8, 0, (IntPtr*)token.ToPointer());
you are not passing 'token' by reference, so your local reference will not be updated, which is why it is still zero when you pass it to the WindowsIdentity.
I think that the answer is in MSDN article describing the LogonUser function:
The LOGON32_LOGON_NETWORK logon type is fastest, but it has the following limitations:
The function returns an impersonation token, not a primary token. You cannot use this token directly in the CreateProcessAsUser function. However, you can call the DuplicateTokenEx function to convert the token to a primary token, and then use it in CreateProcessAsUser.