Windows 信息保护 (WIP) 开发人员指南

启发式应用区分企业和个人数据,并知道根据管理员定义的 Windows 信息保护 (WIP) 策略保护哪些内容。

在本指南中,我们将介绍如何生成一个。 完成后,策略管理员将能够信任你的应用来使用其组织的数据。 员工也愿意他们的个人数据在设备上保持不变,即使取消注册组织的移动设备管理 (MDM) 或完全退出组织也是如此。

请注意 ,本指南可帮助你启发 UWP 应用。 若要启发C++ Windows 桌面应用,请参阅 Windows 信息保护 (WIP) 开发人员指南(C++)。

可以在此处阅读有关 WIP 和启发式应用的详细信息:Windows 信息保护(WIP)。

可在此处找到完整的示例

如果已准备好完成每个任务,让我们开始吧。

首先,收集所需的内容

需要以下项:

  • 运行 Windows 10 版本 1607 或更高版本的测试虚拟机(VM)。 你将针对此测试 VM 调试应用。

  • 运行 Windows 10 版本 1607 或更高版本的开发计算机。 如果已安装 Visual Studio,这可能是测试 VM。

设置开发环境

你将执行以下操作:

在测试 VM 上安装 WIP 设置开发人员助手

使用此工具在测试 VM 上设置 Windows 信息保护策略。

请在此处下载该工具: WIP 设置开发人员助手

创建保护策略

通过将信息添加到 WIP 设置开发人员助手中的每个部分来定义策略。 选择任何设置旁边的帮助图标,了解有关如何使用它的详细信息。

有关如何使用此工具的更多常规指南,请参阅应用下载页上的“版本说明”部分。

设置 Visual Studio 项目

  1. 在开发计算机上,打开项目。

  2. 为 通用 Windows 平台 (UWP) 添加对桌面和移动扩展的引用。

    添加 UWP 扩展

  3. 将此功能添加到包清单文件:

       <rescap:Capability Name="enterpriseDataPolicy"/>
    

    可选阅读:“回顾”前缀表示 受限功能。 请参阅 “特殊”和“受限”功能

  4. 将此命名空间添加到包清单文件:

      xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
    
  5. 将命名空间前缀添加到 <ignorableNamespaces> 包清单文件的元素。

        <IgnorableNamespaces="uap mp rescap">
    

    这样,如果你的应用在不支持受限功能的 Windows 操作系统版本上运行,Windows 将忽略该功能 enterpriseDataPolicy

设置远程调试

仅当在 VM 以外的计算机上开发应用时,才在测试 VM 上安装 Visual Studio 远程工具。 然后,在开发计算机上启动远程调试器,并查看应用是否在测试 VM 上运行。

请参阅 远程电脑说明

将这些命名空间添加到代码文件

将这些 using 语句添加到代码文件的顶部(本指南中的代码片段使用这些语句):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.EnterpriseData;
using Windows.Web.Http;
using Windows.Storage.Streams;
using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.ApplicationModel.Activation;
using Windows.Web.Http.Filters;
using Windows.Storage;
using Windows.Data.Xml.Dom;
using Windows.Foundation.Metadata;
using Windows.Web.Http.Headers;

确定是否在应用中使用 WIP API

确保运行应用的操作系统支持 WIP,并在设备上启用 WIP。

bool use_WIP_APIs = false;

if ((ApiInformation.IsApiContractPresent
    ("Windows.Security.EnterpriseData.EnterpriseDataContract", 3)
    && ProtectionPolicyManager.IsProtectionEnabled))
{
    use_WIP_APIs = true;
}
else
{
    use_WIP_APIs = false;
}

如果操作系统不支持 WIP 或未在设备上启用 WIP,请不要调用 WIP API。

读取企业数据

若要读取从共享协定接受的受保护文件、网络终结点、剪贴板数据和数据,应用必须请求访问权限。

如果应用位于保护策略的允许列表中,Windows 信息保护会授予应用权限。

本节内容:

从文件中读取数据

步骤 1:获取文件句柄

    Windows.Storage.StorageFolder storageFolder =
        Windows.Storage.ApplicationData.Current.LocalFolder;

    Windows.Storage.StorageFile file =
        await storageFolder.GetFileAsync(fileName);

步骤 2:确定应用是否可以打开文件

调用 FileProtectionManager.GetProtectionInfoAsync 以确定应用是否可以打开该文件。

FileProtectionInfo protectionInfo = await FileProtectionManager.GetProtectionInfoAsync(file);

if ((protectionInfo.Status != FileProtectionStatus.Protected &&
    protectionInfo.Status != FileProtectionStatus.Unprotected))
{
    return false;
}
else if (protectionInfo.Status == FileProtectionStatus.Revoked)
{
    // Code goes here to handle this situation. Perhaps, show UI
    // saying that the user's data has been revoked.
}

ProtectedFileProtectionStatus 值意味着文件受到保护,并且你的应用可以打开它,因为你的应用位于策略允许的列表上。

UnProtectedFileProtectionStatus 值意味着文件不受保护,并且你的应用可以打开该文件,即使你的应用不在策略允许的列表上也是如此。

API
FileProtectionManager.GetProtectionInfoAsync
FileProtectionInfo
FileProtectionStatus
ProtectionPolicyManager.IsIdentityManaged

步骤 3:将文件读入流或缓冲区

将文件读入流

var stream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);

将文件读入缓冲区

var buffer = await Windows.Storage.FileIO.ReadBufferAsync(file);

从网络终结点读取数据

创建受保护的线程上下文以从企业终结点读取。

步骤 1:获取网络终结点的标识

Uri resourceURI = new Uri("http://contoso.com/stockData.xml");

Windows.Networking.HostName hostName =
    new Windows.Networking.HostName(resourceURI.Host);

string identity = await ProtectionPolicyManager.
    GetPrimaryManagedIdentityForNetworkEndpointAsync(hostName);

如果终结点不受策略管理,你将返回一个空字符串。

API
ProtectionPolicyManager.GetPrimaryManagedIdentityForNetworkEndpointAsync

步骤 2:创建受保护的线程上下文

如果终结点由策略管理,请创建受保护的线程上下文。 这会标记在标识所在的同一线程上建立的任何网络连接。

它还允许你访问由该策略管理的企业网络资源。

if (!string.IsNullOrEmpty(identity))
{
    using (ThreadNetworkContext threadNetworkContext =
            ProtectionPolicyManager.CreateCurrentThreadNetworkContext(identity))
    {
        return await GetDataFromNetworkRedirectHelperMethod(resourceURI);
    }
}
else
{
    return await GetDataFromNetworkRedirectHelperMethod(resourceURI);
}

此示例将套接字调用括在块 using 中。 如果不执行此操作,请确保在检索资源后关闭线程上下文。 请参阅 ThreadNetworkContext.Close

不要在该受保护的线程上创建任何个人文件,因为这些文件将自动加密。

ProtectionPolicyManager.CreateCurrentThreadNetworkContext 方法返回 ThreadNetworkContext 对象,无论终结点是否由策略管理。 如果应用同时处理个人和企业资源,请为所有标识调用 ProtectionPolicyManager.CreateCurrentThreadNetworkContext 获取资源后,释放 ThreadNetworkContext 以清除当前线程中的任何标识标记。

API
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity
ProtectionPolicyManager.CreateCurrentThreadNetworkContext

步骤 3:将资源读入缓冲区

private static async Task<IBuffer> GetDataFromNetworkHelperMethod(Uri resourceURI)
{
    HttpClient client;

    client = new HttpClient();

    try { return await client.GetBufferAsync(resourceURI); }

    catch (Exception) { return null; }
}

(可选)使用标头令牌而不是创建受保护的线程上下文

public static async Task<IBuffer> GetDataFromNetworkbyUsingHeader(Uri resourceURI)
{
    HttpClient client;

    Windows.Networking.HostName hostName =
        new Windows.Networking.HostName(resourceURI.Host);

    string identity = await ProtectionPolicyManager.
        GetPrimaryManagedIdentityForNetworkEndpointAsync(hostName);

    if (!string.IsNullOrEmpty(identity))
    {
        client = new HttpClient();

        HttpRequestHeaderCollection headerCollection = client.DefaultRequestHeaders;

        headerCollection.Add("X-MS-Windows-HttpClient-EnterpriseId", identity);

        return await GetDataFromNetworkbyUsingHeaderHelperMethod(client, resourceURI);
    }
    else
    {
        client = new HttpClient();
        return await GetDataFromNetworkbyUsingHeaderHelperMethod(client, resourceURI);
    }

}

private static async Task<IBuffer> GetDataFromNetworkbyUsingHeaderHelperMethod(HttpClient client, Uri resourceURI)
{

    try { return await client.GetBufferAsync(resourceURI); }

    catch (Exception) { return null; }
}

处理页面重定向

有时,Web 服务器会将流量重定向到资源的最新版本。

若要处理此问题,请在请求的响应状态为“确定之前发出请求。

然后使用该响应的 URI 获取终结点的标识。 下面是执行此操作的一种方法:

private static async Task<IBuffer> GetDataFromNetworkRedirectHelperMethod(Uri resourceURI)
{
    HttpClient client = null;

    HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
    filter.AllowAutoRedirect = false;

    client = new HttpClient(filter);

    HttpResponseMessage response = null;

        HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, resourceURI);
        response = await client.SendRequestAsync(message);

    if (response.StatusCode == HttpStatusCode.MultipleChoices ||
        response.StatusCode == HttpStatusCode.MovedPermanently ||
        response.StatusCode == HttpStatusCode.Found ||
        response.StatusCode == HttpStatusCode.SeeOther ||
        response.StatusCode == HttpStatusCode.NotModified ||
        response.StatusCode == HttpStatusCode.UseProxy ||
        response.StatusCode == HttpStatusCode.TemporaryRedirect ||
        response.StatusCode == HttpStatusCode.PermanentRedirect)
    {
        message = new HttpRequestMessage(HttpMethod.Get, message.RequestUri);
        response = await client.SendRequestAsync(message);

        try { return await response.Content.ReadAsBufferAsync(); }

        catch (Exception) { return null; }
    }
    else
    {
        try { return await response.Content.ReadAsBufferAsync(); }

        catch (Exception) { return null; }
    }
}

API
ProtectionPolicyManager.GetPrimaryManagedIdentityForNetworkEndpointAsync
ProtectionPolicyManager.CreateCurrentThreadNetworkContext
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity

从剪贴板读取数据

获取从剪贴板使用数据的权限

若要从剪贴板获取数据,请向 Windows 请求权限。 使用 DataPackageView.RequestAccessAsync 执行此操作。

public static async Task PasteText(TextBox textBox)
{
    DataPackageView dataPackageView = Clipboard.GetContent();

    if (dataPackageView.Contains(StandardDataFormats.Text))
    {
        ProtectionPolicyEvaluationResult result = await dataPackageView.RequestAccessAsync();

        if (result == ProtectionPolicyEvaluationResult..Allowed)
        {
            string contentsOfClipboard = await dataPackageView.GetTextAsync();
            textBox.Text = contentsOfClipboard;
        }
    }
}

API
DataPackageView.RequestAccessAsync

隐藏或禁用使用剪贴板数据的功能

确定当前视图是否有权获取剪贴板上的数据。

如果没有,可以禁用或隐藏控件,以便用户粘贴剪贴板中的信息或预览其内容。

private bool IsClipboardAllowedAsync()
{
    ProtectionPolicyEvaluationResult protectionPolicyEvaluationResult = ProtectionPolicyEvaluationResult.Blocked;

    DataPackageView dataPackageView = Clipboard.GetContent();

    if (dataPackageView.Contains(StandardDataFormats.Text))

        protectionPolicyEvaluationResult =
            ProtectionPolicyManager.CheckAccess(dataPackageView.Properties.EnterpriseId,
                ProtectionPolicyManager.GetForCurrentView().Identity);

    return (protectionPolicyEvaluationResult == ProtectionPolicyEvaluationResult.Allowed |
        protectionPolicyEvaluationResult == ProtectionPolicyEvaluationResult.ConsentRequired);
}

API
ProtectionPolicyEvaluationResult
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity

阻止用户出现同意对话框提示

新文档不是个人或企业文档。 只是新。 如果用户将企业数据粘贴到其中,Windows 会强制实施策略,并提示用户进行同意对话框。 此代码可防止发生这种情况。 此任务不在于帮助保护数据。 当应用创建全新的项目时,它更要防止用户收到同意对话框。

private async void PasteText(bool isNewEmptyDocument)
{
    DataPackageView dataPackageView = Clipboard.GetContent();

    if (dataPackageView.Contains(StandardDataFormats.Text))
    {
        if (!string.IsNullOrEmpty(dataPackageView.Properties.EnterpriseId))
        {
            if (isNewEmptyDocument)
            {
                ProtectionPolicyManager.TryApplyProcessUIPolicy(dataPackageView.Properties.EnterpriseId);
                string contentsOfClipboard = contentsOfClipboard = await dataPackageView.GetTextAsync();
                // add this string to the new item or document here.          

            }
            else
            {
                ProtectionPolicyEvaluationResult result = await dataPackageView.RequestAccessAsync();

                if (result == ProtectionPolicyEvaluationResult.Allowed)
                {
                    string contentsOfClipboard = contentsOfClipboard = await dataPackageView.GetTextAsync();
                    // add this string to the new item or document here.
                }
            }
        }
    }
}

API
DataPackageView.RequestAccessAsync
ProtectionPolicyEvaluationResult
ProtectionPolicyManager.TryApplyProcessUIPolicy

从共享协定读取数据

当员工选择你的应用来共享其信息时,你的应用将打开包含该内容的新项。

如前所述,新项目不是个人或企业项目。 只是新。 如果代码将企业内容添加到项目,Windows 会强制实施策略,并提示用户进行同意对话框。 此代码可防止发生这种情况。

protected override async void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
    bool isNewEmptyDocument = true;
    string identity = "corp.microsoft.com";

    ShareOperation shareOperation = args.ShareOperation;
    if (shareOperation.Data.Contains(StandardDataFormats.Text))
    {
        if (!string.IsNullOrEmpty(shareOperation.Data.Properties.EnterpriseId))
        {
            if (isNewEmptyDocument)
                // If this is a new and empty document, and we're allowed to access
                // the data, then we can avoid popping the consent dialog
                ProtectionPolicyManager.TryApplyProcessUIPolicy(shareOperation.Data.Properties.EnterpriseId);
            else
            {
                // In this case, we can't optimize the workflow, so we just
                // request consent from the user in this case.

                ProtectionPolicyEvaluationResult protectionPolicyEvaluationResult = await shareOperation.Data.RequestAccessAsync();

                if (protectionPolicyEvaluationResult == ProtectionPolicyEvaluationResult.Allowed)
                {
                    string text = await shareOperation.Data.GetTextAsync();

                    // Do something with that text.
                }
            }
        }
        else
        {
            // If the data has no enterprise identity, then we already have access.
            string text = await shareOperation.Data.GetTextAsync();

            // Do something with that text.
        }

    }

}

API
ProtectionPolicyManager.RequestAccessAsync
ProtectionPolicyEvaluationResult
ProtectionPolicyManager.TryApplyProcessUIPolicy

保护企业数据

保护离开应用的企业数据。 数据会在页面中显示时离开应用,将其保存到文件或网络终结点,或通过共享协定保存。

本节内容:

保护显示在页面中的数据

在页面中显示数据时,让 Windows 知道它是哪种类型的数据(个人或企业)。 为此, 请标记 当前应用视图或标记整个应用进程。

标记视图或进程时,Windows 会针对它强制执行策略。 这有助于防止因应用无法控制的操作而导致的数据泄漏。 例如,在计算机上,用户可以使用 CTRL-V 从视图中复制企业信息,然后将该信息粘贴到另一个应用。 Windows 可防范此情况。 Windows 还有助于强制实施共享协定。

标记当前应用视图

如果你的应用具有多个视图,其中某些视图使用企业数据,有些视图使用个人数据,则执行此操作。


// tag as enterprise data. "identity" the string that contains the enterprise ID.
// You'd get that from a file, network endpoint, or clipboard data package.
ProtectionPolicyManager.GetForCurrentView().Identity = identity;

// tag as personal data.
ProtectionPolicyManager.GetForCurrentView().Identity = String.Empty;

API
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity

标记进程

如果应用中的所有视图仅处理一种类型的数据(个人或企业),请执行此操作。

这可以防止你不得不管理独立标记的视图。



// tag as enterprise data. "identity" the string that contains the enterprise ID.
// You'd get that from a file, network endpoint, or clipboard data package.
bool result =
            ProtectionPolicyManager.TryApplyProcessUIPolicy(identity);

// tag as personal data.
ProtectionPolicyManager.ClearProcessUIPolicy();

API
ProtectionPolicyManager.TryApplyProcessUIPolicy

将数据保护到文件

创建受保护的文件,然后将其写入。

步骤 1:确定应用是否可以创建企业文件

如果标识字符串由策略管理,并且应用位于该策略的“允许”列表中,则应用可以创建企业文件。

  if (!ProtectionPolicyManager.IsIdentityManaged(identity)) return false;

API
ProtectionPolicyManager.IsIdentityManaged

步骤 2:创建文件并将其保护到标识

StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile storageFile = await storageFolder.CreateFileAsync("sample.txt",
    CreationCollisionOption.ReplaceExisting);

FileProtectionInfo fileProtectionInfo =
    await FileProtectionManager.ProtectAsync(storageFile, identity);

API
FileProtectionManager.ProtectAsync

步骤 3:将流或缓冲区写入文件

编写流

    if (fileProtectionInfo.Status == FileProtectionStatus.Protected)
    {
        var stream = await storageFile.OpenAsync(FileAccessMode.ReadWrite);

        using (var outputStream = stream.GetOutputStreamAt(0))
        {
            using (var dataWriter = new DataWriter(outputStream))
            {
                dataWriter.WriteString(enterpriseData);
            }
        }

    }

写入缓冲区

     if (fileProtectionInfo.Status == FileProtectionStatus.Protected)
     {
         var buffer = Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(
             enterpriseData, Windows.Security.Cryptography.BinaryStringEncoding.Utf8);

         await FileIO.WriteBufferAsync(storageFile, buffer);

      }

API
FileProtectionInfo
FileProtectionStatus

将数据作为后台进程保护到文件

锁定设备屏幕时,此代码可以运行。 如果管理员配置了安全的“锁定下的数据保护”(DPL)策略,Windows 会删除从设备内存访问受保护资源所需的加密密钥。 如果设备丢失,这将防止数据泄漏。 此功能还会在关闭句柄时删除与受保护文件关联的密钥。

创建文件时,必须使用保持文件句柄打开的方法。

步骤 1:确定是否可以创建企业文件

如果使用的标识由策略管理,并且应用位于该策略的允许列表中,则可以创建企业文件。

if (!ProtectionPolicyManager.IsIdentityManaged(identity)) return false;

API
ProtectionPolicyManager.IsIdentityManaged

步骤 2:创建文件并将其保护到标识

FileProtectionManager.CreateProtectedAndOpenAsync 创建受保护的文件,并在写入文件时保持文件句柄打开。

StorageFolder storageFolder = ApplicationData.Current.LocalFolder;

ProtectedFileCreateResult protectedFileCreateResult =
    await FileProtectionManager.CreateProtectedAndOpenAsync(storageFolder,
        "sample.txt", identity, CreationCollisionOption.ReplaceExisting);

API
FileProtectionManager.CreateProtectedAndOpenAsync

步骤 3:将流或缓冲区写入文件

此示例将流写入文件。

if (protectedFileCreateResult.ProtectionInfo.Status == FileProtectionStatus.Protected)
{
    IOutputStream outputStream =
        protectedFileCreateResult.Stream.GetOutputStreamAt(0);

    using (DataWriter writer = new DataWriter(outputStream))
    {
        writer.WriteString(enterpriseData);
        await writer.StoreAsync();
        await writer.FlushAsync();
    }

    outputStream.Dispose();
}
else if (protectedFileCreateResult.ProtectionInfo.Status == FileProtectionStatus.AccessSuspended)
{
    // Perform any special processing for the access suspended case.
}

API
ProtectedFileCreateResult.ProtectionInfo
FileProtectionStatus
ProtectedFileCreateResult.Stream

保护文件的一部分

在大多数情况下,单独存储企业和个人数据更简洁,但如果需要,可以将它们存储在同一文件中。 例如,Microsoft Outlook 可以将企业邮件与个人邮件一起存储在单个存档文件中。

加密企业数据,但不加密整个文件。 这样,即使用户取消注册 MDM 或其企业数据访问权限,用户也可以继续使用该文件。 此外,你的应用应跟踪它加密的数据,以便知道在将文件读回内存时要保护哪些数据。

步骤 1:将企业数据添加到加密流或缓冲区

string enterpriseDataString = "<employees><employee><name>Bill</name><social>xxx-xxx-xxxx</social></employee></employees>";

var enterpriseData= Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(
        enterpriseDataString, Windows.Security.Cryptography.BinaryStringEncoding.Utf8);

BufferProtectUnprotectResult result =
   await DataProtectionManager.ProtectAsync(enterpriseData, identity);

enterpriseData= result.Buffer;

API
DataProtectionManager.ProtectAsync
BufferProtectUnprotectResult.buffer

步骤 2:将个人数据添加到未加密的流或缓冲区

string personalDataString = "<recipies><recipe><name>BillsCupCakes</name><cooktime>30</cooktime></recipe></recipies>";

var personalData = Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(
    personalDataString, Windows.Security.Cryptography.BinaryStringEncoding.Utf8);

步骤 3:将流或缓冲区写入文件

StorageFolder storageFolder = ApplicationData.Current.LocalFolder;

StorageFile storageFile = await storageFolder.CreateFileAsync("data.xml",
    CreationCollisionOption.ReplaceExisting);

 // Write both buffers to the file and save the file.

var stream = await storageFile.OpenAsync(FileAccessMode.ReadWrite);

using (var outputStream = stream.GetOutputStreamAt(0))
{
    using (var dataWriter = new DataWriter(outputStream))
    {
        dataWriter.WriteBuffer(enterpriseData);
        dataWriter.WriteBuffer(personalData);

        await dataWriter.StoreAsync();
        await outputStream.FlushAsync();
    }
}

步骤 4:跟踪文件中企业数据的位置

应用负责跟踪企业拥有的该文件中的数据。

可以将该信息存储在与文件关联的属性、数据库中或文件中的某些标头文本中。

此示例将该信息保存到单独的 XML 文件中。

StorageFile metaDataFile = await storageFolder.CreateFileAsync("metadata.xml",
   CreationCollisionOption.ReplaceExisting);

await Windows.Storage.FileIO.WriteTextAsync
    (metaDataFile, "<EnterpriseDataMarker start='0' end='" + enterpriseData.Length.ToString() +
    "'></EnterpriseDataMarker>");

读取文件的受保护部分

下面介绍如何从该文件中读取企业数据。

步骤 1:获取文件中企业数据的位置

Windows.Storage.StorageFolder storageFolder =
    Windows.Storage.ApplicationData.Current.LocalFolder;

 Windows.Storage.StorageFile metaDataFile =
   await storageFolder.GetFileAsync("metadata.xml");

string metaData = await Windows.Storage.FileIO.ReadTextAsync(metaDataFile);

XmlDocument doc = new XmlDocument();

doc.LoadXml(metaData);

uint startPosition =
    Convert.ToUInt16((doc.FirstChild.Attributes.GetNamedItem("start")).InnerText);

uint endPosition =
    Convert.ToUInt16((doc.FirstChild.Attributes.GetNamedItem("end")).InnerText);

步骤 2:打开数据文件并确保它不受保护

Windows.Storage.StorageFile dataFile =
    await storageFolder.GetFileAsync("data.xml");

FileProtectionInfo protectionInfo =
    await FileProtectionManager.GetProtectionInfoAsync(dataFile);

if (protectionInfo.Status == FileProtectionStatus.Protected)
    return false;

API
FileProtectionManager.GetProtectionInfoAsync
FileProtectionInfo
FileProtectionStatus

步骤 3:从文件读取企业数据

var stream = await dataFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);

stream.Seek(startPosition);

Windows.Storage.Streams.Buffer tempBuffer = new Windows.Storage.Streams.Buffer(50000);

IBuffer enterpriseData = await stream.ReadAsync(tempBuffer, endPosition, InputStreamOptions.None);

步骤 4:解密包含企业数据的缓冲区

DataProtectionInfo dataProtectionInfo =
   await DataProtectionManager.GetProtectionInfoAsync(enterpriseData);

if (dataProtectionInfo.Status == DataProtectionStatus.Protected)
{
    BufferProtectUnprotectResult result = await DataProtectionManager.UnprotectAsync(enterpriseData);
    enterpriseData = result.Buffer;
}
else if (dataProtectionInfo.Status == DataProtectionStatus.Revoked)
{
    // Code goes here to handle this situation. Perhaps, show UI
    // saying that the user's data has been revoked.
}

API
DataProtectionInfo
DataProtectionManager.GetProtectionInfoAsync

保护文件夹的数据

可以创建文件夹并对其进行保护。 这样,添加到该文件夹的任何项目都会自动受到保护。

private async Task<bool> CreateANewFolderAndProtectItAsync(string folderName, string identity)
{
    if (!ProtectionPolicyManager.IsIdentityManaged(identity)) return false;

    StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
    StorageFolder newStorageFolder =
        await storageFolder.CreateFolderAsync(folderName);

    FileProtectionInfo fileProtectionInfo =
        await FileProtectionManager.ProtectAsync(newStorageFolder, identity);

    if (fileProtectionInfo.Status != FileProtectionStatus.Protected)
    {
        // Protection failed.
        return false;
    }
    return true;
}

在保护文件夹之前,请确保该文件夹为空。 无法保护已包含项目的文件夹。

API
ProtectionPolicyManager.IsIdentityManaged
FileProtectionManager.ProtectAsync
FileProtectionInfo.Identity
FileProtectionInfo.Status

将数据保护到网络终结点

创建受保护的线程上下文,以将数据发送到企业终结点。

步骤 1:获取网络终结点的标识

Windows.Networking.HostName hostName =
    new Windows.Networking.HostName(resourceURI.Host);

string identity = await ProtectionPolicyManager.
    GetPrimaryManagedIdentityForNetworkEndpointAsync(hostName);

API
ProtectionPolicyManager.GetPrimaryManagedIdentityForNetworkEndpointAsync

步骤 2:创建受保护的线程上下文并将数据发送到网络终结点

HttpClient client = null;

if (!string.IsNullOrEmpty(m_EnterpriseId))
{
    ProtectionPolicyManager.GetForCurrentView().Identity = identity;

    using (ThreadNetworkContext threadNetworkContext =
            ProtectionPolicyManager.CreateCurrentThreadNetworkContext(identity))
    {
        client = new HttpClient();
        HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Put, resourceURI);
        message.Content = new HttpStreamContent(dataToWrite);

        HttpResponseMessage response = await client.SendRequestAsync(message);

        if (response.StatusCode == HttpStatusCode.Ok)
            return true;
        else
            return false;
    }
}
else
{
    return false;
}

API
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity
ProtectionPolicyManager.CreateCurrentThreadNetworkContext

通过共享协定保护应用共享的数据

如果希望用户共享应用中的内容,则必须实现共享协定并处理 DataTransferManager.DataRequested 事件。

在事件处理程序中,在数据包中设置企业标识上下文。

private void OnShareSourceOperation(object sender, RoutedEventArgs e)
{
    // Register the current page as a share source (or you could do this earlier in your app).
    DataTransferManager.GetForCurrentView().DataRequested += OnDataRequested;
    DataTransferManager.ShowShareUI();
}

private void OnDataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
    if (!string.IsNullOrEmpty(this.shareSourceContent))
    {
        var protectionPolicyManager = ProtectionPolicyManager.GetForCurrentView();
        DataPackage requestData = args.Request.Data;
        requestData.Properties.Title = this.shareSourceTitle;
        requestData.Properties.EnterpriseId = protectionPolicyManager.Identity;
        requestData.SetText(this.shareSourceContent);
    }
}

API
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity

保护复制到其他位置的文件

private async void CopyProtectionFromOneFileToAnother
    (StorageFile sourceStorageFile, StorageFile targetStorageFile)
{
    bool copyResult = await
        FileProtectionManager.CopyProtectionAsync(sourceStorageFile, targetStorageFile);

    if (!copyResult)
    {
        // Copying failed. To diagnose, you could check the file's status.
        // (call FileProtectionManager.GetProtectionInfoAsync and
        // check FileProtectionInfo.Status).
    }
}

API
FileProtectionManager.CopyProtectionAsync

锁定设备屏幕时保护企业数据

锁定设备时,删除内存中的所有敏感数据。 当用户解锁设备时,你的应用可以安全地添加回该数据。

处理 ProtectionPolicyManager.ProtectedAccessSuspending 事件,使应用知道何时锁定屏幕。 仅当管理员在锁定策略下配置安全数据保护时,才会引发此事件。 Windows 会暂时删除设备上预配的数据保护密钥。 Windows 会删除这些密钥,以确保设备被锁定且可能不拥有其所有者时对加密数据没有未经授权的访问。

处理 ProtectionPolicyManager.ProtectedAccessResumed 事件,使应用知道何时解锁屏幕。 无论管理员是否在锁定策略下配置安全数据保护,都会引发此事件。

锁定屏幕时删除内存中的敏感数据

保护敏感数据,并关闭应用在受保护文件上打开的任何文件流,以帮助确保系统不会在内存中缓存任何敏感数据。

本示例将文本块中的内容保存到加密缓冲区,并从该文本块中删除内容。

private async void ProtectionPolicyManager_ProtectedAccessSuspending(object sender, ProtectedAccessSuspendingEventArgs e)
{
    Deferral deferral = e.GetDeferral();

    if (ProtectionPolicyManager.GetForCurrentView().Identity != String.Empty)
    {
        IBuffer documentBodyBuffer = CryptographicBuffer.ConvertStringToBinary
           (documentTextBlock.Text, BinaryStringEncoding.Utf8);

        BufferProtectUnprotectResult result = await DataProtectionManager.ProtectAsync
            (documentBodyBuffer, ProtectionPolicyManager.GetForCurrentView().Identity);

        if (result.ProtectionInfo.Status == DataProtectionStatus.Protected)
        {
            this.protectedDocumentBuffer = result.Buffer;
            documentTextBlock.Text = null;
        }
    }

    // Close any open streams that you are actively working with
    // to make sure that we have no unprotected content in memory.

    // Optionally, code goes here to use e.Deadline to determine whether we have more
    // than 15 seconds left before the suspension deadline. If we do then process any
    // messages queued up for sending while we are still able to access them.

    deferral.Complete();
}

API
ProtectionPolicyManager.ProtectedAccessSuspending
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity
DataProtectionManager.ProtectAsync
BufferProtectUnprotectResult.buffer
ProtectedAccessSuspendingEventArgs.GetDeferral
Deferral.Complete

解锁设备时添加回敏感数据

当设备解锁且密钥再次在设备上可用时,将引发 ProtectionPolicyManager.ProtectedAccessResumed

如果管理员未在锁定策略下配置安全数据保护,ProtectedAccessResumedEventArgs.Identities 是空集合。

此示例执行上一示例的反向操作。 它会解密缓冲区,将该缓冲区中的信息添加回文本框,然后释放缓冲区。

private async void ProtectionPolicyManager_ProtectedAccessResumed(object sender, ProtectedAccessResumedEventArgs e)
{
    if (ProtectionPolicyManager.GetForCurrentView().Identity != String.Empty)
    {
        BufferProtectUnprotectResult result = await DataProtectionManager.UnprotectAsync
            (this.protectedDocumentBuffer);

        if (result.ProtectionInfo.Status == DataProtectionStatus.Unprotected)
        {
            // Restore the unprotected version.
            documentTextBlock.Text = CryptographicBuffer.ConvertBinaryToString
                (BinaryStringEncoding.Utf8, result.Buffer);
            this.protectedDocumentBuffer = null;
        }
    }

}

API
ProtectionPolicyManager.ProtectedAccessResumed
ProtectionPolicyManager.GetForCurrentView
ProtectionPolicyManager.Identity
DataProtectionManager.UnprotectAsync
BufferProtectUnprotectResult.Status

撤销受保护内容时处理企业数据

如果希望应用在从 MDM 取消注册设备或策略管理员显式撤销对企业数据的访问权限时收到通知,请处理 ProtectionPolicyManager_ProtectedContentRevoked 事件。

此示例确定电子邮件应用的企业邮箱中的数据是否已撤销。

private string mailIdentity = "contoso.com";

void MailAppSetup()
{
    ProtectionPolicyManager.ProtectedContentRevoked += ProtectionPolicyManager_ProtectedContentRevoked;
    // Code goes here to set up mailbox for 'mailIdentity'.
}

private void ProtectionPolicyManager_ProtectedContentRevoked(object sender, ProtectedContentRevokedEventArgs e)
{
    if (!new System.Collections.Generic.List<string>(e.Identities).Contains
        (this.mailIdentity))
    {
        // This event is not for our identity.
        return;
    }

    // Code goes here to delete any metadata associated with 'mailIdentity'.
}

API
ProtectionPolicyManager_ProtectedContentRevoked

Windows 信息保护 (WIP) 示例