Sample: Creating Custom IRM Protectors

Applies to: SharePoint Foundation 2010

You can create custom rights-management protectors that convert files to rights-management formats when the user downloads them by using the extensible Information Rights Management (IRM) architecture in Microsoft SharePoint Foundation. You can convert those files back to nonencrypted file formats by using the same custom protectors when the user uploads the files for storage in a SharePoint document library.

Developers can control the location of the protected content, issuance license (IL), and end-user license (EUL) within the protected version of the file, and vary the format of the protected document based on file type by using a custom IRM protector. Developers can also remove and replace the protection type with a different rights management platform.

This code sample, the SampleDocProtector project, shows how to use IRM protectors for text files. The IRM protector sample protects the text files by using the Rights Management Services (RMS) infrastructure in SharePoint Foundation. It can also be set to encrypt the text files itself, using an encryption method of your choice.

Note

This topic assumes that you are familiar with IRM in SharePoint Foundation, RMS, the IRM interfaces, and IRM classes. For more information, see Information Rights Management in SharePoint Foundation, I_IrmProtector Interface, and Custom IRM Protectors.

You can create two types of IRM protectors that use the IRM framework in SharePoint Foundation: integrated protectors and autonomous protectors.

Note

For more information about integrated and autonomous protectors, see Custom IRM Protectors.

The SampleDocProtector code sample shows how you can use both integrated protectors and autonomous protectors. Integrated protectors rely on SharePoint Foundation for access to the Active Directory RMS platform for generating protected versions of files and for removing protection from rights-managed files. Autonomous protectors, however, must configure and execute the entire rights-management process by themselves. Autonomous protectors may access the RMS platform directly or employ some other rights-management platform.

Note

In an actual implementation, you use either the integrated or autonomous protectors, but not both at the same time. This sample is just a demonstration of how to use the IRM protectors.

Important

Do not use the sample as is. This sample is a simple demonstration on how to use custom IRM protectors. No client-side application, such as Notepad, can consume the encrypted file format specified in the sample. Notepad cannot open and decrypt the text file.

Code Sample Location

You can find the SampleDocProtector project in the SampleDocProtector code sample folder in the ECM Starter Kit folder in the Microsoft SharePoint 2010 Software Development Kit (SDK) download.

Sample Project Files

For more information about the various files that compose the sample project and their purposes, see the ReadMe.txt file included in the SampleDocProtector project folder.

Code Sample Walkthrough

Each IRM protector must be a COM component that implements the I_IrmProtector interface. Although both integrated and autonomous protectors must implement the I_IrmProtector interface, each type of protector implements the interface differently, because SharePoint Foundation calls the two types by using different methods of the interface. SharePoint Foundation invokes integrated protectors by using the HrProtectRMS and HrUnprotectRMS methods; it invokes autonomous protectors by using the HrProtect and HrUnprotect methods.

The HrProtectRMS function in the SampleDocProtector project has a parameter named piid. The piid parameter is a link to functions and data that assist the encryption and decryption process. The piid argument, which is an I_IrmPolicyInfoRMS object, contains multiple helper functions.

Note that the sample HrProtectRMS function does not call the Active Directory RMS APIs. This is because all calls to the RMS API, including calls to the RMS server, are handled automatically by SharePoint Foundation. Instead, the HrProtectRMS function accesses the RMS objects, such as the document IL and EULs, through the piid argument, and then saves them to a stream.

The StgIsStorageILockBytes method is a simple way of storing binary data. The HrProtectRMS function is shown in the following code.

Note

You can find the following code in the SampleProtector.cpp file in the SampleDocProtector project.

HRESULT CSampleProtector::HrProtectRMS(ILockBytes *pilbInput, ILockBytes *pilbOutput, 
  I_IrmPolicyInfoRMS *piid, DWORD *pdwStatus)
{
    HRESULT hr = S_OK;
    IStorage *pistgOutput = NULL;
        IStorage *pistgLicenses = NULL;
        IStream *pistmT = NULL;
        BSTR bstr = NULL;
    UINT cEULs = 0;
    BSTR *rgbstrEUL = NULL;
    BSTR *rgbstrId = NULL;
    I_IrmCrypt *piic = NULL;
    BYTE rgbBuffer[STACK_RW_BUF_SIZE];
          = 16;
    ULARGE_INTEGER ulOffset = { 0 };

    if (pilbInput == NULL || pilbOutput == NULL || piid == NULL || pdwStatus == NULL)
    {
        hr = E_INVALIDARG;
        goto LExit;
    }

    *pdwStatus = MSOIPI_STATUS_UNKNOWN;

    hr = StgIsStorageILockBytes(pilbOutput);
    if (FAILED(hr))
        goto LExit;

    if (hr != S_FALSE)
    {
        hr = StgOpenStorageOnILockBytes(pilbOutput, NULL, 
        STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, NULL, &pistgOutput);
        if (hr == STG_E_FILEALREADYEXISTS)
            goto LCreateStorage;
    }
    else
    {
LCreateStorage:
        hr = StgCreateDocfileOnILockBytes(pilbOutput, STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, 
          NULL, &pistgOutput);
    }
    if (FAILED(hr))
        goto LExit;

        // Write out the key.
        BSTR key = SysAllocString(wzKey);
        hr = HrWriteSubStream(pistgOutput, wzKey, &key);

        // Write out the signed IL.
    hr = piid->HrGetSignedIL(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
        hr = HrWriteSubStream(pistgOutput, wzSignedIL, &bstr);

        // Write out the server ID.
    hr = piid->HrGetServerId(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
        hr = HrWriteSubStream(pistgOutput, wzServerID, &bstr);
        if (FAILED(hr))
            goto LExit;

        // Write out the licenses.
        hr = HrEnsureStg(pistgOutput, wzLicenses, true /*fReadWrite*/, &pistgLicenses);
        if (FAILED(hr))
            goto LExit;
    hr = piid->HrGetEULs(NULL, NULL, &cEULs);
        // printf("Got %d EULS", cEULs);
    if (FAILED(hr))
        goto LExit;
    rgbstrEUL = (BSTR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cEULs * sizeof(BSTR));
    if (rgbstrEUL == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto LExit;
    }
    rgbstrId = (BSTR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cEULs * sizeof(BSTR));
    if (rgbstrId == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto LExit;
    }
    hr = piid->HrGetEULs(rgbstrEUL, rgbstrId, &cEULs);

    if (FAILED(hr))
        goto LExit;
        WCHAR    streamId[STREAM_MAX];
    for (UINT iEUL = 0; iEUL < cEULs; iEUL++)
    {
        if (rgbstrEUL[iEUL] != NULL && rgbstrId[iEUL] != NULL)
            {
            hr = StringCchCopy(streamId, cElements(streamId), wzEULPrefix);
            WCHAR   *pwz         = streamId + wcslen(streamId);
            *pwz++ = (WCHAR) iEUL;
            *pwz = NULL;
            hr = HrWriteSubStream(pistgLicenses, streamId, &rgbstrEUL[iEUL]);
            if (FAILED(hr))
                goto LExit;
        }
        if (rgbstrEUL[iEUL] != NULL)
            SysFreeString(rgbstrEUL[iEUL]);
        if (rgbstrId[iEUL] != NULL)
            SysFreeString(rgbstrId[iEUL]);
    }
    HeapFree(GetProcessHeap(), 0, rgbstrId);
    rgbstrId = NULL;
    HeapFree(GetProcessHeap(), 0, rgbstrEUL);
    rgbstrEUL = NULL;
    pistgLicenses->Release();
    pistgLicenses = NULL;

    // Write out the rights template.
    hr = piid->HrGetRightsTemplate(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzRightsTemplate, &bstr);
    if (FAILED(hr))
        goto LExit;

    // Write out the list GUID.
    hr = piid->HrGetListGuid(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzListGuid, &bstr);
    if (FAILED(hr))
        goto LExit;

    // Write out the content.
    hr = HrEnsureStm(pistgOutput, wzContent, true /*fReadWrite*/, &pistmT);
    if (FAILED(hr))
        goto LExit;
    hr = piid->HrGetICrypt(&piic);
    if (FAILED(hr) || piic == NULL)
        goto LExit;
    ulOffset.QuadPart = 0;
    while (true)
    {
        ULONG cbRead = cElements(rgbBuffer);
        ULONG cbWritten = 0;
        hr = pilbInput->ReadAt(ulOffset, rgbBuffer, cbRead, &cbRead);
        if (FAILED(hr))
            goto LExit;
        if (cbRead == 0)
            goto LFinished;
        // cbRead needs to be a multiple of 16 for RMS.
        // If it is not, pad it for encryption.
        if(cbRead % BLOCK_SIZE)
        {
            ULONG nCbRead = ((cbRead /BLOCK_SIZE)+1)*BLOCK_SIZE;
            for(ULONG pad = 0; pad < (nCbRead - cbRead);pad++)
            {
                rgbBuffer[cbRead + pad] = '0';
            }
            cbRead = nCbRead;
        }
        hr = piic->HrEncrypt(0, rgbBuffer, cbRead, 0);
        if (FAILED(hr))
            goto LExit;
        hr = pistmT->Write(rgbBuffer, cbRead, &cbWritten);
        if (FAILED(hr))
            goto LExit;
        if (cbRead != cbWritten)
        {
            hr = STG_E_CANTSAVE;
            goto LExit;
        }

        ulOffset.QuadPart += cbRead;
    }

LFinished:
    piic->Release();
    piic = NULL;
    hr = pistmT->Commit(STGC_DEFAULT);

LExit:
    *pdwStatus = SUCCEEDED(hr) ? MSOIPI_STATUS_PROTECT_SUCCESS : MSOIPI_STATUS_CANT_PROTECT;
    if (piic != NULL)
        piic->Release();
    if (rgbstrId != NULL)
        HeapFree(GetProcessHeap(), 0, rgbstrId);
    if (rgbstrEUL != NULL)
        HeapFree(GetProcessHeap(), 0, rgbstrEUL);
    if (bstr != NULL)
        SysFreeString(bstr);
    if (pistmT != NULL)
    {
        pistmT->Release();
        pistmT = NULL;
    }
    if (pistgLicenses != NULL)
        pistgLicenses->Release();
    if (pistgOutput != NULL)
        pistgOutput->Release();

    return hr;
}

The HrInit function initializes the IRM protector. In the HrInit function, the pfUseRMS parameter is set to true to use an integrated protector. To use an autonomous protector, set it to false. It then contacts the RMS server directly to request a EUL for the document. Do not implement it this way in a real application. You implement either an integrated or autonomous protector, but not both in your code.

The code for the HrInit function is as follows.

/*-----------------------------------------------------------------------------
    CSampleProtector::HrInit
------------------------------------------------------------------------------*/
STDMETHODIMP CSampleProtector::HrInit(BSTR *pbstrProduct, DWORD *pdwVersion, BSTR *pbstrExtensions, 
  BOOL *pfUseRMS)
{
    if ( pbstrExtensions == NULL || pfUseRMS == NULL)
        return E_INVALIDARG;
    *pfUseRMS        = true; // Set to "false" to use the non-RMS infrastructure.
    *pbstrProduct    = SysAllocString(L"SampleIrmProtector");
    *pdwVersion      = 1;
    *pbstrExtensions = SysAllocString(L"txt");
    return S_OK;

The HrProtect function handles the encryption. This example uses a simple encryption method. For your implementation for use in your organization, you want to use your organization's encryption method of choice.

The code for the HrProtect function is as follows.

/*---------------------------------------------------------------------
    CSampleProtector::HrProtect
---------------------------------------------------------------------*/
HRESULT CSampleProtector::HrProtect(ILockBytes *pilbInput, 
  ILockBytes *pilbOutput, I_IrmPolicyInfo *piid, DWORD *pdwStatus)
{
    HRESULT hr = S_OK;
    IStorage *pistgOutput = NULL;
    IStream *pistmT = NULL;
    BSTR bstr = NULL;
    DWORD dw = 0;
    BOOL bRequestingUserIsSystem = false;
    BYTE rgbBuffer[STACK_RW_BUF_SIZE];
    ULARGE_INTEGER ulOffset = { 0 };
    if (pilbInput == NULL || pilbOutput == NULL || piid == NULL || pdwStatus == NULL)
    {
        hr = E_INVALIDARG;
        goto LExit;
    }
    *pdwStatus = MSOIPI_STATUS_UNKNOWN;
    hr = StgIsStorageILockBytes(pilbOutput);
    if (FAILED(hr))
        goto LExit;
    if (hr != S_FALSE)
    {
        hr = StgOpenStorageOnILockBytes(pilbOutput, NULL, 
          STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, NULL, &pistgOutput);
        if (hr == STG_E_FILEALREADYEXISTS)
            goto LCreateStorage;
    }
    else
    {
LCreateStorage:
        hr = StgCreateDocfileOnILockBytes(pilbOutput, 
          STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE, NULL, &pistgOutput);
    }
    if (FAILED(hr))
        goto LExit;
    // Write out the key.
    BSTR key = SysAllocString(wzKey);
    hr = HrWriteSubStream(pistgOutput, wzKey, &key);
    // Write out the list GUID.
    hr = piid->HrGetListGuid(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzListGuid, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the rights mask.
    hr = piid->HrGetRightsMask(&dw);
    if (FAILED(hr))
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzRightsMask, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the requesting user.
    hr = piid->HrGetRequestingUser(&bstr, &bRequestingUserIsSystem);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzRequestingUser, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the URL.
    hr = piid->HrGetURL(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzURL, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the title.
    hr = piid->HrGetPolicyTitle(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzPolicyTitle, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the description.
    hr = piid->HrGetPolicyTitle(&bstr);
    if (FAILED(hr) || bstr == NULL)
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzPolicyDescription, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the offline days.
    hr = piid->HrGetOfflineDays(&dw);
    if (FAILED(hr))
        goto LExit;
    hr = HrWriteSubStream(pistgOutput, wzOfflineDays, &bstr);
    if (FAILED(hr))
        goto LExit;
    // Write out the content.
    hr = HrEnsureStm(pistgOutput, wzContent, true /*fReadWrite*/, &pistmT);
    if (FAILED(hr))
        goto LExit;
    ulOffset.QuadPart = 0;
    while (true)
    {
        ULONG cbRead = cElements(rgbBuffer);
        ULONG cbWritten = 0;
        hr = pilbInput->ReadAt(ulOffset, rgbBuffer, cbRead, &cbRead);
        if (FAILED(hr))
            goto LExit;
        if (cbRead == 0)
            goto LFinished;
        for (ULONG i = 0; i < cbRead; i++)
            //Simple encryption: flip bits
            rgbBuffer[i] ^= 1;
        hr = pistmT->Write(rgbBuffer, cbRead, &cbWritten);
        if (FAILED(hr))
            goto LExit;
        if (cbRead != cbWritten)
        {
            hr = STG_E_CANTSAVE;
            goto LExit;
        }
        ulOffset.QuadPart += cbRead;
    }
LFinished:
    hr = pistmT->Commit(STGC_DEFAULT);
LExit:
    *pdwStatus = SUCCEEDED(hr) ? MSOIPI_STATUS_PROTECT_SUCCESS : MSOIPI_STATUS_CANT_PROTECT;
    if (bstr != NULL)
        SysFreeString(bstr);
    if (pistmT != NULL)
        pistmT->Release();
    if (pistgOutput != NULL)
        pistgOutput->Release();
    return hr;
}

Registering an IRM Protector

After you compile your custom IRM protector, you must register that protector with SharePoint Foundation to make it available for document libraries. You register the IRM protector, which is a COM component, the way you would any COM component.

For this sample, the registration syntax is as follows.

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\12.0\IrmProtectors]
"{6EC4BB1F-3F73-4799-BC98-A3DF9AE23A0B}"="SampleDocProtector"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Shared Tools\Web Server Extensions\SampleDocProtector]
"Extensions"="doc"
"Product"="SampleIrmProtector"
"Version"="1"

You can find the registration information in the IrmProtectors.reg file in the SampleDocProtector project.

Setting and Enabling IRM Add-in Property

To use an integrated protector or IRM protectors included in SharePoint Foundation, you enable the IRM by specifying a location to your RMS server using the settings in the IRM Settings page in Central Administration.

However, if you use autonomous protectors, you must enable the IRM add-in by using stsadm.exe. You do this by setting the irmaddinsenabled property to true. If the IRM add-in is not enabled, the autonomous protectors will not encrypt the downloaded content.

The following command shows how you can enable the IRM add-in property using stdsadm.exe.

stsadm -o setproperty -propertyname irmaddinsenabled -propertyvalue TRUE

See Also

Tasks

How to: Register an IRM Protector

Reference

I_IrmProtector Interface

I_IrmPolicyInfoRMS Class

I_IrmCrypt Class

I_IrmPolicyInfo Class

Concepts

Information Rights Management in SharePoint Foundation

IRM Framework Architecture in SharePoint Foundation

IRM File Processing

Custom IRM Protectors