見出し画像

Officeファイル保護を自分で実装してみる Part1.AADアプリケーション登録、トークンキャッシュ実装編

はじめに

こんにちは、SHIFTの秋吉です。

この記事では、MIP (Microsoft Information Protection) を利用した、Officeファイルの保護を行うプログラムの実装方法を軸に、付随する MSAL (Microsoft Authentication Library) の使用方法や、AAD (Azure Active Directory) の利用方法(AADへのプログラムの登録方法)について説明します。

この Part1 では、まずは、独自プログラムを AADに登録して、各種 API を使用するまでを説明します。
これは、MIP 限定ではなく、「AAD に登録して API を使用するアプリケーション プログラム」全般に共通の汎用的なものになります。
さらにトークンをローカルファイルにキャッシュする方法についても説明します。 これも、MSALでトークン認証を行うときの汎用的な情報になります。

さて、本文に入る前に、この記事で取り扱う機能や用語について少し補足します。ご存じの方は、飛ばして「この記事でわかること」へお進みください。

MIP (Microsoft Information Protection)

これは、2000年代に入って、マイクロソフト Office で実装された、「アクセス制御の仕組み」のことです。当初、この「仕組み」は、Office 文書(Word、Excel、PowerPoint) だけが対応していましたが、その後 API が公開され、Office文書以外のファイル形式でも利用できるようになりました。

ここで、「アクセス制御」というのは、文書を作った人が、「Aさんは、中身を見てもいいが、ほかの人は見ることができない」というような、対象者を明示・限定して、ファイルの中身を見せることができる機能です。また、 「見せ方」(できること)も、細かく制御でき、例えば、パソコン上で中身を表示することはできても、印刷や画面キャプチャを取ることはできい、などのように制限することができます。

ここで一つのポイントとなるのは、表示や印刷などを、権限を与えられてない人では「出来なく」するためには、そもそも中身を見ること自体を出来なくする必要があることです。このため、これらの権限制御を行う場合は、暗号化とセットで使用されることになります。
権限を持った人だけが暗号化ファイルを復号して中身を見ることができるし、そのあとの印刷などの各種操作も出来るようになるわけです。

ファイル内容の参照はできるが更新はできないとか、印刷はできないなどの、細かい制御は、それを実装してくれるプログラムに依存します。
暗号化されたものでも、暗号を解除したあと一般的な汎用のエディターに渡してしまえば、ファイル作成者の意図にはお構いなしに、なんでもできることになります。

このため、現在「細かい権限制御」ができる一般に市販されているプログラムは、マイクロソフト Office と、Adobe Acrobat Reader (PDF) のみとなっています。これら以外の、例えば、jpeg画像ファイルの汎用的な表示・編集プログラムは、暗号を解除して渡さなければ、読み込むこともできませんし、ひとたび暗号を解除して渡せば、作成者の意図とは関係なく何でもできることになります。つまりこれらのファイルは、編集や印刷などを含め、すべてのことができるか、暗号を解除できず全く何もできないかのいずれかの制御しかできないことになります。
それでも、ファイルを暗号化して、特定の人にだけ中身を見せることができる暗号化書庫ファイルとしては使えることになります。また、その時に暗号のパスワードを持ちまわる必要がないのも特徴です。

MSAL (Microsoft Authentication Library)

これらの手続きをプログラムで行うための API (Application Programming Interface) はマイクロソフトから提供されています。
これを Microsoft Authentication Library、略して MSAL と呼んでいます。
プログラマーは、この API を利用して、MIP の処理のできるプログラムを作るわけです。

AAD (Azure Active Directory)

暗号の解除のためには、暗号解除のための「キー」(鍵)が必要となりますが、これの取得は AAD から行います。

例えば、MIPで保護されたWordのドキュメントには、「Aさんは、中身を見てもいい」と書かれているので(もちろんこの権限を記述した部分も勝手に書き換えられないように保護されています)、Aさんは、自分がAさんであることを AAD で認証して、正しく認証されると「キー」を取得出来て中身を見ることができるわけです。

また、この処理も出所不明のプログラムで好き勝手にやられては、「キー」を抜き取られたり、文書内容を盗聴したりされてしいまいますので、実行するプログラム自体も「身分保証」される必要ががあります。このために、あらかじめこの「プログラム」を AAD に登録しておきますし、そのプログラムが出来ること(やってもいいこと)も、AADであらかじめ宣言・承認しておきます。なので、後述の「プログラムの(アプリケーションの)登録」を行うことが必要になるわけです。

この記事でわかること

  • 認証を行うための AAD へのアプリケーション登録手順

  • MIP API を使うための API アクセス許可の設定手順

  • トークン キャッシュの実装方法と注意点

以下、Part2 へ続きます。

  • MIP SDK を使用して、「機密ラベル」を使用しない、独自保護(暗号化)を行う方法

  • 特定のユーザー、グループ指定でなく、ある組織に属する(=ある AAD テナントに属する)ユーザー全員("everyone" イメージ) に権限を付与する方法

前提環境

  • アプリケーション プログラムを AAD に登録するためには、
    「アプリケーション管理者」以上の権限が必要です。
    (厳密には登録のみには権限は不要ですが、プログラムの権限を承認するために アプリケーション管理者 権限が必要です。)

  • MIPを使用するためには、次のサブスクリプションのいずれかにサインアップしている必要があります。

    • Office 365 Enterprise E3 または E5

    • Enterprise Mobility and Security E3 または E5

    • Azure Information Protection Premium P1 または P2

    • Microsoft 365 E3、E5、または F1

なお、Microsoft 365 デベロッパー センター で、無料の開発者向けインスタント サンドボックス (90 日間無料の Microsoft 365 E5 開発者サブスクリプション) を取得することができます。

AAD へのアプリケーション登録

ここでは、OAuth2トークン認証を行うためのプログラムのAADへの登録のポイントを記載します。 プログラムを作ってから登録することも、登録してからプログラムを作ることも可能です。
(つまり、まだプログラム実体がなくても登録は可能です。)
既定では、プログラムの登録に管理者権限は必要ありません。

  1. Azureポータルにサインインし、ナビゲーション パネルで、
    Azure Active Directory を選択します。
    アプリの登録 画面に進み、+ 新規登録 に進みます。
    以下の内容で、登録 します。

2.自動的に登録されたアプリの概要ページに遷移します。
アプリケーション (クライアント) IDディレクトリ (テナント) ID をメモしておきます。(あとでプログラム実装で必要です。)

3.概要ページのまま リダイレクト URI を追加します。
リダイレクト URI を追加する から、+ プラットフォーム に進み、
モバイルアプリケーションとデスクトップ アプリケーション を選び、
リダイレクト URLとして選択肢から https://login.microsoftonline.com/common/oauth2/nativeclient を選んで 構成 します。

なお、Webアプリの場合には、実際に認証後にここで指定した URL にリダイレクトされますが、デスクトップ アプリの場合には、遷移しませんので、実は何でもいいことになります。ただし、プログラムから認証時に渡されるURL文字列と一致していないと エラー になりますので、プログラムの実装とは合わせておく必要があります。

ちなみに、前述のURLを指定すると、プログラムの中で「デフォルトで動け!」(.WithDefaultRedirectUri()) という実装をすることで、URL文字列をプログラム中で明示しなくていいようになります。

API のアクセス許可

作ったプログラムが必要な API を使用できるよう権限を付与します。
アクセス許可設定そのものには管理者権限は必要ありませんが、本項最後の「TENANT-NAME に管理者の同意を与えます」のみ アプリケーション管理者以上の権限が必要で、これがないと、付与したアクセス許可が有効となりません。

  1. API のアクセス許可 に進みます。

  2. + アクセス許可の追加 を押して Azure Rights Management Services
    を選択します。
    委任されたアクセス許可 を選び、user_impersonation にチェックを入れ、アクセス許可の追加を押します。

  3. さらに、+ アクセス許可の追加 を押して 所属する組織で使用している API タブを選択し、Microsoft Information Protection Sync Service
    選択します。(表示されていない場合、項目が多くて見つけにくい場合
    には、検索窓に[Microsoft」などを入力して絞りこみます。)
    委任されたアクセス許可 を選択して、UnifiedPolicy.User.Read にチェックを入れ、+ アクセス許可の追加 を押します。

  4. TENANT-NAME に管理者の同意を与えます を押します。

プログラム コードの実装

ここでは、マイクロソフト社より提供されている サンプル コードをベースとしてまず利用し、若干の修正を施すことで、トークンキャッシュや、ローレベル ファイル保護の実装について説明したいと思います。
まず、こちらより、ソースコードをダウンロードして、ビルドします。App.config ファイルの次の項目を実環境に合わせて更新します。

上記、App.config の設定変更を行ったら、ソリューションをリビルドして実際に動かしてみましょう。

プログラムの改善

サンプル プログラム、ちゃんと、動作しましたか? 多分動作したと思いますが、動作した場合、

  1. AADへのサインイン

  2. 組織であらかじめ定義されている MIP のラベルの表示と選択

  3. 保護をかける対象ファイル(元ファイル)のパスの入力

  4. 保護をかけたあとの出力ファイルのパスの入力

  5. 保護付きファイルが出力される

という動作(ユーザー入力)になったかと思います。 動作するには動作しますが、2点ほど改善ポイントがあるかと思います。

  1. AAD へのサインインが複数回求められ煩わしい

  2. 独自の保護設定ができない

1 は、トークンキャッシュの仕組みが実装されていないため、MIP APIの初期化や、実際のファイル保護、ラベルの参照などのたびにサインインが求められることになります。
2 は、ラベルはあらかじめ定義されている必要がありますし、独自のラベルを定義するためには、ラベル作成関連の権限が必要となります。
そこで、ラベルを使用せずファイル保護を実現する手段を説明したいと思いますが、記事が長くなってしまいましたので、2 については、
"Part2 MIP ローレベルファイル保護実装編" で、説明することにします。

トークンキャッシュの実装

AAD に何度もサインインする必要がないように(ユーザーID、パスワードを何度も求められないように)、認証情報(認証で取得したトークン)をキャッシュするようにプログラムを書き換えます。
AuthDelegateImplementation.cs の AcquireTokenAsync() を次のように更新します。

using Microsoft.Identity.Client.Extensions.Msal;                

        private static readonly string CacheFile = ConfigurationManager.AppSettings["ida:CacheFile"];
        private static readonly string CacheDir = ConfigurationManager.AppSettings["ida:CacheDir"];
        private bool bForceRefreshTokenCache = "true".Equals(ConfigurationManager.AppSettings["ida:ForceRefreshToken"]);

        public async Task<AuthenticationResult> AcquireTokenAsync(string authority, string resource, string claims, bool isMultiTenantApp = true)
        {
            AuthenticationResult result = null;

            // Create an auth context using the provided authority and token cache
            if (isMultitenantApp)
                _app = PublicClientApplicationBuilder.Create(appInfo.ApplicationId)
                    .WithAuthority(authority)
                    .WithDefaultRedirectUri()
                    .Build();
            else
            {
                if (authority.ToLower().Contains("common"))
                {
                    var authorityUri = new Uri(authority);
                    authority = String.Format("https://{0}/{1}", authorityUri.Host, tenant);
                }
                _app = PublicClientApplicationBuilder.Create(appInfo.ApplicationId)
                    .WithAuthority(authority)
                    .WithDefaultRedirectUri()
                    .Build();

            }

            // MSAL.NET でのトークン キャッシュのシリアル化
            var storageProperties = new StorageCreationPropertiesBuilder(CacheFile, CacheDir)
                                        .Build();
            Task<MsalCacheHelper> chTask = MsalCacheHelper.CreateAsync(storageProperties);
            var cacheHelper = chTask.ConfigureAwait(false).GetAwaiter().GetResult();
            cacheHelper.RegisterCache(_app.UserTokenCache);

            var accounts = (_app.GetAccountsAsync()).GetAwaiter().GetResult();

            if (bForceRefreshTokenCache)
            {
                // Refresh 1st time only.
                bForceRefreshTokenCache = false;

                // clear the cache
                while (accounts.Any())
                {
                    await _app.RemoveAsync(accounts.First());
                    accounts = (_app.GetAccountsAsync()).GetAwaiter().GetResult();
                }
            }

            // Append .default to the resource passed in to AcquireToken().
            string[] scopes = new string[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };

            try
            {
                result = await _app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                    .ExecuteAsync();
            }

            catch (MsalUiRequiredException)
            {
                result = _app.AcquireTokenInteractive(scopes)
                    .WithAccount(accounts.FirstOrDefault())
                    .WithPrompt(Prompt.SelectAccount)
                    .ExecuteAsync()
                    .ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
            }

            // Return the token. The token is sent to the resource.                           
            return result;
        }

さらに、設定項目を App.config ファイルに追加します。

上記 App.config の設定変更を行ったら(ida:ForceRefreshToken は ひとまず false で)、ソリューションをリビルドして実際に動かしてみましょう。
どうでしょうか?
AAD に対するサインインが一度しか求められなくなったと思います。
それどころか、トークンキャッシュを実装すると、次の実行時もそれ以降もう二度とサインインは求められなくなり、ユーザーの切り替えもできなくなります。このため、上記コードでは、キャッシュクリアのロジックもあらかじめ組み込んでおいて、別のユーザーでのサインインを可能にしています。
キャッシュクリアのコードがない場合は、AAD でパスワードを変更したときぐらいしか、再サインインは求められなくなります。
(トークンには必ず有効期限がありますが、MSAL はこのトークンの期限切れもユーザーの手を煩わせることなく自動で更新します。)

その他補足説明

トークン キャッシュの内容をプレーン テキストで保存させる方法

暗号化されていないトークンをプレーン テキストで保存させることができます。(デバッグ目的など。)
StorageCreationPropertiesBuilder を WithUnprotectedFile オプション付きで作成します。
詳細は、Plain-text fallback mode を参照してください。

MipContext について

今回、マイクロソフト社提供のサンプル コードをベースに説明していますので、MIP の動作の基本的な部分の説明は割愛していますが、MipContext について補足しておきます。 MipContext は、SDK の最上位オブジェクトで、アプリケーションまたはサービスの一部として作成される可能性がある、すべてのプロファイル間の状態を管理するためのものです。
注意点として、MipContext は、プロセスごとに 1つであり、複数作成すると、予期しない動作が発生する可能性があります。MipContext はアプリの起動時に作成され、アプリケーションの有効期間中、同じ MipContext を使用します。
常駐プログラム(Windowsサービスプログラムなど)で使用するときは、注意してください。

MIP SDK キャッシュ ストレージについて

今回、マイクロソフト社提供のサンプル コードをベースに説明していますので、MIP の動作の基本的な部分の説明は割愛していますが、SDK キャッシュ ストレージについても補足しておきます。
MIP SDK は、SDK キャッシュ ストレージを保持するための SQLite3 データベースを実装しています。 このデータベースを保持する手段として、メモリ、ディスク、暗号化ディスクが選べます。
今回ベースにしたサンプルコードでは、暗号化ディスクを指定しています(Action.cs の CreateFileProfile() 参照)が、Webサーバー上で動作するプログラムの場合には、プロセスの権限的にここで例外となってしまいますので、ほかのキャッシュストレージを指定します。

参考


《この公式ブロガーの関連記事》


執筆者プロフィール:秋吉 利彰
"IT" と呼ばれる前から、この業界に身を置き早やうん十年。
PC 上で動作するニッチなプログラミングから
クラウド (Azure) でのアーキテクチャまで、これまでの経験で得られた、Tipsや "てみた" 的な記事を書いていこうと思います。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら 
https://recruit.shiftinc.jp/career/


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!