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.