Interagire con EFS da C#: QueryUsersOnEncryptedFile

Chi si è trovato a dover interagire con l'Encrypted File System (EFS) durante lo sviluppo di un'applicazione .NET si è accorto di come il framework non contenga classi che aiutino in tal senso. Per farlo è necessario ricorrere alle relative API presenti in advapi32.dll e crypt32.dll, ma ciò presenta alcune difficoltà perché tali funzioni utilizzano molte strutture che necessitano di una conversione.

Vediamo ora come ottenere in C# l'elenco degli utenti che hanno accesso ad un file cifrato con EFS; per farlo utilizzeremo la funzione QueryUsersOnEncryptedFile presente nella libreria advapi32.dll.

Per prima cosa dobbiamo definire la chiamata esterna a QueryUsersOnEncryptedFile:

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

Invocare tale funzione è molto semplice perché basta passargli una stringa contenente il path completo del file ed un puntatore per riferimento nel quale verrà scritto l'indirizzo di memoria da utilizzare per puntare al risultato cercato:

string encryptedFile = @"C:\temp\test.txt";
IntPtr pHashList = IntPtr.Zero;
int retCode = QueryUsersOnEncryptedFile(encryptedFile, ref pHashList);

A questo punto se la funzione non ha restituito alcun errore (retCode==0), non ci resta che scorrere l'elenco degli utenti. Le difficoltà nascono qui perché pHashList è un puntatore ad una struttura di tipo ENCRYPTION_CERTIFICATE_HASH_LIST che dobbiamo definire:

        [StructLayout(LayoutKind.Sequential)]
        internal struct ENCRYPTION_CERTIFICATE_HASH_LIST
        {
            internal int nCert_Hash;
            internal IntPtr pUsers;
        }

A sua volta pUsers è un puntatore ad una struttura ENCRYPTION_CERTIFICATE_HASH che definiamo come segue:

        [StructLayout(LayoutKind.Sequential)]
        internal struct ENCRYPTION_CERTIFICATE_HASH
        {
            internal int cbTotalLength;
            internal IntPtr pUserSid;
            internal IntPtr pHash;
            [MarshalAs(UnmanagedType.LPWStr)]internal string lpDisplayInformation;
        }

Anche pUserSid e pHash sono dei puntatori ad altre strutture che andrebbero definite, ma nel nostro esempio possiamo farne a meno. Torniamo allora al nostro obiettivo: ottenere l'elenco degli utenti che hanno accesso ad un determinato file cifrato con EFS. Abbiamo visto come la funzione QueryUsersOnEncryptedFile torni un puntatore ad una struttura contenente l'elenco dei certificate hash. Ogni certificate hash contiene un puntatore al SID dell'utente. Ovviamente non ci accontentiamo del SID ma vogliamo ottenere l'alias dell'utente e per farlo utilizziamo un'altra funzione presente in advapi32.dll: LookupAccountSid . Definiamola:

        [DllImport( "advapi32.dll", CharSet=CharSet.Auto, SetLastError=true, PreserveSig=true)]
        internal static extern bool LookupAccountSid(
            string lpSystemName,
            IntPtr psid,
            StringBuilder lpName,
            ref int cchName,
            StringBuilder domainName,
            ref int cbdomainLength,
            ref int use
            );

Tale funzione necessita in input di un puntatore al SID dell'utente, proprio quello presente nella struttura ENCRYPTION_CERTIFICATE_HASH. Abbiamo quindi tutti gli elementi per ottenere l'elenco di utenti cercato:

ArrayList Users = null;
ENCRYPTION_CERTIFICATE_HASH_LIST hashList = (ENCRYPTION_CERTIFICATE_HASH_LIST) Marshal.PtrToStructure(pHashList, typeof(ENCRYPTION_CERTIFICATE_HASH_LIST));
Users = new ArrayList(hashList.nCert_Hash);
for(int i=0; i<hashList.nCert_Hash; i++)
{
    IntPtr pUsers = Marshal.ReadIntPtr(hashList.pUsers, i*IntPtr.Size);
    ENCRYPTION_CERTIFICATE_HASH hash = (ENCRYPTION_CERTIFICATE_HASH) Marshal.PtrToStructure(pUsers, typeof(ENCRYPTION_CERTIFICATE_HASH));
    StringBuilder UserName = new StringBuilder(250);
    StringBuilder DomainName = new StringBuilder(250);
    int NameLength = 250;
    int DomainLength = 250;
    int accountType=0;
    LookupAccountSid(String.Empty, hash.pUserSid, UserName, ref NameLength, DomainName, ref DomainLength, ref accountType);
    Users.Add(DomainName.ToString().Trim() + @"\" + UserName.ToString());
}

Dopo aver popolato l'ArrayList con gli utenti dobbiamo liberare la memoria (contenente l'elenco dei certificate hash) allocata da QueryUsersOnEncryptedFile e per farlo occorre invocare un'altra funzione presente in advapi32.dll: FreeEncryptionCertificateHashList . Definiamola:

        [DllImport( "advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
        internal static extern void FreeEncryptionCertificateHashList(
            IntPtr pHashes
            );

Per liberare la memoria, basta quindi invocare tale funzione passando il puntatore alla struttura contenente la lista dei certificate hash:

FreeEncryptionCertificateHashList(pHashList);

E' mia intenzione realizzare una libreria d'esempio che illustri l'utilizzo delle principali funzioni di interazione con EFS. Appena pronta la pubblicherò su questo blog.