Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / Interop / May 2004

Tip: Looking for answers? Try searching our database.

NullReferenceException after a Marshal.StructureToPtr

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Seasanctuary - 14 May 2004 19:21 GMT
I'm trying to interface C# with the AddUsersToEncryptedFile function.
That requires a list of "ENCRYPTION_CERTIFICATE"s each with a Sid and
an EFS_CERTIFICATE_BLOB.  I can get that data well enough, but after I
take the managed list of "ENCRYPTION_CERTIFICATE"s and do
Marshal.StructureToPtr on the list in order to pass it to
AddUsersToEncryptedFile...I get a null reference exception.

I have checked to make sure that what I'm passing is not equal to
IntPtr.Zero.  The other argument is a path, and that's not null
either.

What really bugs me is that a few lines before, I marshal another
structure to an IntPtr...and that works ok, but maybe there's a
problem with nesting these things.

Any help is appreciated...code snippets follow below.

Thanks!

------------------------------------------------------

...the problem comes the last line in this section:

ENCRYPTION_CERTIFICATE encryptCertificate;
           encryptCertificate.pCertBlob = certificateBlob;
           encryptCertificate.pUserSid = QuerySid("John Smith");
           encryptCertificate.cbTotalLength = 0; //Initialize struct
so we can check its size in the next line.
           encryptCertificate.cbTotalLength =
Marshal.SizeOf(encryptCertificate);
           
           IntPtr encryptCertificatePointer =
Marshal.AllocHGlobal(Marshal.SizeOf(encryptCertificate));
           Marshal.StructureToPtr(encryptCertificate,
encryptCertificatePointer, true);

           ENCRYPTION_CERTIFICATE_LIST ecList;
           ecList.nUser = 1; //Using 1 member lists for testing
           ecList.pUsers = encryptCertificatePointer;

           IntPtr ecListPointer =
Marshal.AllocHGlobal(Marshal.SizeOf(ecList));
           Marshal.StructureToPtr(ecList, ecListPointer, true);

           string targetFile = @"C:\test.txt";
           Win32.AddUsersToEncryptedFile(targetFile, ecListPointer);
<==NullReferenceException

------------------------------------------------------

Here's the setup for that last function call causing the problem:

       [DllImport( "advapi32.dll", CharSet=CharSet.Auto,
            SetLastError=true)]
       public static extern int AddUsersToEncryptedFile(
           string lpFileName,
           IntPtr pUsers );

------------------------------------------------------

Here's the setup for a couple of the structures used in the code
sample:

   [StructLayout(LayoutKind.Sequential)]
   public struct ENCRYPTION_CERTIFICATE
   {
       public int cbTotalLength;
       public IntPtr pUserSid;
       public EFS_CERTIFICATE_BLOB pCertBlob;
   }

   [StructLayout(LayoutKind.Sequential)]
   public struct ENCRYPTION_CERTIFICATE_LIST
   {
       public int nUser;
       public IntPtr pUsers;
   }
Mattias Sj?gren - 16 May 2004 21:12 GMT
>ENCRYPTION_CERTIFICATE encryptCertificate;
>            encryptCertificate.pCertBlob = certificateBlob;
[quoted text clipped - 3 lines]
>            encryptCertificate.cbTotalLength =
>Marshal.SizeOf(encryptCertificate);

If you use Marshal.SizeOf(typeof(ENCRYPTION_CERTIFICATE)) instead, you
don't need the bogus assignment to cbTotalLength.

>            IntPtr ecListPointer =
>Marshal.AllocHGlobal(Marshal.SizeOf(ecList));
>            Marshal.StructureToPtr(ecList, ecListPointer, true);

You should pass false to the last parameter, since there's nothing in
the newly allocated buffer that needs to be freed.

Also note that you can skip this step and pass the struct directly to
the function if you declare it as

public static extern int AddUsersToEncryptedFile(string lpFileName,
ref ENCRYPTION_CERTIFICATE_LIST pUsers);

>    [StructLayout(LayoutKind.Sequential)]
>    public struct ENCRYPTION_CERTIFICATE
[quoted text clipped - 3 lines]
>        public EFS_CERTIFICATE_BLOB pCertBlob;
>    }

pCertBlob should be a pointer to EFS_CERTIFICATE_BLOB, i.e. an IntPtr.

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 - 17 May 2004 19:39 GMT
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

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.