Creating Security Label Policy Modules

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

Uma Subramanian
Microsoft Corporation

November 2001

Applies to:
     Microsoft® Outlook® 2000 SR-1 and later

Summary: This article describes how to use Microsoft Outlook 2000 Federal Release Security Labels Application Programming Interface (API) to create security label policy modules that can be used to define the sensitivity of message content in your organization. (32 printed pages)

Contents

Introduction
Implementing Security Policy Modules
Implementing the Interfaces
   ISMimePolicySimpleEdit Interface
     GetPolicyInfo Method
     GetClassifications Method
     GetDefaultPolicyInfo Method
     IsLabelValid Method
   ISMimePolicyCheckAccess Interface
     IsAccessGranted Method
   ISMimePolicyLabelInfo Interface
     GetLabelAsStrings Method
     GetStringizedLabel Method
     DisplayAdvancedProperties Method
   ISMimePolicyValidateSend Interface
     IsValidLabelSignerCert Method
     IsValidLabelRecipientCert Method
   ISMimePolicyFullEdit Interface
     DoAdvancedEdit Method
     IsLabelValidAdvanced Method
Security Policy Configuration Settings
Conclusion

Introduction

A security label is an optional property of a digitally signed message. It lets you add information to the message header about the sensitivity of the message content that is protected by S/MIME encapsulation. Security labels can be used to enforce a user's authorization to access the contents of the message. The label can also restrict which recipients can open, forward, or send the message.

Depending on your organization's needs, you can define one or more security policies and implement them programmatically. For example, an Internal Use Only label might be implemented as a security label to apply to mail that should not be sent or forwarded outside of your company.

Components of a Security Label

Security Policy Identifier A security policy identifier is used to identify the security policy to which the security label relates.

Security Classification The labels typically describe ranked levels such as "top secret," "confidential," and "restricted." However, labels can also be role-based; describing which kind of people can see the information such as "patient's health-care team," "medical billing agents," and "unrestricted." A security classification may have one of such value. An organization can develop its own security policy that defines a hierarchy of security classification values and their meanings.

Privacy Mark A privacy mark is a string that is displayed on the user interface to denote the security label that is in force.

Processing Security Labels

A sending agent may include a security label in a digitally signed message. A receiving agent examines the security label on a received message and determines whether or not the recipient is allowed to see the contents of the message.

Default Security Policy

The default policy module options are a subset of the general policy module. The default policy cannot be used to send security labels. It will only be looked at to grant or deny access for e-mail with security labels whose specified policy cannot be found on the user's machine. The default behavior of the default policy is to deny access. However, you can implement your own default policy and override the default behavior as per your needs.

Implementing Security Policy Modules

The following section describes how you can use the Microsoft® Outlook® 2000 Federal Release Security Labels API to create security policy modules. This API is included in Outlook 2000 SR-1 and later. For information about Outlook SR-1, see Outlook 2000 SR-1 Update: MultiLanguage Pack E-mail Security.

The Outlook 2000 Federal Release Security Labels API provides the following five interfaces:

  • ISMimePolicySimpleEdit
  • ISMimePolicyFullEdit
  • ISMimePolicyLabelInfo
  • ISMimePolicyValidateSend
  • ISMimePolicyCheckAccess

To implement a security policy module, typically you need to do the following:

  • Write functions to register the security policy module in the registry.
  • Write an exported function <szOidEntryFuncName> in the security policy DLL that returns an IUnknown interface pointer to the required ISMimePolicy* interface.
  • Implement the interfaces and their methods.
  • Define the security policy configuration settings.

Registration

The policy module should support registration. In the security policy DLL, you need to write functions to register the security policy module in the registry.

Covering how to register DLLs in the registry is beyond the scope of this article. For help on using the registry functions, see Registry Functions.

The Exported Function

The security policy module DLL should have an exported function <szOidEntryFuncName> that would return an IUnknown interface pointer to the required ISMimePolicy* interface object. Note that the object needs to be stateless since it may be shared between multiple message items.

Given below is a simple implementation of an exported function that returns an IUnknown interface pointer to the required ISMimePolicy* interface object.

HRESULT WINAPI GetSMimePolicy(
                    /* in  */ DWORD      dwFlags,
                    /* in  */ LPSTR      szOid, 
                    /* in  */ LCID       lcid, 
                    /* in  */ REFIID     iid,
                    /* out */ LPUNKNOWN *ppunk)
{
    HRESULT        hr = E_FAIL;
    CSecurityPolicy *pCSsp = NULL;

    // Unreferenced local vars.
    dwFlags; lcid; 

    // If this is the policy we support or if it is the default policy.
    if ( (0 == strcmp(szOid, c_szPolicyOid)) ||  
         (0 == strcmp(c_szPolicyOidDefault, c_szPolicyOid)) ) {
        pCSsp = new CSecurityPolicy;
    }
    else {
        AssertSz(FALSE, "GetSMimePolicy : Unsupported policy oid");
        hr = E_NOINTERFACE;
        goto Error;
    }

    if (NULL == pCSsp) {
        Assert(FALSE);
        hr = E_FAIL;
        goto Error;
    }

    hr = pCSsp->QueryInterface(iid, (LPVOID *) ppunk);
    if (FAILED(hr)) {
        goto Error;
    }

    // success.
    hr = S_OK;
    goto Cleanup;

Error:
    if (pCSsp) {
        free(pCSsp);
    }
    
Cleanup:
    return hr;
}

//
// Initialization. 
//
HRESULT HrInitialize()
{
    HRESULT hr = S_OK;
    TCHAR   szPath[MAX_PATH];
    TCHAR  *pch = NULL;
    ULONG   cch;

    // Where is our DLL?
    cch = GetModuleFileName(HinstDll, szPath, DimensionOf(szPath));
    if (!cch) {
        DWORD   dw = GetLastError();
        hr = HRESULT_FROM_WIN32(dw);
        goto err;
    }
    Assert(_tcslen(szPath) > 0);
    
    // Name of the .ini file which contains the configuration information.
    pch = _tcsrchr(szPath, '.'); // STRING_OK
    if (NULL == pch) {
        AssertSz(pch != NULL, "module name doesn't have a . in it");
        _tcscat(szPath, ".ini"); 
    }
    else {
        _tcscpy(pch, ".ini");    
    }

    // Create the policy .ini file helper.
    g_ptpi = new CTestPolicyIniFile();
    if (NULL == g_ptpi) {
        hr = E_OUTOFMEMORY;
        goto err;
    }

    // Initialize the policy .ini file helper.
    hr = g_ptpi->HrReset(szPath);

err:
    return hr;
}
        
// Constructor for CSecurityPolicy. 
CSecurityPolicy::CSecurityPolicy() 
{
    m_dwlcid = CP_ACP;
    m_cRef = 0;
}

// Destructor for CSecurityPolicy.
CSecurityPolicy::~CSecurityPolicy()
{
}

//
// IUnknown Methods.
//
STDMETHODIMP CSecurityPolicy::QueryInterface(REFIID riid, LPVOID * ppv)
{
    if (riid == IID_IUnknown) {
        *ppv = (LPUNKNOWN) (LPSMIMEPOLICYSIMPLEEDIT) this;
    }
    else if (riid == IID_ISMimePolicySimpleEdit) {
        *ppv = (LPSMIMEPOLICYSIMPLEEDIT) this;
    } 
    else if (riid == IID_ISMimePolicyFullEdit) {
        if (g_ptpi->FSupportsAdvanced()) {
            *ppv = (LPSMIMEPOLICYFULLEDIT) this;
        }
        else {
            *ppv = NULL;
            return ResultFromScode(E_NOINTERFACE);
        }
    } 
    else if (riid == IID_ISMimePolicyLabelInfo) {
        *ppv = (LPSMIMEPOLICYLABELINFO) this;
    } 
    else if (riid == IID_ISMimePolicyCheckAccess) {
        *ppv = (LPSMIMEPOLICYCHECKACCESS) this;
    }
    else if (riid == IID_ISMimePolicyValidateSend) {
        *ppv = (LPSMIMEPOLICYVALIDATESEND) this;
    }
    else {
        *ppv = NULL;
        return ResultFromScode(E_NOINTERFACE);
    }
    AddRef();
    return S_OK;
}

STDMETHODIMP_(ULONG) CSecurityPolicy::AddRef() { return m_cRef++; }

STDMETHODIMP_(ULONG) CSecurityPolicy::Release()
{ 
    m_cRef -= 1;
    if (m_cRef != 0) {
        return  m_cRef;
    }
    delete this;
    return 0;
}

Implementing the Interfaces

The ISMimePolicySimpleEdit Interface

The ISMimePolicySimpleEdit interface defines the methods that allow basic editing of security labels. The methods enable you to specify and change the policy module that is effective, the classification, and the privacy mark. In other words, the methods return the strings for populating the display user interface on the Tools | Options | Security dialog box and the File | Properties | Security dialog box and the initial defaults for the security label.

Methods

GetPolicyInfo Gets general information about the security policy. General information includes whether the policy module supports advanced configuration, forces encryption, validates sender, and if the privacy mark is read-only. Returns S_OK or {E_FAIL, E_InVALIDAGRS}.

Here's a basic implementation of the GetPolicyInfo method.

HRESULT CSecurityPolicy::GetPolicyInfo(DWORD dwFlags, 
   DWORD *pdwPolicyFlags)
{
    HRESULT  hr = S_OK;
    DWORD    dwPolicyFlags = 0;

    // Unreferenced local vars.
    dwFlags;
    
    // Validate input params and init output params. 
    if (NULL == pdwPolicyFlags) {
        hr = E_INVALIDARG;
        goto Error; 
    }
    *pdwPolicyFlags = 0;
    
    
    // Set the dwPolicyFlags.
    if (g_ptpi->FSupportsAdvanced())    dwPolicyFlags |= 
      SMIME_POLICY_MODULE_SUPPORTS_ADV_CONFIG;
    if (g_ptpi->FForceEncryption())     dwPolicyFlags |= 
      SMIME_POLICY_MODULE_FORCE_ENCRYPTION;
    if (g_ptpi->FValidateSender())      dwPolicyFlags |= 
      SMIME_POLICY_MODULE_VALIDATE_SENDER;
    if (g_ptpi->FValidateRecipient())   dwPolicyFlags |= 
      SMIME_POLICY_MODULE_VALIDATE_RECIPIENT;
    if (g_ptpi->FPrivacyMarkReadOnly()) dwPolicyFlags |= 
      SMIME_POLICY_MODULE_PRIVACYMARK_READONLY;

    // Return the policy flags. 
    if (NULL != pdwPolicyFlags) *pdwPolicyFlags = dwPolicyFlags;
    

Error:
    return hr;
}

GetClassifications Enumerates supported classifications. Output includes the classification identifiers, the count of classifications, the classification display strings, and the default classification. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the GetClassifications method.

STDMETHODIMP CSecurityPolicy::GetClassifications(
                   DWORD    dwFlags, 
                   ULONG   *pcClassifications,    // Count of supported 
               classifications.
                   LPWSTR **rgwszClassifications, // The classification 
               strings.
                   LPDWORD *rgdwClassifications, // The classification 
               identifiers.
                   DWORD   *pdwDefault            // The default 
               classification. 
                   )
{
    HRESULT  hr = E_FAIL;
    ULONG    iClsf;
    ULONG    cClsf = 0;
    ULONG    cb1;
    ULONG    cb2;
    ULONG    cwch;
    LPDWORD  pdw   = NULL;
    LPWSTR  *pwsz  = NULL;
    LPBYTE   pb;
    LPBYTE   pbEnd;
    DWORD    rgdw[MAX_CLASSIFICATIONS];
    XString  rgstr[MAX_CLASSIFICATIONS];

    // Unrefenced local vars.
    dwFlags;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    if (! (pcClassifications && rgwszClassifications && 
           rgdwClassifications && pdwDefault) ) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pcClassifications = NULL;
    *rgwszClassifications = NULL;
    *rgdwClassifications = NULL;
    *pdwDefault = NULL;

    // Get the classification info from the .ini file.
    cClsf = DimensionOf(rgdw);
    if (g_ptpi->GetClassifications(&rgstr[0], &rgdw[0], &cClsf)) {

        // Calculate the size required for the output buffers.
        cb1 = sizeof(DWORD) * cClsf;
        cb2 = sizeof(LPWSTR) * cClsf;
        for (iClsf = 0; iClsf<cClsf; iClsf++) {
            cb2 += UlCbForWszFromStr(&rgstr[iClsf] );
        }
        pdw  = (LPDWORD) SecPolicyAlloc(cb1);
        pwsz = (LPWSTR*) SecPolicyAlloc(cb2);
        if ((NULL == pwsz) || (NULL == pdw)) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }

        // Copy the data over to the output buffers.
        pb = (LPBYTE) (& pwsz[cClsf] );
        pbEnd = ((LPBYTE)pwsz) + cb2;
        for (iClsf = 0; iClsf<cClsf; iClsf++) {
            pdw[iClsf] = rgdw[iClsf];
            pwsz[iClsf] = (LPWSTR) pb;
            // Convert from a str to a wsz.
            cwch = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, 
            (LPSTR) rgstr[iClsf], -1,
                                       (LPWSTR) pb, (pbEnd-pb)/sizeof(WCHAR)); 
            Assert(cwch >= 0);
            pb += (sizeof(WCHAR) * (cwch+1));
            Assert(pb <= pbEnd);
        }
        Assert(pb == pbEnd);
    }
        
    // Set the return values.
    *pcClassifications    = cClsf;
    *rgwszClassifications = (LPWSTR *) pwsz;
    *rgdwClassifications  = (LPDWORD) pdw;
    *pdwDefault           = g_ptpi->NGetDefaultClassification();
    
    hr = S_OK;

Exit:
    return hr;
    
Error:
    SecPolicyFree(pdw);
    SecPolicyFree(pwsz);
    goto Exit;
}

GetDefaultPolicyInfo Gets default classification and privacy mark. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the GetDefaultPolicyInfo method.

STDMETHODIMP CSecurityPolicy::GetDefaultPolicyInfo(
                     DWORD   dwFlags, 
                     DWORD  *pdwClassification, 
                     LPWSTR *pwszPrivacyMark)
{
    HRESULT  hr = S_OK;
    LPWSTR   pwch = NULL;
    Xstring  strPrivMark;

    // Unreferenced local vars.
    dwFlags;

    // Not implemented for the default policy.
    If (g_ptpi->FisDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    If ( (NULL == pdwClassification) || (NULL == pwszPrivacyMark) ) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pdwClassification = 0;
    *pwszPrivacyMark = NULL;

    // Get the defaults.
    
    if ((0 != g_ptpi->GetDefaultPrivacyMark(&strPrivMark)) && 
        (! strPrivMark.IsEmpty())) {
        pwch = WszFromStr(&strPrivMark);
    }

    // Set the return values.
    *pdwClassification = g_ptpi->NgetDefaultClassification();
    *pwszPrivacyMark = pwch;
    hr = S_OK;
    
Exit:
    return hr;

Error:
    SecPolicyFree(pwch);
    goto Exit;
}

IsLabelValid Validates if the security label is valid for a security policy module. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the IsLabelValid method. At places commented with Add code here, you might need to add code based on your organization's needs.

STDMETHODIMP CSecurityPolicy::IsLabelValid(
                  DWORD dwFlags,               // For example, 
                        NoUI, NoEdit flags.
                  HWND hwndParent,              
                  PSMIME_SECURITY_LABEL *pplabel // The label.
                  )
{
    HRESULT  hr = S_OK;
    // BOOL     fIsValid = fTrue;
    BOOL     fNoEdit = fFalse;
    PSMIME_SECURITY_LABEL plabel = NULL;

    // Unreferenced local vars.
    dwFlags; hwndParent;
    
    // Validate input params and init output params. 
    if ( (NULL == pplabel) || (NULL == *pplabel) )  {
        hr = E_INVALIDARG;
        goto Error;
    }
    plabel = *pplabel;
    fNoEdit = (dwFlags & SMIME_POLICY_MODULE_NOEDIT);

    // Add code here to check if the label is valid.
    // Check if pszObjIdSecurityPolicy is valid.
      // Check if wszprivacyMark is valid.
      // Chcek if {cCategories,rgCategories} is valid. 

    // Check if dwClassification is valid.
#if 0
    if (plabel->fHasClassification &&
        ( (plabel->dwClassification < 
         GetClassificationFromRid(IDS_CLASSIFICATION_BEGIN)) ||
          (plabel->dwClassification > 
         GetClassificationFromRid(IDS_CLASSIFICATION_END)) )) {
        fIsValid = fFalse;
        hr = E_FAIL;
        goto Error;
    }
#endif // 0

    // All validity checks passed, return success. 
    
    hr = S_OK;
    goto Cleanup;

Error:
    ;

Cleanup:
    return hr;
}

The ISMimePolicyCheckAccess Interface

The ISMimePolicyCheckAccess interface has a single method that grants or denies access to the message.

Method

IsAccessGranted Checks if access is granted. Returns S_OK or {MIME_E_SECURITY_LABEL_ACCESSDENIED, MIME_E_SECURITY_LABEL_ACCESSCANCELLED}.

Here's a basic implementation of the IsAccessGranted method. At places commented with Add code here, you might need to add code based on your organization's needs.

STDMETHODIMP CSecurityPolicy::IsAccessGranted(
        DWORD dwFlags,                // For example, NoUI flag.
        HWND hwndParent, 
        const PSMIME_SECURITY_LABEL plabel, // The label.
        const PCCERT_CONTEXT pccertDecrypt,  // [optional] <closest> 
         Cert used for decryption.
        const PCCERT_CONTEXT pccertSign,    // [optional] 
         Cert used for signing the label.
        const HCERTSTORE     hcertstor      // [optional] 
         Cert store containing cert hierarchy.
      )
{
    HRESULT  hr = S_OK;
    INT      nAccess;
    INT      n;

    // Unreferenced local vars.
    dwFlags; pccertDecrypt; pccertSign; hcertstor;

    // Validate input params and init output params. 
    if (NULL == plabel) { 
        hr = E_INVALIDARG;
        goto Error;
    }

    // If default policy, then use the default policy's .ini file 
         information.
    if (g_ptpi->FIsDefaultPolicy()) {
        if (NULL != plabel->pszObjIdSecurityPolicy) {
            if (g_ptpi->FisDefaultPolicyAccessGranted
            (plabel->pszObjIdSecurityPolicy, 
                                    plabel->dwClassification)) {
                hr = S_OK;
            }
            else {
                hr = MIME_E_SECURITY_LABELACCESSDENIED;
            }            
        }
        else {
            hr = E_INVALIDARG;
        }
        goto Cleanup;
    }
            
    // First check if the label is valid. 
    hr = IsLabelValid(dwFlags | SMIME_POLICY_MODULE_NOEDIT,
                      hwndParent, 
                      const_cast<PSMIME_SECURITY_LABEL*>(&plabel) );
    if (S_OK != hr) {
        // Invalid label => Access denied.
        hr = MIME_E_SECURITY_LABELACCESSDENIED;
        goto Error;
    }

    // If present, use the categories to check access. 
    if ((plabel->cCategories > 0) && (NULL != plabel->rgCategories)) {
        hr = HrCheckCategoriesAccess(dwFlags, plabel);
        if (FAILED(hr)) {
            goto Error;
        }
    }
    
    // what kind of access check are we to perform?
    nAccess = g_ptpi->NGetTypeOfAccessCheck();

    switch (nAccess) {
    
        case ACCESS_CHECK_DENY : 
            hr = MIME_E_SECURITY_LABELACCESSDENIED; 
            break;
            
        case ACCESS_CHECK_GRANT: 
            hr = S_OK; 
            break;
            
        case ACCESS_CHECK_ASK  : 
            if (0 == (SMIME_POLICY_MODULE_NOUI & dwFlags)) {
                n = MessageBox(hwndParent, "Is Access Granted?", 
            "Do you grant access?",
                               MB_YESNOCANCEL | MB_ICONQUESTION | 
                     MB_DEFBUTTON1);
                switch (n) {
                    case IDYES    : hr = S_OK; break;
                    case IDNO     : hr = 
               MIME_E_SECURITY_LABELACCESSDENIED; break;
                    case IDCANCEL : hr = 
               MIME_E_SECURITY_LABELACCESSCANCELLED; break;
                    default       : Assert(FALSE); hr = E_FAIL; break;
                }
            }
            else {
                hr = MIME_E_SECURITY_LABELUIREQUIRED;
            }
            break;
            
        case ACCESS_CHECK_ENUM:
            //Add code here
            hr = S_OK;
            break;
            
        default : 
            Assert(FALSE); 
            hr = E_FAIL; 
            break;
    }
        
    goto Cleanup;

Error:
    ;
Cleanup:
    return hr;
}

The ISMimePolicyLabelInfo Interface

The ISMimePolicyLabelInfo interface contains methods for retrieving strings from the label, and for displaying the label's advanced properties.

Methods

GetLabelAsStrings Gets the label subcomponents as strings. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the GetLabelAsStrings method.

STDMETHODIMP CSecurityPolicy::GetLabelAsStrings(
                    DWORD    dwFlags, 
                    const PSMIME_SECURITY_LABEL plabel,
                    LPWSTR  *pwszPolicyName,
                    LPWSTR  *pwszClassification,
                    LPWSTR  *pwszPrivacyMark,
                    LPWSTR  *pwszCategory
                    )
{
    HRESULT  hr = S_OK;
    ULONG    cwch = 0;
    XString  strPolicyName;
    XString  strClsf;
    LPWSTR   pwchPolicyName     = NULL;
    LPWSTR   pwchClassification = NULL;
    LPWSTR   pwchPrivacyMark    = NULL;
    LPWSTR   pwchCategory       = NULL;

    // Unreferenced local vars.
    dwFlags;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    if (! (plabel && pwszPolicyName && pwszClassification && 
           pwszPrivacyMark && pwszCategory) ) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pwszPolicyName = NULL;
    *pwszClassification = NULL;
    *pwszPrivacyMark = NULL;
    *pwszCategory = NULL;

    // (1) Get the policy Name.
    // Verify that the policy oid is one that we support.
    if ( (0 != strcmp(c_szPolicyOid1, plabel->pszObjIdSecurityPolicy)) &&
         (0 != strcmp(c_szPolicyOid2, plabel->pszObjIdSecurityPolicy)) &&
         (0 != strcmp(c_szPolicyOidTest, plabel->pszObjIdSecurityPolicy)) ) {
        AssertSz(FALSE, "GetLabelAsStrings: Error: Unsupported policy oid");
        hr = E_FAIL;
        goto Error;
    }
    // Get the common (displayable) name for the policy.
    g_ptpi->GetPolicyName(&strPolicyName);
    pwchPolicyName = WszFromStr(&strPolicyName);
    if (NULL == pwszPolicyName) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
                       
    // (2) Get the classification Name.
    if (plabel->fHasClassification) {        
        g_ptpi->GetClassification(plabel->dwClassification, &strClsf);
        pwchClassification = WszFromStr(&strClsf);
        if (0 == pwchClassification) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }
    }
    
    // (3) Get the Privacy Mark.
    if (plabel->wszPrivacyMark) {
        cwch = wcslen(plabel->wszPrivacyMark);
        if (cwch > 0) {
            pwchPrivacyMark = (LPWSTR) SecPolicyAlloc((cwch+1) 
                  * sizeof(WCHAR));
            if (NULL == pwchPrivacyMark) {
                hr = E_OUTOFMEMORY;
                goto Error;
            }
            wcscpy(pwchPrivacyMark, plabel->wszPrivacyMark);
        }
    }
        
    // (4) Get the Category.
    if (plabel->cCategories > 0) {
        Assert(plabel->rgCategories);
        hr = HrGetCategoriesAsString(0, plabel, &pwchCategory);
        if (FAILED(hr)) {
            goto Error;
        }
    }
        
    // Success: set the return values. 
    *pwszPolicyName     = pwchPolicyName;
    *pwszClassification = pwchClassification;
    *pwszPrivacyMark    = pwchPrivacyMark;
    *pwszCategory       = pwchCategory;
    hr = S_OK;

    goto Cleanup;

Error:
    SecPolicyFree(pwchPolicyName);
    SecPolicyFree(pwchClassification);
    SecPolicyFree(pwchPrivacyMark);
    SecPolicyFree(pwchCategory);

Cleanup:
    return hr;
}

GetStringizedLabel Gets a stringized version of the whole label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the GetStringizedLabel method.

STDMETHODIMP CSecurityPolicy::GetStringizedLabel(DWORD    dwFlags, 
                                               const PSMIME_SECURITY_LABEL 
                           plabel,
                                               LPWSTR  *pwszLabel)
{
    HRESULT  hr = S_OK;
    ULONG    cwch = 0;
    XString  strLabel;
    LPWSTR   wszLabel           = NULL;
    LPWSTR   pwchLabel          = NULL;
    LPWSTR   pwchPolicyName     = NULL;
    LPWSTR   pwchClassification = NULL;
    LPWSTR   pwchPrivacyMark    = NULL;
    LPWSTR   pwchCategory       = NULL;
    LPWSTR   rgpwsz[5];
    rgpwsz[4] = (LPWSTR) -1; // Sentinal value.

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;

    // Validate input params and init output params. 
    if (! (plabel && pwszLabel) ) {
        hr = E_INVALIDARG;
        goto Error;
    }
    *pwszLabel = NULL;

    // Get a stringized version of the label. 
    // (Something suitable to be displayed in the item header.) 

    // Get the individual strings. 
    hr = GetLabelAsStrings(dwFlags, plabel, &pwchPolicyName, 
                  &pwchClassification,
                           &pwchPrivacyMark, &pwchCategory);
    if (S_OK != hr) {
        hr = E_FAIL;
        goto Error;
    }

    rgpwsz[0] = pwchPolicyName;
    rgpwsz[1] = pwchClassification;
    rgpwsz[2] = pwchPrivacyMark;
    rgpwsz[3] = pwchCategory;
                  
    g_ptpi->GetTextizedLabel(&strLabel);
    wszLabel = WszFromStr(&strLabel);
    if (NULL == wszLabel) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    cwch = MyFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
      FORMAT_MESSAGE_FROM_STRING |FORMAT_MESSAGE_ARGUMENT_ARRAY,
                            wszLabel, 0, m_dwlcid, (LPWSTR) &pwchLabel, 
               0, (va_list *) rgpwsz);
    if ((0 == cwch) || (NULL == pwchLabel)) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    
    // Success: set the return values.
    *pwszLabel = pwchLabel;
    hr = S_OK;
    
Exit:
    SecPolicyFree(wszLabel);
    SecPolicyFree(pwchPolicyName);
    SecPolicyFree(pwchClassification);
    SecPolicyFree(pwchPrivacyMark);
    SecPolicyFree(pwchCategory);
    return hr;

Error:
    LocalFree(pwchLabel);
    goto Exit;
}

DisplayAdvancedProperties Displays advanced label properties. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the DisplayAdvancedProperties method.

STDMETHODIMP CSecurityPolicy::DisplayAdvancedLabelProperties(
                   DWORD dwFlags, 
                   HWND hwndParent, 
                   const PSMIME_SECURITY_LABEL plabel
                   )
{
    HRESULT  hr = S_OK;
    INT      iResult;
    DisplayLabelInfo dli;

    // Unreferenced local vars.
    dwFlags; hwndParent;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    if (NULL == plabel) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Display the read-only label-info dialog.
    dli.plabel = plabel;
    dli.psp = this;
    iResult = DialogBoxParam(HinstDll, 
                 MAKEINTRESOURCE(IDD_SECLABEL_DISPLAYLABEL),
                 hwndParent, SecLabelDisplayLabelDlgProc, 
                 (LPARAM) &dli);
    Assert(IDOK == iResult);

    // Success.
    hr = S_OK;
    goto Cleanup;

Error:

Cleanup:
    return hr;
}

The ISMimePolicyValidateSend Interface

The ISMimePolicyValidateSend interface defines methods that validate if the sender and recipient certificates are valid for a security given label.

Methods

IsValidLabelSignerCert Checks if the given signer certificate is allowed for the given label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the IsValidLabelSignerCert method.

STDMETHODIMP CSecurityPolicy::IsValidLabelSignerCert(
                  DWORD                       dwFlags, 
                  HWND                        hwndParent, 
                  const PSMIME_SECURITY_LABEL plabel,
                  const PCCERT_CONTEXT        pccert
                  )
{
    HRESULT  hr = S_OK;
    BOOL     fIsValid = fTrue;
    XString  strCAs;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    if (! (plabel && pccert) ) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // First check if the label is valid.
    hr = IsLabelValid(dwFlags | SMIME_POLICY_MODULE_NOEDIT, 
                      hwndParent, 
                      const_cast<PSMIME_SECURITY_LABEL*>(&plabel) );
    if (S_OK != hr) {
        fIsValid = fFalse;
        hr = E_FAIL;
        goto Error;
    }

    // Check if given signer cert is allowed with this label.
    if (g_ptpi->GetSenderCAs(&strCAs) && (!strCAs.IsEmpty())) {
        if (! FIsCertAllowed(pccert, &strCAs)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    
    hr = S_OK;
    goto Cleanup;

Error:

Cleanup:
    return hr;
}

IsValidLabelRecipientCert Checks if the given recipient certificate is allowed for the given label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

Here's a basic implementation of the IsValidLabelRecipientCert method.

STDMETHODIMP CSecurityPolicy::IsValidLabelRecipientCert(
                  DWORD                       dwFlags, 
                  HWND                        hwndParent, 
                  const PSMIME_SECURITY_LABEL plabel,
                  const PCCERT_CONTEXT        pccert
                  )
{
    HRESULT  hr = S_OK;
    BOOL     fIsValid = fTrue;
    XString  strCAs;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input params and init output params. 
    if (! (plabel && pccert) ) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // First check if the label is valid.
    hr = IsLabelValid(dwFlags | SMIME_POLICY_MODULE_NOEDIT, 
                      hwndParent, 
                      const_cast<PSMIME_SECURITY_LABEL*>(&plabel) );
    if (S_OK != hr) {
        fIsValid = fFalse;
        hr = E_FAIL;
        goto Error;
    }

    // Check if given recipient cert is allowed with this label.
    if (g_ptpi->GetRecipCAs(&strCAs) && (!strCAs.IsEmpty())) {
        if (! FIsCertAllowed(pccert, &strCAs)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    
    hr = S_OK;
    goto Cleanup;

Error:

Cleanup:
    return hr;
}

The ISMimePolicyFullEdit Interface

The ISMimePolicyFullEdit interface contains methods to invoke the advanced user interface that could optionally be shown by the policy module itself and to perform advanced label validation.

Methods

DoAdvancedEdit Invokes the advanced configuration user interface. Returns S_FALSE if the label was updated and S_OK if the label wasn't updated.

Here's a basic implementation of the DoAdvancedEdit method.

STDMETHODIMP CSecurityPolicy::DoAdvancedEdit(DWORD dwFlags, 
         HWND hwndParent, PSMIME_SECURITY_LABEL *pplabel)
{
    HRESULT hr = S_OK;
    INT     iResult;

    // Unused args.
    dwFlags; hwndParent; pplabel; 

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Just return success if policy doesn't support advanced config.
    if (! g_ptpi->FSupportsAdvanced())  {
        hr = E_NOTIMPL;
        goto Error;
    }

    // Input parameter validation.
    if ((NULL == pplabel) || (NULL == *pplabel)) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Show the configuration dialog, and update the *pplabel if required.
    iResult = DialogBoxParam(HinstDll, 
                 MAKEINTRESOURCE(IDD_SECLABEL_CONFIG),
                 hwndParent, SecLabelConfigureDlgProc, 
                 (LPARAM) pplabel);
    if (IDOK == iResult) {
        hr = S_FALSE; // The label was updated.
    }
    else if (IDCANCEL == iResult) {
        hr = S_OK; // The label wasn't updated.
    }
    
Exit:
    return hr;

Error:
    goto Exit;
}

IsLabelValidAdvanced Performs advanced label validation prior to sending the message.

Here's a basic implementation of the IsValidLabelAdvanced method. At places commented with Add code here, you might need to add code based on your organization's needs.

STDMETHODIMP CSecurityPolicy::IsLabelValidAdvanced(DWORD dwFlags, 
         HWND hwndParent, PSMIME_SECURITY_LABEL *pplabel)
{
    HRESULT hr = S_OK;

    // Not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    if (! g_ptpi->FSupportsAdvanced())  {
        // If policy doesn't support full edit, just do ordinary 
            label validation.
        hr = IsLabelValid(dwFlags, hwndParent, pplabel);
        goto Exit;
    }

    // Input parameter validation.
    if ((NULL == pplabel) || (NULL == *pplabel)) {
        hr = E_INVALIDARG;
        goto Error;
    }
    
    // Add code here to perform advanced label validation.

Exit:
    return hr;

Error:
    goto Exit;
}

Security Policy Configuration Settings

The security policy module will read-in its self-configuration information from an .ini file with the name "<myname>.ini". For example, if the module is named module1.dll, it will read in the information from a file named module1.ini. The policy module will not cache its reads, and will read the values as and when required.

The string encoding in the .ini file will be assumed to be UTF-8. Here are the contents of a sample .ini file.

[PolicyInfo]
; ForceEncryption=1                            // Default is 0.
; ValidateSender=yes                           // Default is 1.
; ValidateRecipient=yes                        // Default is 1.
; PrivacyMarkReadOnly=yes                      // Default is 0.
DefaultClassification=3                        // Default is 0.
DefaultPrivacyMark=Privacy Mark                // Default is none.

CommonName=Security Policy - Test 1         

TextizedLabel=Policy Name : %1 ; Classification : %2 ; PrivacyMark : %3 ; 
   Categories : %4 ;    // Textized label format. Default is 
      "PolicyName: %1; Classification: %2; PrivacyMark: %3". The 
         policy module will use this to format the labelinfo. The 
            parameters will be of the form szPolicyName, 
               szClassification, szPrivacyMark, szCategory.

SupportsAdvancedConfig=yes

//CertificateAuthoritesAllowed;
 SenderCAs=C=US, S=WA, L=Redmond, O=Outlook, OU=Microsoft, CN=Outlab 
; RecipCAs=C=US, S=WA, L=Redmond, O=Outlook, OU=Microsoft, CN=Outlab

[Classifications]
0=Unmarked
1=Unclassified
2=Restricted
3=Confidential
4=Secret
5=Top-Secret
6=Company Restricted
7=Company Confidential
8=Company Secret
9=Company Top-Secret

[AccessCheck]
; 0=Deny, 1=Grant, 2=Ask(default), 3=Enum      //Default is 2.
TypeOfAccessCheck=2

Conclusion

In this article, we looked at how to create security label policy modules using the API included in Office 2000 SR-1 and later versions of Outlook. We saw that in order to implement a security policy module, you need to write functions to register the security policy module in the registry, write an exported function <szOidEntryFuncName> in the security policy DLL that returns a IUnknown interface pointer to the required ISMimePolicy* interface, implement the interfaces and their methods, and define the security policy configuration settings.