WIP(Windows Information Protection) 개발자 가이드

지원 앱은 회사 및 개인 데이터를 구분하며 관리자가 정의한 WIP(Windows Information Protection) 정책에 따라 보호할 항목을 알고 있습니다.

이 가이드에서는 이를 구축하는 방법에 대해 설명합니다. 완료되면 정책 관리자는 앱을 신뢰하여 조직의 데이터를 사용할 수 있습니다. 또한 직원은 조직의 MDM(모바일 디바이스 관리)에서 등록을 취소하거나 조직에서 완전히 퇴사한 경우에도 자신의 개인 데이터가 디바이스에 그대로 유지되기를 바랍니다.

참고 이 가이드는 UWP 앱을 지원하는 데 도움이 됩니다. C++ Windows 데스크톱 앱을 지원하려는 경우 WIP(Windows Information Protection) 개발자 가이드(C++)를 참조하세요.

WIP 및 지원 앱에 대한 자세한 내용은 WIP(Windows Information Protection)에서 확인할 수 있습니다.

전체 샘플은 여기에서 찾을 수 있습니다.

각 작업을 진행할 준비가 되었다면 시작해 보겠습니다.

먼저 필요한 항목을 수집합니다.

다음과 같은 항목이 필요합니다.

  • Windows 10, 버전 1607 이상을 실행하는 테스트 VM(가상 머신)입니다. 이 테스트 VM에 대해 앱을 디버그합니다.

  • Windows 10, 버전 1607 이상을 실행하는 개발 컴퓨터가 있어야 합니다. Visual Studio가 설치된 경우 테스트 VM일 수 있습니다.

개발 환경 설정

수행할 단계:

테스트 VM에 WIP 설정 개발자 도우미 설치

이 도구를 사용하여 테스트 VM에서 Windows Information Protection 정책을 설정합니다.

WIP 설치 개발자 도우미에서 도구를 다운로드합니다.

보호 정책 만들기

WIP 설치 개발자 도우미의 각 섹션에 정보를 추가하여 정책을 정의합니다. 사용 방법에 대해 자세히 알아보려면 설정 옆에 있는 도움말 아이콘을 선택합니다.

이 도구를 사용하는 방법에 대한 일반적인 지침은 앱 다운로드 페이지의 버전 참고 사항 섹션을 참조하세요.

Visual Studio 프로젝트 설정

  1. 개발 컴퓨터에서 프로젝트를 엽니다.

  2. UWP(유니버설 Windows 플랫폼)용 데스크톱 및 모바일 확장에 대한 참조를 추가합니다.

    Add UWP Extensions

  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에서 실행되는지 확인합니다.

원격 PC 지침을 참조하세요.

이러한 네임스페이스를 코드 파일에 추가합니다

이러한 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 Information Protection에서 권한을 앱에 부여합니다.

이 섹션의 내용:

파일에서 데이터 읽기

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

FileProtectionStatus 값이 Protected이면 파일이 보호되고 앱이 정책의 허용 목록에 있으므로 앱에서 파일을 열 수 있음을 의미합니다.

FileProtectionStatus 값이 UnProtected이면 파일이 보호되지 않고 앱이 정책의 허용 목록에 없어도 읽을 수 있음을 의미합니다.

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단계: 네트워크 엔드포인트의 ID 가져오기

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단계: 보호된 스레드 컨텍스트 만들기

엔드포인트가 정책에 의해 관리되는 경우 보호된 스레드 컨텍스트를 만듭니다. 이렇게 하면 동일한 스레드에서 ID에 대해 만드는 모든 네트워크 연결에 태그를 지정합니다.

또한 해당 정책에 의해 관리되는 엔터프라이즈 네트워크 리소스에 대한 액세스를 제공합니다.

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 개체를 반환합니다. 앱이 개인 및 엔터프라이즈 리소스를 모두 처리하는 경우 모든 ID에 대해 ProtectionPolicyManager.CreateCurrentThreadNetworkContext를 호출합니다. 리소스를 받은 후 ThreadNetworkContext를 삭제하여 현재 스레드에서 ID 태그를 지웁니다.

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

페이지 리디렉션 처리

경우에 따라 웹 서버는 트래픽을 더 최신 버전의 리소스로 리디렉션합니다.

이를 처리하려면 요청의 응답 상태에 OK 값이 있을 때까지 요청을 수행합니다.

그런 다음, 해당 응답의 URI를 사용하여 엔드포인트의 ID를 가져옵니다. 이를 수행하는 한 가지 방법은 다음과 같습니다.

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단계: 앱이 엔터프라이즈 파일을 만들 수 있는지 확인

ID 문자열이 정책에 의해 관리되고 앱이 해당 정책의 허용 목록에 있는 경우 앱에서 엔터프라이즈 파일을 만들 수 있습니다.

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

API
ProtectionPolicyManager.IsIdentityManaged

2단계: 파일을 만들고 ID로 보호

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단계: 엔터프라이즈 파일을 만들 수 있는지 확인

사용 중인 ID가 정책에 의해 관리되고 앱이 해당 정책의 허용 목록에 있는 경우 엔터프라이즈 파일을 만들 수 있습니다.

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

API
ProtectionPolicyManager.IsIdentityManaged

2단계: 파일을 만들고 ID로 보호

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단계: 네트워크 엔드포인트의 ID 가져오기

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 이벤트를 처리해야 합니다.

이벤트 처리기에서 데이터 패키지의 엔터프라이즈 ID 컨텍스트를 설정합니다.

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 이벤트를 처리합니다. 이 이벤트는 관리자가 잠금 정책에서 보안 데이터 보호를 구성하는지 여부에 관계없이 발생합니다.

화면이 잠겨 있을 때 메모리의 중요한 데이터를 제거합니다

중요한 데이터를 보호하고 보호된 파일에서 앱에서 연 파일 스트림을 닫아 시스템이 메모리의 중요한 데이터를 캐시하지 않도록 합니다.

다음은 textblock의 콘텐츠를 암호화된 버퍼에 저장하고 해당 textblock에서 콘텐츠를 제거하는 예제입니다.

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

WIP(Windows Information Protection) 샘플