
Signature
Mattias Sjögren [MVP] mattias @ mvps.org
http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Please reply only to the newsgroup.
Thank you for the tips, Mattias.
I've taken those into account, cleaned up my code, and gone over it
again closely...yet I'm still getting the NullReferenceException on
the line
> Win32.AddUsersToEncryptedFile(targetFile, ref ecList);
That's the culmination too. I'm trying to write a command line tool
to add users who can read EFS files. I suspect that I'm either using
the wrong levels of dereference somewhere, or I'm not properly
maintaining the unmanaged memory I'm allocating...and the pointers
start dangling. I haven't been marking anything unsafe, but perhaps I
should.
I would appreciate further advice from any of you.
My thanks,
Seasanctuary
///Full ~2 pages of code follows.
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace BlueTea
{
public sealed class Win32
{
[DllImport( "advapi32.dll", CharSet=CharSet.Auto,
SetLastError=true)]
public static extern int AddUsersToEncryptedFile(string
lpFileName,
ref ENCRYPTION_CERTIFICATE_LIST pUsers );
[DllImport( "advapi32.dll", CharSet=CharSet.Auto,
SetLastError=true, PreserveSig=true)]
public static extern bool LookupAccountName(string
lpSystemName,
string lpAccountName, IntPtr psid, ref int cbsid,
StringBuilder domainName,
ref int cbdomainLength, ref int use );
[DllImport("crypt32.dll", CharSet=CharSet.Auto,
SetLastError=true)]
public static extern IntPtr CertOpenSystemStore(IntPtr
hCryptProv, string storename);
[DllImport("crypt32.dll", SetLastError=true)]
public static extern bool CertCloseStore(IntPtr hCertStore,
uint dwFlags) ;
[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertFindCertificateInStore(IntPtr
hCertStore,
uint dwCertEncodingType, uint dwFindFlags, uint
dwFindType,
[In, MarshalAs(UnmanagedType.LPWStr)]String pszFindString,
IntPtr pPrevCertCntxt) ;
[DllImport("crypt32.dll", SetLastError=true)]
public static extern bool CertFreeCertificateContext(IntPtr
hCertStore) ;
[StructLayout(LayoutKind.Sequential)]
public struct CERT_CONTEXT
{
public uint dwCertEncodingType;
public IntPtr pbCertEncoded;
public int cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}
[StructLayout(LayoutKind.Sequential)]
public struct EFS_CERTIFICATE_BLOB
{
public uint dwCertEncodingType;
public int cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential)]
public struct ENCRYPTION_CERTIFICATE
{
public int cbTotalLength;
public IntPtr pUserSid;
public IntPtr pCertBlob;
}
[StructLayout(LayoutKind.Sequential)]
public struct ENCRYPTION_CERTIFICATE_LIST
{
public int nUser;
public IntPtr pUsers;
}
public struct HeaderConstants
{
public const string MyStore = "MY";
public const string OtherStore = "AddressBook";
const uint PKCS_7_ASN_ENCODING = 0x00010000;
const uint X509_ASN_ENCODING = 0x00000001;
public static uint CERTIFICATE_ENCODING =
PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ;
public const uint CERT_FIND_SUBJECT_STR = 0x00080007;
}
private Win32()
{}
}
class AddEfsUsers
{
static void Main(string[] args)
{
string certificateSubject = "Mike"; //TODO: Unify these
names..they follow slightly different rules.
string sidName = "Mike Smith";
//Open a system certificate store.
Win32.HeaderConstants.MyStore looks in the local user's Personal
// certificate store. Win32.HeaderConstants.OtherStore
looks in the local user's Other People
// certificate store.
IntPtr certificateStore = IntPtr.Zero;
certificateStore = Win32.CertOpenSystemStore(IntPtr.Zero,
Win32.HeaderConstants.OtherStore);
if (certificateStore == IntPtr.Zero)
{ Console.WriteLine("Error: System store not found.");
return;
}
//Query certificate store for a certain username.
IntPtr certificateContextHandle = IntPtr.Zero;
certificateContextHandle =
Win32.CertFindCertificateInStore(
certificateStore,
Win32.HeaderConstants.CERTIFICATE_ENCODING,
0, Win32.HeaderConstants.CERT_FIND_SUBJECT_STR,
certificateSubject, IntPtr.Zero);
if (certificateContextHandle == IntPtr.Zero)
{ Console.WriteLine("Error: Subject not found.");
return;
}
Win32.CERT_CONTEXT binaryContext =
(Win32.CERT_CONTEXT)Marshal.PtrToStructure(certificateContextHandle,
typeof(Win32.CERT_CONTEXT));
//Use the binaryContext to fill in the
EFS_CERTIFICATE_BLOB structure.
Win32.EFS_CERTIFICATE_BLOB certificateBlob;
certificateBlob.dwCertEncodingType =
binaryContext.dwCertEncodingType;
certificateBlob.cbData = binaryContext.cbCertEncoded;
certificateBlob.pbData = binaryContext.pbCertEncoded;
//We actually need a pointer to the EFS_CERTIFICATE_BLOB.
IntPtr blobPointer =
Marshal.AllocHGlobal(Marshal.SizeOf(certificateBlob));
Marshal.StructureToPtr(certificateBlob, blobPointer,
false);
//Populate the encryption certificate with
EFS_CERTIFICATE_BLOB and SID info.
Win32.ENCRYPTION_CERTIFICATE encryptionCertificate;
encryptionCertificate.pCertBlob = blobPointer;
encryptionCertificate.pUserSid = QuerySid(sidName);
encryptionCertificate.cbTotalLength =
Marshal.SizeOf(typeof(Win32.ENCRYPTION_CERTIFICATE));
//And we need a pointer to the encryption certificate.
IntPtr encryptionCertificatePointer =
Marshal.AllocHGlobal(Marshal.SizeOf(encryptionCertificate));
Marshal.StructureToPtr(encryptionCertificate,
encryptionCertificatePointer, false);
//Put the one encryption certificate in a list (of one
member).
Win32.ENCRYPTION_CERTIFICATE_LIST ecList;
ecList.nUser = 1;
ecList.pUsers = encryptionCertificatePointer;
//Finally, the encryption certificate list can be applied
to a certain file.
string targetFile = @"C:\test.txt";
Win32.AddUsersToEncryptedFile(targetFile, ref ecList);
//////////NullReferenceException on previous
line///////////////////////
//Clean references
if(certificateContextHandle != IntPtr.Zero)
Win32.CertFreeCertificateContext(certificateContextHandle);
if(certificateStore != IntPtr.Zero)
Win32.CertCloseStore(certificateStore, 0);
}
static IntPtr QuerySid(string accountName)
{
//pointer and size for the SID
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
//StringBuilder and size for the domain name
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
//account-type variable for lookup
int accountType = 0;
//get required buffer size
Win32.LookupAccountName(String.Empty, accountName, sid,
ref sidSize,
domainName, ref nameSize, ref accountType);
//allocate buffers
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
//lookup the SID for the account
bool result = Win32.LookupAccountName(String.Empty,
accountName, sid,
ref sidSize, domainName, ref nameSize, ref
accountType);
return sid;
}
}
}
Seasanctuary - 18 May 2004 18:38 GMT
I've noticed that if I set ecList.nUser = 0 instead of = 1 then
AddUsersToEncryptedFile completes without the NullReferenceException.
I'm inclined to think that I'm missing an array initialization.
pUsers is supposed to be a pointer to the first member of the array of
certificates. I'm just giving it a pointer to the one certificate
I've set up. Perhaps the marshaller needs to be told it's dealing
with an array so it remembers to initialize one, even if it will only
hold the one value and even if the number of array members is listed
in nUser.
Am I on the right track here?
Thanks,
Seasanctuary
////Excerpt from what I posted last time for quick reference...
//Put the one encryption certificate in a list (of one member).
Win32.ENCRYPTION_CERTIFICATE_LIST ecList;
ecList.nUser = 1;
ecList.pUsers = encryptionCertificatePointer;
//Finally, the encryption certificate list can be applied to a certain
file.
string targetFile = @"C:\test.txt";
Win32.AddUsersToEncryptedFile(targetFile, ref ecList);
Mattias Sj?gren - 18 May 2004 23:40 GMT
Looks like ENCRYPTION_CERTIFICATE_LIST.pUsers is actually supposed to
be a pointer to an array of pointers to the ENCRYPTION_CERTIFICATE
structs, so you need an extra level of indirection there. Try this
IntPtr listPointer = Marshal.AllocHGLobal( IntPtr.Size );
Marshal.WriteIntPtr( listPointer, encryptionCertificatePointer );
Win32.ENCRYPTION_CERTIFICATE_LIST ecList;
ecList.nUser = 1;
ecList.pUsers = listPointer;
Also, your code is leaking memory. You have to free any memory
allocated with AllocHGlobal with a corresponding call to FreeHGlobal.
Mattias

Signature
Mattias Sjögren [MVP] mattias @ mvps.org
http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Please reply only to the newsgroup.
Seasanctuary - 19 May 2004 17:26 GMT
Thanks, Mattias, that did it. I now see that when there's both a *
and a 'p' that adds up to two levels of indirection.
I plan on refining this tool substantially in the next couple of
weeks, then posting that version here. It's certainly been a learning
experience.
Cheers,
Seasanctuary