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

启发式应用可区分公司和个人数据,并且知道应基于管理员定义的 Windows 信息保护 (WIP) 策略保护哪些数据。

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

注意 本指南有助于启发你创建 UWP 应用。 如果你想要启发 C++ Windows 桌面应用,请参阅 Windows 信息保护 (WIP) 开发人员指南 (C++)

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

可以在此处找到完整示例。

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

首先,收集所需内容

你将需要以下内容:

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

  • 运行Windows 10版本 1607 或更高版本的开发计算机。 这可能是你的测试 VM,前提是你已在其上安装了 Visual Studio。

设置你的开发环境

你将执行以下操作:

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

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

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

创建保护策略

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

有关如何使用此工具的更一般指南,请参阅应用下载页面上的版本说明部分。

设置 Visual Studio 项目

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

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

    添加 UWP 扩展

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

       <rescap:Capability Name="enterpriseDataPolicy"/>
    

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

  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.
}

FileProtectionStatusProtected 表示文件受保护,并且你的应用可以打开它,因为你的应用在策略的允许列表中。

FileProtectionStatusUnProtected 表示文件不受保护,即使你的应用不在策略的允许列表中,你的应用仍然可以打开该文件。

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 服务器将通信重定向到资源的更新版本。

若要处理此情况,请发出请求,直到请求的响应状态的值为 OK

然后使用该响应的 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) 示例