Creating Security Label Policy Modules for Outlook 2007

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Summary: This article describes how to use the 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. This article is an update to the original article Creating Security Label Policy Modules that was authored by Uma Subramanian and published for Microsoft Outlook 2000 SR-1. This article includes a link to a download of the complete code sample. (20 printed pages)

Angela Chu-Hatoun, Microsoft Corporation

First published: November 2001

Updated: March 2009

Applies to: Microsoft Office Outlook 2007, Microsoft Office Outlook 2003, Microsoft Outlook 2002, Microsoft Outlook 2000 Service Pack 2

Download: Messaging Developer Samples

Contents

  • Overview

  • Components of a Security Label

  • Processing Security Labels

  • Default Security Policy

  • Implementing Security Policy Modules

  • Implementing the Interfaces

  • About the Author

  • Additional Resources

Overview

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. You can use security labels 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 categories of people who can see specified information (for example, "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 be used only to grant or deny access for e-mail with security labels whose specified policy cannot be found on the user's computer. The default behavior of the default policy is to deny access. However, you can implement your own default policy and override the default behavior.

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 was introduced in Outlook 2000 SR-1. For information about Outlook SR-1, see Office 2000 Update: Service Pack 3 (SP3).

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

  • ISMimePolicySimpleEdit

  • ISMimePolicyCheckAccess

  • ISMimePolicyLabelInfo

  • ISMimePolicyValidateSend

  • ISMimePolicyFullEdit

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 <szOidEntryFuncName> function 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.

A discussion of how to register DLLs in the registry is beyond the scope of this article. For more information about how to use the registry functions, see Registry Functions.

Exported Function

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

The following code example shows 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;

    // Declare unreferenced local variables.
    dwFlags; lcid; 

    // Verify whether this is the policy you support or whether 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;

    // Determine the location of the DLL. 
    cch = GetModuleFileName(HinstDll, szPath, DimensionOf(szPath));
    if (!cch) {
        DWORD dw = GetLastError();
        hr = HRESULT_FROM_WIN32(dw);
        goto err;
    }
    Assert(_tcslen(szPath) > 0);
    
    // Specify the name of the .ini file that 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;
}
        
// Declare the constructor for CSecurityPolicy. 
CSecurityPolicy::CSecurityPolicy() 
{
    m_dwlcid = CP_ACP;
    m_cRef = 0;
}

// Declare the 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

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, 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, and validates the sender, and whether the privacy mark is read-only. Returns S_OK or {E_FAIL, E_InVALIDAGRS}.

The following code example shows a basic implementation of the GetPolicyInfo method.

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

    // Declare unreferenced local variables.
    dwFlags;
    
    // Validate input parameters and initialize output parameters. 
    if (NULL == pdwPolicyFlags) {
        hr = E_INVALIDARG;
        goto Error; 
    }
    *pdwPolicyFlags = 0;
    
    
    // Set the dwPolicyFlags parameter.
    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}.

The following code example shows a basic implementation of the GetClassifications method.

STDMETHODIMP CSecurityPolicy::GetClassifications(
    DWORD    dwFlags, 
    // pcClassifications specifies the count of supported classifications.
    ULONG   *pcClassifications, 
    // rgwszClassifications specifies the classification strings.
    LPWSTR **rgwszClassifications, 
    // rgdwClassifications specifies the classification identifiers.
    LPDWORD *rgdwClassifications, 
    // pdwDefault specifies the default classification.
    DWORD *pdwDefault  
    )
{
    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];

    // Declare unreferenced local variables.
    dwFlags;

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

    // Get the classification information 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 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 the default classification and privacy mark. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

The following code example shows 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;

    // Declare unreferenced local variables.
    dwFlags;

    // This method is not implemented for the default policy.
    If (g_ptpi->FisDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input parameters and initialize output parameters. 
    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
    Verifies whether the security label is valid for a security policy module. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

The following code example shows a basic implementation of the IsLabelValid method. At lines commented with Add code here, you might need to add code based on the needs of your organization.

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

    // Declare unreferenced local variables.
    dwFlags; hwndParent;
    
    // Validate input parameters and initialize output parameters. 
    if ( (NULL == pplabel) || (NULL == *pplabel) )  {
        hr = E_INVALIDARG;
        goto Error;
    }
    plabel = *pplabel;
    fNoEdit = (dwFlags & SMIME_POLICY_MODULE_NOEDIT);

    // Add code here to verify whether the label is valid.
    // Verify whether pszObjIdSecurityPolicy is valid.
      // Verify whether wszprivacyMark is valid.
      // Verify whether {cCategories,rgCategories} is valid. 

    // Verify whether 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

    // If all validity checks pass, return S_OK. 
    
    hr = S_OK;
    goto Cleanup;

Error:
    ;

Cleanup:
    return hr;
}

ISMimePolicyCheckAccess Interface

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

Method

  • IsAccessGranted
    Verifies whether access is granted. Returns S_OK or {MIME_E_SECURITY_LABEL_ACCESSDENIED, MIME_E_SECURITY_LABEL_ACCESSCANCELLED}.

The following code example shows a basic implementation of the IsAccessGranted method. At lines commented with Add code here, you might need to add code based on the needs of your organization.

STDMETHODIMP CSecurityPolicy::IsAccessGranted(
    // dwFlags specifies, for example, the NoUI flag.
    DWORD dwFlags, 
    HWND hwndParent, 
    // plabel specifies the security label.
    const PSMIME_SECURITY_LABEL plabel,
    // [optional] pccertDecrypt specifies the certificate used for decryption.
    const PCCERT_CONTEXT pccertDecrypt,
    // [optional] pccertSign specifies the certificate used for signing the label.
    const PCCERT_CONTEXT pccertSign,
    // [optional] hcertstor specifies the certificate store that contains the certificate hierarchy.
    const HCERTSTORE hcertstor
      )
{
    HRESULT hr = S_OK;
    INT nAccess;
    INT n;

    // Declare unreferenced local variables.
    dwFlags; pccertDecrypt; pccertSign; hcertstor;

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

    // If you use the default policy, 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, verify whether the label is valid. 
    hr = IsLabelValid(dwFlags | SMIME_POLICY_MODULE_NOEDIT,
        hwndParent, 
            const_cast<PSMIME_SECURITY_LABEL*>(&plabel) );
    if (S_OK != hr) {
        // If the label is invalid, deny access.
        hr = MIME_E_SECURITY_LABELACCESSDENIED;
        goto Error;
    }

    // If categories are present, use them to verify access. 
    if ((plabel->cCategories > 0) && (NULL != plabel->rgCategories)) {
        hr = HrCheckCategoriesAccess(dwFlags, plabel);
        if (FAILED(hr)) {
            goto Error;
        }
    }
    
    // Specify the type of access verification to be performed.
    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;
}

ISMimePolicyLabelInfo Interface

The ISMimePolicyLabelInfo interface contains methods used to retrieve 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}.

The following code example shows 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;

    // Declare unreferenced local variables.
    dwFlags;

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

    // First, get the policy name.
    // Verify that the policy object ID (OID) is supported.
    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 (display) name for the policy.
    g_ptpi->GetPolicyName(&strPolicyName);
    pwchPolicyName = WszFromStr(&strPolicyName);
    if (NULL == pwszPolicyName) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
                       
    // Second, get the classification name.
    if (plabel->fHasClassification) {        
        g_ptpi->GetClassification(plabel->dwClassification, &strClsf);
        pwchClassification = WszFromStr(&strClsf);
        if (0 == pwchClassification) {
            hr = E_OUTOFMEMORY;
            goto Error;
        }
    }
    
    // Third, 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);
        }
    }
        
    // Last, get the category.
    if (plabel->cCategories > 0) {
        Assert(plabel->rgCategories);
        hr = HrGetCategoriesAsString(0, plabel, &pwchCategory);
        if (FAILED(hr)) {
            goto Error;
        }
    }
        
    // If you can successfully get the policy name, classification name, privacy mark, and category, 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 string version of the whole label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

The following code example shows 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];
    // Set the last array element as a sentinel value.
    rgpwsz[4] = (LPWSTR) -1; 

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;

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

    // Get a string 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;
    }
    
    // If you can get the string version of the label, 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}.

The following code example shows 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;

    // Declare unreferenced local variables.
    dwFlags; hwndParent;

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input parameters and initialize output parameters. 
    if (NULL == plabel) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // Display the read-only label-information dialog box.
    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;
}

ISMimePolicyValidateSend Interface

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

Methods

  • IsValidLabelSignerCert
    Verifies whether the given signer certificate is allowed for the given label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

The following code example shows 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;

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input parameters and initialize output parameters. 
    if (! (plabel && pccert) ) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // First, verify whether 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;
    }

    // Verify whether the given signer certificate 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
    Verifies whether the given recipient certificate is allowed for the given label. Returns S_OK or {E_INVALIDARG, E_OUTOFMEMORY, E_FAIL}.

The following code example shows 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;

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Validate input parameters and initialize output parameters. 
    if (! (plabel && pccert) ) {
        hr = E_INVALIDARG;
        goto Error;
    }

    // First, verify whether 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;
    }

    // Verify whether the given recipient certificate 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;
}

ISMimePolicyFullEdit Interface

The ISMimePolicyFullEdit interface contains methods to invoke the advanced user interface that can 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 was not updated.

The following code example shows a basic implementation of the DoAdvancedEdit method.

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

    // Declare unused arguments.
    dwFlags; hwndParent; pplabel; 

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    // Return success if the policy does not support advanced configuration.
    if (! g_ptpi->FSupportsAdvanced())  {
        hr = E_NOTIMPL;
        goto Error;
    }

    // Validate the input parameters.
    if ((NULL == pplabel) || (NULL == *pplabel)) {
        hr = E_INVALIDARG;
        goto Error;
    }

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

Error:
    goto Exit;
}
  • IsValidLabelAdvanced
    Performs advanced label validation prior to sending the message.

The following code example shows a basic implementation of the IsValidLabelAdvanced method. At lines commented with Add code here, you might need to add code based on the needs of your organization.

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

    // This method is not implemented for the default policy.
    if (g_ptpi->FIsDefaultPolicy()) return E_NOTIMPL;
    
    if (! g_ptpi->FSupportsAdvanced())  {
        // If the policy does not support full edit, use ordinary 
        // label validation.
        hr = IsLabelValid(dwFlags, hwndParent, pplabel);
        goto Exit;
    }

    // Validate the input parameters.
    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 ModuleName.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 the information it reads, and will read the values when required.

The string encoding in the .ini file will be assumed to be UTF-8. The following code example shows 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         

// Text 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.
TextizedLabel=Policy Name : %1 ; Classification : %2 ; PrivacyMark : %3 ; 
    Categories : %4 ;    

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

This article shows how to create security label policy modules by using the Microsoft Outlook 2000 Federal Release Security Labels API that was introduced in Outlook 2000 SR-1. You can apply the approach discussed in this article to Outlook 2007, Outlook 2003, Outlook 2002, and Outlook 2000 Service Pack 2.

About the Author

Angela Chu-Hatoun is a programmability writer for Outlook.

Additional Resources