本 ガイドの 内 容 は 執 筆 時 点 のものです サンプルコードを 使 用 する 場 合 はこの 点 にあらかじめご 注 意 ください JSSEC ならびに 執 筆 関 係 者 は このガイド 文 書 に 関 するいかなる 責 任 も 負 うものではありません 全 ては 自 己 責 任 にてご

Size: px
Start display at page:

Download "本 ガイドの 内 容 は 執 筆 時 点 のものです サンプルコードを 使 用 する 場 合 はこの 点 にあらかじめご 注 意 ください JSSEC ならびに 執 筆 関 係 者 は このガイド 文 書 に 関 するいかなる 責 任 も 負 うものではありません 全 ては 自 己 責 任 にてご"

Transcription

1 Android アプリのセキュア 設 計 セキュアコーディングガイド 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 (JSSEC) セキュアコーディンググループ 文 書 管 理 番 号 : JSSEC-TECA-SC-GD B

2 本 ガイドの 内 容 は 執 筆 時 点 のものです サンプルコードを 使 用 する 場 合 はこの 点 にあらかじめご 注 意 ください JSSEC ならびに 執 筆 関 係 者 は このガイド 文 書 に 関 するいかなる 責 任 も 負 うものではありません 全 ては 自 己 責 任 にてご 活 用 ください Android は Google, Inc.の 商 標 または 登 録 商 標 です また 本 文 書 に 登 場 する 会 社 名 製 品 名 サービス 名 は 一 般 に 各 社 の 登 録 商 標 または 商 標 です 本 文 中 では TM マークは 明 記 していません この 文 書 の 内 容 の 一 部 は Google, Inc.が 作 成 提 供 しているコンテンツをベースに 複 製 したもので クリエイティブ コモンズの 表 示 3.0 ライセンスに 記 載 の 条 件 に 従 って 使 用 しています

3 Android アプリのセキュア 設 計 セキュアコーディングガイド ベータ 版 2016 年 9 月 1 日 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 セキュアコーディンググループ 目 次 Android アプリのセキュア 設 計 セキュアコーディングガイド はじめに スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 常 にベータ 版 でタイムリーなフィードバックを 本 文 書 の 利 用 許 諾 年 2 月 1 日 版 からの 訂 正 記 事 について ガイド 文 書 の 構 成 開 発 者 コンテキスト サンプルコード ルールブック アドバンスト ガイド 文 書 のスコープ Android セキュアコーディング 関 連 書 籍 の 紹 介 サンプルコードの Android Studio への 取 り 込 み 手 順 セキュア 設 計 セキュアコーディングの 基 礎 知 識 Android アプリのセキュリティ 入 力 データの 安 全 性 を 確 認 する 安 全 にテクノロジーを 活 用 する Activity を 作 る 利 用 する Broadcast を 受 信 する 送 信 する Content Provider を 作 る 利 用 する Service を 作 る 利 用 する SQLite を 使 う ファイルを 扱 う Browsable Intent を 利 用 する LogCat にログ 出 力 する WebView を 使 う Notification を 使 用 する セキュリティ 機 能 の 使 い 方 パスワード 入 力 画 面 を 作 る Permission と Protection Level Account Manager に 独 自 アカウントを 追 加 する HTTPS で 通 信 する プライバシー 情 報 を 扱 う 暗 号 技 術 を 利 用 する All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 1

4 5.7. 指 紋 認 証 機 能 を 利 用 する 難 しい 問 題 Clipboard から 情 報 漏 洩 する 危 険 性 All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ

5 更 新 履 歴 Android アプリのセキュア 設 計 セキュアコーディングガイド 日 付 改 訂 内 容 初 版 下 記 の 構 成 内 容 を 見 直 し 拡 充 致 しました 4.1 Activity を 作 る 利 用 する 4.2 Broadcast を 受 信 する 送 信 する 4.3 Content Provider を 作 る 利 用 する 4.4 Service を 作 る 利 用 する 5.2 Permission と Protection Level 下 記 の 新 しい 記 事 を 追 加 致 しました 2.5 サンプルコードの Android Studio への 取 り 込 み 手 順 3.1 Android アプリのセキュリティ 4.7 Browsable Intent を 利 用 する 5.3 Account Manager に 独 自 アカウントを 追 加 する 6.1 Clipboard から 情 報 漏 洩 する 危 険 性 下 記 の 記 事 の 内 容 を 見 直 し 書 き 直 しました 5.3 Account Manager に 独 自 アカウントを 追 加 する 下 記 の 新 しい 記 事 を 追 加 致 しました 4.8 LogCat にログ 出 力 する 5.4 HTTPS で 通 信 する 4.9 WebView を 使 う 下 記 の 新 しい 記 事 を 追 加 致 しました 5.5 プライバシー 情 報 を 扱 う 5.6 暗 号 技 術 を 利 用 する 下 記 の 方 針 で 本 書 全 体 の 内 容 を 見 直 し 書 き 直 しました 開 発 環 境 の 変 更 (Eclipse -> Android Studio) Android 最 新 版 Lollipop への 対 応 対 応 する API Level の 見 直 し(8 以 降 -> 15 以 降 ) 下 記 の 新 しい 記 事 を 追 加 致 しました 4.10 Notification を 使 用 する 5.7 指 紋 認 証 機 能 を 利 用 する 下 記 の 構 成 内 容 を 見 直 し 拡 充 致 しました 5.2 Permission と Protection Level 下 記 の 構 成 内 容 を 見 直 し 拡 充 致 しました 2.5 サンプルコードの Android Studio への 取 り 込 み 手 順 5.4 HTTPS で 通 信 する 5.6 暗 号 技 術 を 利 用 する 新 版 の 公 開 にあたり 皆 様 から 頂 いたご 意 見 コメントを 元 に 本 ガイドの 内 容 を 更 新 しました All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 3

6 制 作 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 技 術 部 会 アプリケーションワーキンググループ セキュアコーディンググループ リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 荒 木 成 治 Android セキュリティ 部 大 内 智 美 福 本 郁 哉 武 井 滋 紀 山 地 秀 典 安 藤 彰 大 谷 三 岳 小 木 曽 純 奥 山 謙 島 野 英 司 谷 口 岳 満 園 大 祐 株 式 会 社 SRA 株 式 会 社 SRA エヌ ティ ティ ソフトウェア 株 式 会 社 ソニー 株 式 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 タオソフトウェア 株 式 会 社 タオソフトウェア 株 式 会 社 日 本 システム 開 発 株 式 会 社 ( 執 筆 関 係 者 社 名 五 十 音 順 ) 4 All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ

7 2016 年 2 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 安 達 正 臣 福 本 郁 哉 星 本 英 史 武 井 滋 紀 大 園 通 安 藤 彰 伊 藤 妙 子 大 谷 三 岳 Android セキュリティ 部 株 式 会 社 SRA エヌ ティ ティ ソフトウェア 株 式 会 社 シスコシステムズ 合 同 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 奥 山 謙 楫 節 子 西 村 宗 晃 山 地 秀 典 笠 原 正 弘 島 野 英 司 谷 口 岳 ソニーモバイルコミュニケーションズ 株 式 会 社 ソフトバンクモバイル 株 式 会 社 タオソフトウェア 株 式 会 社 ( 執 筆 関 係 者 社 名 五 十 音 順 ) All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 5

8 2015 年 6 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 星 本 英 史 武 井 滋 紀 大 園 通 安 藤 彰 奥 山 謙 西 村 宗 晃 笠 原 正 弘 島 野 英 司 谷 口 岳 八 津 川 直 伸 谷 田 部 茂 今 西 杏 丞 河 原 豊 近 藤 昭 雄 株 式 会 社 SRA エヌ ティ ティ ソフトウェア 株 式 会 社 シスコシステムズ 合 同 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 ソフトバンクモバイル 株 式 会 社 タオソフトウェア 株 式 会 社 日 本 ユニシス 株 式 会 社 株 式 会 社 フォーマルハウト テクノ ソリューションズ 株 式 会 社 ブリリアントサービス 志 村 直 彦 新 谷 正 人 原 昇 平 藤 澤 智 之 藤 田 竜 史 三 竹 一 馬 ( 執 筆 関 係 者 社 名 五 十 音 順 ) 6 All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ

9 2014 年 7 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 熊 澤 努 星 本 英 史 武 井 滋 紀 竹 森 敬 祐 磯 原 隆 将 大 園 通 安 藤 彰 伊 藤 妙 子 奥 山 謙 楫 節 子 株 式 会 社 SRA エヌ ティ ティ ソフトウェア 株 式 会 社 KDDI 株 式 会 社 シスコシステムズ 合 同 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 片 岡 良 典 笠 原 正 弘 島 野 英 司 谷 口 岳 佐 藤 導 吉 八 津 川 直 伸 谷 田 部 茂 ソフトバンクモバイル 株 式 会 社 タオソフトウェア 株 式 会 社 東 京 システムハウス 株 式 会 社 日 本 ユニシス 株 式 会 社 株 式 会 社 フォーマルハウト テクノ ソリューションズ ( 執 筆 関 係 者 社 名 五 十 音 順 ) All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 7

10 2013 年 4 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 安 達 正 臣 長 谷 川 智 之 安 部 勇 気 大 内 智 美 熊 澤 努 Android セキュリティ 部 株 式 会 社 SRA 澤 田 寿 実 畑 清 志 比 嘉 陽 一 福 井 悠 福 本 郁 哉 星 本 英 史 横 井 俊 吉 澤 孝 和 藤 原 健 武 井 滋 紀 竹 森 敬 祐 久 保 正 樹 熊 谷 裕 志 戸 田 洋 三 NRI セキュアテクノロジーズ 株 式 会 社 エヌ ティ ティ ソフトウェア 株 式 会 社 KDDI 株 式 会 社 一 般 社 団 法 人 JPCERT コーディネーションセンター (JPCERT/CC) 大 園 通 新 井 幹 也 坂 本 昌 彦 浅 野 徹 安 藤 彰 池 邉 亮 志 小 木 曽 純 シスコシステムズ 合 同 会 社 株 式 会 社 セキュアスカイ テクノロジー ソニーデジタルネットワークアプリケーションズ 株 式 会 社 奥 山 謙 片 岡 良 典 西 村 宗 晃 古 澤 浩 司 山 岡 研 二 谷 口 岳 八 津 川 直 伸 谷 田 部 茂 タオソフトウェア 株 式 会 社 日 本 ユニシス 株 式 会 社 株 式 会 社 フォーマルハウト テクノ ソリューションズ ( 執 筆 関 係 者 社 名 五 十 音 順 ) 8 All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ

11 2012 年 11 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 佐 藤 勝 彦 中 口 明 彦 大 内 智 美 大 平 直 之 熊 澤 努 Android セキュリティ 部 株 式 会 社 SRA 関 川 未 来 中 野 正 剛 比 嘉 陽 一 福 本 郁 哉 星 本 英 史 安 田 章 一 八 尋 唯 行 吉 澤 孝 和 武 井 滋 紀 竹 森 敬 祐 久 保 正 樹 熊 谷 裕 志 戸 田 洋 三 エヌ ティ ティ ソフトウェア 株 式 会 社 KDDI 株 式 会 社 一 般 社 団 法 人 JPCERT コーディネーションセンター (JPCERT/CC) 大 園 通 浅 野 徹 安 藤 彰 池 邉 亮 志 市 川 茂 シスコシステムズ 合 同 会 社 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 大 谷 三 岳 小 木 曽 純 奥 山 謙 片 岡 良 典 佐 藤 郁 恵 西 村 宗 晃 山 岡 一 夫 吉 川 岳 流 谷 口 岳 島 野 英 司 北 村 久 雄 山 川 隆 郎 石 原 正 樹 森 靖 晃 八 津 川 直 伸 谷 田 部 茂 藤 井 茂 樹 タオソフトウェア 株 式 会 社 一 般 社 団 法 人 日 本 オンラインゲーム 協 会 日 本 システム 開 発 株 式 会 社 日 本 ユニシス 株 式 会 社 株 式 会 社 フォーマルハウト テクノ ソリューションズ ユニアデックス 株 式 会 社 ( 執 筆 関 係 者 社 名 五 十 音 順 ) All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 9

12 2012 年 6 月 1 日 版 制 作 者 リーダー 松 並 勝 ソニーデジタルネットワークアプリケーションズ 株 式 会 社 メンバー 佐 藤 勝 彦 大 内 智 美 比 嘉 陽 一 星 本 英 史 武 井 滋 紀 千 田 雅 明 久 保 正 樹 熊 谷 裕 志 戸 田 洋 三 Android セキュリティ 部 株 式 会 社 SRA エヌ ティ ティ ソフトウェア 株 式 会 社 グリー 株 式 会 社 一 般 社 団 法 人 JPCERT コーディネーションセンター (JPCERT/CC) 大 園 通 谷 田 部 茂 田 口 陽 一 坂 本 昌 彦 安 藤 彰 市 川 茂 奥 山 謙 佐 藤 郁 恵 シスコシステムズ 合 同 会 社 株 式 会 社 システムハウス. アイエヌジー 株 式 会 社 セキュアスカイ テクノロジー ソニーデジタルネットワークアプリケーションズ 株 式 会 社 西 村 宗 晃 山 岡 一 夫 谷 口 岳 島 野 英 司 北 村 久 雄 佐 藤 導 吉 服 部 正 和 八 津 川 直 伸 谷 田 部 茂 藤 井 茂 樹 タオソフトウェア 株 式 会 社 東 京 システムハウス 株 式 会 社 トレンドマイクロ 株 式 会 社 日 本 ユニシス 株 式 会 社 株 式 会 社 フォーマルハウト テクノ ソリューションズ ユニアデックス 株 式 会 社 ( 執 筆 関 係 者 社 名 五 十 音 順 ) 10 All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ

13 1. はじめに 1.1. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 本 ガイドは Android アプリケーション 開 発 者 向 けのセキュア 設 計 セキュアコーディングのノウハウをまとめた Tips 集 です できるだけ 多 くの Android アプリケーション 開 発 者 に 活 用 していただきたく 思 い ここに 公 開 いたします 昨 今 スマートフォン 市 場 は 急 拡 大 しており さらにその 勢 いは 増 すばかりです スマートフォン 市 場 の 急 拡 大 は 多 種 多 彩 なアプリケーション 群 によってもたらされています 従 来 の 携 帯 電 話 ではセキュリティ 制 約 によって 利 用 できなか ったさまざまな 携 帯 電 話 の 重 要 な 機 能 がスマートフォンアプリケーションには 開 放 され 従 来 の 携 帯 電 話 では 実 現 で きなかった 多 種 多 彩 なアプリケーション 群 がスマートフォンの 魅 力 を 引 き 立 てています スマートフォンのアプリケーション 開 発 者 にはそれ 相 応 の 責 任 が 生 じています 従 来 の 携 帯 電 話 ではあらかじめ 課 せ られたセキュリティ 制 約 によって セキュリティについてあまり 意 識 せずに 開 発 したアプリケーションであっても 比 較 的 安 全 性 が 保 たれていました スマートフォンでは 前 述 のとおり 携 帯 電 話 の 重 要 な 機 能 がアプリケーション 開 発 者 に 開 放 されているため アプリケーション 開 発 者 がセキュリティを 意 識 して 設 計 コーディングをしなければ スマートフォ ン 利 用 者 の 個 人 情 報 が 漏 洩 したり 料 金 の 発 生 する 携 帯 電 話 機 能 をマルウェアに 悪 用 されたりといった 被 害 が 生 じ ます Android スマートフォンは iphone に 比 べると アプリケーション 開 発 者 のセキュリティへの 配 慮 がより 多 く 求 められま す iphone に 比 べ Android スマートフォンはアプリケーション 開 発 者 に 開 放 された 携 帯 電 話 機 能 が 多 く App Store に 比 べ Google Play( 旧 Android Market)は 無 審 査 でアプリケーション 公 開 ができるなど アプリケーションのセキ ュリティがほぼ 全 面 的 にアプリケーション 開 発 者 に 任 されているためです スマートフォン 市 場 の 急 拡 大 にともない 様 々な 分 野 のソフトウェア 技 術 者 が 一 気 にスマートフォンアプリケーション 開 発 市 場 に 流 れ 込 んできており スマートフォン 特 有 のセキュリティを 考 慮 したセキュア 設 計 セキュアコーディングのノ ウハウ 集 約 共 有 が 急 務 となっています このような 状 況 を 踏 まえ 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 はセキュアコーディンググループを 立 ち 上 げ Android アプリケーションのセキュア 設 計 セキュアコーディングのノウハウを 集 めて 公 開 することにいたしま した それがこのガイド 文 書 です 多 くの Android アプリケーション 開 発 者 にセキュア 設 計 セキュアコーディングのノ ウハウを 知 っていただき アプリケーション 開 発 に 活 かしていただくことで 市 場 にリリースされる 多 くの Android アプ リケーションのセキュリティを 高 めることを 狙 っています その 結 果 安 心 安 全 なスマートフォン 社 会 づくりに 貢 献 した いと 考 えています All rights reserved Japan Smartphone Security Association. スマートフォンを 安 心 して 利 用 出 来 る 社 会 へ 11

14 1.2. 常 にベータ 版 でタイムリーなフィードバックを 私 たち JSSEC セキュアコーディンググループはこのガイド 文 書 の 内 容 について できるだけ 間 違 いがないように 心 が けておりますが その 正 しさを 保 証 するものではありません 私 たちはタイムリーにノウハウを 公 開 し 共 有 していくこと が 第 一 と 考 え 最 新 かつその 時 点 で 正 しいと 思 われることをできるだけ 記 載 公 開 し 間 違 いがあればフィードバック を 頂 いて 常 に 正 しい 情 報 に 更 新 し タイムリーに 提 供 するよう 心 がける いわゆる 常 にベータ 版 というアプローチをと っています このアプローチはこのガイド 文 書 をご 利 用 いただく 多 くの Android アプリケーション 開 発 者 のみなさまに とって 有 意 義 であると 私 たちは 信 じています このガイド 文 書 とサンプルコードの 最 新 版 はいつでも 下 記 URL から 入 手 できます ガイド 文 書 サンプルコード 一 式 12 All rights reserved Japan Smartphone Security Association. 常 にベータ 版 でタイムリーなフィードバックを

15 1.3. 本 文 書 の 利 用 許 諾 このガイド 文 書 のご 利 用 に 際 しては 次 の 2 つの 注 意 事 項 に 同 意 いただく 必 要 がございます 1. このガイド 文 書 には 間 違 いが 含 まれている 可 能 性 があります ご 自 身 の 責 任 のもとでご 利 用 ください 2. このガイド 文 書 に 含 まれる 間 違 いを 見 つけた 場 合 には 下 記 連 絡 先 までメールにてご 連 絡 ください ただしお 返 事 することや 修 正 をお 約 束 するものではありませんのでご 了 承 ください 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 セキュアコーディンググループ 問 い 合 わせ メール 宛 先 : jssec-securecoding-qa@googlegroups.com 件 名 : コメント 応 募 Android アプリのセキュア 設 計 セキュアコーディングガイド 内 容 : 氏 名 ( 任 意 )/ 所 属 ( 任 意 )/ 連 絡 先 ( 任 意 )/ご 意 見 ( 必 須 )/その 他 ご 希 望 ( 任 意 ) All rights reserved Japan Smartphone Security Association. 本 文 書 の 利 用 許 諾 13

16 年 2 月 1 日 版 からの 訂 正 記 事 について 本 節 では 前 版 の 記 事 について 事 実 関 係 と 照 らし 合 わせることで 判 明 した 訂 正 事 項 を 一 覧 にして 掲 載 しています 各 訂 正 記 事 は 執 筆 者 による 継 続 的 な 調 査 結 果 だけでなく 読 者 の 方 々の 貴 重 なご 指 摘 を 広 く 取 り 入 れたものです 特 に いただいたご 指 摘 は 本 改 訂 版 をより 実 践 に 即 したガイドとして 高 い 完 成 度 を 得 るための 最 も 重 要 な 糧 となってい ます 前 版 を 元 にアプリケーション 開 発 を 進 めていた 読 者 は 以 下 の 訂 正 記 事 一 覧 に 特 に 目 を 通 していただきますようお 願 いいたします なお ここで 掲 げる 項 目 には 誤 植 の 修 正 記 事 の 追 加 構 成 の 変 更 単 なる 表 現 上 の 改 善 は 含 みま せん 本 ガイドに 対 するコメントは 今 後 もお 気 軽 にお 寄 せくださいますようよろしくお 願 いいたします 訂 正 記 事 一 覧 2016 年 2 月 1 日 版 の 修 正 個 所 本 改 訂 版 の 訂 正 記 事 訂 正 の 要 旨 2.5 サンプルコードの Android Studio への 取 り 込 み 手 順 2.5 サンプルコードの Andro id Studio への 取 り 込 み 手 順 Android Studio Android 6.0(API 23)に 対 応 した 手 順 に 更 新 しました HTTPS 通 信 する HTTPS 通 信 する サーバー 証 明 書 の 検 証 の 観 点 として 接 続 先 サーバーのホスト 名 と SAN の 一 致 が 必 要 であ る 旨 を 追 記 しました HTTPS 通 信 する HTTPS 通 信 する サンプルコードでは SSLv3 を 用 いた 通 信 が 許 容 されているため サンプルコードを 利 用 する 際 の 注 意 点 を 追 記 しました ( 該 当 なし) HTTP リクエストヘッ ダを 設 定 する 際 の 注 意 点 ( 該 当 なし) ピンニングによる 検 証 の 注 意 点 と 実 装 例 ( 該 当 なし) Google Play 開 発 者 サービスを 利 用 した Open HTTP リクエストヘッダを 設 定 する 際 の 注 意 点 を 追 記 しました ピンニングによる 検 証 を 実 装 する 際 の 注 意 点 を 追 記 しました Provider Installer を 利 用 した OpenSSL の 脆 弱 性 対 策 について 追 記 しました SSL の 脆 弱 性 対 策 ( 該 当 なし) Google Play 開 発 者 サービスによる Security P rovider の 脆 弱 性 対 策 Provider Installer を 利 用 し た Security Provider の 脆 弱 性 対 策 について 追 記 しまし た 14 All rights reserved Japan Smartphone Security Association 年 2 月 1 日 版 からの 訂 正 記 事 について

17 2. ガイド 文 書 の 構 成 2.1. 開 発 者 コンテキスト セキュアコーディング 系 のガイド 文 書 は こういうコーディングは 危 ない だからこのようにコーディングすべき といっ た 内 容 で 構 成 されることが 多 いのですが このような 構 成 はすでにコーディングされたソースコードをレビューするとき には 役 立 つ 反 面 これから 開 発 者 がコーディングしようというときには どの 記 事 を 読 んだらよいのか 分 かりにくいとい う 問 題 があります このガイド 文 書 では 開 発 者 がいま 何 をしようとしているか?という 開 発 者 コンテキストに 着 目 し 開 発 者 コンテキスト に 合 わせた 切 り 口 の 記 事 を 用 意 する 方 針 をとっています たとえば Activity を 作 る 利 用 する や SQLite を 使 う と いう 開 発 者 が 行 うであろう 作 業 単 位 ごとに 記 事 を 用 意 しています 開 発 者 コンテキストに 合 わせて 記 事 を 用 意 することにより 開 発 者 は 必 要 な 記 事 を 見 つけやすく 業 務 にすぐ 役 立 つ ようになると 考 えています All rights reserved Japan Smartphone Security Association. 開 発 者 コンテキスト 15

18 2.2. サンプルコード ルールブック アドバンスト それぞれの 記 事 はサンプルコード ルールブック アドバンストの 3 つのセクションで 構 成 されています お 急 ぎの 方 はサンプルコードとルールブックをご 覧 ください ある 程 度 再 利 用 可 能 なパターンに 落 とし 込 んだ 内 容 にしてあります サンプルコードセクションとルールブックセクションに 収 まらない 課 題 をお 持 ちの 方 はアドバンストをご 覧 ください 個 別 課 題 の 解 決 方 法 を 検 討 するための 考 慮 材 料 を 記 載 してあります なお サンプルコードおよび 記 事 の 内 容 は 特 別 な 記 述 がない 限 り Android 4.0.3(API Level 15) 以 降 を 対 象 にして います Android 4.0.3(API Level 15)より 前 のバージョンにおいては 動 作 確 認 をしておらず 対 策 として 効 果 がない 場 合 もありますのでご 注 意 ください また 対 象 範 囲 内 のバージョンであっても 組 み 込 んだ 端 末 で 動 作 をご 確 認 の 上 ご 自 身 の 責 任 のもとでご 利 用 ください サンプルコード サンプルコードセクションでは その 記 事 がテーマとする 開 発 者 コンテキストにおいて 基 本 的 なお 手 本 となるサンプル コードを 掲 載 しています 複 数 のパターンがある 場 合 はその 分 類 方 法 とそれぞれのパターンのサンプルコードを 用 意 しています 解 説 においては 簡 潔 さを 心 がけており セキュリティ 上 考 慮 すべきポイントを 本 文 中 で ポイント: 部 分 に 番 号 付 き 箇 条 書 きで 記 載 し その 箇 条 書 き 番 号 N に 対 応 するサンプルコードにも ポイント N と 記 載 しコメントで 解 説 しています 一 つのポイントがサンプルコード 上 では 複 数 個 所 に 対 応 する 場 合 があることにご 注 意 ください この ようにセキュリティを 考 慮 すべき 個 所 はソースコード 全 体 に 対 して 僅 かな 量 ですが それらの 個 所 は 点 在 します セキ ュリティの 考 慮 が 必 要 な 個 所 を 見 渡 すことができるように サンプルコードはクラス 単 位 でまるごと 掲 載 するようにして います このガイド 文 書 で 掲 載 しているサンプルコードは 一 部 です すべてのサンプルコードをまとめた 圧 縮 ファイルも 下 記 の URL に 公 開 しています Apache License, Version 2.0 で 公 開 していますので 自 由 にサンプルコードをコピー&ペ ーストしてご 利 用 いただけます ただしエラー 処 理 についてはサンプルコードが 長 くなり 過 ぎないように 最 小 限 にして いますのでご 注 意 ください ガイド 文 書 サンプルコード 一 式 サンプルコードに 添 付 する Projects/keystore ファイルは APK 署 名 用 の 開 発 者 鍵 を 含 んだキーストアファイルです パスワードは android です 自 社 限 定 系 のサンプルコードを APK 署 名 する 際 にご 利 用 ください デバッグ 用 にキーストアファイル debug.keystore を 用 意 しているので Android Studio で 開 発 する 場 合 は Andr oid Studio の 個 別 のプロジェクトで 設 定 しておくと 自 社 限 定 系 のサンプルコードの 動 作 確 認 に 便 利 です また 複 数 の APK から 成 るサンプルコードにおいて 各 APK 間 の 連 携 動 作 を 確 認 するためには 各 々の AndroidManifest. xml 内 の android:debuggable の 設 定 を 合 わせる 必 要 があります Android Studio から APK をインストールす る 場 合 は 明 示 的 に 設 定 が 無 ければ 自 動 的 に android:debuggable= true になります 16 All rights reserved Japan Smartphone Security Association. サンプルコード ルールブック アドバンスト

19 サンプルコードおよびキーストアファイルを Android Studio に 取 り 込 む 方 法 については 2.5 サンプルコードの Android Studio への 取 り 込 み 手 順 をご 参 照 ください ルールブック ルールブックセクションでは その 記 事 がテーマとする 開 発 者 コンテキストにおいて セキュリティ 観 点 から 守 るべきル ールや 考 慮 事 項 を 掲 載 しています ルールブックセクションの 冒 頭 にはそのセクションで 扱 っているルールを 表 形 式 で 一 覧 表 示 し 必 須 または 推 奨 のレベル 分 けをしています ルールには 肯 定 文 または 否 定 文 の 2 種 類 がありま すので 必 須 の 肯 定 文 は やらなきゃだめ 推 奨 の 肯 定 文 は やったほうがよい 必 須 の 否 定 文 は やったらだめ 推 奨 の 否 定 文 は やらないほうがよい といったレベル 感 で 表 現 しています もちろんこのレベル 分 けは 執 筆 者 の 主 観 に 基 づくものですので 参 考 程 度 としてお 取 扱 いください サンプルコードセクションに 掲 載 されているサンプルコードはこれらのルールや 考 慮 事 項 が 反 映 されたものとなってい ますが その 詳 しい 説 明 はルールブックセクションに 記 載 されています また サンプルコードセクションでは 扱 ってい ないルールや 考 慮 事 項 についてもルールブックセクションでは 扱 っています アドバンスト アドバンストセクションでは その 記 事 がテーマとする 開 発 者 コンテキストにおいて サンプルコードセクションやルー ルブックセクションで 説 明 できなかった しかし 注 意 を 要 する 事 項 について 記 載 しています その 記 事 がテーマとする 開 発 者 コンテキストにまつわる コラム 的 な 話 題 や Android OS の 限 界 に 関 する 話 題 など サンプルコードセクション やルールブックセクションの 内 容 で 解 決 できなかった 個 別 課 題 の 解 決 方 法 を 検 討 するための 考 慮 材 料 として 役 立 て ることができます 開 発 者 のみなさんは 常 に 多 忙 です 開 発 者 の 多 くは Android の 深 遠 なるセキュリティの 構 造 について 深 く 理 解 する ことよりも ある 程 度 の Android セキュリティの 知 識 を 持 って 迅 速 にかつ 安 全 な Android アプリケーションをどんど ん 生 産 することが 求 められます 一 方 セキュリティ 設 計 が 重 要 なアプリケーションもあります このようなアプリケー ションの 開 発 者 は Android のセキュリティについて 深 く 理 解 している 必 要 があります このようにスピード 重 視 の 開 発 者 とセキュリティ 重 視 の 開 発 者 の 両 方 を 支 援 するために このガイド 文 書 のすべての 記 事 はサンプルコード ルールブック アドバンストの 3 つのセクションに 分 けて 記 述 しています サンプルコードとル ールブックセクションは そういうことがしたければ これをしておけば 安 全 ですよ といった 一 般 化 できる 内 容 が 書 いて あり 可 能 な 限 りソースコードのコピー&ペーストで 自 動 的 に 安 全 なコーディングができることを 狙 っています アドバン ストセクションは こんなときはこういう 問 題 があって こういう 考 え 方 をするとよい といった 考 えるための 材 料 が 書 い てあり 開 発 者 が 取 り 組 んでいる 個 別 のアプリケーションで 最 適 なセキュア 設 計 セキュアコーディングを 検 討 できる ことを 狙 っています All rights reserved Japan Smartphone Security Association. サンプルコード ルールブック アドバンスト 17

20 2.3. ガイド 文 書 のスコープ このガイド 文 書 は 一 般 の Android アプリケーション 開 発 者 に 必 要 なセキュリティ Tips を 集 めることを 目 的 としていま す そのため 主 にマーケットで 配 布 される Android アプリケーションの 開 発 におけるセキュリティ Tips( 下 図 の アプリ のセキュリティ )が 主 なスコープとなっています アプリの セキュリティ 端 末 の セキュリティ 図 Android OS 層 以 下 の Android 端 末 実 装 に 関 するセキュリティ( 上 図 の 端 末 のセキュリティ )はスコープ 外 です ま た Android 端 末 にユーザーがインストールする 一 般 の Android アプリケーションと Android 端 末 メーカーがプレ インストールする Android アプリケーションでは 気 を 付 けるべきセキュリティの 観 点 で 異 なるところがありますが 特 に 現 行 版 においては 前 者 のみを 扱 っており 後 者 については 扱 っていません 現 行 版 では Java により 実 装 する Tips だけを 記 載 しておりますが JNI 実 装 についても 今 後 の 版 で 記 載 していく 予 定 です root 権 限 が 奪 取 される 脅 威 についても 今 のところ 扱 っていません 基 本 的 には root 権 限 が 奪 われていないセキュア な Android 端 末 を 前 提 とし Android OS のセキュリティモデルを 活 用 したセキュリティ Tips をまとめています なお 資 産 と 脅 威 の 扱 いについては 資 産 分 類 と 保 護 施 策 にて 詳 しく 説 明 しておりますので 合 わせてご 確 認 くださ い 18 All rights reserved Japan Smartphone Security Association. ガイド 文 書 のスコープ

21 2.4. Android セキュアコーディング 関 連 書 籍 の 紹 介 このガイド 文 書 では Android セキュアコーディングのすべてを 扱 うことはとてもできないので 下 記 で 紹 介 する 書 籍 を 併 用 することをお 勧 めします Android Security 安 全 なアプリケーションを 作 成 するために 著 者 :タオソフトウェア 株 式 会 社 ISBN Java セキュアコーディングスタンダード CERT/ Oracle 版 著 者 :Fred Long, Dhruv Mohindra, Robert C. Seacord, Dean F. Sutherland, David Svoboda 監 修 : 歌 代 和 正 翻 訳 : 久 保 正 樹, 戸 田 洋 三 ISBN All rights reserved Japan Smartphone Security Association. Android セキュアコーディング 関 連 書 籍 の 紹 介 19

22 2.5. サンプルコードの Android Studio への 取 り 込 み 手 順 サンプルコードの Android Studio への 取 り 込 み 手 順 を 説 明 します サンプルコードは 目 的 ごとに 複 数 のプロジェクト にわかれています これらのプロジェクトをまとめて 取 り 込 む 方 法 を サンプルプロジェクトを 取 り 込 む に 選 択 して 取 り 込 む 方 法 を サンプルの 各 プロジェクトを 選 択 して 取 り 込 む に 示 します プロジェクトの 取 り 込 みが 終 わ ったら サンプルコード 動 作 確 認 用 debug.keystore を 設 定 する を 参 照 して debug.keystore ファイルを Android Studio に 設 定 してください なお 確 認 は 下 記 の 環 境 で 行 っております OS Windows 7 Ultimate SP1 Android Studio Android SDK Android 6.0(API 23) 特 に 注 意 のないサンプルプロジェクトは Android 6.0(API 23)でビルドできます サンプルプロジェクトを 取 り 込 む 1. サンプルコードをダウンロードする サンプルコード で 紹 介 した URL よりサンプルコードを 取 得 します 2. サンプルコードを 展 開 する Zip で 圧 縮 されたサンプルコードを 右 クリックし 表 示 されたメニューの すべて 展 開 をクリックします 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

23 3. 展 開 先 を 指 定 する ここでは C: android_securecoding という 名 前 でワークスペースを 作 成 します そのため C: を 指 定 し 展 開 ボタンをクリックします 図 " 展 開 "ボタンをクリックすると C: 直 下 に android_securecoding というフォルダが 作 成 されます 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 21

24 android_securecoding フォルダの 中 にはサンプルコードが 含 まれています 例 えば 4.1 Activity を 作 る 利 用 する の パートナー 限 定 Activity を 作 る 利 用 する においてサン プルコードを 参 照 したい 場 合 は 以 下 をご 覧 ください android_securecoding/ Create Use Activity/ Activity PartnerActivity/ 以 上 のように android_securecoding フォルダ 配 下 は 節 ごとに サンプルコードのプロジェクト が 配 置 さ れた 構 成 となります 4. Android Studio を 起 動 しワークスペースを 指 定 する スタートメニューやデスクトップアイコンなどから Android Studio を 起 動 します 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

25 起 動 後 表 示 されたダイアログからインポートを 行 います 図 また 既 にプロジェクトを 読 み 込 んでいる 場 合 は その Window が 表 示 されるため メニューより"File -> Close Project"で 表 示 しているプロジェクトをクローズします 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 23

26 5. インポートを 開 始 する 表 示 されているダイアログの"Import project (Eclipse ADT, Gradle, etc.)"をクリックします 図 プロジェクトを 選 択 する インポートするプロジェクトフォルダを 展 開 し 同 フォルダ 内 の "gradle build.gradle" を 選 択 します 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

27 本 ガイドのサンプルコードプロジェクトと 使 用 している Android Studio の Gradle バージョンが 異 なる 場 合 Gr adle が 最 適 化 されます 図 画 面 に 従 い "Update"をクリックし Android Gradle Plugin のアップデートを 開 始 してください 図 以 下 のメッセージが 表 示 されるので"Fix Gradle wapper and re-import project Gradle setting"をクリック し Gradle Wrapper の 更 新 を 行 ってください 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 25

28 7. インポートの 完 了 プロジェクトがインポートされ 完 了 します 図 Android Studio は Eclipse とは 違 い 1つのプロジェクトに 対 して1つの Window で 表 示 されます 違 うプロジ ェクトをインポートし 開 く 場 合 は "File -> Import Project..."をクリックしインポートします 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

29 サンプルコード 動 作 確 認 用 debug.keystore を 設 定 する サンプルコードから 作 成 したアプリを Android 端 末 やエミュレーターで 動 作 させるためには 署 名 が 必 要 です この 署 名 に 使 うデバッグ 用 の 鍵 ファイル debug.keystore を Android Studio のプロジェクトに 設 定 します 1. File ->Project Structure...をクリックする 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 27

30 2. Signing を 追 加 する Android アプリのセキュア 設 計 セキュアコーディングガイド 左 欄 の Modules からプロジェクト 名 を 選 択 し Signing タブを 選 択 後 + ボタンをクリックします 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

31 3. debug.keystore を 選 択 する debug.keystore はサンプルコードに 含 まれています (android_securecoding フォルダ 直 下 ) 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 29

32 4. Signing の 名 前 を 入 力 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

33 5. Build Types で Signing Config を 設 定 Build Types タブを 選 択 し debug ビルド 用 の Signing Config を Singning で 追 加 した"debug"を 選 択 し OK をクリックします 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順 31

34 6. build.gradle ファイルで 確 認 signingconfigs に 選 択 した debug.keystore のパスが 表 示 され buildtypes の debug に signingconfig が 表 示 されます 図 All rights reserved Japan Smartphone Security Association. サンプルコードの Android Studio への 取 り 込 み 手 順

35 3. セキュア 設 計 セキュアコーディングの 基 礎 知 識 このガイド 文 書 は Android アプリ 開 発 におけるセキュリティ Tips をまとめるものであるが この 章 では Android スマ ートフォン/タブレットを 例 に 一 般 的 なセキュア 設 計 セキュアコーディングの 基 礎 知 識 を 扱 う 後 続 の 章 において 一 般 的 なセキュア 設 計 セキュアコーディングの 解 説 が 必 要 なときに 本 章 の 記 事 を 参 照 するため 後 続 の 章 を 読 み 進 め る 前 に 本 章 の 内 容 に 一 通 り 目 を 通 しておくことをお 勧 めする 3.1. Android アプリのセキュリティ システムやアプリのセキュリティについて 検 討 するとき 定 番 の 考 え 方 のフレームワークがある まずそのシステムや アプリにおいて 守 るべき 対 象 を 把 握 する これを 資 産 と 呼 ぶ 次 にその 資 産 を 脅 かす 攻 撃 を 把 握 する これを 脅 威 と 呼 ぶ 最 後 に 資 産 を 脅 威 から 守 るための 施 策 を 検 討 実 施 する この 施 策 を 対 策 と 呼 ぶ ここで 対 策 とは システムやアプリに 適 切 なセキュア 設 計 セキュアコーディングを 施 すことであり このガイド 文 書 では 4 章 以 降 でこれを 扱 っている 本 節 では 資 産 および 脅 威 について 焦 点 を 当 てる 資 産 守 るべき 対 象 システムやアプリにおける 守 るべき 対 象 には 情 報 と 機 能 の 2 つがある これらをそれぞれ 情 報 資 産 と 機 能 資 産 と 呼 ぶ 情 報 資 産 とは 許 可 された 人 だけが 参 照 や 変 更 ができる 情 報 のことであり それ 以 外 の 人 には 一 切 参 照 や 変 更 ができてはならない 情 報 のことである 機 能 資 産 とは 許 可 された 人 だけが 利 用 できる 機 能 のことであり それ 以 外 の 人 には 一 切 利 用 できてはならない 機 能 のことである 以 下 Android スマートフォン/タブレットにおける 情 報 資 産 と 機 能 資 産 にどのようなものがあるかを 紹 介 する Android アプリや Android スマートフォン/タブレットを 活 用 したシステムを 開 発 するときの 資 産 の 洗 い 出 しの 参 考 にしてほしい 以 降 では Android スマートフォン/タブレットを 総 称 して Android スマートフォンと 呼 ぶ Android スマートフォンにおける 情 報 資 産 表 および 表 は Android スマートフォンに 入 っている 情 報 の 一 例 である これらの 情 報 はスマートフォ ンユーザーに 関 する 個 人 情 報 プライバシー 情 報 またはそれらに 類 する 情 報 に 該 当 するため 適 切 な 保 護 が 必 要 であ る 表 Android スマートフォンが 管 理 する 情 報 の 例 情 報 電 話 番 号 通 話 履 歴 IMEI IMSI 備 考 スマートフォン 自 身 の 電 話 番 号 受 発 信 の 日 時 や 相 手 番 号 スマートフォンの 端 末 ID 回 線 契 約 者 ID All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 33

36 センサー 情 報 各 種 設 定 情 報 アカウント 情 報 メディアデータ GPS 地 磁 気 加 速 度 WiFi 設 定 値 各 種 アカウント 情 報 認 証 情 報 写 真 動 画 音 楽 録 音 表 アプリが 管 理 する 情 報 の 例 情 報 電 話 帳 E メールアドレス E メールメールボックス Web ブックマーク Web 閲 覧 履 歴 カレンダー Facebook Twitter 備 考 知 人 の 連 絡 先 ユーザーのメールアドレス 送 受 信 メール 本 文 添 付 ブックマーク 閲 覧 履 歴 予 定 ToDo イベント SNS コンテンツ SNS コンテンツ 表 の 情 報 は 主 に Android スマートフォン 本 体 または SD カードに 含 まれる 情 報 であり 表 の 情 報 は 主 にアプリが 管 理 する 情 報 である 特 に 表 の 情 報 については アプリがインストールされればされるほど ど んどん 本 体 の 中 に 増 えていくことになるのである 表 は 電 話 帳 の 1 件 のエントリに 含 まれる 情 報 である この 情 報 はスマートフォンユーザーに 関 する 情 報 では なく スマートフォンユーザーの 知 人 友 人 等 に 関 する 情 報 である つまりスマートフォンにはその 利 用 者 であるユー ザーのみならず ほかの 人 々の 情 報 も 含 まれていることに 注 意 が 必 要 だ 表 電 話 帳 (Contacts)の 1 件 のエントリに 含 まれる 情 報 の 例 情 報 電 話 番 号 E メールアドレス プロフィール 画 像 インスタントメッセンジャー ニックネーム 住 所 グループ ウェブサイト イベント 関 係 する 人 物 内 容 自 宅 携 帯 電 話 仕 事 FAX MMS 自 宅 仕 事 携 帯 電 話 サムネール 画 像 大 きな 画 像 AIM MSN Yahoo Skype QQ Google Talk ICQ Jabber Netmeeting 略 称 イニシャル 旧 姓 別 名 国 郵 便 番 号 地 域 地 方 町 通 り お 気 に 入 り 家 族 友 達 同 僚 ブログ プロフィールサイト ホームページ FTP サーバー 自 宅 会 社 誕 生 日 記 念 日 その 他 配 偶 者 子 供 父 母 マネージャー 助 手 同 棲 関 係 パートナー 34 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

37 SIP アドレス 自 宅 仕 事 その 他 これまでの 説 明 では 主 にスマートフォンユーザーの 情 報 を 紹 介 してきたが アプリはユーザー 以 外 の 情 報 も 扱 ってい る 図 は 1 つのアプリが 管 理 している 情 報 を 表 しており 大 きく 分 けるとプログラム 部 分 とデータ 部 分 に 分 か れる プログラム 部 分 は 主 にアプリメーカーの 情 報 であり データ 部 分 は 主 にユーザーの 情 報 である アプリメーカー の 情 報 の 中 には 勝 手 にユーザーに 利 用 されたくない 情 報 もあり 得 るため そうした 情 報 についてはユーザーが 参 照 変 更 できないような 保 護 が 必 要 である Picture Manager プログラム /data/app/com.sonydna.picturemanager.apk AndroidManifest.xml classes.dex Javaコード(バイナリ) resources.arsc 文 字 列 等 のリソース assets AppAbout_en.html バンドルしたデータ res drawable-hdpi broken_image.png 画 像 ファイル layout about.xml レイアウト 情 報 xml setting.xml 任 意 のXMLファイル ( 主 に)アプリメーカーの 情 報 データ ( 主 に)ユーザーの 情 報 /data/data/com.sonydna.picturemanager cache webviewcache WebView 用 キャッシュ databases label.db アプリ 用 DB metadata.db webview.db WebView 用 DB webviewcache.db WebView 用 キャッシュDB files MediaList1.dat アプリ 用 データファイル lib shared_prefs プリファレンス com.sonydna.picturemanager_preferences.xml 図 アプリが 抱 えている 情 報 Android アプリを 作 る 場 合 には 図 のようなアプリ 自 身 が 管 理 する 情 報 のみならず 表 表 表 のような Android スマートフォン 本 体 や 他 のアプリから 取 得 した 情 報 に 関 しても 適 切 に 保 護 する 必 要 があ ることにも 注 意 が 必 要 だ Android スマートフォンにおける 機 能 資 産 表 は Android OS がアプリに 提 供 する 機 能 の 一 例 である これらの 機 能 がマルウェア 等 に 勝 手 に 利 用 されて しまうとユーザーの 意 図 しない 課 金 が 生 じたり プライバシーが 損 なわれるなどの 被 害 が 生 じたりする そのため 情 報 資 産 と 同 様 にこうした 機 能 資 産 も 適 切 に 保 護 されなければならない 表 Android OS がアプリに 提 供 する 機 能 の 一 例 機 能 機 能 SMS メッセージを 送 受 する 機 能 カメラ 撮 影 機 能 電 話 を 掛 ける 機 能 音 量 変 更 機 能 ネットワーク 通 信 機 能 電 話 番 号 携 帯 状 態 の 読 み 取 り 機 能 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 35

38 GPS 等 で 現 在 位 置 を 得 る 機 能 Bluetooth 通 信 機 能 NFC 通 信 機 能 インターネット 通 話 (SIP) 機 能 SD カード 書 き 込 み 機 能 システム 設 定 変 更 機 能 ログデータの 読 み 取 り 機 能 実 行 中 アプリ 情 報 の 取 得 機 能 Android OS がアプリに 提 供 する 機 能 に 加 え Android アプリのアプリ 間 連 携 機 能 も 機 能 資 産 に 含 まれる Android アプリはそのアプリ 内 で 実 現 している 機 能 を 他 のアプリから 利 用 できるように 提 供 することができ このような 仕 組 み をアプリ 間 連 携 と 呼 んでいる この 機 能 は 便 利 である 反 面 Android アプリの 開 発 者 がセキュアコーディングの 知 識 がないために アプリ 内 部 だけで 利 用 する 機 能 を 誤 って 他 のアプリから 利 用 できるようにしてしまっているケースもあ る 他 のアプリから 利 用 できる 機 能 の 内 容 によっては マルウェアから 利 用 されては 困 ることもあるため 意 図 したア プリだけから 利 用 できるように 適 切 な 保 護 が 必 要 となることがある 脅 威 資 産 を 脅 かす 攻 撃 前 節 では Android スマートフォンにおける 資 産 について 解 説 した ここではそれらの 脅 威 つまり 資 産 を 脅 かす 攻 撃 について 解 説 する 資 産 が 脅 かされるとは 簡 単 に 言 えば 情 報 資 産 が 他 人 に 勝 手 に 参 照 変 更 削 除 作 成 されるこ とを 言 い 機 能 資 産 が 他 人 に 勝 手 に 利 用 されることを 言 う といった 具 合 だ こうした 資 産 を 直 接 的 および 間 接 的 に 操 作 する 攻 撃 行 為 を 脅 威 と 呼 ぶ また 攻 撃 行 為 を 行 う 人 や 物 のことを 脅 威 源 と 呼 ぶ 攻 撃 者 やマルウェアは 脅 威 源 であって 脅 威 ではない 攻 撃 者 やマルウェアが 行 う 攻 撃 行 為 のことを 脅 威 と 呼 ぶのである これら 用 語 間 の 関 係 を 図 資 産 脅 威 脅 威 源 脆 弱 性 被 害 の 関 係 に 示 す アプリケーション 資 産 脅 威 被 害 脅 威 脆 弱 性 脅 威 源 図 資 産 脅 威 脅 威 源 脆 弱 性 被 害 の 関 係 図 は Android アプリが 動 作 する 一 般 的 な 環 境 を 表 現 したものだ 以 降 ではこの 図 をベースにして Android アプリにおける 脅 威 の 説 明 を 展 開 するため 初 めにこの 図 の 見 方 を 解 説 する 36 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

39 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォン サーバー アプリ 3G/4G/Wi-Fi Web サービス 1ユーザー の 情 報 全 ユーザー の 情 報 図 Android アプリが 動 作 する 一 般 的 な 環 境 図 の 左 右 にスマートフォンとサーバーを 配 置 している スマートフォンやサーバーは 3G/4G/Wi-Fi およびインターネ ットを 経 由 して 通 信 している スマートフォンの 中 には 複 数 のアプリが 存 在 するが 以 降 の 説 明 で 1 つのアプリに 関 す る 脅 威 を 説 明 するため この 図 では 1 つのアプリに 絞 って 説 明 している スマートフォン 上 のアプリはそのユーザーの 情 報 を 主 に 扱 うが サーバー 上 の Web サービスは 全 ユーザーの 情 報 を 集 中 管 理 することを 表 現 している そのため 従 来 同 様 にサーバーセキュリティの 重 要 性 は 変 わらない サーバーセキュリティについては このガイド 文 書 ではスコ ープ 外 であるため 言 及 しない 以 降 ではこの 図 を 使 って Android アプリにおける 脅 威 を 説 明 していく All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 37

40 ネットワーク 上 の 第 三 者 による 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォン 攻 撃 攻 撃 サーバー アプリ 3G/4G/Wi-Fi ネットワーク 上 の 悪 意 ある 第 三 者 Web サービス 全 ユーザー の 情 報 図 ネットワーク 上 の 悪 意 ある 第 三 者 がアプリを 攻 撃 する スマートフォンアプリはユーザーの 情 報 をサーバーで 管 理 する 形 態 が 一 般 的 である そのため 情 報 資 産 がネットワー ク 上 を 移 動 することになる 図 に 示 すように ネットワーク 上 の 悪 意 ある 第 三 者 は 通 信 中 の 情 報 を 参 照 ( 盗 聴 ) したり 情 報 を 変 更 ( 改 ざん)したりしようとする また 本 物 のサーバーになりすまして アプリの 通 信 相 手 になろうとす る もちろん 従 来 同 様 ネットワーク 上 の 悪 意 ある 第 三 者 はサーバーも 攻 撃 する ユーザーがインストールしたマルウェアによる 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォン サーバー Market マル ウェア 攻 撃 アプリ Web サービス うっかり ユーザー 全 ユーザー の 情 報 図 ユーザーがインストールしてしまったマルウェアがアプリを 攻 撃 する スマートフォンは 多 種 多 様 なアプリをマーケットから 入 手 しインストールすることで 機 能 拡 張 できることがその 最 大 の 38 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

41 特 徴 である ユーザーがうっかりマルウェアアプリをインストールしてしまうこともある 図 が 示 すように マル ウェアはアプリ 間 連 携 機 能 やアプリの 脆 弱 性 を 悪 用 してアプリの 情 報 資 産 や 機 能 資 産 にアクセスしようとする アプリの 脆 弱 性 を 悪 用 する 攻 撃 ファイルによる 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォン アプリ サーバー Web サービス 受 動 的 攻 撃 うっかり ユーザー SD 攻 撃 ファイル 全 ユーザー の 情 報 図 アプリの 脆 弱 性 を 悪 用 する 攻 撃 ファイルがアプリを 攻 撃 する インターネット 上 には 音 楽 や 写 真 動 画 文 書 など 様 々なタイプのファイルが 大 量 に 公 開 されており ユーザーがそ れらのファイルを SD カードにダウンロードし スマートフォンで 利 用 する 形 態 が 一 般 的 である またスマートフォンで 受 信 したメールに 添 付 されるファイルを 利 用 する 形 態 も 一 般 的 である これらのファイルは 閲 覧 用 や 編 集 用 のアプリで オープンされ 利 用 される こうしたファイルを 処 理 するアプリの 機 能 に 脆 弱 性 があると 攻 撃 ファイルにより そのアプリの 情 報 資 産 や 機 能 資 産 が 悪 用 されてしまう 特 に 複 雑 なデータ 構 造 を 持 ったファイル 形 式 の 処 理 においては 脆 弱 性 が 入 り 込 みやすい 攻 撃 ファイルは 巧 みに 脆 弱 性 を 悪 用 してアプリを 操 作 し 攻 撃 ファイルの 作 成 者 の 目 的 を 達 成 する 図 に 示 すように 攻 撃 ファイルは 脆 弱 なアプリによってオープンされるまでは 何 もせず いったんオープンされ るとアプリの 脆 弱 性 を 悪 用 した 攻 撃 を 始 める 攻 撃 者 が 能 動 的 に 行 う 攻 撃 行 為 と 比 較 して このような 攻 撃 手 法 を 受 動 的 攻 撃 (Passive Attack)と 呼 ぶ All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 39

42 悪 意 あるスマートフォンユーザーによる 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォン アプリ サーバー Web サービス 攻 撃 adb debug 悪 意 あるスマートフォン ユーザー USB 全 ユーザー の 情 報 図 悪 意 あるスマートフォンユーザーがアプリを 攻 撃 する Android スマートフォンのアプリ 開 発 においては 一 般 ユーザーに 対 してアプリを 開 発 解 析 する 環 境 や 機 能 が 公 式 に 提 供 されている 提 供 されている 機 能 の 中 でも 特 に ADB と 呼 ばれる 充 実 したデバッグ 機 能 は 誰 でも 何 の 登 録 審 査 もなく 利 用 可 能 であり この 機 能 により Android スマートフォンユーザーは OS 解 析 行 為 やアプリ 解 析 行 為 を 容 易 に 行 うことができる 図 に 示 すように 悪 意 あるスマートフォンユーザーは ADB 等 のデバッグ 機 能 を 利 用 してアプリを 解 析 し アプ リが 抱 える 情 報 資 産 や 機 能 資 産 にアクセスしようとする アプリが 抱 える 資 産 がユーザー 自 身 のものである 場 合 には 問 題 とならないが アプリメーカー 等 ユーザー 以 外 のステークホルダーの 資 産 である 場 合 に 問 題 となる このようにス マートフォンのユーザー 自 身 が 悪 意 を 持 ってアプリ 内 の 資 産 を 狙 うことがあることにも 注 意 が 必 要 だ 40 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

43 スマートフォンの 近 くにいる 第 三 者 による 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォンの 近 くにいる 悪 意 ある 第 三 者 攻 撃 スマートフォン BT 攻 撃 サーバー アプリ Web サービス 全 ユーザー の 情 報 図 スマートフォンの 近 くにいる 悪 意 ある 第 三 者 がアプリを 攻 撃 する スマートフォンはその 携 帯 性 の 良 さや Bluetooth 等 の 近 距 離 無 線 通 信 機 能 を 標 準 搭 載 していることから 物 理 的 に スマートフォンの 近 くにいる 悪 意 ある 第 三 者 から 攻 撃 され 得 ることも 忘 れてはならない 攻 撃 者 はユーザーが 入 力 中 のパスワードを 肩 越 しに 覗 き 見 したり 図 に 示 すように Bluetooth 通 信 機 能 を 持 つアプリに 対 して Bluetooth でアクセスしたり スマートフォン 自 体 を 盗 んだり 破 壊 したりする 特 にスマートフォン 自 体 の 窃 盗 や 破 壊 に ついては 機 密 度 の 高 さからスマートフォン 内 から 一 切 外 に 出 さない 運 用 としている 情 報 資 産 が 失 われてしまう 脅 威 であり アプリ 設 計 の 段 階 で 見 過 ごされてしまうこともあるので 注 意 が 必 要 だ All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 41

44 様 々な 脅 威 スマートフォンセキュリティの 領 域 従 来 のサーバーセキュリティの 領 域 スマートフォンの 近 くにいる 悪 意 ある 第 三 者 攻 撃 BT Market マル ウェア スマートフォン 攻 撃 アプリ 攻 撃 攻 撃 3G/4G/Wi-Fi ネットワーク 上 の 悪 意 ある 第 三 者 攻 撃 サーバー Web サービス うっかり ユーザー SD 攻 撃 データ 受 動 的 攻 撃 攻 撃 adb debug 悪 意 あるスマートフォン ユーザー USB 全 ユーザー の 情 報 図 スマートフォンアプリは 様 々な 攻 撃 にさらされている 図 はこれまで 説 明 した 脅 威 をひとまとめにした 図 である このようにスマートフォンアプリを 取 り 巻 く 脅 威 には 様 々なものがあり この 図 はそのすべてを 書 き 出 しているものではない 日 々の 情 報 収 集 により Android アプリを 取 り 巻 く 脅 威 について 認 識 を 広 め アプリのセキュア 設 計 セキュアコーディングに 活 かしていく 努 力 が 必 要 である 一 般 社 団 法 人 日 本 スマートフォンセキュリティ 協 会 が 作 成 した 次 の 文 書 もスマートフォンの 脅 威 について 役 立 つ 情 報 を 提 供 しているので 参 考 にしていただきたい スマートフォン&タブレットの 業 務 利 用 に 関 するセキュリティガイドライン 第 二 版 (English) スマートフォンネットワークセキュリティ 実 装 ガイド 第 一 版 スマートフォンの 業 務 利 用 におけるクラウド 活 用 ガイド ベータ 版 MDM 導 入 運 用 検 討 ガイド 第 一 版 42 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

45 資 産 分 類 と 保 護 施 策 前 節 までで 解 説 した 通 り Android スマートフォンには 様 々な 脅 威 が 存 在 する それら 脅 威 から アプリが 扱 うすべて の 資 産 を 保 護 することは 開 発 にかかる 時 間 や 技 術 的 限 界 などから 困 難 な 場 合 がある そのため Android アプリ 開 発 者 は アプリが 扱 う 資 産 の 重 要 度 や 容 認 される 被 害 レベルを 判 断 基 準 とした 優 先 度 に 応 じて 資 産 に 対 する 妥 当 な 対 策 を 検 討 することが 必 要 になる 資 産 毎 の 保 護 施 策 を 決 めるため アプリが 扱 うそれぞれの 資 産 について その 重 要 度 保 護 に 関 する 法 的 根 拠 被 害 発 生 時 の 影 響 開 発 者 (または 組 織 )の 社 会 的 責 任 などを 検 討 した 上 で 資 産 を 分 類 し 分 類 毎 に 保 護 施 策 のレベル を 定 める これが それぞれの 資 産 をどのように 扱 い どのような 対 策 を 施 すかを 決 める 判 断 基 準 となる この 分 類 は アプリ 開 発 者 (または 組 織 )として 資 産 をどのように 扱 い 保 護 するかを 定 める 基 準 そのものであるため アプリ 開 発 者 (または 組 織 )がそれぞれの 事 情 に 合 わせて 分 類 方 法 や 対 策 内 容 を 定 める 必 要 がある 参 考 までに 本 ガイドにおける 資 産 分 類 と 保 護 施 策 のレベルを 以 下 に 示 す 表 資 産 分 類 と 保 護 施 策 のレベル 資 産 分 類 資 産 のレベル 保 護 施 策 のレベル 高 位 中 位 資 産 が 被 害 にあった 場 合 組 織 または 個 人 の 活 動 に 致 命 的 または 壊 滅 的 な 影 響 を あたえるもの 例 ) 資 産 が 被 害 にあった 場 合 当 該 組 織 が 事 業 を 継 続 できなくなるレベル 資 産 が 被 害 にあった 場 合 組 織 または 個 Android OS のセキュリティモデルを 破 る root 権 限 を 奪 取 した 状 態 からの 攻 撃 や APK の dex 部 分 を 改 造 すると いった 高 度 な 攻 撃 に 対 しても 保 護 す る UX 等 の 他 要 素 よりもセキュリティ 確 保 を 優 先 する Android OS のセキュリティモデルを 人 の 活 動 に 重 大 な 影 響 をあたえるもの 例 ) 資 産 が 被 害 にあった 場 合 当 該 組 織 の 利 益 が 悪 化 し 事 業 に 影 響 を 及 ぼ 活 用 し その 範 囲 内 で 保 護 する UX 等 の 他 要 素 よりもセキュリティ 確 保 を 優 先 する 本 ガ イド すレベル 低 位 資 産 が 被 害 にあった 場 合 組 織 または 個 人 の 活 動 に 限 定 的 な 影 響 をあたえるもの 例 ) 資 産 が 被 害 にあった 場 合 当 該 組 織 の 利 益 に 影 響 を 与 え 得 るが 他 の 要 素 により 利 益 の 補 てんが 可 能 なレベ Android OS のセキュリティモデルを 活 用 し その 範 囲 内 で 保 護 する UX 等 の 他 要 素 とセキュリティを 比 較 し 他 要 素 を 優 先 しても 良 い の 対 象 範 囲 ル 本 ガイドにおける 資 産 分 類 と 保 護 施 策 は 基 本 的 には root 権 限 が 奪 われていないセキュアな Android 端 末 を 前 提 とし Android OS のセキュリティモデルを 活 用 したセキュリティ 施 策 を 基 準 にしている 具 体 的 には 資 産 分 類 で 中 位 レベル 以 下 の 資 産 に 対 して Android OS のセキュリティモデルが 機 能 していることを 前 提 に Android OS のセキ ュリティモデルを 活 用 した 保 護 施 策 を 想 定 している 一 方 高 位 レベルの 資 産 は root 権 限 が 奪 取 された 状 態 からの All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ 43

46 攻 撃 や APK 解 析 改 造 による 攻 撃 といった Android OS のセキュリティモデルが 破 られた 状 態 での 攻 撃 からも 保 護 が 必 要 な 資 産 であると 想 定 している このような 資 産 を 守 るためには Android OS のセキュリティモデルが 活 用 で きないため 暗 号 化 難 読 化 ハードウェア 支 援 サーバー 支 援 など 複 数 の 手 段 を 組 み 合 わせて 高 度 な 防 御 設 計 を する これはガイド 文 書 に 簡 潔 に 書 けるようなノウハウではないし 個 別 の 状 況 に 応 じて 適 切 な 防 御 設 計 は 異 なるた め 本 ガイドの 対 象 外 としている root 権 限 奪 取 からの 攻 撃 や APK 解 析 改 造 などの 高 度 な 攻 撃 に 対 する 保 護 が 必 要 な 場 合 は Android の 耐 タンパ 設 計 に 詳 しいセキュリティ 専 門 家 に 相 談 することをお 勧 めする センシティブな 情 報 本 ガイドのこれ 以 降 の 文 章 において 情 報 資 産 を センシティブな 情 報 と 表 現 している 前 節 で 述 べたようにアプリが 扱 う 個 々の 情 報 資 産 ごとに 資 産 のレベルや 保 護 施 策 のレベルを 判 断 しなければならない 44 All rights reserved Japan Smartphone Security Association. Android アプリのセキュリティ

47 3.2. 入 力 データの 安 全 性 を 確 認 する 入 力 データの 安 全 性 確 認 はもっとも 基 礎 的 で 効 果 の 高 いセキュアコーディング 作 法 である プログラムに 入 力 される データのうち 攻 撃 者 が 直 接 的 間 接 的 にそのデータの 値 を 操 作 可 能 であるものはすべて 入 力 データの 安 全 性 確 認 が 必 要 である 以 下 Activity をプログラムに 見 立 て Intent を 入 力 データに 見 立 てた 場 合 を 例 にして 入 力 デー タの 安 全 性 確 認 の 在 り 方 について 解 説 する Activity が 受 け 取 った Intent には 攻 撃 者 が 細 工 したデータが 含 まれている 可 能 性 がある 攻 撃 者 はプログラマが 想 定 していない 形 式 値 のデータを 送 り 付 けることで アプリの 誤 動 作 を 誘 発 し 結 果 として 何 らかのセキュリティ 被 害 を 生 じさせるのである ユーザーも 攻 撃 者 の 一 人 となり 得 ることも 忘 れてはならない Intent は action や data extras などのデータで 構 成 されるが 攻 撃 者 が 制 御 可 能 なデータはすべて 気 を 付 けなけ ればならない 攻 撃 者 が 制 御 可 能 なデータを 扱 うコードでは 必 ず 次 の 事 項 を 確 認 しなければならない (a) 受 け 取 ったデータは プログラマが 想 定 した 形 式 であって 値 は 想 定 の 範 囲 内 に 収 まっているか? (b) 想 定 している 形 式 値 のあらゆるデータを 受 け 取 っても そのデータを 扱 うコードが 想 定 外 の 動 作 をしないと 保 証 できるか? 次 の 例 は 指 定 URL の Internet 上 の Web ページの HTML を 取 得 し 画 面 上 の TextView に 表 示 するだけの 簡 単 なサンプルである しかしこれには 不 具 合 がある Internet 上 の Web ページの HTML を TextView に 表 示 するサンプルコード TextView tv = (TextView) findviewbyid(r.id.textview); InputStreamReader isr = null; char[] text = new char[1024]; int read; try { String urlstr = getintent().getstringextra("webpage_url"); URL url = new URL(urlstr); isr = new InputStreamReader(url.openConnection().getInputStream()); while ((read=isr.read(text))!= -1) { tv.append(new String(text, 0, read)); catch (MalformedURLException e) {... (a)の 観 点 で urlstr が 正 しい URL である ことを new URL()で MalformedURLException が 発 生 しないことにより 確 認 している しかしこれは 不 十 分 であり urlstr に file://~ 形 式 の URL が 指 定 されると Internet 上 の Web ペー ジではなく 内 部 ファイルシステム 上 のファイルを 開 いて TextView に 表 示 してしまう プログラマが 想 定 した 動 作 を 保 証 していないため (b)の 観 点 を 満 たしていない 次 は 改 善 例 である (a)の 観 点 で urlstr は 正 規 の URL であって protocol は http または https に 限 定 される こと を 確 認 し て い る こ れにより (b) の 観 点 でも url.openconnection().getinputstream() で Internet 経 由 の InputStream を 取 得 することが 保 証 される All rights reserved Japan Smartphone Security Association. 入 力 データの 安 全 性 を 確 認 する 45

48 Internet 上 の Web ページの HTML を TextView に 表 示 するサンプルコードの 修 正 版 TextView tv = (TextView) findviewbyid(r.id.textview); InputStreamReader isr = null; char[] text = new char[1024]; int read; try { String urlstr = getintent().getstringextra("webpage_url"); URL url = new URL(urlstr); String prot = url.getprotocol(); if (! http.equals(prot) &&! https.equals(prot)) { throw new MalformedURLException("invalid protocol"); isr = new InputStreamReader(url.openConnection().getInputStream()); while ((read=isr.read(text))!= -1) { tv.append(new String(text, 0, read)); catch (MalformedURLException e) {... 入 力 データの 安 全 性 確 認 は Input Validation と 呼 ばれる 基 礎 的 なセキュアコーディング 作 法 である Input Validation という 言 葉 の 語 感 から(a)の 観 点 のみ 気 を 付 けて(b)の 観 点 を 忘 れてしまいがちである データはプログラ ムに 入 ってきたときではなく プログラムがそのデータを 使 う ときに 被 害 が 発 生 することに 気 を 付 けていただきたい 下 記 URL もぜひ 参 考 にしていただきたい IPA セキュアプログラミング 講 座 JPCERT CC Java セキュアコーディングスタンダード CERT/Oracle 版 JPCERT CC Java Android アプリケーション 開 発 へのルールの 適 用 46 All rights reserved Japan Smartphone Security Association. 入 力 データの 安 全 性 を 確 認 する

49 4. 安 全 にテクノロジーを 活 用 する Android で 言 えば Activity や SQLite など テクノロジーごとにセキュリティ 観 点 の 癖 というものがある そうしたセキ ュリティの 癖 を 知 らずに 設 計 コーディングしていると 思 わぬ 脆 弱 性 をつくりこんでしまうことがある この 章 では 開 発 者 が Android のテクノロジーを 活 用 するシーンを 想 定 した 記 事 を 扱 う 4.1. Activity を 作 る 利 用 する サンプルコード Activity がどのように 利 用 されるかによって Activity が 抱 えるリスクや 適 切 な 防 御 手 段 が 異 なる ここでは Activity がどのように 利 用 されるかという 観 点 で Activity を 4 つのタイプに 分 類 した 次 の 判 定 フローによって 作 成 する Activity がどのタイプであるかを 判 断 できる なお どのような 相 手 を 利 用 するかによって 適 切 な 防 御 手 段 が 決 まるため Activity の 利 用 側 の 実 装 についても 合 わせて 説 明 する はじめ Yes アプリ 内 でのみ 利 用 する No Yes 不 特 定 多 数 の アプリに 利 用 を 認 める No Yes 特 定 他 社 の アプリに 利 用 を 認 める No Private 非 公 開 Activity Public 公 開 Activity Exclusive パートナー 限 定 Activity Proprietary 自 社 限 定 Activity 図 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 47

50 非 公 開 Activity を 作 る 利 用 する 非 公 開 Activity は 同 一 アプリ 内 でのみ 利 用 される Activity であり もっとも 安 全 性 の 高 い Activity である 同 一 アプリ 内 だけで 利 用 される Activity( 非 公 開 Activity)を 利 用 する 際 は クラスを 指 定 する 明 示 的 Intent を 使 え ば 誤 って 外 部 アプリに Intent を 送 信 してしまうことがない ただし Activity を 呼 び 出 す 際 に 使 用 する Intent は 第 三 者 によって 読 み 取 られる 恐 れがある そのため Activity に 送 信 する Intent にセンシティブな 情 報 を 格 納 する 場 合 に は その 情 報 が 悪 意 のある 第 三 者 に 読 み 取 られることのないように 適 切 な 対 応 を 実 施 する 必 要 がある 以 下 に 非 公 開 Activity を 作 る 側 のサンプルコードを 示 す ポイント(Activity を 作 る): 1. taskaffinity を 指 定 しない 2. launchmode を 指 定 しない 3. exported="false"により 明 示 的 に 非 公 開 設 定 する 4. 同 一 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 5. 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい Activity を 非 公 開 設 定 するには AndroidManifest.xml の activity 要 素 の exported 属 性 を false と 指 定 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.privateactivity" > <application android:allowbackup="false" > <!-- 非 公 開 Activity --> <!-- ポイント 1 taskaffinity を 指 定 しない --> <!-- ポイント 2 launchmode を 指 定 しない --> <!-- ポイント 3 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <activity android:name=".privateactivity" android:label="@string/app_name" android:exported="false" /> <!-- ランチャーから 起 動 する 公 開 Activity --> <activity android:name=".privateuseractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> 48 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

51 </manifest> PrivateActivity.java package org.jssec.android.activity.privateactivity; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PrivateActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.private_activity); // ポイント 4 同 一 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = getintent().getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { // ポイント 5 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい Intent intent = new Intent(); intent.putextra("result", "センシティブな 情 報 "); setresult(result_ok, intent); finish(); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 49

52 次 に 非 公 開 Activity を 利 用 する 側 のサンプルコードを 示 す ポイント(Activity を 利 用 する): 6. Activity に 送 信 する Intent には フラグ FLAG_ACTIVITY_NEW_TASK を 設 定 しない 7. 同 一 アプリ 内 Activity はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す 8. 利 用 先 アプリは 同 一 アプリであるから センシティブな 情 報 を putextra()を 使 う 場 合 に 限 り 送 信 してもよい 1 9. 同 一 アプリ 内 Activity からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する PrivateUserActivity.java package org.jssec.android.activity.privateactivity; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PrivateUserActivity extends Activity { private static final int REQUEST_CODE = 1; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user_activity); public void onuseactivityclick(view view) { // ポイント 6 Activity に 送 信 する Intent には フラグ FLAG_ACTIVITY_NEW_TASK を 設 定 しない // ポイント 7 同 一 アプリ 内 Activity はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す Intent intent = new Intent(this, PrivateActivity.class); // ポイント 8 利 用 先 アプリは 同 一 アプリであるから センシティブな 情 報 を putextra()を 使 う 場 合 に 限 り 送 信 し てもよい intent.putextra("param", "センシティブな 情 報 "); startactivityforresult(intent, REQUEST_CODE); public void onactivityresult(int requestcode, int resultcode, Intent data) { super.onactivityresult(requestcode, resultcode, data); if (resultcode!= RESULT_OK) return; switch (requestcode) { case REQUEST_CODE: String result = data.getstringextra("result"); // ポイント 9 同 一 アプリ 内 Activity からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する 1 ただし ポイント 1, 2, 6 を 遵 守 している 場 合 を 除 いては Intent が 第 三 者 に 読 み 取 られるおそれがあることに 注 意 する 必 要 がある 詳 細 はルールブックセクションの を 参 照 すること 50 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

53 // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, String.format(" 結 果 %s を 受 け 取 った ", result), Toast.LENGTH_LONG).show(); break; All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 51

54 公 開 Activity を 作 る 利 用 する 公 開 Activity は 不 特 定 多 数 のアプリに 利 用 されることを 想 定 した Activity である マルウェアが 送 信 した Intent を 受 信 することがあることに 注 意 が 必 要 である また 公 開 Activity を 利 用 する 場 合 には 送 信 する Intent がマルウェ アに 受 信 される あるいは 読 み 取 られることがあることに 注 意 が 必 要 である 以 下 に 公 開 Activity を 作 る 側 のサンプルコードを 示 す ポイント(Activity を 作 る): 1. exported="true"により 明 示 的 に 公 開 設 定 する 2. 受 信 Intent の 安 全 性 を 確 認 する 3. 結 果 を 返 す 場 合 センシティブな 情 報 を 含 めない AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.publicactivity" > <application android:allowbackup="false" > <!-- 公 開 Activity --> <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <activity android:name=".publicactivity" android:label="@string/app_name" android:exported="true" > <!-- Action 指 定 による 暗 黙 的 Intent を 受 信 するように Intent Filter を 定 義 --> <intent-filter> <action android:name="org.jssec.android.activity.my_action" /> <category android:name="android.intent.category.default" /> </intent-filter> </activity> </application> </manifest> PublicActivity.java package org.jssec.android.activity.publicactivity; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PublicActivity extends Activity { 52 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

55 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // ポイント 2 受 信 Intent の 安 全 性 を 確 認 する // 公 開 Activity であるため 利 用 元 アプリがマルウェアである 可 能 性 がある // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = getintent().getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { // ポイント 3 結 果 を 返 す 場 合 センシティブな 情 報 を 含 めない // 公 開 Activity であるため 利 用 元 アプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい Intent intent = new Intent(); intent.putextra("result", "センシティブではない 情 報 "); setresult(result_ok, intent); finish(); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 53

56 次 に 公 開 Activity を 利 用 する 側 のサンプルコードを 示 す ポイント(Activity を 利 用 する): 4. センシティブな 情 報 を 送 信 してはならない 5. 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する PublicUserActivity.java package org.jssec.android.activity.publicuser; import android.app.activity; import android.content.activitynotfoundexception; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PublicUserActivity extends Activity { private static final int REQUEST_CODE = 1; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void onuseactivityclick(view view) { try { // ポイント 4 センシティブな 情 報 を 送 信 してはならない Intent intent = new Intent("org.jssec.android.activity.MY_ACTION"); intent.putextra("param", "センシティブではない 情 報 "); startactivityforresult(intent, REQUEST_CODE); catch (ActivityNotFoundException e) { Toast.makeText(this, " 利 用 先 Activity が 見 つからない ", Toast.LENGTH_LONG).show(); public void onactivityresult(int requestcode, int resultcode, Intent data) { super.onactivityresult(requestcode, resultcode, data); // ポイント 5 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (resultcode!= RESULT_OK) return; switch (requestcode) { case REQUEST_CODE: String result = data.getstringextra("result"); Toast.makeText(this, String.format(" 結 果 %s を 受 け 取 った ", result), Toast.LENGTH_LONG).show(); break; 54 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

57 パートナー 限 定 Activity を 作 る 利 用 する パートナー 限 定 Activity は 特 定 のアプリだけから 利 用 できる Activity である パートナー 企 業 のアプリと 自 社 アプリ が 連 携 してシステムを 構 成 し パートナーアプリとの 間 で 扱 う 情 報 や 機 能 を 守 るために 利 用 される Activity を 呼 び 出 す 際 に 使 用 する Intent は 第 三 者 によって 読 み 取 られる 恐 れがある そのため Activity に 送 信 す る Intent にセンシティブな 情 報 を 格 納 する 場 合 には その 情 報 が 悪 意 のある 第 三 者 に 読 み 取 られることのないように 適 切 な 対 応 を 実 施 する 必 要 がある 以 下 にパートナー 限 定 Activity を 作 る 側 のサンプルコードを 示 す ポイント(Activity を 作 る): 1. taskaffinity を 指 定 しない 2. launchmode を 指 定 しない 3. Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する 4. 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 5. パートナーアプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 6. パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい ホワイトリストを 用 いたアプリの 確 認 方 法 については 利 用 元 アプリを 確 認 する を 参 照 すること また ホ ワイトリストに 指 定 する 利 用 先 アプリの 証 明 書 ハッシュ 値 の 確 認 方 法 は アプリの 証 明 書 のハッシュ 値 を 確 認 する 方 法 を 参 照 すること AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.partneractivity" > <application android:allowbackup="false" > <!-- パートナー 限 定 Activity --> <!-- ポイント 1 taskaffinity を 指 定 しない --> <!-- ポイント 2 launchmode を 指 定 しない --> <!-- ポイント 3 Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する --> <activity android:name=".partneractivity" android:exported="true" /> </application> </manifest> PartnerActivity.java package org.jssec.android.activity.partneractivity; All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 55

58 import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PartnerActivity extends Activity { // ポイント 4 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナーアプリ org.jssec.android.activity.partneruser の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.activity.partneruser", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"partner key"の 証 明 書 ハッシュ 値 "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // 以 下 同 様 に 他 のパートナーアプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // ポイント 4 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(this, getcallingactivity().getpackagename())) { Toast.makeText(this, " 利 用 元 アプリはパートナーアプリではない ", Toast.LENGTH_LONG).show(); finish(); return; // ポイント 5 パートナーアプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, "パートナーアプリからアクセスあり", Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { // ポイント 6 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい Intent intent = new Intent(); intent.putextra("result", "パートナーアプリに 開 示 してよい 情 報 "); setresult(result_ok, intent); finish(); 56 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

59 PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 57

60 Android アプリのセキュア 設 計 セキュアコーディングガイド if (pkginfo.signatures.length!= 1) return null; Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; // 複 数 署 名 は 扱 わない private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 58 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

61 次 にパートナー 限 定 Activity を 利 用 する 側 のサンプルコードを 示 す ここではパートナー 限 定 Activity を 呼 び 出 す 方 法 を 説 明 する ポイント(Activity を 利 用 する): 7. 利 用 先 パートナー 限 定 Activity アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 8. Activity に 送 信 する Intent には フラグ FLAG_ACTIVITY_NEW_TASK を 設 定 しない 9. 利 用 先 パートナー 限 定 アプリに 開 示 してよい 情 報 は putextra()を 使 う 場 合 に 限 り 送 信 してよい 10. 明 示 的 Intent によりパートナー 限 定 Activity を 呼 び 出 す 11. startactivityforresult()によりパートナー 限 定 Activity を 呼 び 出 す 12. パートナー 限 定 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する ホワイトリストを 用 いたアプリの 確 認 方 法 については 利 用 元 アプリを 確 認 する を 参 照 すること また ホ ワイトリストに 指 定 する 利 用 先 アプリの 証 明 書 ハッシュ 値 の 確 認 方 法 は アプリの 証 明 書 のハッシュ 値 を 確 認 する 方 法 を 参 照 すること AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.partneruser" > <application android:allowbackup="false" > <activity android:name=".partneruseractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> PartnerUserActivity.java package org.jssec.android.activity.partneruser; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activity; import android.content.activitynotfoundexception; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 59

62 public class PartnerUserActivity extends Activity { // ポイント 7 利 用 先 パートナー 限 定 Activity アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナー 限 定 Activity アプリ org.jssec.android.activity.partneractivity の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.activity.partneractivity", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"my company key"の 証 明 書 ハッシュ 値 "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"); // 以 下 同 様 に 他 のパートナー 限 定 Activity アプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); private static final int REQUEST_CODE = 1; // 利 用 先 のパートナー 限 定 Activity に 関 する 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.activity.partneractivity"; private static final String TARGET_ACTIVITY = "org.jssec.android.activity.partneractivity.partneractivity"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void onuseactivityclick(view view) { る show(); // ポイント 7 利 用 先 パートナー 限 定 Activity アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 す if (!checkpartner(this, TARGET_PACKAGE)) { Toast.makeText(this, " 利 用 先 Activity アプリはホワイトリストに 登 録 されていない ", Toast.LENGTH_LONG). return; try { Intent intent = new Intent(); // ポイント 8 Activity に 送 信 する Intent には フラグ FLAG_ACTIVITY_NEW_TASK を 設 定 しない い // ポイント 9 利 用 先 パートナー 限 定 アプリに 開 示 してよい 情 報 を putextra()を 使 う 場 合 に 限 り 送 信 してよ intent.putextra("param", "パートナーアプリに 開 示 してよい 情 報 "); // ポイント 10 明 示 的 Intent によりパートナー 限 定 Activity を 呼 び 出 す intent.setclassname(target_package, TARGET_ACTIVITY); // ポイント 11 startactivityforresult()によりパートナー 限 定 Activity を 呼 び 出 す startactivityforresult(intent, REQUEST_CODE); catch (ActivityNotFoundException e) { 60 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

63 Android アプリのセキュア 設 計 セキュアコーディングガイド Toast.makeText(this, " 利 用 先 Activity が 見 つからない ", Toast.LENGTH_LONG).show(); public void onactivityresult(int requestcode, int resultcode, Intent data) { super.onactivityresult(requestcode, resultcode, data); if (resultcode!= RESULT_OK) return; switch (requestcode) { case REQUEST_CODE: String result = data.getstringextra("result"); // ポイント 12 パートナー 限 定 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, String.format(" 結 果 %s を 受 け 取 った ", result), Toast.LENGTH_LONG).show(); break; PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 61

64 PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 62 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

65 自 社 限 定 Activity を 作 る 利 用 する 自 社 限 定 Activity は 自 社 以 外 のアプリから 利 用 されることを 禁 止 する Activity である 複 数 の 自 社 製 アプリでシス テムを 構 成 し 自 社 アプリが 扱 う 情 報 や 機 能 を 守 るために 利 用 される Activity を 呼 び 出 す 際 に 使 用 する Intent は 第 三 者 によって 読 み 取 られる 恐 れがある そのため Activity に 送 信 す る Intent にセンシティブな 情 報 を 格 納 する 場 合 には その 情 報 が 悪 意 のある 第 三 者 に 読 み 取 られることのないように 適 切 な 対 応 を 実 施 する 必 要 がある 以 下 に 自 社 限 定 Activity を 作 る 側 のサンプルコードを 示 す ポイント(Activity を 作 る): 1. 独 自 定 義 Signature Permission を 定 義 する 2. taskaffinity を 指 定 しない 3. launchmode を 指 定 しない 4. 独 自 定 義 Signature Permission を 要 求 宣 言 する 5. Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する 6. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 7. 自 社 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 8. 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい 9. 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.inhouseactivity" > <!-- ポイント 1 独 自 定 義 Signature Permission を 定 義 する --> <permission android:name="org.jssec.android.activity.inhouseactivity.my_permission" android:protectionlevel="signature" /> <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- 自 社 限 定 Activity --> <!-- ポイント 2 taskaffinity を 指 定 しない --> <!-- ポイント 3 launchmode を 指 定 しない --> <!-- ポイント 4 独 自 定 義 Signature Permission を 要 求 宣 言 する --> <!-- ポイント 5 Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する --> <activity android:name=".inhouseactivity" android:exported="true" android:permission="org.jssec.android.activity.inhouseactivity.my_permission" > </activity> </application> All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 63

66 </manifest> Android アプリのセキュア 設 計 セキュアコーディングガイド InhouseActivity.java package org.jssec.android.activity.inhouseactivity; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class InhouseActivity extends Activity { // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // ポイント 6 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); finish(); return; // ポイント 7 自 社 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = getintent().getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { // ポイント 8 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい Intent intent = new Intent(); intent.putextra("result", "センシティブな 情 報 "); 64 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

67 setresult(result_ok, intent); finish(); SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 65

68 Android アプリのセキュア 設 計 セキュアコーディングガイド if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 66 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

69 ポイント 9 APK を Export するときに 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 次 に 自 社 限 定 Activity を 利 用 する 側 のサンプルコードを 示 す ここでは 自 社 限 定 Activity を 呼 び 出 す 方 法 を 説 明 す る ポイント(Activity を 利 用 する): 10. 独 自 定 義 Signature Permission を 利 用 宣 言 する 11. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 12. 利 用 先 アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する 13. 利 用 先 アプリは 自 社 アプリであるから センシティブな 情 報 を putextra()を 使 う 場 合 に 限 り 送 信 してもよい 14. 明 示 的 Intent により 自 社 限 定 Activity を 呼 び 出 す 15. 自 社 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する 16. 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.inhouseuser" > <!-- ポイント 10 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.activity.inhouseactivity.my_permission" /> <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 67

70 android:name="org.jssec.android.activity.inhouseuser.inhouseuseractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> InhouseUserActivity.java package org.jssec.android.activity.inhouseuser; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.activitynotfoundexception; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class InhouseUserActivity extends Activity { // 利 用 先 の Activity 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity"; private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.inhouseactivity"; // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; private static final int REQUEST_CODE = 1; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void onuseactivityclick(view view) { 68 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

71 // ポイント 11 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); return; // ポイント 12 利 用 先 アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する if (!PkgCert.test(this, TARGET_PACKAGE, mycerthash(this))) { Toast.makeText(this, " 利 用 先 アプリは 自 社 アプリではない ", Toast.LENGTH_LONG).show(); return; try { Intent intent = new Intent(); // ポイント 13 利 用 先 アプリは 自 社 アプリであるから センシティブな 情 報 を putextra()を 使 う 場 合 に 限 り 送 信 してもよい intent.putextra("param", "センシティブな 情 報 "); // ポイント 14 明 示 的 Intent により 自 社 限 定 Activity を 呼 び 出 す intent.setclassname(target_package, TARGET_ACTIVITY); startactivityforresult(intent, REQUEST_CODE); catch (ActivityNotFoundException e) { Toast.makeText(this, " 利 用 先 Activity が 見 つからない ", Toast.LENGTH_LONG).show(); public void onactivityresult(int requestcode, int resultcode, Intent data) { super.onactivityresult(requestcode, resultcode, data); if (resultcode!= RESULT_OK) return; switch (requestcode) { case REQUEST_CODE: String result = data.getstringextra("result"); // ポイント 15 自 社 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, String.format(" 結 果 %s を 受 け 取 った ", result), Toast.LENGTH_LONG).show(); break; SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 69

72 public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; 70 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

73 private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); ポイント 16 APK を Export するときに 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 71

74 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド Activity を 作 る 際 または Activity に Intent を 送 信 する 際 には 以 下 のルールを 守 ること 1. アプリ 内 でのみ 使 用 する Activity は 非 公 開 設 定 する ( 必 須 ) 2. taskaffinity を 指 定 しない 3. launchmode を 指 定 しない ( 必 須 ) ( 必 須 ) 4. Activity に 送 信 する Intent には FLAG_ACTIVITY_NEW_TASK を 設 定 しない ( 必 須 ) 5. 受 信 Intent の 安 全 性 を 確 認 する ( 必 須 ) 6. 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 7. 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) 8. 利 用 先 Activity が 固 定 できる 場 合 は 明 示 的 Intent で Activity を 利 用 する ( 必 須 ) 9. 利 用 先 Activity からの 戻 り Intent の 安 全 性 を 確 認 する ( 必 須 ) 10. 他 社 の 特 定 アプリと 連 携 する 場 合 は 利 用 先 Activity を 確 認 する ( 必 須 ) 11. 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) 12. センシティブな 情 報 はできる 限 り 送 らない ( 推 奨 ) アプリ 内 でのみ 使 用 する Activity は 非 公 開 設 定 する ( 必 須 ) 同 一 アプリ 内 からのみ 利 用 される Activity は 他 のアプリから Intent を 受 け 取 る 必 要 がない またこのような Activity では 開 発 者 も Activity を 攻 撃 する Intent を 想 定 しないことが 多 い このような Activity は 明 示 的 に 非 公 開 設 定 し 非 公 開 Activity とする AndroidManifest.xml <!-- 非 公 開 Activity --> <!-- ポイント 3 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <activity android:name=".privateactivity" android:label="@string/app_name" android:exported="false" /> 同 一 アプリ 内 からのみ 利 用 される Activity では Intent Filter を 設 置 するような 設 計 はしてはならない Intent Filter の 性 質 上 同 一 アプリ 内 の 非 公 開 Activity を 呼 び 出 すつもりでも Intent Filter 経 由 で 呼 び 出 したときに 意 図 せず 他 アプリの Activity を 呼 び 出 してしまう 可 能 性 もある 詳 細 は アドバンスト exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Activity の 場 合 ) を 参 照 すること AndroidManifest.xml( 非 推 奨 ) <!-- 非 公 開 Activity --> <!-- ポイント 3 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <activity android:name=".pictureactivity" android:label="@string/picture_name" android:exported="false" > <intent-filter> 72 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

75 <action android:name= org.jssec.android.activity.open /> </intent-filter> </activity> taskaffinity を 指 定 しない ( 必 須 ) Android では Activity はタスクによって 管 理 される タスクの 名 前 は ルート Activity の 持 つアフィニティによって 決 定 される 一 方 でルート 以 外 の Activity に 関 しては 所 属 するタスクがアフィニティだけでは 決 定 されず Activity の 起 動 モードにも 依 存 する 詳 細 は ルート Activity について を 参 照 すること デフォルト 設 定 では 各 Activity はパッケージ 名 をアフィニティとして 持 つ その 結 果 タスクはアプリごとに 割 り 当 てら れるので 同 一 アプリ 内 の 全 ての Activity は 同 一 タスクに 所 属 する タスクの 割 り 当 てを 変 更 するには AndroidManifest.xml への 明 示 的 なアフィニティ 記 述 や Activity に 送 信 する Intent へのフラグ 設 定 をすればよい ただし タスクの 割 り 当 てを 変 更 した 場 合 は 異 なるタスクに 属 する Activity に 送 信 した Intent を 別 アプリによって 読 み 出 せる 可 能 性 がある セ ン シ テ ィ ブ な 情 報 を 含 む 送 信 Intent お よ び 受 信 Intent の 内 容 を 読 み 取 ら れ な い よ う に す る に は AndroidManifest.xml 内 の application 要 素 および activity 要 素 で android:taskaffinity を 指 定 せず デフォ ルト(パッケージ 名 と 同 一 )のままにすべきである 以 下 に 非 公 開 Activity の 作 成 側 と 利 用 側 における AndroidManifest.xml を 示 す AndroidManifest.xml <!-- ポイント 1 taskaffinity を 指 定 しない --> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- ポイント 1 taskaffinity を 指 定 しない --> <activity android:name=".privateuseractivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 非 公 開 Activity --> <!-- ポイント 1 taskaffinity を 指 定 しない --> <activity android:name=".privateactivity" android:label="@string/app_name" android:exported="false" /> </application> All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 73

76 タスクとアフィニティの 詳 細 な 解 説 は Google Android プログラミング 入 門 2 あるいは Google Developers API Guide Tasks and Back Stack 3 の 解 説 および Activity に 送 信 される Intent の 読 み 取 り ルー ト Activity について を 参 照 すること launchmode を 指 定 しない ( 必 須 ) Activity の 起 動 モードとは Activity を 呼 び 出 す 際 に Activity のインスタンスの 新 規 生 成 や タスクの 新 規 生 成 を 制 御 するための 設 定 である デフォルト 設 定 は standard である standard 設 定 では Intent を 使 って Activity を 呼 び 出 すときには 常 に 新 規 インスタンスを 生 成 し タスクは 呼 び 出 し 側 Activity が 属 するタスクに 従 い 新 規 にタス クが 生 成 されることはない タスクが 新 規 に 生 成 されると 呼 び 出 しに 使 った Intent が 別 のアプリから 読 み 取 り 可 能 に なる そのため センシティブな 情 報 を Intent に 含 む 場 合 には Activity の 起 動 モードには standard を 用 いるべき である Activity の 起 動 モードは AndroidManifest.xml 内 にて android:launchmode で 明 示 的 に 設 定 可 能 であるが 上 記 の 理 由 により 各 Activity に 対 して android:launchmode を 指 定 せず 値 をデフォルトのまま standard とする べきである AndroidManifest.xml <!-- ポイント 2 Activity には launchmode を 指 定 せず 値 をデフォルトのまま standard とする --> <activity android:name=".privateuseractivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 非 公 開 Activity --> <!-- ポイント 2 Activity には launchmode を 指 定 せず 値 をデフォルトのまま standard とする --> <activity android:name=".privateactivity" android:label="@string/app_name" android:exported="false" /> </application> Activity に 送 信 される Intent の 読 み 取 り ルート Activity について を 参 照 すること Activity に 送 信 する Intent には FLAG_ACTIVITY_NEW_TASK を 設 定 しない ( 必 須 ) Activity の 起 動 モードは startactivity()あるいは startactivityforresult()の 実 行 時 にも 変 更 することが 可 能 であり タスクが 新 規 に 生 成 される 場 合 がある そのため Activity の 起 動 モードを 実 行 時 に 変 更 しないようにする 必 要 があ 2 江 川 藤 井 麻 野 藤 田 山 田 山 岡 佐 野 竹 端 著 Google Android プログラミング 入 門 (アスキー メディアワー クス 2009 年 7 月 ) All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

77 る Activity の 起 動 モードを 変 更 するには setflags()や addflags()を 用 いて Intent にフラグを 設 定 し その Intent を startactivity() ま た は startactivityforresult() の 引 数 と す る タ ス ク を 新 規 に 生 成 す る た め の フ ラ グ は FLAG_ACTIVITY_NEW_TASK である FLAG_ACTIVITY_NEW_TASK が 設 定 されると 呼 び 出 された Activity のタ ス ク が バ ッ ク グ ラ ウ ン ド あ る い は フ ォ ア グ ラ ウ ン ド 上 に 存 在 し な い 場 合 に 新 規 に タ ス ク が 生 成 さ れ る FLAG_ACTIVITY_MULTIPLE_TASK は FLAG_ACTIVITY_NEW_TASK と 同 時 に 設 定 することもできる この 場 合 に は タスクが 必 ず 新 規 生 成 される どちらの 設 定 もタスクを 生 成 する 可 能 性 があるため センシティブな 情 報 を 扱 う Intent には 設 定 しないようにすべきである Intent の 送 信 例 Intent intent = new Intent(); // ポイント 6 Activity に 送 信 する Intent には フラグ FLAG_ACTIVITY_NEW_TASK を 設 定 しない intent.setclass(this, PrivateActivity.class); intent.putextra("param", "センシティブな 情 報 "); startactivityforresult(intent, REQUEST_CODE); なお Activity に 送 信 する Intent に FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS フラグを 明 示 的 に 設 定 するこ とで タスクが 生 成 されたとしてもその 内 容 が 読 み 取 られないようにできると 考 えるかもしれない しかしながら この 方 法 を 用 いても 送 信 された Intent の 内 容 を 読 み 取 ることが 可 能 である したがって FLAG_ACTIVITY_NEW_TASK の 使 用 は 避 けるべきである exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Activity の 場 合 ) および Activity に 送 信 され る Intent の 読 み 取 り ルート Activity について も 参 照 すること 受 信 Intent の 安 全 性 を 確 認 する ( 必 須 ) Activity のタイプによって 若 干 リスクは 異 なるが 受 信 Intent のデータを 処 理 する 際 には まず 受 信 Intent の 安 全 性 を 確 認 しなければならない 公 開 Activity は 不 特 定 多 数 のアプリから Intent を 受 け 取 るため マルウェアの 攻 撃 Intent を 受 け 取 る 可 能 性 があ る 非 公 開 Activity は 他 のアプリから Intent を 直 接 受 け 取 ることはない しかし 同 一 アプリ 内 の 公 開 Activity が 他 のアプリから 受 け 取 った Intent のデータを 非 公 開 Activity に 転 送 することがあるため 受 信 Intent を 無 条 件 に 安 全 であると 考 えてはならない パートナー 限 定 Activity や 自 社 限 定 Activity はその 中 間 のリスクであるため やはり 受 信 Intent の 安 全 性 を 確 認 する 必 要 がある 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 75

78 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 自 社 アプリだけから 利 用 できる 自 社 限 定 Activity を 作 る 場 合 独 自 定 義 Signature Permission により 保 護 しなけ ればならない AndroidManifest.xml での Permission 定 義 Permission 要 求 宣 言 だけでは 保 護 が 不 十 分 であ るため 5.2 Permission と Protection Level の 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 を 参 照 すること 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) Activity のタイプによって setresult()を 用 いて 結 果 情 報 を 返 送 する 際 の 返 送 先 アプリの 信 用 度 が 異 なる 公 開 Activity が 結 果 情 報 を 返 送 する 場 合 結 果 返 送 先 アプリがマルウェアである 可 能 性 があり 結 果 情 報 が 悪 意 を 持 っ て 使 われる 危 険 性 がある 非 公 開 Activity や 自 社 限 定 Activity の 場 合 は 結 果 返 送 先 は 自 社 アプリであるため 結 果 情 報 の 扱 いをあまり 心 配 する 必 要 はない パートナー 限 定 Activity の 場 合 はその 中 間 に 位 置 する このように Activity から 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 の 漏 洩 に 配 慮 しなければならない 結 果 情 報 を 返 送 する 場 合 の 例 public void onreturnresultclick(view view) { // ポイント 6 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい Intent intent = new Intent(); intent.putextra("result", "パートナーアプリに 開 示 してよい 情 報 "); setresult(result_ok, intent); finish(); 利 用 先 Activity が 固 定 できる 場 合 は 明 示 的 Intent で Activity を 利 用 する ( 必 須 ) 暗 黙 的 Intent により Activity を 利 用 すると 最 終 的 にどの Activity に Intent が 送 信 されるかは Android OS 任 せ になってしまう もしマルウェアに Intent が 送 信 されてしまうと 情 報 漏 洩 が 生 じる 一 方 明 示 的 Intent により Activity を 利 用 すると 指 定 した Activity 以 外 が Intent を 受 信 することはなく 比 較 的 安 全 である 処 理 を 任 せるアプリ(の Activity)をユーザーに 選 択 させるなど 利 用 先 Activity を 実 行 時 に 決 定 したい 場 合 を 除 け ば 利 用 先 Activity はあらかじめ 特 定 できる このような Activity を 利 用 する 場 合 には 明 示 的 Intent を 利 用 すべき である 同 一 アプリ 内 の Activity を 明 示 的 Intent で 利 用 する Intent intent = new Intent(this, PictureActivity.class); intent.putextra("barcode", barcode); startactivity(intent); 他 のアプリの 公 開 Activity を 明 示 的 Intent で 利 用 する Intent intent = new Intent(); intent.setclassname( 76 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

79 "org.jssec.android.activity.publicactivity", "org.jssec.android.activity.publicactivity.publicactivity"); startactivity(intent); ただし 他 のアプリの 公 開 Activity を 明 示 的 Intent で 利 用 した 場 合 も 相 手 先 Activity を 含 むアプリがマルウェアで ある 可 能 性 がある 宛 先 をパッケージ 名 で 限 定 したとしても 相 手 先 アプリが 実 は 本 物 アプリと 同 じパッケージ 名 を 持 つ 偽 物 アプリである 可 能 性 があるからだ このようなリスクを 排 除 したい 場 合 は パートナー 限 定 Activity や 自 社 限 定 Activity の 使 用 を 検 討 する 必 要 がある exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Activity の 場 合 ) も 参 照 すること 利 用 先 Activity からの 戻 り Intent の 安 全 性 を 確 認 する ( 必 須 ) Activity のタイプによって 若 干 リスクは 異 なるが 戻 り 値 として 受 信 した Intent のデータを 処 理 する 際 には まず 受 信 Intent の 安 全 性 を 確 認 しなければならない 利 用 先 Activity が 公 開 Activity の 場 合 不 特 定 のアプリから 戻 り Intent を 受 け 取 るため マルウェアの 攻 撃 Intent を 受 け 取 る 可 能 性 がある 利 用 先 Activity が 非 公 開 Activity の 場 合 同 一 アプリ 内 から 戻 り Intent を 受 け 取 るので リスクはないように 考 えがちだが 他 のアプリから 受 け 取 った Intent のデータを 間 接 的 に 戻 り 値 として 転 送 することが あるため 受 信 Intent を 無 条 件 に 安 全 であると 考 えてはならない 利 用 先 Activity がパートナー 限 定 Activity や 自 社 限 定 Activity の 場 合 その 中 間 のリスクであるため やはり 受 信 Intent の 安 全 性 を 確 認 する 必 要 がある 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 他 社 の 特 定 アプリと 連 携 する 場 合 は 利 用 先 Activity を 確 認 する ( 必 須 ) 他 社 の 特 定 アプリと 連 携 する 場 合 にはホワイトリストによる 確 認 方 法 がある 自 アプリ 内 に 利 用 先 アプリの 証 明 書 ハ ッシュを 予 め 保 持 しておく 利 用 先 の 証 明 書 ハッシュと 保 持 している 証 明 書 ハッシュが 一 致 するかを 確 認 することで なりすましアプリに Intent を 発 行 することを 防 ぐことができる 具 体 的 な 実 装 方 法 についてはサンプルコードセクショ ン パートナー 限 定 Activity を 作 る 利 用 する を 参 照 すること また 技 術 的 な 詳 細 に 関 しては 利 用 元 アプリを 確 認 する を 参 照 すること 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) Permission により 保 護 されている 情 報 資 産 および 機 能 資 産 を 他 のアプリに 二 次 的 に 提 供 する 場 合 には 提 供 先 ア プリに 対 して 同 一 の Permission を 要 求 するなどして その 保 護 水 準 を 維 持 しなければならない Android の Permission セキュリティモデルでは 保 護 された 資 産 に 対 するアプリからの 直 接 アクセスについてのみ 権 限 管 理 を 行 う この 仕 様 上 の 特 性 により アプリに 取 得 された 資 産 がさらに 他 のアプリに 保 護 のために 必 要 な Permission を 要 求 することなく 提 供 される 可 能 性 がある このことは Permission を 再 委 譲 (Redelegation)していることと 実 質 的 に 等 価 なので Permission の 再 委 譲 問 題 と 呼 ばれる Permission の 再 委 譲 問 題 を 参 照 すること All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 77

80 センシティブな 情 報 はできる 限 り 送 らない ( 推 奨 ) 不 特 定 多 数 のアプリと 連 携 する 場 合 にはセンシティブな 情 報 を 送 ってはならない 特 定 のアプリと 連 携 する 場 合 にお いても 意 図 しないアプリに Intent を 発 行 してしまった 場 合 や 第 三 者 による Intent の 盗 聴 などで 情 報 が 漏 洩 してしま うリスクがある Activity 利 用 時 のログ 出 力 について を 参 照 すること センシティブな 情 報 を Activity に 送 付 する 場 合 その 情 報 の 漏 洩 リスクを 検 討 しなければならない 公 開 Activity に 送 付 した 情 報 は 必 ず 漏 洩 すると 考 えなければならない またパートナー 限 定 Activity や 自 社 限 定 Activity に 送 付 し た 情 報 もそれら Activity の 実 装 に 依 存 して 情 報 漏 洩 リスクの 大 小 がある 非 公 開 Activity に 送 付 する 情 報 に 至 って も Intent の data に 含 めた 情 報 は LogCat 経 由 で 漏 洩 するリスクがある Intent の extras は LogCat に 出 力 され ないので センシティブな 情 報 は extras で 送 付 するとよい センシティブな 情 報 はできるだけ 送 付 しないように 工 夫 すべきである 送 付 する 場 合 も 利 用 先 Activity は 信 頼 でき る Activity に 限 定 し Intent の 情 報 が LogCat へ 漏 洩 しないように 配 慮 しなければならない また ルート Activity にはセンシティブな 情 報 を 送 ってはならない ルート Activity とは タスクが 生 成 された 時 に 最 初 に 呼 び 出 された Activity のことである 例 えば ランチャーから 起 動 された Activity は 常 にルート Activity である ルート Activity に 関 しての 詳 細 は Activity に 送 信 される Intent の 読 み 取 り ルート Activit y について も 参 照 すること 78 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

81 アドバンスト exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Activity の 場 合 ) このガイド 文 書 では Activity の 用 途 から 非 公 開 Activity 公 開 Activity パートナー 限 定 Activity 自 社 限 定 Activity の 4 タイプの Activity について 実 装 方 法 を 述 べている 各 タイプに 許 されている AndroidManifest.xml の exported 属 性 と intent-filter 要 素 の 組 み 合 わせを 次 の 表 にまとめた 作 ろうとしている Activity のタイプと exported 属 性 および intent-filter 要 素 の 対 応 が 正 しいことを 確 認 すること 表 exported 属 性 の 値 true false 無 指 定 intent-filter 定 義 がある 公 開 ( 使 用 禁 止 ) ( 使 用 禁 止 ) intent-filter 定 義 がない 公 開 パートナー 限 定 非 公 開 ( 使 用 禁 止 ) 自 社 限 定 exported 属 性 の 値 で intent-filter 定 義 がある & exported= false を 使 用 禁 止 にしているのは Android の 振 る 舞 いに 抜 け 穴 があり Intent Filter の 性 質 上 意 図 せず 他 アプリの Activity を 呼 び 出 してしまう 場 合 が 存 在 する ためである 以 下 の 2 つの 図 は その 説 明 のためのものである 図 は 同 一 アプリ 内 からしか 非 公 開 Activity(アプリ A)を 暗 黙 的 Intent で 呼 び 出 せない 正 常 な 動 作 の 例 である Intent-filter( 図 中 action="x")を 定 義 しているのが アプリ A しかいないので 意 図 通 りの 動 きとなっている アプリA 暗 黙 的 Intentを 使 って Activityを 呼 び 出 す Intent( X ) 非 公 開 Activity A-1 exported= false action= X アプリC 暗 黙 的 Intentを 使 って Activityを 呼 び 出 す Intent( X ) Activity A-1は 非 公 開 Activityなので ア プリAからしか 呼 び 出 すことはできない Android 端 末 図 図 は アプリ A に 加 えてアプリ B でも 同 じ intent-filter( 図 中 action="x")を 定 義 している 場 合 である 図 では アプリ A が 暗 黙 的 Intent を 送 信 して 同 一 アプリ 内 の 非 公 開 Activity を 呼 び 出 そうとするが アプリケ ーションの 選 択 ダイアログが 表 示 され ユーザーの 選 択 によって 公 開 Activity(B-1)が 呼 び 出 されてしまう 例 を 示 し ている これにより 他 アプリに 対 してセンシティブな 情 報 を 送 信 したり 意 図 せぬ 戻 り 値 を 受 け 取 る 可 能 性 が 生 じてし All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 79

82 まう Android アプリのセキュア 設 計 セキュアコーディングガイド アプリA 暗 黙 的 Intentを 使 って Activityを 呼 び 出 す Intent( X ) 非 公 開 Activity A-1 exported= false action= X アプリケー ションの 選 択 A-1 B-1 アプリB 公 開 Activity B-1 exported= true action= X 同 じactionを 定 義 したActivity B-1がある と OSにより 選 択 ダイアログが 表 示 され ユーザーの 選 択 によって 公 開 Activity B-1が 呼 ばれてしまう Android 端 末 図 このように Intent Filter を 用 いた 非 公 開 Activity の 暗 黙 的 Intent 呼 び 出 しは 意 図 せぬアプリとの 情 報 のやり 取 りを 許 してしまうので 行 うべきではない なお この 挙 動 はアプリ A アプリ B のインストール 順 序 には 依 存 しないことを 確 認 している 利 用 元 アプリを 確 認 する ここではパートナー 限 定 Activity の 実 装 に 関 する 技 術 情 報 を 解 説 する パートナー 限 定 Activity はホワイトリストに 登 録 された 特 定 のアプリからのアクセスを 許 可 し それ 以 外 のアプリからはアクセスを 拒 否 する Activity である 自 社 以 外 のアプリもアクセス 許 可 対 象 となるため Signature Permission による 防 御 手 法 は 利 用 できない 基 本 的 な 考 え 方 は パートナー 限 定 Activity の 利 用 元 アプリの 身 元 を 確 認 し ホワイトリストに 登 録 されたアプリであ ればサービスを 提 供 する 登 録 されていないアプリであればサービスを 提 供 しないというものである 利 用 元 アプリの 身 元 確 認 は 利 用 元 アプリが 持 つ 証 明 書 を 取 得 し その 証 明 書 のハッシュ 値 をホワイトリストのハッシュ 値 と 比 較 する ことで 行 う ここでわざわざ 利 用 元 アプリの 証 明 書 を 取 得 せずとも 利 用 元 アプリの パッケージ 名 との 比 較 で 十 分 ではない か?と 疑 問 を 持 たれた 方 もいるかと 思 う しかしパッケージ 名 は 任 意 に 指 定 できるため 他 のアプリへの 成 りすましが 簡 単 である 成 りすまし 可 能 なパラメータは 身 元 確 認 用 には 使 えない 一 方 アプリの 持 つ 証 明 書 であれば 身 元 確 認 に 使 うことができる 証 明 書 に 対 応 する 署 名 用 の 開 発 者 鍵 は 本 物 のアプリ 開 発 者 しか 持 っていないため 第 三 者 が 同 じ 証 明 書 を 持 ち 尚 且 つ 署 名 検 証 が 成 功 するアプリを 作 成 することはできないからだ ホワイトリストはアクセスを 80 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

83 許 可 したいアプリの 証 明 書 データを 丸 ごと 保 持 してもよいが サンプルコードではホワイトリストのデータサイズを 小 さ くするために 証 明 書 データの SHA-256 ハッシュ 値 を 保 持 することにしている この 方 法 には 次 の 二 つの 制 約 条 件 がある 利 用 元 アプリにおいて startactivity()ではなく startactivityforresult()を 使 用 しなければならない 利 用 元 アプリにおいて Activity 以 外 から 呼 び 出 すことはできない 2 つ 目 の 制 約 事 項 はいわば 1 つ 目 の 制 約 事 項 の 結 果 として 課 される 制 約 であるので 厳 密 には 1 つの 同 じ 制 約 と 言 える この 制 約 は 呼 び 出 し 元 アプリのパッケージ 名 を 取 得 する Activity.getCallingPackage()の 制 約 により 生 じてい る Activity.getCallingPackage()は startactivityforresult()で 呼 び 出 された 場 合 にのみ 利 用 元 アプリのパッケ ージ 名 を 返 すが 残 念 ながら startactivity()で 呼 び 出 された 場 合 には null を 返 す 仕 様 となっている そのためここで 紹 介 する 方 法 は 必 ず 利 用 元 アプリが たとえ 戻 り 値 が 不 要 であったとしても startactivityforresult()を 使 わなけれ ばならないという 制 約 がある さらに startactivityforresult()は Activity クラスでしか 使 えないため 利 用 元 は Activity に 限 定 されるという 制 約 もある PartnerActivity.java package org.jssec.android.activity.partneractivity; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PartnerActivity extends Activity { // ポイント 4 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナーアプリ org.jssec.android.activity.partneruser の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.activity.partneruser", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"partner key"の 証 明 書 ハッシュ 値 "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // 以 下 同 様 に 他 のパートナーアプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 81

84 setcontentview(r.layout.main); // ポイント 4 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(this, getcallingactivity().getpackagename())) { Toast.makeText(this, " 利 用 元 アプリはパートナーアプリではない ", Toast.LENGTH_LONG).show(); finish(); return; // ポイント 5 パートナーアプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, "パートナーアプリからアクセスあり", Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { // ポイント 6 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい Intent intent = new Intent(); intent.putextra("result", "パートナーアプリに 開 示 してよい 情 報 "); setresult(result_ok, intent); finish(); PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); 82 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

85 PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 83

86 Activity に 送 信 される Intent の 読 み 取 り(Android 5.0 より 前 のバージョンについて) Android 5.0(API Level 21) 以 降 では getrecenttasks()から 取 得 できる 情 報 が 自 分 自 身 のタスク 情 報 およびホ ームアプリのような 機 密 情 報 ではないものに 限 定 された しかし Android 5.0 より 前 のバージョンでは 自 分 自 身 の タスク 以 外 の 情 報 も 読 み 取 ることができる 以 下 に Android 5.0 より 前 のバージョンで 発 生 する 本 問 題 の 内 容 を 解 説 する タスクのルート Activity に 送 信 された Intent は タスク 履 歴 に 追 加 される ルート Activity とはタスクの 起 点 となる Activity のことである タスク 履 歴 に 追 加 された Intent は ActivityManager クラスを 使 うことでどのアプリからも 自 由 に 読 み 出 すことが 可 能 である アプリ から タスク 履 歴 を 参 照 す るためのサンプルコードを 以 下 に 示 す タスク 履 歴 を 参 照 す るためには AndroidManifest.xml に GET_TASKS Permission の 利 用 を 指 定 する AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.android.intent.maliciousactivity" > <!-- GET_TASKS Permission を 指 定 する --> <uses-permission android:name="android.permission.get_tasks" /> <application android:allowbackup="false" > <activity android:name=".maliciousactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> MaliciousActivity.java package org.jssec.android.intent.maliciousactivity; import java.util.list; import java.util.set; import android.app.activity; import android.app.activitymanager; import android.content.intent; import android.os.bundle; import android.util.log; public class MaliciousActivity extends Activity { 84 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

87 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.malicious_activity); // ActivityManager を 取 得 する ActivityManager activitymanager = (ActivityManager) getsystemservice(activity_service); // タスクの 履 歴 を 最 新 100 件 取 得 する List<ActivityManager.RecentTaskInfo> list = activitymanager.getrecenttasks(100, ActivityManager.RECENT_WITH_EXCLUDED); for (ActivityManager.RecentTaskInfo r : list) { // ルート Activity に 送 信 された Intent を 取 得 し Log に 表 示 する Intent intent = r.baseintent; Log.v("baseIntent", intent.tostring()); Log.v(" action:", intent.getaction()); Log.v(" data:", intent.getdatastring()); if (r.origactivity!= null) { Log.v(" pkg:", r.origactivity.getpackagename() + r.origactivity.getclassname()); Bundle extras = intent.getextras(); if (extras!= null) { Set<String> keys = extras.keyset(); for(string key : keys) { Log.v(" extras:", key + "=" + extras.get(key).tostring()); ActivityManager クラスの getrecenttasks()により 指 定 した 件 数 のタスク 履 歴 を 取 得 することができる 各 タスク の 情 報 は ActivityManager.RecentTaskInfo クラスのインスタンスに 格 納 されるが そのメンバー 変 数 baseintent には タスクのルート Activity に 送 信 された Intent が 格 納 されている ルート Activity とはタスクが 生 成 された 時 に 呼 び 出 された Activity であるので Activity を 呼 び 出 す 際 には 以 下 の 条 件 をどちらも 満 たさないように 注 意 しなけ ればならない Activity が 呼 び 出 された 際 に タスクが 新 規 に 生 成 される 呼 び 出 された Activity がバックグラウンドあるいはフォアグラウンド 上 に 既 に 存 在 するタスクのルート Activity で ある ルート Activity について ルート Activity とはタスクの 起 点 となる Activity のことである タスクが 生 成 された 時 に 起 動 された Activity のことで ある と 言 ってもよい 例 えば デフォルト 設 定 の Activity がランチャーから 起 動 された 場 合 その Activity はルート Activity となる Android の 仕 様 によると ルート Activity に 送 信 される Intent の 内 容 は 任 意 のアプリによって 読 み 取 られる 恐 れがある そこで ルート Activity へセンシティブな 情 報 を 送 信 しないように 対 策 を 講 じる 必 要 がある 本 ガイドでは 呼 び 出 された Activity がルートとなるのを 防 ぐために 以 下 の 3 点 をルールに 掲 げた All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 85

88 taskaffinity を 指 定 しない launchmode を 指 定 しない Activity に 送 信 する Intent には FLAG_ACTIVITY_NEW_TASK を 設 定 しない 以 下 Activity がルートとなるのはどのような 場 合 かを 中 心 にルート Activity について 考 察 する 呼 び 出 された Activity がルートとなるための 条 件 に 関 連 するのは 以 下 に 挙 げる 項 目 である 呼 び 出 される Activity の 起 動 モード 呼 び 出 される Activity のタスクとその 起 動 状 態 まず 呼 び 出 される Activity の 起 動 モード について 説 明 する Activity の 起 動 モードは AndroidManifest.xml に android:launchmode を 記 述 することで 設 定 できる 記 述 しない 場 合 は standard とみなされる また 起 動 モー ドは Intent に 設 定 するフラグによっても 変 更 可 能 である FLAG_ACTIVITY_NEW_TASK フラグは Activity を singletask モードで 起 動 させる 指 定 可 能 な 起 動 モードは 次 のとおりである 特 に ルート Activity との 関 連 に 焦 点 を 当 てて 説 明 する standard このモードで 呼 び 出 された Activity はルートとなることはなく 呼 び 出 し 側 のタスクに 所 属 する また 呼 び 出 しの 度 に Activity のインスタンスが 生 成 される singletop standard と 同 様 であるが フォアグラウンドタスクの 最 前 面 に 表 示 されている Activity を 起 動 する 場 合 には インスタンスが 生 成 されないという 点 が 異 なる singletask Activity はアフィニティの 値 に 従 って 所 属 するタスクが 決 まる Activity のアフィニティと 一 致 するタスクがバック グラウンドあるいはフォアグラウンドに 存 在 しない 場 合 には タスクが Activity のインスタンスとともに 新 規 に 生 成 される 存 在 する 場 合 にはどちらも 生 成 されない 前 者 では 起 動 された Activity のインスタンスはルートとな る singleinstance singletask と 同 様 であるが 次 の 点 で 異 なる 新 規 に 生 成 されたタスクには ルート Activity のみが 所 属 でき る 点 である したがって このモードで 起 動 された Activity のインスタンスは 常 にルートである ここで 注 意 が 必 要 なのは 呼 び 出 される Activity が 持 つアフィニティと 同 じ 名 前 のタスクが 既 に 存 在 している 場 合 であっても 呼 び 出 される Activity とタスクに 含 まれる Activity のクラス 名 が 異 なる 場 合 である その 場 合 は 新 規 にタスクが 生 成 される 以 上 より singletask または singleinstance で 起 動 された Activity はルートになる 可 能 性 があることが 分 かる アプリの 安 全 性 を 確 保 するためには これらのモードで 起 動 しないようにしなければならない 次 に 呼 び 出 される Activity のタスクとその 起 動 状 態 について 説 明 する たとえ Activity が standard モードで 呼 び 出 されたとしても その Activity が 所 属 するタスクの 状 態 によってルート Activity となる 場 合 がある 86 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

89 例 として 呼 び 出 される Activity のタスクが 既 にバックグラウンドで 起 動 している 場 合 を 考 える 問 題 となるのは そ のタスクの Activity インスタンスが singleinstance で 起 動 している 場 合 である standard で 呼 び 出 された Activity のアフィニティがタスクと 同 じだった 時 に 既 存 の singleinstance の Activity の 制 限 により 新 規 タスクが 生 成 される ただし それぞれの Activity のクラス 名 が 同 じ 場 合 は タスクは 生 成 されず インスタンスは 既 存 のもの が 利 用 される いずれにしろ 呼 び 出 された Activity はルート Activity になる 以 上 のように ルート Activity が 呼 び 出 される 条 件 は 実 行 時 の 状 態 に 依 存 するなど 複 雑 である アプリ 開 発 の 際 に は standard モードで Activity を 呼 び 出 すように 工 夫 すべきである 非 公 開 Activity に 送 信 される Intent が 他 アプリから 読 み 取 られる 例 として 非 公 開 Activity の 呼 び 出 し 側 Activity を singleinstance モードで 起 動 する 場 合 のサンプルコードを 以 下 に 示 す このサンプルコードでは 非 公 開 Activityが standard モードで 起 動 されるが 呼 び 出 し 側 Activityの singleinstance モードの 条 件 により 非 公 開 Activity は 新 規 タスクのルート Activity となってしまう この 時 非 公 開 Activity に 送 信 されるセンシティブな 情 報 は タスク 履 歴 に 記 録 されるため 任 意 のアプリから 読 み 取 り 可 能 である なお 呼 び 出 し 側 Activity 非 公 開 Activity と もに 同 一 のアフィニティを 持 つ AndroidManifest.xml( 非 推 奨 ) <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.activity.singleinstanceactivity" > <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- ルート Activity の 起 動 モードを singleinstance とする --> <!-- アフィニティは 設 定 せず アプリのパッケージ 名 とする --> <activity android:name=".privateuseractivity" android:label="@string/app_name" android:launchmode="singleinstance" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 非 公 開 Activity --> <!-- 起 動 モードを standard とする --> <!-- アフィニティは 設 定 せず アプリのパッケージ 名 とする --> <activity android:name=".privateactivity" android:label="@string/app_name" android:exported="false" /> </application> </manifest> 非 公 開 Activity は 受 信 した Intent に 対 して 結 果 を 返 すのみである All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 87

90 PrivateActivity.java package org.jssec.android.activity.singleinstanceactivity; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PrivateActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.private_activity); // 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = getintent().getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); public void onreturnresultclick(view view) { Intent intent = new Intent(); intent.putextra("result", "センシティブな 情 報 "); setresult(result_ok, intent); finish(); 非 公 開 Activity の 呼 び 出 し 側 では Intent にフラグを 設 定 せずに standard モードで 非 公 開 Activity を 起 動 して いる PrivateUserActivity.java package org.jssec.android.activity.singleinstanceactivity; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class PrivateUserActivity extends Activity { private static final int REQUEST_CODE = 1; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user_activity); public void onuseactivityclick(view view) { // 非 公 開 Activity を"standard"モードで 起 動 する Intent intent = new Intent(); intent.setclass(this, PrivateActivity.class); 88 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

91 intent.putextra("param", "センシティブな 情 報 "); startactivityforresult(intent, REQUEST_CODE); public void onactivityresult(int requestcode, int resultcode, Intent data) { super.onactivityresult(requestcode, resultcode, data); if (resultcode!= RESULT_OK) return; switch (requestcode) { case REQUEST_CODE: String result = data.getstringextra("result"); // 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(this, String.format(" 結 果 %s を 受 け 取 った ", result), Toast.LENGTH_LONG).show(); break; Activity 利 用 時 のログ 出 力 について Activity を 利 用 する 際 に ActivityManager が Intent の 内 容 を LogCat に 出 力 する 以 下 の 内 容 は LogCat に 出 力 されるため センシティブな 情 報 が 含 まれないように 注 意 すべきだ 利 用 先 パッケージ 名 利 用 先 クラス 名 Intent#setData()で 設 定 した URI 例 えば メール 送 信 する 場 合 URI にメールアドレスを 指 定 して Intent を 発 行 するとメールアドレスが LogCat に 出 力 されてしまう Intent#putExtra()で 設 定 した 値 は LogCat に 出 力 されないため Extras に 設 定 して 送 るようにした 方 が 良 い 次 のようにメール 送 信 すると LogCat にメールアドレスが 表 示 されてしまう MainActivity.java // URI は LogCat に 出 力 される Uri uri = Uri.parse("mailto:test@gmail.com"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); startactivity(intent); 次 のように Extras を 使 用 すると LogCat にメールアドレスが 表 示 されなくなる MainActivity.java // Extra に 設 定 した 内 容 は LogCat に 出 力 されない Uri uri = Uri.parse("mailto:"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); intent.putextra(intent.extra_ , new String[] {"test@gmail.com"); startactivity(intent); All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する 89

92 ただし ActivityManager#getRecentTasks() によって Intent の Extras を 他 のアプリから 直 接 読 める 場 合 がある ので 注 意 すること 詳 しくは taskaffinity を 指 定 しない ( 必 須 ) launchmode を 指 定 しない ( 必 須 ) および Activity に 送 信 する Intent には FLAG_ACTIVITY_NEW_TASK を 設 定 しない ( 必 須 ) を 参 照 のこと PreferenceActivity の Fragment Injection 対 策 について PreferenceActivity を 継 承 したクラスが 公 開 Activity となっている 場 合 Fragment Injection 4 と 呼 ばれる 問 題 が 発 生 する 可 能 性 がある この 問 題 を 防 ぐためには PreferenceActivity.IsValidFragment()を override し 引 数 の 値 を 適 切 にチェックすることで Activity が 意 図 しない Fragment を 扱 わないようにする 必 要 がある ( 入 力 データ の 安 全 性 については 3.2 入 力 データの 安 全 性 を 確 認 する 参 照 ) 以 下 に IsValidFragment()を override したサンプルを 示 す なお ソースコードの 難 読 化 を 行 うと クラス 名 が 変 わ り 引 数 の 値 との 比 較 結 果 が 変 わってまう 可 能 性 があるので 別 途 対 応 が 必 要 になる override した isvalidfragment()メソッドの 例 protected boolean isvalidfragment(string fragmentname) { // 難 読 化 時 の 対 応 は 別 途 行 うこと return PreferenceFragmentA.class.getName().equals(fragmentName) PreferenceFragmentB.class.getName().equals(fragmentName) PreferenceFragmentC.class.getName().equals(fragmentName) PreferenceFragmentD.class.getName().equals(fragmentName); なお アプリの targetsdkversion が 19 以 上 である 場 合 PreferenceActivity.isValidFragment()を override し ないと Fragment が 挿 入 された 段 階 (isvalidfragment()が 呼 ばれた 段 階 )でセキュリティ 例 外 が 発 生 しアプリが 終 了 するため PreferenceActivity.isValidFragment()の override が 必 須 である 4 Fragment Injection の 詳 細 は 以 下 の URL を 参 照 のこと 90 All rights reserved Japan Smartphone Security Association. Activity を 作 る 利 用 する

93 4.2. Broadcast を 受 信 する 送 信 する サンプルコード Broadcast を 受 信 するには Broadcast Receiver を 作 る 必 要 がある どのような Broadcast を 受 信 するかによって Broadcast Receiver が 抱 えるリスクや 適 切 な 防 御 手 段 が 異 なる 次 の 判 定 フローによって 作 成 する Broadcast Receiver がどのタイプであるかを 判 断 できる ちなみに パートナー 限 定 の 連 携 に 必 要 な Broadcast 送 信 元 アプリ のパッケージ 名 を 受 信 側 アプリで 確 認 する 手 段 がないため パートナー 限 定 Broadcast Receiver を 作 る 事 はできな い はじめ Yes アプリ 内 だけで Broadcastを 送 受 信 する No No 自 社 アプリ 間 だけで Broadcastを 送 受 信 する Yes Private 非 公 開 Broadcast Receiver Public 公 開 Broadcast Receiver Proprietary 自 社 限 定 Broadcast Receiver 図 また Broadcast Receiver にはその 定 義 方 法 により 静 的 Broadcast Receiver と 動 的 Broadcast Receiver との 2 種 類 があり 下 表 のような 特 徴 の 違 いがある サンプルコード 中 では 両 方 の 実 装 方 法 を 紹 介 している なお どのよう な 相 手 に Broadcast を 送 信 するかによって 送 信 する 情 報 の 適 切 な 防 御 手 段 が 決 まるため 送 信 側 の 実 装 について も 合 わせて 説 明 する 表 静 的 Broadcast Receiver 定 義 方 法 AndroidManifest.xml に <receiver> 要 素 を 記 述 す ることで 定 義 する 特 徴 シ ス テ ム か ら 送 信 さ れ る 一 部 の Broadcast (ACTION_BATTERY_CHANGED など)を 受 信 で きない 制 約 がある アプリが 初 回 起 動 してからアンインストールされる までの 間 Broadcast を 受 信 できる 動 的 Broadcast Receiver プ ロ グ ラ ム 中 で registerreceiver() お よ び 静 的 Broadcast Receiver では 受 信 できない Broadcast でも 受 信 できる All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 91

94 unregisterreceiver() を 呼 び 出 すことにより 動 的 に Broadcast Receiver を 登 録 / 登 録 解 除 する Activity が 前 面 に 出 ている 期 間 だけ Broadcast を 受 信 したいなど Broadcast の 受 信 可 能 期 間 を プログラムで 制 御 できる 非 公 開 の Broadcast Receiver を 作 ることはでき ない 92 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

95 非 公 開 Broadcast Receiver - Broadcast を 受 信 する 送 信 する 非 公 開 Broadcast Receiver は 同 一 アプリ 内 から 送 信 された Broadcast だけを 受 信 できる Broadcast Receiver であり もっとも 安 全 性 の 高 い Broadcast Receiver である 動 的 Broadcast Receiver を 非 公 開 で 登 録 することはで きないため 非 公 開 Broadcast Receiver では 静 的 Broadcast Receiver だけで 構 成 される ポイント(Broadcast を 受 信 する): 1. exported="false"により 明 示 的 に 非 公 開 設 定 する 2. 同 一 アプリ 内 から 送 信 された Broadcast であっても 受 信 Intent の 安 全 性 を 確 認 する 3. 結 果 を 返 す 場 合 送 信 元 は 同 一 アプリ 内 であるから センシティブな 情 報 を 返 送 してよい AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.broadcast.privatereceiver" > <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <!-- 非 公 開 Broadcast Receiver を 定 義 する --> <!-- ポイント 1 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <receiver android:name=".privatereceiver" android:exported="false" /> <activity android:name=".privatesenderactivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> PrivateReceiver.java package org.jssec.android.broadcast.privatereceiver; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.widget.toast; public class PrivateReceiver extends BroadcastReceiver { public void onreceive(context context, Intent intent) { All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 93

96 // ポイント 2 同 一 アプリ 内 から 送 信 された Broadcast であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = intent.getstringextra("param"); Toast.makeText(context, String.format(" %s を 受 信 した ", param), Toast.LENGTH_SHORT).show(); // ポイント 3 送 信 元 は 同 一 アプリ 内 であるから センシティブな 情 報 を 返 送 してよい setresultcode(activity.result_ok); setresultdata("センシティブな 情 報 from Receiver"); abortbroadcast(); 次 に 非 公 開 Broadcast Receiver へ Broadcast 送 信 するサンプルコードを 示 す ここで 説 明 する 非 公 開 Broadcast Receiver への Broadcast 送 信 方 法 は セキュリティ 面 では 安 全 であるものの Sticky が 使 えないという 制 約 がある ことに 注 意 が 必 要 である ポイント(Broadcast を 送 信 する): 4. 同 一 アプリ 内 Receiver はクラス 指 定 の 明 示 的 Intent で Broadcast 送 信 する 5. 送 信 先 は 同 一 アプリ 内 Receiver であるため センシティブな 情 報 を 送 信 してよい 6. 同 一 アプリ 内 Receiver からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する PrivateSenderActivity.java package org.jssec.android.broadcast.privatereceiver; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.textview; public class PrivateSenderActivity extends Activity { public void onsendnormalclick(view view) { // ポイント 4 同 一 アプリ 内 Receiver はクラス 指 定 の 明 示 的 Intent で Broadcast 送 信 する Intent intent = new Intent(this, PrivateReceiver.class); // ポイント 5 送 信 先 は 同 一 アプリ 内 Receiver であるため センシティブな 情 報 を 送 信 してよい intent.putextra("param", "センシティブな 情 報 from Sender"); sendbroadcast(intent); public void onsendorderedclick(view view) { // ポイント 4 同 一 アプリ 内 Receiver はクラス 指 定 の 明 示 的 Intent で Broadcast 送 信 する Intent intent = new Intent(this, PrivateReceiver.class); // ポイント 5 送 信 先 は 同 一 アプリ 内 Receiver であるため センシティブな 情 報 を 送 信 してよい intent.putextra("param", "センシティブな 情 報 from Sender"); sendorderedbroadcast(intent, null, mresultreceiver, null, 0, null, null); 94 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

97 private BroadcastReceiver mresultreceiver = new BroadcastReceiver() { public void onreceive(context context, Intent intent) { ; // ポイント 6 同 一 アプリ 内 Receiver からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String data = getresultdata(); PrivateSenderActivity.this.logLine( String.format(" 結 果 %s を 受 信 した ", data)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 95

98 公 開 Broadcast Receiver - Broadcast を 受 信 する 送 信 する 公 開 Broadcast Receiver は 不 特 定 多 数 のアプリから 送 信 された Broadcast を 受 信 できる Broadcast Receiver である マルウェアが 送 信 した Broadcast を 受 信 することがあることに 注 意 が 必 要 だ ポイント(Broadcast を 受 信 する): 1. exported="true"により 明 示 的 に 公 開 設 定 する 2. 受 信 Intent の 安 全 性 を 確 認 する 3. 結 果 を 返 す 場 合 センシティブな 情 報 を 含 めない 公 開 Broadcast Receiver のサンプルコードである Public Receiver は 静 的 Broadcast Receiver および 動 的 Broadcast Receiver の 両 方 で 利 用 される PublicReceiver.java package org.jssec.android.broadcast.publicreceiver; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.widget.toast; public class PublicReceiver extends BroadcastReceiver { private static final String MY_BROADCAST_PUBLIC = "org.jssec.android.broadcast.my_broadcast_public"; public boolean isdynamic = false; private String getname() { return isdynamic? " 公 開 動 的 Broadcast Receiver" : " 公 開 静 的 Broadcast Receiver"; public void onreceive(context context, Intent intent) { // ポイント 2 受 信 Intent の 安 全 性 を 確 認 する // 公 開 Broadcast Receiver であるため 利 用 元 アプリがマルウェアである 可 能 性 がある // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (MY_BROADCAST_PUBLIC.equals(intent.getAction())) { String param = intent.getstringextra("param"); Toast.makeText(context, String.format("%s:\n %s を 受 信 した ", getname(), param), Toast.LENGTH_SHORT).show(); // ポイント 3 結 果 を 返 す 場 合 センシティブな 情 報 を 含 めない // 公 開 Broadcast Receiver であるため // Broadcast の 送 信 元 アプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい setresultcode(activity.result_ok); setresultdata(string.format("センシティブではない 情 報 from %s", getname())); abortbroadcast(); 96 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

99 静 的 Broadcast Receiver は AndroidManifest.xml で 定 義 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.broadcast.publicreceiver" > <application android:allowbackup="false" > <!-- 公 開 静 的 Broadcast Receiver を 定 義 する --> <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <receiver android:name=".publicreceiver" android:exported="true" > <intent-filter> <action android:name="org.jssec.android.broadcast.my_broadcast_public" /> </intent-filter> </receiver> <service android:name=".dynamicreceiverservice" android:exported="false" /> <activity android:name=".publicreceiveractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> 動 的 Broadcast Receiver はプログラム 中 で registerreceiver()および unregisterreceiver()を 呼 び 出 すことによ り 登 録 / 登 録 解 除 する ボタン 操 作 により 登 録 / 登 録 解 除 を 行 うために PublicReceiverActivity 上 にボタンを 配 置 している 動 的 Broadcast Receiver インスタンスは PublicReceiverActivity より 生 存 期 間 が 長 いため PublicRec eiveractivity のメンバー 変 数 として 保 持 することはできない そのため DynamicReceiverService のメンバー 変 数 として 動 的 Broadcast Receiver のインスタンスを 保 持 させ DynamicReceiverService を PublicReceiverActivit y から 開 始 / 終 了 することにより 動 的 Broadcast Receiver を 間 接 的 に 登 録 / 登 録 解 除 している DynamicReceiverService.java package org.jssec.android.broadcast.publicreceiver; import android.app.service; import android.content.intent; import android.content.intentfilter; All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 97

100 import android.os.ibinder; import android.widget.toast; public class DynamicReceiverService extends Service { private static final String MY_BROADCAST_PUBLIC = "org.jssec.android.broadcast.my_broadcast_public"; private PublicReceiver mreceiver; public IBinder onbind(intent intent) { return null; public void oncreate() { super.oncreate(); // 公 開 動 的 Broadcast Receiver を 登 録 する mreceiver = new PublicReceiver(); mreceiver.isdynamic = true; IntentFilter filter = new IntentFilter(); filter.addaction(my_broadcast_public); filter.setpriority(1); // 静 的 Broadcast Receiver より 動 的 Broadcast Receiver を 優 先 させる registerreceiver(mreceiver, filter); Toast.makeText(this, " 動 的 Broadcast Receiver を 登 録 した ", Toast.LENGTH_SHORT).show(); public void ondestroy() { super.ondestroy(); // 公 開 動 的 Broadcast Receiver を 登 録 解 除 する unregisterreceiver(mreceiver); mreceiver = null; Toast.makeText(this, " 動 的 Broadcast Receiver を 登 録 解 除 した ", Toast.LENGTH_SHORT).show(); PublicReceiverActivity.java package org.jssec.android.broadcast.publicreceiver; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; public class PublicReceiverActivity extends Activity { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); 98 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

101 public void onregisterreceiverclick(view view) { Intent intent = new Intent(this, DynamicReceiverService.class); startservice(intent); public void onunregisterreceiverclick(view view) { Intent intent = new Intent(this, DynamicReceiverService.class); stopservice(intent); 次 に 公 開 Broadcast Receiver へ Broadcast 送 信 するサンプルコードを 示 す 公 開 Broadcast Receiver に Broadcast を 送 信 する 場 合 送 信 する Broadcast がマルウェアに 受 信 されることがあることに 注 意 が 必 要 である ポイント(Broadcast を 送 信 する): 4. センシティブな 情 報 を 送 信 してはならない 5. 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する PublicSenderActivity.java package org.jssec.android.broadcast.publicsender; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.textview; public class PublicSenderActivity extends Activity { private static final String MY_BROADCAST_PUBLIC = "org.jssec.android.broadcast.my_broadcast_public"; public void onsendnormalclick(view view) { // ポイント 4 センシティブな 情 報 を 送 信 してはならない Intent intent = new Intent(MY_BROADCAST_PUBLIC); intent.putextra("param", "センシティブではない 情 報 from Sender"); sendbroadcast(intent); public void onsendorderedclick(view view) { // ポイント 4 センシティブな 情 報 を 送 信 してはならない Intent intent = new Intent(MY_BROADCAST_PUBLIC); intent.putextra("param", "センシティブではない 情 報 from Sender"); sendorderedbroadcast(intent, null, mresultreceiver, null, 0, null, null); public void onsendstickyclick(view view) { // ポイント 4 センシティブな 情 報 を 送 信 してはならない Intent intent = new Intent(MY_BROADCAST_PUBLIC); intent.putextra("param", "センシティブではない 情 報 from Sender"); //sendstickybroadcast メソッドは API Level 21 で deprecated となった sendstickybroadcast(intent); All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 99

102 Android アプリのセキュア 設 計 セキュアコーディングガイド public void onsendstickyorderedclick(view view) { // ポイント 4 センシティブな 情 報 を 送 信 してはならない Intent intent = new Intent(MY_BROADCAST_PUBLIC); intent.putextra("param", "センシティブではない 情 報 from Sender"); //sendstickybroadcast メソッドは API Level 21 で deprecated となった sendstickyorderedbroadcast(intent, mresultreceiver, null, 0, null, null); public void onremovestickyclick(view view) { Intent intent = new Intent(MY_BROADCAST_PUBLIC); //removestickybroadcast メソッドは deprecated となっている removestickybroadcast(intent); private BroadcastReceiver mresultreceiver = new BroadcastReceiver() { public void onreceive(context context, Intent intent) { ; // ポイント 5 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String data = getresultdata(); PublicSenderActivity.this.logLine( String.format(" 結 果 %s を 受 信 した ", data)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); 100 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

103 自 社 限 定 Broadcast Receiver - Broadcast を 受 信 する 送 信 する 自 社 限 定 Broadcast Receiver は 自 社 以 外 のアプリから 送 信 された Broadcast を 一 切 受 信 しない Broadcast Receiver である 複 数 の 自 社 製 アプリでシステムを 構 成 し 自 社 アプリが 扱 う 情 報 や 機 能 を 守 るために 利 用 される ポイント(Broadcast を 受 信 する): 1. Broadcast 受 信 用 の 独 自 定 義 Signature Permission を 定 義 する 2. 結 果 受 信 用 の 独 自 定 義 Signature Permission を 利 用 宣 言 する 3. exported="true"により 明 示 的 に 公 開 設 定 する 4. 静 的 Broadcast Receiver 定 義 にて 独 自 定 義 Signature Permission を 要 求 宣 言 する 5. 動 的 Broadcast Receiver を 登 録 するとき 独 自 定 義 Signature Permission を 要 求 宣 言 する 6. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 7. 自 社 アプリからの Broadcast であっても 受 信 Intent の 安 全 性 を 確 認 する 8. Broadcast 送 信 元 は 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい 9. Broadcast 送 信 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 自 社 限 定 Broadcast Receiver のサンプルコードである ProprietaryReciver は 静 的 Broadcast Receiver および 動 的 Broadcast Receiver の 両 方 で 利 用 される InhouseReceiver.java package org.jssec.android.broadcast.inhousereceiver; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.widget.toast; public class InhouseReceiver extends BroadcastReceiver { // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousereceiver.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 101

104 private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.my_broadcast_inhouse"; public boolean isdynamic = false; private String getname() { return isdynamic? " 自 社 限 定 動 的 Broadcast Receiver" : " 自 社 限 定 静 的 Broadcast Receiver"; public void onreceive(context context, Intent intent) { // ポイント 6 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(context, MY_PERMISSION, mycerthash(context))) { Toast.makeText(context, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LE NGTH_LONG).show(); return; // ポイント 7 自 社 アプリからの Broadcast であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (MY_BROADCAST_INHOUSE.equals(intent.getAction())) { String param = intent.getstringextra("param"); Toast.makeText(context, String.format("%s:\n %s を 受 信 した ", getname(), param), Toast.LENGTH_SHORT).show(); // ポイント 8 送 信 元 は 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい setresultcode(activity.result_ok); setresultdata(string.format("センシティブな 情 報 from %s", getname())); abortbroadcast(); 静 的 Broadcast Receiver は AndroidManifest.xml で 定 義 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.broadcast.inhousereceiver" > <!-- ポイント 1 Broadcast 受 信 用 の 独 自 定 義 Signature Permission を 定 義 する --> <permission android:name="org.jssec.android.broadcast.inhousereceiver.my_permission" android:protectionlevel="signature" /> <!-- ポイント 2 結 果 受 信 用 の 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.broadcast.inhousesender.my_permission" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false"> <!-- ポイント 3 exported="true"により 明 示 的 に 公 開 設 定 する --> <!-- ポイント 4 静 的 Broadcast Receiver 定 義 にて 独 自 定 義 Signature Permission を 要 求 宣 言 する --> <receiver 102 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

105 android:name="org.jssec.android.broadcast.inhousereceiver.inhousereceiver" android:exported="true" android:permission="org.jssec.android.broadcast.inhousereceiver.my_permission"> <intent-filter> <action android:name="org.jssec.android.broadcast.my_broadcast_inhouse" /> </intent-filter> </receiver> <service android:name="org.jssec.android.broadcast.inhousereceiver.dynamicreceiverservice" android:exported="false" /> <activity android:name="org.jssec.android.broadcast.inhousereceiver.inhousereceiveractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> 動 的 Broadcast Receiver はプログラム 中 で registerreceiver()および unregisterreceiver()を 呼 び 出 すことによ り 登 録 / 登 録 解 除 する ボタン 操 作 により 登 録 / 登 録 解 除 を 行 うために ProprietaryReceiverActivity 上 にボタンを 配 置 している 動 的 Broadcast Receiver インスタンスは ProprietaryReceiverActivity より 生 存 期 間 が 長 いため ProprietaryReceiverActivity のメンバー 変 数 として 保 持 することはできない そのため DynamicReceiverService のメンバー 変 数 として 動 的 Broadcast Receiver のインスタンスを 保 持 させ DynamicReceiverService を ProprietaryReceiverActivity から 開 始 / 終 了 することにより 動 的 Broadcast Receiver を 間 接 的 に 登 録 / 登 録 解 除 している InhouseReceiverActivity.java package org.jssec.android.broadcast.inhousereceiver; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; public class InhouseReceiverActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void onregisterreceiverclick(view view) { Intent intent = new Intent(this, DynamicReceiverService.class); startservice(intent); public void onunregisterreceiverclick(view view) { All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 103

106 Android アプリのセキュア 設 計 セキュアコーディングガイド Intent intent = new Intent(this, DynamicReceiverService.class); stopservice(intent); DynamicReceiverService.java package org.jssec.android.broadcast.inhousereceiver; import android.app.service; import android.content.intent; import android.content.intentfilter; import android.os.ibinder; import android.widget.toast; public class DynamicReceiverService extends Service { private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.my_broadcast_inhouse"; private InhouseReceiver mreceiver; public IBinder onbind(intent intent) { return null; public void oncreate() { super.oncreate(); mreceiver = new InhouseReceiver(); mreceiver.isdynamic = true; IntentFilter filter = new IntentFilter(); filter.addaction(my_broadcast_inhouse); filter.setpriority(1); // 静 的 Broadcast Receiver より 動 的 Broadcast Receiver を 優 先 させる // ポイント 5 動 的 Broadcast Receiver を 登 録 するとき 独 自 定 義 Signature Permission を 要 求 宣 言 する registerreceiver(mreceiver, filter, "org.jssec.android.broadcast.inhousereceiver.my_permission", null); Toast.makeText(this, " 動 的 Broadcast Receiver を 登 録 した ", Toast.LENGTH_SHORT).show(); public void ondestroy() { super.ondestroy(); unregisterreceiver(mreceiver); mreceiver = null; Toast.makeText(this, " 動 的 Broadcast Receiver を 登 録 解 除 した ", Toast.LENGTH_SHORT).show(); SigPerm.java package org.jssec.android.shared; 104 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

107 import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 105

108 Android アプリのセキュア 設 計 セキュアコーディングガイド PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 106 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

109 ポイント 9 APK を Export するときに Broadcast 送 信 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 次 に 自 社 限 定 Broadcast Receiver へ Broadcast 送 信 するサンプルコードを 示 す 自 社 限 定 Broadcast Receiver に Broadcast を 送 信 する 場 合 Broadcast Receiver 側 に 独 自 定 義 Signature Permission を 要 求 する 必 要 がある ため Sticky が 使 えないという 制 約 があることに 注 意 が 必 要 である ポイント(Broadcast を 送 信 する): 10. 結 果 受 信 用 の 独 自 定 義 Signature Permission を 定 義 する 11. Broadcast 受 信 用 の 独 自 定 義 Signature Permission を 利 用 宣 言 する 12. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 13. Receiver は 自 社 アプリ 限 定 であるから センシティブな 情 報 を 送 信 してもよい 14. Receiver に 独 自 定 義 Signature Permission を 要 求 する 15. 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する 16. Receiver 側 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.broadcast.inhousesender" > <uses-permission android:name="android.permission.broadcast_sticky"/> <!-- ポイント 10 結 果 受 信 用 の 独 自 定 義 Signature Permission を 定 義 する --> <permission android:name="org.jssec.android.broadcast.inhousesender.my_permission" android:protectionlevel="signature" /> <!-- ポイント 11 Broadcast 受 信 用 の 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.broadcast.inhousereceiver.my_permission" /> All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 107

110 <application android:allowbackup="false" > <activity android:name="org.jssec.android.broadcast.inhousesender.inhousesenderactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> InhouseSenderActivity.java package org.jssec.android.broadcast.inhousesender; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.broadcastreceiver; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.textview; import android.widget.toast; public class InhouseSenderActivity extends Activity { // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.broadcast.inhousesender.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; private static final String MY_BROADCAST_INHOUSE = "org.jssec.android.broadcast.my_broadcast_inhouse"; public void onsendnormalclick(view view) { // ポイント 12 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { 108 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

111 Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); return; // ポイント 13 Receiver は 自 社 アプリ 限 定 であるから センシティブな 情 報 を 送 信 してもよい Intent intent = new Intent(MY_BROADCAST_INHOUSE); intent.putextra("param", "センシティブな 情 報 from Sender"); // ポイント 14 Receiver に 独 自 定 義 Signature Permission を 要 求 する sendbroadcast(intent, "org.jssec.android.broadcast.inhousesender.my_permission"); public void onsendorderedclick(view view) { // ポイント 12 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); return; // ポイント 13 Receiver は 自 社 アプリ 限 定 であるから センシティブな 情 報 を 送 信 してもよい Intent intent = new Intent(MY_BROADCAST_INHOUSE); intent.putextra("param", "センシティブな 情 報 from Sender"); // ポイント 14 Receiver に 独 自 定 義 Signature Permission を 要 求 する sendorderedbroadcast(intent, "org.jssec.android.broadcast.inhousesender.my_permission", mresultreceiver, null, 0, null, null); private BroadcastReceiver mresultreceiver = new BroadcastReceiver() { public void onreceive(context context, Intent intent) { ; // ポイント 15 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String data = getresultdata(); InhouseSenderActivity.this.logLine(String.format(" 結 果 %s を 受 信 した ", data)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 109

112 SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; 110 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

113 Android アプリのセキュア 設 計 セキュアコーディングガイド try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); ポイント 16 APK を Export するときに Receiver 側 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 111

114 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド Broadcast を 送 受 信 する 際 には 以 下 のルールを 守 ること 1. アプリ 内 でのみ 使 用 する Broadcast Receiver は 非 公 開 設 定 する ( 必 須 ) 2. 受 信 Intent の 安 全 性 を 確 認 する ( 必 須 ) 3. 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 4. 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) 5. センシティブな 情 報 を Broadcast 送 信 する 場 合 は 受 信 可 能 な Receiver を 制 限 する ( 必 須 ) 6. Sticky Broadcast にはセンシティブな 情 報 を 含 めない ( 必 須 ) 7. receiverpermission パラメータの 指 定 なし Ordered Broadcast は 届 かないことがあることに 注 意 ( 必 須 ) 8. Broadcast Receiver からの 返 信 データの 安 全 性 を 確 認 する ( 必 須 ) 9. 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) アプリ 内 でのみ 使 用 する Broadcast Receiver は 非 公 開 設 定 する ( 必 須 ) アプリ 内 でのみ 使 用 される Broadcast Receiver は 非 公 開 設 定 する これにより 他 のアプリから 意 図 せず Broadca st を 受 け 取 ってしまうことがなくなり アプリの 機 能 を 利 用 されたり アプリの 動 作 に 異 常 をきたしたりするのを 防 ぐこと ができる 同 一 アプリ 内 からのみ 利 用 される Receiver では Intent Filter を 設 置 するような 設 計 はしてはならない Intent Filt er の 性 質 上 同 一 アプリ 内 の 非 公 開 Receiver を 呼 び 出 すつもりでも Intent Filter 経 由 で 呼 び 出 したときに 意 図 せ ず 他 アプリの 公 開 Receiver を 呼 び 出 してしまう 場 合 が 存 在 するからである AndroidManifest.xml( 非 推 奨 ) <!-- 外 部 アプリに 非 公 開 とする Broadcast Receiver --> <!-- ポイント 1: exported= false とする --> <receiver android:name=".privatereceiver" android:exported="false" > <intent-filter> <action android:name="org.jssec.android.broadcast.my_action" /> </intent-filter> </receiver> 使 用 してよい exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Receiver の 場 合 ) も 参 照 すること 受 信 Intent の 安 全 性 を 確 認 する ( 必 須 ) Broadcast Receiver のタイプによって 若 干 リスクは 異 なるが 受 信 Intent のデータを 処 理 する 際 には まず 受 信 I ntent の 安 全 性 を 確 認 しなければならない 公 開 Broadcast Receiver は 不 特 定 多 数 のアプリから Intent を 受 け 取 るため マルウェアの 攻 撃 Intent を 受 け 取 る 可 能 性 がある 非 公 開 Broadcast Receiver は 他 のアプリから Intent を 直 接 受 け 取 ることはない しかし 同 一 アプリ 内 の 公 開 Component が 他 のアプリから 受 け 取 った Intent のデータを 非 公 開 Broadcast Receiver に 転 送 するこ 112 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

115 とがあるため 受 信 Intent を 無 条 件 に 安 全 であると 考 えてはならない 自 社 限 定 Broadcast Receiver はその 中 間 のリスクであるため やはり 受 信 Intent の 安 全 性 を 確 認 する 必 要 がある 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 自 社 のアプリから 送 信 された Broadcast だけを 受 信 し それ 以 外 の Broadcast を 一 切 受 信 しない 自 社 限 定 Broadc ast Receiver を 作 る 場 合 独 自 定 義 Signature Permission により 保 護 しなければならない AndroidManifest. xml での Permission 定 義 Permission 要 求 宣 言 だけでは 保 護 が 不 十 分 であるため 5.2 Permission と Prote ction Level の 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 を 参 照 すること また 独 自 定 義 Signature Permission を receiverpermission パラメータに 指 定 して Broadcast 送 信 する 場 合 も 同 様 に 確 認 する 必 要 がある 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) Broadcast Receiver のタイプによって setresult()により 結 果 情 報 を 返 すアプリの 信 用 度 が 異 なる 公 開 Broadcas t Receiver の 場 合 は 結 果 返 送 先 のアプリがマルウェアである 可 能 性 もあり 結 果 情 報 が 悪 意 を 持 って 使 われる 危 険 性 がある 非 公 開 Broadcast Receiver や 自 社 限 定 Broadcast Receiver の 場 合 は 結 果 返 送 先 は 自 社 開 発 ア プリであるため 結 果 情 報 の 扱 いをあまり 心 配 する 必 要 はない このように Broadcast Receiver から 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 の 漏 洩 に 配 慮 しなけ ればならない センシティブな 情 報 を Broadcast 送 信 する 場 合 は 受 信 可 能 な Receiver を 制 限 する ( 必 須 ) Broadcast という 名 前 が 表 すように そもそも Broadcast は 不 特 定 多 数 のアプリに 情 報 を 一 斉 送 信 したり タイミング を 通 知 したりすることを 意 図 して 作 られた 仕 組 みである そのためセンシティブな 情 報 を Broadcast 送 信 する 場 合 に は マルウェアに 情 報 を 取 得 されないような 注 意 深 い 設 計 が 必 要 となる センシティブな 情 報 を Broadcast 送 信 する 場 合 信 頼 できる Broadcast Receiver だけが 受 信 可 能 であり 他 の Br oadcast Receiver は 受 信 不 可 能 である 必 要 がある そのための Broadcast 送 信 方 法 には 以 下 のようなものがあ る 明 示 的 Intent で Broadcast 送 信 することで 宛 先 を 固 定 し 意 図 した 信 頼 できる Broadcast Receiver だけに B roadcast を 届 ける 方 法 この 方 法 には 次 の 2 つのパターンがある 同 一 アプリ 内 Broadcast Receiver 宛 てであれば Intent#setClass(Context, Class)により 宛 先 を 限 定 する 具 体 的 なコードについてはサンプルコードセクション 非 公 開 Broadcast Receiver - Broa dcast を 参 考 にすること 他 のアプリの Broadcast Receiver 宛 てであれば Intent#setClassName(String, String)により 宛 先 を All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 113

116 限 定 するが Broadcast 送 信 に 先 立 ち 宛 先 パッケージの APK 署 名 の 開 発 者 鍵 をホワイトリストと 照 合 して 許 可 したアプリであることを 確 認 してから Broadcast を 送 信 する 実 際 には 暗 黙 的 Intent を 利 用 できる 次 の 方 法 が 実 用 的 である receiverpermission パラメータに 独 自 定 義 Signature Permission を 指 定 して Broadcast 送 信 し 信 頼 する Broadcast Receiver に 当 該 Signature Permission を 利 用 宣 言 してもらう 方 法 具 体 的 なコードについては サンプルコードセクション 自 社 限 定 Broadcast Receiver を 参 考 にすること またこの Broadcast 送 信 方 法 を 実 装 するにはルール 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) も 適 用 しなければならない Sticky Broadcast にはセンシティブな 情 報 を 含 めない ( 必 須 ) 通 常 の Broadcast は Broadcast 送 信 時 に 受 信 可 能 状 態 にある Broadcast Receiver に 受 信 処 理 されると その B roadcast は 消 滅 してしまう 一 方 Sticky Broadcast(および Sticky Ordered Broadcast 以 下 同 様 )は 送 信 時 に 受 信 状 態 にある Broadcast Receiver に 受 信 処 理 された 後 もシステム 上 に 存 在 しつづけ その 後 registerrecei ver()により 受 信 できることが 特 徴 である 不 要 になった Sticky Broadcast は removestickybroadcast()により 任 意 のタイミングで 削 除 できる Sticky Broadcast は 暗 黙 的 Intent による 使 用 が 前 提 であり receiverpermission パラメータを 指 定 した Broadc ast 送 信 はできない したがって Sticky Broadcast で 送 信 した 情 報 はマルウェアを 含 む 不 特 定 多 数 のアプリから 取 得 できてしまう したがってセンシティブな 情 報 を Sticky Broadcast で 送 信 してはならない receiverpermission パラメータの 指 定 なし Ordered Broadcast は 届 かないことがあることに 注 意 ( 必 須 ) receiverpermission パラメータを 指 定 せずに 送 信 された Ordered Broadcast は マルウェアを 含 む 不 特 定 多 数 の アプリが 受 信 可 能 である Ordered Broadcast は Receiver からの 返 り 情 報 を 受 け 取 るため または 複 数 の Recei ver に 順 次 処 理 をさせるために 利 用 される 優 先 度 の 高 い Receiver から 順 次 Broadcast が 配 送 されるため 優 先 度 を 高 くしたマルウェアが 最 初 に Broadcast を 受 信 し abortbroadcast()すると 後 続 の Receiver に Broadcast が 配 信 されなくなる Broadcast Receiver からの 返 信 データの 安 全 性 を 確 認 する ( 必 須 ) 結 果 データを 送 り 返 してきた Broadcast Receiver のタイプによって 若 干 リスクは 異 なるが 基 本 的 には 受 信 した 結 果 データが 攻 撃 データである 可 能 性 を 考 慮 して 安 全 に 処 理 しなければならない 返 信 元 Broadcast Receiver が 公 開 Broadcast Receiver の 場 合 不 特 定 のアプリから 戻 りデータを 受 け 取 るため マルウェアの 攻 撃 データを 受 け 取 る 可 能 性 がある 返 信 元 Broadcast Receiver が 非 公 開 Broadcast Receiver の 場 合 同 一 アプリ 内 からの 結 果 データであるのでリスクはないように 考 えがちだが 他 のアプリから 受 け 取 ったデー タを 間 接 的 に 結 果 データとして 転 送 することがあるため 結 果 データを 無 条 件 に 安 全 であると 考 えてはならない 返 信 元 Broadcast Receiver が 自 社 限 定 Broadcast Receiver の 場 合 その 中 間 のリスクであるため やはり 結 果 デー タが 攻 撃 データである 可 能 性 を 考 慮 して 安 全 に 処 理 しなければならない 114 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

117 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) Permission により 保 護 されている 情 報 資 産 および 機 能 資 産 を 他 のアプリに 二 次 的 に 提 供 する 場 合 には 提 供 先 ア プリに 対 して 同 一 の Permission を 要 求 するなどして その 保 護 水 準 を 維 持 しなければならない Android の Permi ssion セキュリティモデルでは 保 護 された 資 産 に 対 するアプリからの 直 接 アクセスについてのみ 権 限 管 理 を 行 う こ の 仕 様 上 の 特 性 により アプリに 取 得 された 資 産 がさらに 他 のアプリに 保 護 のために 必 要 な Permission を 要 求 す ることなく 提 供 される 可 能 性 がある このことは Permission を 再 委 譲 していることと 実 質 的 に 等 価 なので Permissi on の 再 委 譲 問 題 と 呼 ばれる Permission の 再 委 譲 問 題 を 参 照 すること All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 115

118 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド 使 用 してよい exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Receiver の 場 合 ) 表 は Receiver を 実 装 するときに 使 用 してもよい exported 属 性 と intent-filter 要 素 の 組 み 合 わせを 示 し ている exported= false かつ intent-filter 定 義 あり の 使 用 を 原 則 禁 止 としている 理 由 については 以 下 で 説 明 する 表 exported 属 性 と intent-filter 要 素 の 組 み 合 わせの 使 用 可 否 exported 属 性 の 値 true false 無 指 定 intent-filter 定 義 がある 可 原 則 禁 止 禁 止 intent-filter 定 義 がない 可 可 禁 止 intent-filter を 定 義 し かつ exported= false を 指 定 することを 原 則 禁 止 としているのは 同 一 アプリ 内 の 非 公 開 Receiver に 向 けて Broadcast を 送 信 したつもりでも 意 図 せず 他 アプリの 公 開 Receiver を 呼 び 出 してしまう 場 合 が 存 在 するからである 以 下 の 2 つの 図 で 意 図 せぬ 呼 び 出 しが 起 こる 様 子 を 説 明 する 図 は 同 一 アプリ 内 からしか 非 公 開 Receiver(アプリ A)を 暗 黙 的 Intent で 呼 び 出 せない 正 常 な 動 作 の 例 で ある Intent-filter( 図 中 action="x")を 定 義 しているのが アプリ A しかいないので 意 図 通 りの 動 きとなっている アプリA 暗 黙 的 Intentを 使 って Receiverを 呼 び 出 す Intent( X ) 非 公 開 Receiver A-1 exported= false action= X アプリC 暗 黙 的 Intentを 使 って Receiverを 呼 び 出 す Intent( X ) 非 公 開 Receiverを 持 ったアプリAのみが インストールされている 場 合 は 同 一 ア プリ 内 でのみBroadcast 送 受 信 が 行 われ る Android 端 末 図 図 は アプリ A に 加 えてアプリ B でも 同 じ intent-filter( 図 中 action="x")を 定 義 している 場 合 である まず 116 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

119 他 のアプリ(アプリ C)が 暗 黙 的 Intent で Broadcast を 送 信 するのは 非 公 開 Receiver(A-1) 側 は 受 信 をしないので 特 にセキュリティ 的 には 問 題 にならない( 図 の 橙 色 の 矢 印 ) セキュリティ 面 で 問 題 になるのは アプリ A による 同 一 アプリ 内 の 非 公 開 Receiver の 呼 び 出 しである アプリ A が 暗 黙 的 Intent を Broadcast すると 同 一 アプリ 内 の 非 公 開 Receiver に 加 えて 同 じ Intent-filter を 定 義 した B の 持 つ 公 開 Receiver(B-1)もその Intent を 受 信 できてしまうからである( 図 の 赤 色 の 矢 印 ) A からアプリ B に 対 してセン シティブな 情 報 を 送 信 する 可 能 性 が 生 じてしまう アプリ B がマルウェアであれば そのままセンシティブな 情 報 の 漏 洩 に 繋 がる また Broadcast が Ordered Broadcast であった 場 合 は 意 図 しない 結 果 情 報 を 受 け 取 ってしまう 可 能 性 もある アプリA 暗 黙 的 Intentを 使 って Receiverを 呼 び 出 す Intent( X ) 非 公 開 Receiver A-1 exported= false action= X アプリC 暗 黙 的 Intentを 使 って Receiverを 呼 び 出 す Intent( X ) アプリB 公 開 Receiver B-1 exported= true action= X 同 じactionを 定 義 したReceiverが 持 った 複 数 のアプリがインストールされた 場 合 は すべてのReceiverにIntentが 配 信 さ れる Android 端 末 図 ただし システムの 送 信 する Broadcast Intent のみを 受 信 する BroadcastReceiver を 実 装 する 場 合 には exported= false かつ intent-filter 定 義 あり を 使 用 すること かつ これ 以 外 の 組 み 合 わせは 使 っていけない これは システムが 送 信 する Broadcast Intent に 関 しては exported= false でも 受 信 可 能 であるという 事 実 にもと づく システムが 送 信 する Broadcast Intent と 同 じ ACTION の Intent を 他 アプリが 送 信 した 場 合 それを 受 信 して しまうと 意 図 しない 動 作 を 引 き 起 こす 可 能 性 があるが これは exported= false を 指 定 することによって 防 ぐことが できる Android 3.1 以 降 はアプリを 起 動 しないと Receiver が 登 録 されない Android 3.1 以 降 では AndroidManifest.xml に 静 的 に 定 義 した Broadcast Receiver は インストールしただけ では 有 効 にならないので 注 意 が 必 要 である アプリを 1 回 起 動 することで それ 以 降 の Broadcast を 受 信 できるよう になる インストール 後 に Broadcast 受 信 をトリガーにして 処 理 を 起 動 させることはできなくなった ただし Broadcast All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 117

120 の 送 信 側 で Intent に Intent.FLAG_INCLUDE_STOPPED_PACKAGES を 設 定 して Broadcast 送 信 した 場 合 は 一 度 も 起 動 していないアプリであってもこの Broadcast を 受 信 することができる 同 じ UID を 持 つアプリから 送 信 された Broadcast は 非 公 開 Broadcast Receiver でも 受 信 できる 複 数 のアプリに 同 じ UID を 持 たせることができる この 場 合 たとえ 非 公 開 Broadcast Receiver であっても 同 じ UID のアプリから 送 信 された Broadcast は 受 信 してしまう しかしこれはセキュリティ 上 問 題 となることはない 同 じ UID を 持 つアプリは APK を 署 名 する 開 発 者 鍵 が 一 致 すること が 保 証 されており 非 公 開 Broadcast Receiver が 受 信 するのは 自 社 アプリから 送 信 された Broadcast に 限 定 され るからである Broadcast の 種 類 とその 特 徴 送 信 する Broadcast は Ordered かそうでないか Sticky かそうでないかの 組 み 合 わせにより 4 種 類 の Broadcast が 存 在 する Broadcast 送 信 用 メソッドに 応 じて 送 信 する Broadcast の 種 類 が 決 まる 表 Broadcast の 種 類 送 信 用 メソッド Ordered? Sticky? Normal Broadcast sendbroadcast() No No Ordered Broadcast sendorderedbroadcast() Yes No Sticky Broadcast sendstickybroadcast() No Yes Sticky Ordered Broadcast sendstickyorderedbroadcast() Yes Yes それぞれの Broadcast の 特 徴 は 次 のとおりである 表 Broadcast の 種 類 Normal Broadcast Broadcast の 種 類 ごとの 特 徴 Normal Broadcast は 送 信 時 に 受 信 可 能 な 状 態 にある Broadcast Receiver に 配 送 されて 消 滅 する Ordered Broadcast と 異 なり 複 数 の Broadcast Receiver が 同 時 に Broadcast を 受 信 す る の が 特 徴 で あ る 特 定 の Permission を 持 つアプリの Broadcast Receiver だけに Broadcast を 受 信 さ せることもできる Ordered Broadcast Ordered Broadcast は 送 信 時 に 受 信 可 能 な 状 態 に あ る Broadcast Receiver が 一 つずつ 順 番 に Broadcast を 受 信 することが 特 徴 である より priority 値 が 大 きい Broadcast Receiver が 先 に 受 信 する すべての Broadcast Receiver に 配 送 完 了 するか 途 中 の Broadcast Receiver が abortbroadcast()を 呼 び 出 した 場 合 に Broadcast は 消 滅 する 特 定 の Permission を 利 用 宣 言 したアプリの Broadcast Receiver だけに Broadcast を 受 信 さ せ る こ と も で き る ま た Ordered Broadcast では 送 信 元 が 118 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

121 Broadcast Receiver からの 結 果 情 報 を 受 け 取 ることもできる SMS 受 信 通 知 Broadcast(SMS_RECEIVED)は Ordered Broadcast の 代 表 例 である Sticky Broadcast Sticky Broadcast は 送 信 時 に 受 信 可 能 な 状 態 にある Broadcast Receiver に 配 送 さ れ た 後 に 消 滅 す る こ と は な く シ ス テ ム に 残 り 続 け 後 に registerreceiver()を 呼 び 出 したアプリが Sticky Broadcast を 受 信 すること ができることが 特 徴 である Sticky Broadcast は 他 の Broadcast と 異 なり 自 動 的 に 消 滅 することはないので Sticky Broadcast が 不 要 になったときに 明 示 的 に removestickybroadcast()を 呼 び 出 して Sticky Broadcast を 消 滅 さ せる 必 要 がある 他 の Broadcast と 異 なり 特 定 の Permission を 持 つアプリ の Broadcast Receiver だけに Broadcast を 受 信 させることはできない バッ テリー 状 態 変 更 通 知 Broadcast(ACTION_BATTERY_CHANGED)は Sticky Broadcast の 代 表 例 である Sticky Ordered Broadcast Ordered Broadcast と Sticky Broadcast の 両 方 の 特 徴 を 持 った Broadcast である Sticky Broadcast と 同 様 特 定 の Permission を 持 つアプリの Broadcast Receiver だけに Broadcast を 受 信 させることはできない Broadcast の 特 徴 的 な 振 る 舞 いの 視 点 で 上 表 を 逆 引 き 的 に 再 整 理 すると 下 表 になる 表 Broadcast の 特 徴 的 な 振 る 舞 い Normal Ordered Sticky Sticky Ordered Broadcast Broadcast Broadcast Broadcast 受 信 可 能 なBroadcast Receiver を Permission により 制 限 する Broadcast Receiver からの 処 理 結 果 を 取 得 する 順 番 に Broadcast Receiver に Broadcast を 処 理 させる 既 に 送 信 されている Broadcast を 後 から 受 信 する Broadcast 送 信 した 情 報 が LogCat に 出 力 される 場 合 がある Broadcast の 送 受 信 は 基 本 的 に LogCat に 出 力 されない しかし 受 信 側 の Permission 不 足 によるエラーや 送 信 側 の Permission 不 足 によるエラーの 際 に LogCat にエラーログが 出 力 される エラーログには Broadcast で 送 信 する Intent 情 報 も 含 まれるので エラー 発 生 時 には Broadcast 送 信 する 場 合 は LogCat に 表 示 されることに 注 意 してほしい 送 信 側 の Permission 不 足 時 のエラー W/ActivityManager(266): Permission Denial: broadcasting Intent { act=org.jssec.android.broadcastreceiver.creating.action.my_action from org.jssec.android.broadcast.sending (pid=4685, uid=10058) requires org.jssec.android.per mission.my_permission due to receiver org.jssec.android.broadcastreceiver.creating/org.jssec.android.broadcastrec All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する 119

122 eiver.creating.creatingtype3receiver 受 信 側 の Permission 不 足 時 のエラー W/ActivityManager(275): Permission Denial: receiving Intent { act=org.jssec.android.broadcastreceiver.creating.ac tion.my_action to org.jssec.android.broadcastreceiver.creating requires org.jssec.android.permission.my_permiss ION due to sender org.jssec.android.broadcast.sending (uid 10158) ホーム 画 面 (アプリ)にショートカットを 配 置 する 際 の 注 意 点 ホーム 画 面 にアプリを 起 動 するためのショートカットボタンや Web ブラウザのブックマークのような URL ショートカット を 作 成 する 場 合 の 注 意 点 について 説 明 する 例 として 以 下 のような 実 装 を 考 えてみる ホーム 画 面 (アプリ)にショートカットを 配 置 する Intent targetintent = new Intent(this, TargetActivity.class); // ショートカット 作 成 依 頼 のための Intent Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); // ショートカットのタップ 時 に 起 動 する Intent を 指 定 intent.putextra(intent.extra_shortcut_intent, targetintent); Parcelable icon = Intent.ShortcutIconResource.fromContext(context, iconresource); intent.putextra(intent.extra_shortcut_icon_resource, icon); intent.putextra(intent.extra_shortcut_name, title); intent.putextra("duplicate", false); // BroadCast を 使 って システムにショートカット 作 成 を 依 頼 する context.sendbroadcast(intent); 上 記 で 送 信 している Broadcast は 受 け 手 がホーム 画 面 アプリであり パッケージ 名 を 特 定 することが 難 しいため 暗 黙 的 Intent による 公 開 Receiver への 送 信 となっていることに 注 意 が 必 要 である つまり 上 記 で 送 信 した Broadcast はマルウェアを 含 めた 任 意 のアプリが 受 信 することができ そのため Intent にセンシティブな 情 報 が 含 まれていると 情 報 漏 えいの 被 害 につながる 可 能 性 がある URL を 基 にしたショートカットを 作 成 する 場 合 URL に 秘 密 の 情 報 が 含 まれていることもあるため 特 に 注 意 が 必 要 である 対 策 方 法 としては 公 開 Broadcast Receiver - Broadcast を 受 信 する 送 信 する に 記 載 されているポイ ントに 従 い 送 信 する Intent にセンシティブな 情 報 が 含 まないようにすることが 必 要 である 120 All rights reserved Japan Smartphone Security Association. Broadcast を 受 信 する 送 信 する

123 4.3. Content Provider を 作 る 利 用 する ContentResolver と SQLiteDatabase の イ ン タ ー フ ェ ー ス が 似 て い る た め Content Provider は SQLiteDatabase と 密 接 に 関 係 があると 勘 違 いされることが 多 い しかし 実 際 には Content Provider はアプリ 間 デ ータ 共 有 のインターフェースを 規 定 するだけで データ 保 存 の 形 式 は 一 切 問 わないことに 注 意 が 必 要 だ 作 成 する Content Provider 内 部 でデータの 保 存 に SQLiteDatabase を 使 うこともできるし XML ファイルなどの 別 の 保 存 形 式 を 使 うこともできる なお ここで 紹 介 するサンプルコードにはデータを 保 存 する 処 理 を 含 まないので 必 要 に 応 じて 追 加 すること サンプルコード Content Provider がどのように 利 用 されるかによって Content Provider が 抱 えるリスクや 適 切 な 防 御 手 段 が 異 なる 次 の 判 定 フローによって 作 成 する Content Provider がどのタイプであるかを 判 断 できる はじめ Yes 常 時 サービスを 提 供 する No Yes アプリ 内 でのみ 利 用 する No Yes 不 特 定 多 数 の アプリに 利 用 を 認 める No Yes 特 定 他 社 の アプリに 利 用 を 認 める No Private 非 公 開 Content Provider Public 公 開 Content Provider Exclusive パートナー 限 定 Content Provider Proprietary 自 社 限 定 Content Provider Temporary 一 時 許 可 Content Provider 図 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 121

124 非 公 開 Content Provider を 作 る 利 用 する 非 公 開 Content Provider は 同 一 アプリ 内 だけで 利 用 される Content Provider であり もっとも 安 全 性 の 高 い Content Provider である ただし Content Provider の 非 公 開 設 定 は Android 2.2 (API Level 8) 以 前 では 機 能 しないことに 注 意 が 必 要 だ 以 下 非 公 開 Content Provider の 実 装 例 を 示 す ポイント(Content Provider を 作 る): 1. Android 2.2(API Level 8) 以 前 では 非 公 開 Content Provider を 実 装 しない(できない) 2. exported="false"により 明 示 的 に 非 公 開 設 定 する 3. 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する 4. 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.privateprovider"> <application > <activity android:name=".privateuseractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- ポイント 2 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <provider android:name=".privateprovider" android:authorities="org.jssec.android.provider.privateprovider" android:exported="false" /> </application> </manifest> PrivateProvider.java package org.jssec.android.provider.privateprovider; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.urimatcher; import android.database.cursor; import android.database.matrixcursor; import android.net.uri; public class PrivateProvider extends ContentProvider { 122 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

125 public static final String AUTHORITY = "org.jssec.android.provider.privateprovider"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype"; // Content Provider が 提 供 するインターフェースを 公 開 public interface Download { public static final String PATH = "downloads"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); public interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // UriMatcher private static final int DOWNLOADS_CODE = 1; private static final int DOWNLOADS_ID_CODE = 2; private static final int ADDRESSES_CODE = 3; private static final int ADDRESSES_ID_CODE = 4; private static UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(authority, Download.PATH, DOWNLOADS_CODE); surimatcher.adduri(authority, Download.PATH + "/#", DOWNLOADS_ID_CODE); surimatcher.adduri(authority, Address.PATH, ADDRESSES_CODE); surimatcher.adduri(authority, Address.PATH + "/#", ADDRESSES_ID_CODE); // DB を 使 用 せずに 固 定 値 を 返 す 例 にしているため query メソッドで 返 す Cursor を 事 前 に 定 義 private static MatrixCursor saddresscursor = new MatrixCursor(new String[] { "_id", "pref" ); static { saddresscursor.addrow(new String[] { "1", " 北 海 道 " ); saddresscursor.addrow(new String[] { "2", " 青 森 " ); saddresscursor.addrow(new String[] { "3", " 岩 手 " ); private static MatrixCursor sdownloadcursor = new MatrixCursor(new String[] { "_id", "path" ); static { sdownloadcursor.addrow(new String[] { "1", "/sdcard/downloads/sample.jpg" ); sdownloadcursor.addrow(new String[] { "2", "/sdcard/downloads/sample.txt" ); public boolean oncreate() { return true; public String gettype(uri uri) { // ポイント 3 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい // ただし gettype の 結 果 がセンシティブな 意 味 を 持 つことはあまりない switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case ADDRESSES_CODE: return CONTENT_TYPE; All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 123

126 case DOWNLOADS_ID_CODE: case ADDRESSES_ID_CODE: return CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { // ポイント 3 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい // query の 結 果 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case DOWNLOADS_ID_CODE: return sdownloadcursor; case ADDRESSES_CODE: case ADDRESSES_ID_CODE: return saddresscursor; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Uri insert(uri uri, ContentValues values) { // ポイント 3 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい // Insert 結 果 発 番 される ID がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return ContentUris.withAppendedId(Download.CONTENT_URI, 3); case ADDRESSES_CODE: return ContentUris.withAppendedId(Address.CONTENT_URI, 4); default: throw new IllegalArgumentException("Invalid URI:" + uri); public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { // ポイント 3 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい 124 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

127 // Update されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 5; // update されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 15; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); public int delete(uri uri, String selection, String[] selectionargs) { // ポイント 3 同 一 アプリ 内 からのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい // Delete されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 10; // delete されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 20; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 125

128 次 に 非 公 開 Content Provider を 利 用 する Activity の 例 を 示 す ポイント(Content Provider を 利 用 する): 5. 同 一 アプリ 内 へのリクエストであるから センシティブな 情 報 をリクエストに 含 めてよい 6. 同 一 アプリ 内 からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する PrivateUserActivity.java package org.jssec.android.provider.privateprovider; import android.app.activity; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.view.view; import android.widget.textview; public class PrivateUserActivity extends Activity { public void onqueryclick(view view) { logline("[query]"); // ポイント 5 同 一 アプリ 内 へのリクエストであるから センシティブな 情 報 をリクエストに 含 めてよい Cursor cursor = null; try { cursor = getcontentresolver().query( PrivateProvider.Download.CONTENT_URI, null, null, null, null); // ポイント 6 同 一 アプリ 内 からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (cursor == null) { logline(" null cursor"); else { boolean moved = cursor.movetofirst(); while (moved) { logline(string.format(" %d, %s", cursor.getint(0), cursor.getstring(1))); moved = cursor.movetonext(); finally { if (cursor!= null) cursor.close(); public void oninsertclick(view view) { logline("[insert]"); // ポイント 5 同 一 アプリ 内 へのリクエストであるから センシティブな 情 報 をリクエストに 含 めてよい Uri uri = getcontentresolver().insert(privateprovider.download.content_uri, null); // ポイント 6 同 一 アプリ 内 からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(" uri:" + uri); 126 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

129 public void onupdateclick(view view) { logline("[update]"); // ポイント 5 同 一 アプリ 内 へのリクエストであるから センシティブな 情 報 をリクエストに 含 めてよい int count = getcontentresolver().update(privateprovider.download.content_uri, null, null, null); // ポイント 6 同 一 アプリ 内 からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records updated", count)); public void ondeleteclick(view view) { logline("[delete]"); // ポイント 5 同 一 アプリ 内 へのリクエストであるから センシティブな 情 報 をリクエストに 含 めてよい int count = getcontentresolver().delete( PrivateProvider.Download.CONTENT_URI, null, null); // ポイント 6 同 一 アプリ 内 からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records deleted", count)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 127

130 公 開 Content Provider を 作 る 利 用 する 公 開 Content Provider は 不 特 定 多 数 のアプリに 利 用 されることを 想 定 した Content Provider である クライアン トを 限 定 しないことにより マルウェアから select()によって 保 持 しているデータを 抜 き 取 られたり update()によって データを 書 き 換 えられたり insert()/delete()によって 偽 のデータの 挿 入 やデータの 削 除 といった 攻 撃 を 受 けたりして 改 ざんされ 得 ることに 注 意 が 必 要 だ また Android OS 既 定 ではない 独 自 作 成 の 公 開 Content Provider を 利 用 する 場 合 その 公 開 Content Provider に 成 り 済 ましたマルウェアにリクエストパラメータを 受 信 されることがあること および 攻 撃 結 果 データを 受 け 取 ることがあることに 注 意 が 必 要 である Android OS 既 定 の Contacts や MediaStore 等 も 公 開 Content Provider であるが マルウェアはそれら Content Provider に 成 り 済 ましできない 以 下 公 開 Content Provider の 実 装 例 を 示 す ポイント(Content Provider を 作 る): 1. exported="true"により 明 示 的 に 公 開 設 定 する 2. リクエストパラメータの 安 全 性 を 確 認 する 3. センシティブな 情 報 を 返 送 してはならない AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.publicprovider"> <application > <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <provider android:name=".publicprovider" android:authorities="org.jssec.android.provider.publicprovider" android:exported="true"/> </application> </manifest> PublicProvider.java package org.jssec.android.provider.publicprovider; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.urimatcher; import android.database.cursor; import android.database.matrixcursor; import android.net.uri; public class PublicProvider extends ContentProvider { 128 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

131 public static final String AUTHORITY = "org.jssec.android.provider.publicprovider"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype"; // Content Provider が 提 供 するインターフェースを 公 開 public interface Download { public static final String PATH = "downloads"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); public interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // UriMatcher private static final int DOWNLOADS_CODE = 1; private static final int DOWNLOADS_ID_CODE = 2; private static final int ADDRESSES_CODE = 3; private static final int ADDRESSES_ID_CODE = 4; private static UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(authority, Download.PATH, DOWNLOADS_CODE); surimatcher.adduri(authority, Download.PATH + "/#", DOWNLOADS_ID_CODE); surimatcher.adduri(authority, Address.PATH, ADDRESSES_CODE); surimatcher.adduri(authority, Address.PATH + "/#", ADDRESSES_ID_CODE); // DB を 使 用 せずに 固 定 値 を 返 す 例 にしているため query メソッドで 返 す Cursor を 事 前 に 定 義 private static MatrixCursor saddresscursor = new MatrixCursor(new String[] { "_id", "pref" ); static { saddresscursor.addrow(new String[] { "1", " 北 海 道 " ); saddresscursor.addrow(new String[] { "2", " 青 森 " ); saddresscursor.addrow(new String[] { "3", " 岩 手 " ); private static MatrixCursor sdownloadcursor = new MatrixCursor(new String[] { "_id", "path" ); static { sdownloadcursor.addrow(new String[] { "1", "/sdcard/downloads/sample.jpg" ); sdownloadcursor.addrow(new String[] { "2", "/sdcard/downloads/sample.txt" ); public boolean oncreate() { return true; public String gettype(uri uri) { switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case ADDRESSES_CODE: return CONTENT_TYPE; case DOWNLOADS_ID_CODE: case ADDRESSES_ID_CODE: return CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI:" + uri); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 129

132 Android アプリのセキュア 設 計 セキュアコーディングガイド public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { // ポイント 2 リクエストパラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 3 センシティブな 情 報 を 返 送 してはならない // query の 結 果 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 // リクエスト 元 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case DOWNLOADS_ID_CODE: return sdownloadcursor; case ADDRESSES_CODE: case ADDRESSES_ID_CODE: return saddresscursor; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Uri insert(uri uri, ContentValues values) { // ポイント 2 リクエストパラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 3 センシティブな 情 報 を 返 送 してはならない // Insert 結 果 発 番 される ID がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 // リクエスト 元 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return ContentUris.withAppendedId(Download.CONTENT_URI, 3); case ADDRESSES_CODE: return ContentUris.withAppendedId(Address.CONTENT_URI, 4); default: throw new IllegalArgumentException("Invalid URI:" + uri); public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { // ポイント 2 リクエストパラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 3 センシティブな 情 報 を 返 送 してはならない // Update されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 // リクエスト 元 のアプリがマルウェアである 可 能 性 がある 130 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

133 // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 5; // update されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 15; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); public int delete(uri uri, String selection, String[] selectionargs) { // ポイント 2 リクエストパラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 3 センシティブな 情 報 を 返 送 してはならない // Delete されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 // リクエスト 元 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であれば 結 果 として 返 してもよい switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 10; // delete されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 20; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 131

134 次 に 公 開 Content Provider を 利 用 する Activity の 例 を 示 す ポイント(Content Provider を 利 用 する): 4. センシティブな 情 報 をリクエストに 含 めてはならない 5. 結 果 データの 安 全 性 を 確 認 する PublicUserActivity.java package org.jssec.android.provider.publicuser; import android.app.activity; import android.content.contentvalues; import android.content.pm.providerinfo; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.view.view; import android.widget.textview; public class PublicUserActivity extends Activity { // 利 用 先 の Content Provider 情 報 private static final String AUTHORITY = "org.jssec.android.provider.publicprovider"; private interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); public void onqueryclick(view view) { logline("[query]"); if (!providerexists(address.content_uri)) { logline(" Content Provider が 不 在 "); return; // ポイント 4 センシティブな 情 報 をリクエストに 含 めてはならない // リクエスト 先 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であればリクエストに 含 めてもよい Cursor cursor = null; try { cursor = getcontentresolver().query(address.content_uri, null, null, null, null); // ポイント 5 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (cursor == null) { logline(" null cursor"); else { boolean moved = cursor.movetofirst(); while (moved) { logline(string.format(" %d, %s", cursor.getint(0), cursor.getstring(1))); moved = cursor.movetonext(); finally { if (cursor!= null) cursor.close(); 132 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

135 public void oninsertclick(view view) { logline("[insert]"); if (!providerexists(address.content_uri)) { logline(" Content Provider が 不 在 "); return; // ポイント 4 センシティブな 情 報 をリクエストに 含 めてはならない // リクエスト 先 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であればリクエストに 含 めてもよい ContentValues values = new ContentValues(); values.put("pref", " 東 京 都 "); Uri uri = getcontentresolver().insert(address.content_uri, values); // ポイント 5 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(" uri:" + uri); public void onupdateclick(view view) { logline("[update]"); if (!providerexists(address.content_uri)) { logline(" Content Provider が 不 在 "); return; // ポイント 4 センシティブな 情 報 をリクエストに 含 めてはならない // リクエスト 先 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であればリクエストに 含 めてもよい ContentValues values = new ContentValues(); values.put("pref", " 東 京 都 "); String where = "_id =?"; String[] args = { "4" ; int count = getcontentresolver().update(address.content_uri, values, where, args); // ポイント 5 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records updated", count)); public void ondeleteclick(view view) { logline("[delete]"); if (!providerexists(address.content_uri)) { logline(" Content Provider が 不 在 "); return; // ポイント 4 センシティブな 情 報 をリクエストに 含 めてはならない // リクエスト 先 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であればリクエストに 含 めてもよい int count = getcontentresolver().delete(address.content_uri, null, null); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 133

136 // ポイント 5 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records deleted", count)); private boolean providerexists(uri uri) { ProviderInfo pi = getpackagemanager().resolvecontentprovider(uri.getauthority(), 0); return (pi!= null); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); 134 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

137 パートナー 限 定 Content Provider を 作 る 利 用 する パートナー 限 定 Content Provider は 特 定 のアプリだけから 利 用 できる Content Provider である パートナー 企 業 のアプリと 自 社 アプリが 連 携 してシステムを 構 成 し パートナーアプリとの 間 で 扱 う 情 報 や 機 能 を 守 るために 利 用 さ れる 以 下 パートナー 限 定 Content Provider の 実 装 例 を 示 す ポイント(Content Provider を 作 る): 1. exported="true"により 明 示 的 に 公 開 設 定 する 2. 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 3. パートナーアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する 4. パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.partnerprovider"> <application > <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <provider android:name="org.jssec.android.provider.partnerprovider.partnerprovider" android:authorities="org.jssec.android.provider.partnerprovider" android:exported="true"/> </application> </manifest> PartnerProvider.java package org.jssec.android.provider.partnerprovider; import java.util.list; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activitymanager; import android.app.activitymanager.runningappprocessinfo; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.context; import android.content.urimatcher; import android.database.cursor; import android.database.matrixcursor; import android.net.uri; import android.os.binder; import android.os.build; All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 135

138 public class PartnerProvider extends ContentProvider { public static final String AUTHORITY = "org.jssec.android.provider.partnerprovider"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype"; // Content Provider が 提 供 するインターフェースを 公 開 public interface Download { public static final String PATH = "downloads"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); public interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // UriMatcher private static final int DOWNLOADS_CODE = 1; private static final int DOWNLOADS_ID_CODE = 2; private static final int ADDRESSES_CODE = 3; private static final int ADDRESSES_ID_CODE = 4; private static UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(authority, Download.PATH, DOWNLOADS_CODE); surimatcher.adduri(authority, Download.PATH + "/#", DOWNLOADS_ID_CODE); surimatcher.adduri(authority, Address.PATH, ADDRESSES_CODE); surimatcher.adduri(authority, Address.PATH + "/#", ADDRESSES_ID_CODE); // DB を 使 用 せずに 固 定 値 を 返 す 例 にしているため query メソッドで 返 す Cursor を 事 前 に 定 義 private static MatrixCursor saddresscursor = new MatrixCursor(new String[] { "_id", "pref" ); static { saddresscursor.addrow(new String[] { "1", " 北 海 道 " ); saddresscursor.addrow(new String[] { "2", " 青 森 " ); saddresscursor.addrow(new String[] { "3", " 岩 手 " ); private static MatrixCursor sdownloadcursor = new MatrixCursor(new String[] { "_id", "path" ); static { sdownloadcursor.addrow(new String[] { "1", "/sdcard/downloads/sample.jpg" ); sdownloadcursor.addrow(new String[] { "2", "/sdcard/downloads/sample.txt" ); // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナーアプリ org.jssec.android.provider.partneruser の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.provider.partneruser", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"partner key"の 証 明 書 ハッシュ 値 "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // 以 下 同 様 に 他 のパートナーアプリを 登 録... private static boolean checkpartner(context context, String pkgname) { 136 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

139 if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); // 利 用 元 アプリのパッケージ 名 を 取 得 private String getcallingpackage(context context) { String pkgname; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { pkgname = super.getcallingpackage(); else { pkgname = null; ActivityManager am = (ActivityManager) context.getsystemservice(context.activity_service); List<RunningAppProcessInfo> proclist = am.getrunningappprocesses(); int callingpid = Binder.getCallingPid(); if (proclist!= null) { for (RunningAppProcessInfo proc : proclist) { if (proc.pid == callingpid) { pkgname = proc.pkglist[proc.pkglist.length - 1]; break; return pkgname; public boolean oncreate() { return true; public String gettype(uri uri) { switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case ADDRESSES_CODE: return CONTENT_TYPE; case DOWNLOADS_ID_CODE: case ADDRESSES_ID_CODE: return CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(getcontext(), getcallingpackage(getcontext()))) { throw new SecurityException(" 利 用 元 アプリはパートナーアプリではない "); // ポイント 3 パートナーアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 137

140 // ポイント 4 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // query の 結 果 がパートナーアプリに 開 示 してよい 情 報 かどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case DOWNLOADS_ID_CODE: return sdownloadcursor; case ADDRESSES_CODE: case ADDRESSES_ID_CODE: return saddresscursor; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Uri insert(uri uri, ContentValues values) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(getcontext(), getcallingpackage(getcontext()))) { throw new SecurityException(" 利 用 元 アプリはパートナーアプリではない "); // ポイント 3 パートナーアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Insert 結 果 発 番 される ID がパートナーアプリに 開 示 してよい 情 報 かどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return ContentUris.withAppendedId(Download.CONTENT_URI, 3); case ADDRESSES_CODE: return ContentUris.withAppendedId(Address.CONTENT_URI, 4); default: throw new IllegalArgumentException("Invalid URI:" + uri); public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(getcontext(), getcallingpackage(getcontext()))) { throw new SecurityException(" 利 用 元 アプリはパートナーアプリではない "); // ポイント 3 パートナーアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Update されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 5; // update されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: 138 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

141 return 1; case ADDRESSES_CODE: return 15; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); public int delete(uri uri, String selection, String[] selectionargs) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(getcontext(), getcallingpackage(getcontext()))) { throw new SecurityException(" 利 用 元 アプリはパートナーアプリではない "); // ポイント 3 パートナーアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 4 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Delete されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 10; // delete されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 20; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 139

142 次 に パートナー 限 定 Content Provider を 利 用 する Activity の 例 を 示 す ポイント(Content Provider を 利 用 する): 5. 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 6. パートナー 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい 7. パートナー 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する PartnerUserActivity.java package org.jssec.android.provider.partneruser; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activity; import android.content.contentvalues; import android.content.context; import android.content.pm.providerinfo; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.view.view; import android.widget.textview; public class PartnerUserActivity extends Activity { // 利 用 先 の Content Provider 情 報 private static final String AUTHORITY = "org.jssec.android.provider.partnerprovider"; private interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // ポイント 5 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); 登 録 // パートナー 限 定 Content Provider アプリ org.jssec.android.provider.partnerprovider の 証 明 書 ハッシュ 値 を swhitelists.add("org.jssec.android.provider.partnerprovider", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"my company key"の 証 明 書 ハッシュ 値 "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"); // 以 下 同 様 に 他 のパートナー 限 定 Content Provider アプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); // uri を AUTHORITY とする Content Provider のパッケージ 名 を 取 得 private String providerpkgname(uri uri) { String pkgname = null; ProviderInfo pi = getpackagemanager().resolvecontentprovider(uri.getauthority(), 0); 140 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

143 Android アプリのセキュア 設 計 セキュアコーディングガイド if (pi!= null) pkgname = pi.packagename; return pkgname; public void onqueryclick(view view) { logline("[query]"); // ポイント 5 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていること を 確 認 する if (!checkpartner(this, providerpkgname(address.content_uri))) { logline(" 利 用 先 Content Provider アプリはホワイトリストに 登 録 されていない "); return; // ポイント 6 パートナー 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい Cursor cursor = null; try { cursor = getcontentresolver().query(address.content_uri, null, null, null, null); する // ポイント 7 パートナー 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (cursor == null) { logline(" null cursor"); else { boolean moved = cursor.movetofirst(); while (moved) { logline(string.format(" %d, %s", cursor.getint(0), cursor.getstring(1))); moved = cursor.movetonext(); finally { if (cursor!= null) cursor.close(); public void oninsertclick(view view) { logline("[insert]"); // ポイント 5 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていること を 確 認 する if (!checkpartner(this, providerpkgname(address.content_uri))) { logline(" 利 用 先 Content Provider アプリはホワイトリストに 登 録 されていない "); return; // ポイント 6 パートナー 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい ContentValues values = new ContentValues(); values.put("pref", " 東 京 都 "); Uri uri = getcontentresolver().insert(address.content_uri, values); // ポイント 7 パートナー 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(" uri:" + uri); public void onupdateclick(view view) { All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 141

144 logline("[update]"); // ポイント 5 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていること を 確 認 する if (!checkpartner(this, providerpkgname(address.content_uri))) { logline(" 利 用 先 Content Provider アプリはホワイトリストに 登 録 されていない "); return; // ポイント 6 パートナー 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい ContentValues values = new ContentValues(); values.put("pref", " 東 京 都 "); String where = "_id =?"; String[] args = { "4" ; int count = getcontentresolver().update(address.content_uri, values, where, args); // ポイント 7 パートナー 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records updated", count)); public void ondeleteclick(view view) { logline("[delete]"); // ポイント 5 利 用 先 パートナー 限 定 Content Provider アプリの 証 明 書 がホワイトリストに 登 録 されていること を 確 認 する if (!checkpartner(this, providerpkgname(address.content_uri))) { logline(" 利 用 先 Content Provider アプリはホワイトリストに 登 録 されていない "); return; // ポイント 6 パートナー 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい int count = getcontentresolver().delete(address.content_uri, null, null); // ポイント 7 パートナー 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records deleted", count)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); 142 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

145 PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 143

146 Android アプリのセキュア 設 計 セキュアコーディングガイド PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 144 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

147 自 社 限 定 Content Provider を 作 る 利 用 する 自 社 限 定 Content Provider は 自 社 以 外 のアプリから 利 用 されることを 禁 止 する Content Provider である 複 数 の 自 社 製 アプリでシステムを 構 成 し 自 社 アプリが 扱 う 情 報 や 機 能 を 守 るために 利 用 される 以 下 自 社 限 定 Content Provider の 実 装 例 を 示 す ポイント(Content Provider を 作 る): 1. 独 自 定 義 Signature Permission を 定 義 する 2. 独 自 定 義 Signature Permission を 要 求 宣 言 する 3. exported="true"により 明 示 的 に 公 開 設 定 する 4. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 5. 自 社 アプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する 6. 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい 7. 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.inhouseprovider"> <!-- ポイント 1 独 自 定 義 Signature Permission を 定 義 する --> <permission android:name="org.jssec.android.provider.inhouseprovider.my_permission" android:protectionlevel="signature" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- ポイント 2 独 自 定 義 Signature Permission を 要 求 宣 言 する --> <!-- ポイント 3 exported="true"により 明 示 的 に 公 開 設 定 する --> <provider android:name="org.jssec.android.provider.inhouseprovider.inhouseprovider" android:authorities="org.jssec.android.provider.inhouseprovider" android:permission="org.jssec.android.provider.inhouseprovider.my_permission" android:exported="true"/> </application> </manifest> InhouseProvider.java package org.jssec.android.provider.inhouseprovider; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.context; All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 145

148 import android.content.urimatcher; import android.database.cursor; import android.database.matrixcursor; import android.net.uri; public class InhouseProvider extends ContentProvider { public static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype"; // Content Provider が 提 供 するインターフェースを 公 開 public interface Download { public static final String PATH = "downloads"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); public interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // UriMatcher private static final int DOWNLOADS_CODE = 1; private static final int DOWNLOADS_ID_CODE = 2; private static final int ADDRESSES_CODE = 3; private static final int ADDRESSES_ID_CODE = 4; private static UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(authority, Download.PATH, DOWNLOADS_CODE); surimatcher.adduri(authority, Download.PATH + "/#", DOWNLOADS_ID_CODE); surimatcher.adduri(authority, Address.PATH, ADDRESSES_CODE); surimatcher.adduri(authority, Address.PATH + "/#", ADDRESSES_ID_CODE); // DB を 使 用 せずに 固 定 値 を 返 す 例 にしているため query メソッドで 返 す Cursor を 事 前 に 定 義 private static MatrixCursor saddresscursor = new MatrixCursor(new String[] { "_id", "pref" ); static { saddresscursor.addrow(new String[] { "1", " 北 海 道 " ); saddresscursor.addrow(new String[] { "2", " 青 森 " ); saddresscursor.addrow(new String[] { "3", " 岩 手 " ); private static MatrixCursor sdownloadcursor = new MatrixCursor(new String[] { "_id", "path" ); static { sdownloadcursor.addrow(new String[] { "1", "/sdcard/downloads/sample.jpg" ); sdownloadcursor.addrow(new String[] { "2", "/sdcard/downloads/sample.txt" ); // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 146 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

149 Android アプリのセキュア 設 計 セキュアコーディングガイド smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; public boolean oncreate() { return true; public String gettype(uri uri) { switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case ADDRESSES_CODE: return CONTENT_TYPE; case DOWNLOADS_ID_CODE: case ADDRESSES_ID_CODE: return CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { // ポイント 4 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(getContext(), MY_PERMISSION, mycerthash(getcontext()))) { throw new SecurityException(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); // ポイント 5 自 社 アプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 6 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい // query の 結 果 が 自 社 アプリに 開 示 してよい 情 報 かどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case DOWNLOADS_ID_CODE: return sdownloadcursor; case ADDRESSES_CODE: case ADDRESSES_ID_CODE: return saddresscursor; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Uri insert(uri uri, ContentValues values) { // ポイント 4 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 147

150 if (!SigPerm.test(getContext(), MY_PERMISSION, mycerthash(getcontext()))) { throw new SecurityException(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); // ポイント 5 自 社 アプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 6 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい // Insert 結 果 発 番 される ID が 自 社 アプリに 開 示 してよい 情 報 かどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return ContentUris.withAppendedId(Download.CONTENT_URI, 3); case ADDRESSES_CODE: return ContentUris.withAppendedId(Address.CONTENT_URI, 4); default: throw new IllegalArgumentException("Invalid URI:" + uri); public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { // ポイント 4 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(getContext(), MY_PERMISSION, mycerthash(getcontext()))) { throw new SecurityException(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); // ポイント 5 自 社 アプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 6 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい // Update されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 5; // update されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 15; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); public int delete(uri uri, String selection, String[] selectionargs) { // ポイント 4 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(getContext(), MY_PERMISSION, mycerthash(getcontext()))) { throw new SecurityException(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); 148 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

151 // ポイント 5 自 社 アプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 6 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい // Delete されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 10; // delete されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 20; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 149

152 Android アプリのセキュア 設 計 セキュアコーディングガイド PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 150 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

153 ポイント 7 APK を Export するときに 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 次 に 自 社 限 定 Content Provider を 利 用 する Activity の 例 を 示 す ポイント(Content Provider を 利 用 する): 8. 独 自 定 義 Signature Permission を 利 用 宣 言 する 9. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 10. 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する 11. 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい 12. 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する 13. 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.inhouseuser"> <!-- ポイント 7 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.provider.inhouseprovider.my_permission" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name="org.jssec.android.provider.inhouseuser.inhouseuseractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 151

154 <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> InhouseUserActivity.java package org.jssec.android.provider.inhouseuser; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.contentvalues; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.providerinfo; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.view.view; import android.widget.textview; public class InhouseUserActivity extends Activity { // 利 用 先 の Content Provider 情 報 private static final String AUTHORITY = "org.jssec.android.provider.inhouseprovider"; private interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.provider.inhouseprovider.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; // 利 用 先 Content Provider のパッケージ 名 を 取 得 private static String providerpkgname(context context, Uri uri) { String pkgname = null; PackageManager pm = context.getpackagemanager(); ProviderInfo pi = pm.resolvecontentprovider(uri.getauthority(), 0); if (pi!= null) pkgname = pi.packagename; return pkgname; 152 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

155 public void onqueryclick(view view) { logline("[query]"); // ポイント 9 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { logline(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; // ポイント 10 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する String pkgname = providerpkgname(this, Address.CONTENT_URI); if (!PkgCert.test(this, pkgname, mycerthash(this))) { logline(" 利 用 先 Content Provider は 自 社 アプリではない "); return; // ポイント 11 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい Cursor cursor = null; try { cursor = getcontentresolver().query(address.content_uri, null, null, null, null); // ポイント 12 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (cursor == null) { logline(" null cursor"); else { boolean moved = cursor.movetofirst(); while (moved) { logline(string.format(" %d, %s", cursor.getint(0), cursor.getstring(1))); moved = cursor.movetonext(); finally { if (cursor!= null) cursor.close(); public void oninsertclick(view view) { logline("[insert]"); // ポイント 9 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する String correcthash = mycerthash(this); if (!SigPerm.test(this, MY_PERMISSION, correcthash)) { logline(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; // ポイント 10 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する String pkgname = providerpkgname(this, Address.CONTENT_URI); if (!PkgCert.test(this, pkgname, correcthash)) { logline(" 利 用 先 Content Provider は 自 社 アプリではない "); return; // ポイント 11 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい ContentValues values = new ContentValues(); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 153

156 values.put("pref", " 東 京 都 "); Uri uri = getcontentresolver().insert(address.content_uri, values); // ポイント 12 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(" uri:" + uri); public void onupdateclick(view view) { logline("[update]"); // ポイント 9 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する String correcthash = mycerthash(this); if (!SigPerm.test(this, MY_PERMISSION, correcthash)) { logline(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; // ポイント 10 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する String pkgname = providerpkgname(this, Address.CONTENT_URI); if (!PkgCert.test(this, pkgname, correcthash)) { logline(" 利 用 先 Content Provider は 自 社 アプリではない "); return; // ポイント 11 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい ContentValues values = new ContentValues(); values.put("pref", " 東 京 都 "); String where = "_id =?"; String[] args = { "4" ; int count = getcontentresolver().update(address.content_uri, values, where, args); // ポイント 12 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records updated", count)); public void ondeleteclick(view view) { logline("[delete]"); // ポイント 9 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する String correcthash = mycerthash(this); if (!SigPerm.test(this, MY_PERMISSION, correcthash)) { logline(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; // ポイント 10 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する String pkgname = providerpkgname(this, Address.CONTENT_URI); if (!PkgCert.test(this, pkgname, correcthash)) { logline(" 利 用 先 Content Provider は 自 社 アプリではない "); return; // ポイント 11 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい int count = getcontentresolver().delete(address.content_uri, null, null); // ポイント 12 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する 154 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

157 // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(string.format(" %s records deleted", count)); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 155

158 PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 156 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

159 ポイント 13 APK を Export するときに 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 157

160 一 時 許 可 Content Provider を 作 る 利 用 する 一 時 許 可 Content Provider は 基 本 的 には 非 公 開 の Content Provider であるが 特 定 のアプリに 対 して 一 時 的 に 特 定 URI へのアクセスを 許 可 する Content Provider である 特 殊 なフラグを 指 定 した Intent を 対 象 アプリに 送 付 することにより そのアプリに 一 時 的 なアクセス 権 限 が 付 されるようになっている Content Provider 側 アプリが 能 動 的 に 他 のアプリにアクセス 許 可 を 与 えることもできるし 一 時 的 なアクセス 許 可 を 求 めてきたアプリに Content Provider 側 アプリが 受 動 的 にアクセス 許 可 を 与 えることもできる 以 下 一 時 許 可 Content Provider の 実 装 例 を 示 す ポイント(Content Provider を 作 る): 1. Android 2.2(API Level 8) 以 前 では 一 時 許 可 Content Provider を 実 装 しない 2. exported="false により 一 時 許 可 する Path 以 外 を 非 公 開 設 定 する 3. grant-uri-permission により 一 時 許 可 する Path を 指 定 する 4. 一 時 的 に 許 可 したアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する 5. 一 時 的 に 許 可 したアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい 6. 一 時 的 にアクセスを 許 可 する URI を Intent に 指 定 する 7. 一 時 的 に 許 可 するアクセス 権 限 を Intent に 指 定 する 8. 一 時 的 にアクセスを 許 可 するアプリに 明 示 的 Intent を 送 信 する 9. 一 時 許 可 の 要 求 元 アプリに Intent を 返 信 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.provider.temporaryprovider"> <application > <activity android:name=".temporaryactivegrantactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 一 時 許 可 Content Provider --> <!-- ポイント 2 exported="false"により 一 時 許 可 する Path 以 外 を 非 公 開 設 定 する --> <provider android:name=".temporaryprovider" android:authorities="org.jssec.android.provider.temporaryprovider" android:exported="false" > <!-- ポイント 3 grant-uri-permission により 一 時 許 可 する Path を 指 定 する --> <grant-uri-permission android:path="/addresses" /> 158 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

161 </provider> <activity android:name=".temporarypassivegrantactivity" android:exported="true" /> </application> </manifest> TemporaryProvider.java package org.jssec.android.provider.temporaryprovider; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.urimatcher; import android.database.cursor; import android.database.matrixcursor; import android.net.uri; public class TemporaryProvider extends ContentProvider { public static final String AUTHORITIY = "org.jssec.android.provider.temporaryprovider"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.org.jssec.contenttype"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.org.jssec.contenttype"; // Content Provider が 提 供 するインターフェースを 公 開 public interface Download { public static final String PATH = "downloads"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIY + "/" + PATH); public interface Address { public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIY + "/" + PATH); // UriMatcher private static final int DOWNLOADS_CODE = 1; private static final int DOWNLOADS_ID_CODE = 2; private static final int ADDRESSES_CODE = 3; private static final int ADDRESSES_ID_CODE = 4; private static UriMatcher surimatcher; static { surimatcher = new UriMatcher(UriMatcher.NO_MATCH); surimatcher.adduri(authoritiy, Download.PATH, DOWNLOADS_CODE); surimatcher.adduri(authoritiy, Download.PATH + "/#", DOWNLOADS_ID_CODE); surimatcher.adduri(authoritiy, Address.PATH, ADDRESSES_CODE); surimatcher.adduri(authoritiy, Address.PATH + "/#", ADDRESSES_ID_CODE); // DB を 使 用 せずに 固 定 値 を 返 す 例 にしているため query メソッドで 返 す Cursor を 事 前 に 定 義 private static MatrixCursor saddresscursor = new MatrixCursor(new String[] { "_id", "pref" ); static { saddresscursor.addrow(new String[] { "1", " 北 海 道 " ); saddresscursor.addrow(new String[] { "2", " 青 森 " ); saddresscursor.addrow(new String[] { "3", " 岩 手 " ); private static MatrixCursor sdownloadcursor = new MatrixCursor(new String[] { "_id", "path" ); static { All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 159

162 Android アプリのセキュア 設 計 セキュアコーディングガイド sdownloadcursor.addrow(new String[] { "1", "/sdcard/downloads/sample.jpg" ); sdownloadcursor.addrow(new String[] { "2", "/sdcard/downloads/sample.txt" ); public boolean oncreate() { return true; public String gettype(uri uri) { switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case ADDRESSES_CODE: return CONTENT_TYPE; case DOWNLOADS_ID_CODE: case ADDRESSES_ID_CODE: return CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { // ポイント 4 一 時 的 に 許 可 したアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 5 一 時 的 に 許 可 したアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // query の 結 果 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: case DOWNLOADS_ID_CODE: return sdownloadcursor; case ADDRESSES_CODE: case ADDRESSES_ID_CODE: return saddresscursor; default: throw new IllegalArgumentException("Invalid URI:" + uri); public Uri insert(uri uri, ContentValues values) { // ポイント 4 一 時 的 に 許 可 したアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 5 一 時 的 に 許 可 したアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Insert 結 果 発 番 される ID がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return ContentUris.withAppendedId(Download.CONTENT_URI, 3); 160 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

163 case ADDRESSES_CODE: return ContentUris.withAppendedId(Address.CONTENT_URI, 4); default: throw new IllegalArgumentException("Invalid URI:" + uri); public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { // ポイント 4 一 時 的 に 許 可 したアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 5 一 時 的 に 許 可 したアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Update されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 5; // update されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 15; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); public int delete(uri uri, String selection, String[] selectionargs) { // ポイント 4 一 時 的 に 許 可 したアプリからのリクエストであっても パラメータの 安 全 性 を 確 認 する // ここでは uri が 想 定 の 範 囲 内 であることを UriMatcher#match()と switch case で 確 認 している // その 他 のパラメータの 確 認 はサンプルにつき 省 略 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 // ポイント 5 一 時 的 に 許 可 したアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい // Delete されたレコード 数 がセンシティブな 意 味 を 持 つかどうかはアプリ 次 第 switch (surimatcher.match(uri)) { case DOWNLOADS_CODE: return 10; // delete されたレコード 数 を 返 す case DOWNLOADS_ID_CODE: return 1; case ADDRESSES_CODE: return 20; case ADDRESSES_ID_CODE: return 1; default: throw new IllegalArgumentException("Invalid URI:" + uri); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 161

164 Android アプリのセキュア 設 計 セキュアコーディングガイド TemporaryActiveGrantActivity.java package org.jssec.android.provider.temporaryprovider; import android.app.activity; import android.content.activitynotfoundexception; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class TemporaryActiveGrantActivity extends Activity { "; // User Activity に 関 する 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.provider.temporaryuser"; private static final String TARGET_ACTIVITY = "org.jssec.android.provider.temporaryuser.temporaryuseractivity protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.active_grant); // Content Provider 側 アプリが 能 動 的 に 他 のアプリにアクセス 許 可 を 与 えるケース public void onsendclick(view view) { try { Intent intent = new Intent(); // ポイント 6 一 時 的 にアクセスを 許 可 する URI を Intent に 指 定 する intent.setdata(temporaryprovider.address.content_uri); // ポイント 7 一 時 的 に 許 可 するアクセス 権 限 を Intent に 指 定 する intent.setflags(intent.flag_grant_read_uri_permission); // ポイント 8 一 時 的 にアクセスを 許 可 するアプリに 明 示 的 Intent を 送 信 する intent.setclassname(target_package, TARGET_ACTIVITY); startactivity(intent); catch (ActivityNotFoundException e) { Toast.makeText(this, "User Activity が 見 つからない ", Toast.LENGTH_LONG).show(); TemporaryPassiveGrantActivity.java package org.jssec.android.provider.temporaryprovider; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; public class TemporaryPassiveGrantActivity extends Activity { 162 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

165 protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.passive_grant); // 一 時 的 なアクセス 許 可 を 求 めてきたアプリに Content Provider 側 アプリが 受 動 的 にアクセス 許 可 を 与 えるケース public void ongrantclick(view view) { Intent intent = new Intent(); // ポイント 6 一 時 的 にアクセスを 許 可 する URI を Intent に 指 定 する intent.setdata(temporaryprovider.address.content_uri); // ポイント 7 一 時 的 に 許 可 するアクセス 権 限 を Intent に 指 定 する intent.setflags(intent.flag_grant_read_uri_permission); // ポイント 9 一 時 許 可 の 要 求 元 アプリに Intent を 返 信 する setresult(activity.result_ok, intent); finish(); public void oncloseclick(view view) { finish(); 次 に 一 時 許 可 Content Provider を 利 用 する Activity の 例 を 示 す ポイント(Content Provider を 利 用 する): 10. センシティブな 情 報 をリクエストに 含 めてはならない 11. 結 果 データの 安 全 性 を 確 認 する TemporaryUserActivity.java package org.jssec.android.provider.temporaryuser; import android.app.activity; import android.content.activitynotfoundexception; import android.content.intent; import android.content.pm.providerinfo; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.view.view; import android.widget.textview; import android.widget.toast; public class TemporaryUserActivity extends Activity { // Provider Activity に 関 する 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.provider.temporaryprovider"; private static final String TARGET_ACTIVITY = "org.jssec.android.provider.temporaryprovider.temporarypassiveg rantactivity"; // 利 用 先 の Content Provider 情 報 private static final String AUTHORITY = "org.jssec.android.provider.temporaryprovider"; private interface Address { All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 163

166 Android アプリのセキュア 設 計 セキュアコーディングガイド public static final String PATH = "addresses"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + PATH); private static final int REQUEST_CODE = 1; public void onqueryclick(view view) { logline("[query]"); Cursor cursor = null; try { if (!providerexists(address.content_uri)) { logline(" Content Provider が 不 在 "); return; // ポイント 10 センシティブな 情 報 をリクエストに 含 めてはならない // リクエスト 先 のアプリがマルウェアである 可 能 性 がある // マルウェアに 取 得 されても 問 題 のない 情 報 であればリクエストに 含 めてもよい cursor = getcontentresolver().query(address.content_uri, null, null, null, null); // ポイント 11 結 果 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 if (cursor == null) { logline(" null cursor"); else { boolean moved = cursor.movetofirst(); while (moved) { logline(string.format(" %d, %s", cursor.getint(0), cursor.getstring(1))); moved = cursor.movetonext(); catch (SecurityException ex) { logline(" 例 外 :" + ex.getmessage()); finally { if (cursor!= null) cursor.close(); ); // このアプリが 一 時 的 なアクセス 許 可 を 要 求 し Content Provider 側 アプリが 受 動 的 にアクセス 許 可 を 与 えるケース public void ongrantrequestclick(view view) { Intent intent = new Intent(); intent.setclassname(target_package, TARGET_ACTIVITY); try { startactivityforresult(intent, REQUEST_CODE); catch (ActivityNotFoundException e) { logline("grant の 要 求 に 失 敗 しました \ntemporaryprovider がインストールされているか 確 認 してください " private boolean providerexists(uri uri) { ProviderInfo pi = getpackagemanager().resolvecontentprovider(uri.getauthority(), 0); return (pi!= null); private TextView mlogview; 164 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

167 // Content Provider 側 アプリが 能 動 的 にこのアプリにアクセス 許 可 を 与 えるケース public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView)findViewById(R.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 165

168 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド Content Provider の 実 装 時 には 以 下 のルールを 守 ること 1. Android 2.2(API Level 8) 以 前 ではアプリ 内 でのみ 使 用 する Content Provider は 作 らない ( 必 須 ) 2. アプリ 内 でのみ 使 用 する Content Provider は 非 公 開 設 定 する ( 必 須 ) 3. リクエストパラメータの 安 全 性 を 確 認 する ( 必 須 ) 4. 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 5. 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) 6. 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) また 利 用 側 は 以 下 のルールも 守 ること 7. Content Provider の 結 果 データの 安 全 性 を 確 認 する ( 必 須 ) Android 2.2(API Level 8) 以 前 ではアプリ 内 でのみ 使 用 する Content Provider は 作 らない ( 必 須 ) Content Provider の 非 公 開 設 定 は Android 2.2(API Level 8) 以 前 では 機 能 しない 同 一 アプリ 内 でのデータ 共 有 のためなら Content Provider を 使 わず DB などのデータ 格 納 先 へ 直 接 アクセスすることで 代 用 できる アプリ 内 でのみ 使 用 する Content Provider は 非 公 開 設 定 する ( 必 須 ) 同 一 アプリ 内 からのみ 利 用 される Content Provider は 他 のアプリからアクセスできる 必 要 がないだけでなく 開 発 者 も Content Provider を 攻 撃 するアクセスを 考 慮 しないことが 多 い Content Provider はデータ 共 有 するための 仕 組 みであるため デフォルトでは 公 開 扱 いになってしまう 同 一 アプリ 内 からのみ 利 用 される Content Provider は 明 示 的 に 非 公 開 設 定 し 非 公 開 Content Provider とすべきである Android 2.3.1(API Level 9) 以 降 では provider 要 素 に android:exported="false"と 指 定 することで Content Provider を 非 公 開 にできる AndroidManifest.xml <!-- ポイント 1 Android 2.2(API Level 8) 以 前 では 非 公 開 Content Provider を 実 装 しない(できない) --> <uses-sdk android:minsdkversion="9" /> ~ 省 略 ~ <!-- ポイント 2 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <provider android:name=".privateprovider" android:authorities="org.jssec.android.provider.privateprovider" android:exported="false" /> リクエストパラメータの 安 全 性 を 確 認 する ( 必 須 ) Content Provider のタイプによって 若 干 リスクは 異 なるが リクエストパラメータを 処 理 する 際 には まずその 安 全 性 を 確 認 しなければならない 166 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

169 Content Provider の 各 メソッドは SQL 文 の 構 成 要 素 パラメータを 受 け 取 ることを 想 定 したインターフェースになって はいるものの 仕 組 みの 上 では 単 に 任 意 の 文 字 列 を 受 け 渡 すだけのものであり Content Provider 側 では 想 定 外 のパラメータが 与 えられるケースを 想 定 しなければならないことに 注 意 が 必 要 だ 公 開 Content Provider は 不 特 定 多 数 のアプリからリクエストを 受 け 取 るため マルウェアの 攻 撃 リクエストを 受 け 取 る 可 能 性 がある 非 公 開 Content Provider は 他 のアプリからリクエストを 直 接 受 け 取 ることはない しかし 同 一 アプ リ 内 の 公 開 Activity が 他 のアプリから 受 け 取 った Intent のデータを 非 公 開 Content Provider に 転 送 するといった ケースも 考 えられるため リクエストを 無 条 件 に 安 全 であると 考 えてはならない その 他 の Content Provider につい ても やはりリクエストの 安 全 性 を 確 認 する 必 要 がある 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 自 社 アプリだけから 利 用 できる 自 社 限 定 Content Provider を 作 る 場 合 独 自 定 義 Signature Permission により 保 護 しなければならない AndroidManifest.xml での Permission 定 義 Permission 要 求 宣 言 だけでは 保 護 が 不 十 分 であるため 5.2 Permission と Protection Level の 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 を 参 照 すること 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) query()や insert()ではリクエスト 要 求 元 アプリに 結 果 情 報 として Cursor や Uri が 返 送 される 結 果 情 報 にセンシテ ィブな 情 報 が 含 まれる 場 合 返 送 先 アプリから 情 報 漏 洩 する 可 能 性 がある また update()や delete()では 更 新 また は 削 除 されたレコード 数 がリクエスト 要 求 元 アプリに 結 果 情 報 として 返 送 される まれにアプリ 仕 様 によっては 更 新 ま たは 削 除 されたレコード 数 がセンシティブな 意 味 を 持 つ 場 合 があるので 注 意 すべきだ 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) Permission により 保 護 されている 情 報 資 産 および 機 能 資 産 を 他 のアプリに 二 次 的 に 提 供 する 場 合 には 提 供 先 ア プリに 対 して 同 一 の Permission を 要 求 するなどして その 保 護 水 準 を 維 持 しなければならない Android の Permission セキュリティモデルでは 保 護 された 資 産 に 対 するアプリからの 直 接 アクセスについてのみ 権 限 管 理 を 行 う この 仕 様 上 の 特 性 により アプリに 取 得 された 資 産 がさらに 他 のアプリに 保 護 のために 必 要 な Permission を 要 求 することなく 提 供 される 可 能 性 がある このことは Permission を 再 委 譲 していることと 実 質 的 に 等 価 なので Permission の 再 委 譲 問 題 と 呼 ばれる Permission の 再 委 譲 問 題 を 参 照 すること Content Provider の 結 果 データの 安 全 性 を 確 認 する ( 必 須 ) Content Provider のタイプによって 若 干 リスクは 異 なるが 結 果 データを 処 理 する 際 には まず 結 果 データの 安 全 性 を 確 認 しなければならない All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する 167

170 利 用 先 Content Provider が 公 開 Content Provider の 場 合 公 開 Content Provider に 成 り 済 ましたマルウェア が 攻 撃 結 果 データを 返 送 してくる 可 能 性 がある 利 用 先 Content Provider が 非 公 開 Content Provider の 場 合 同 一 アプリ 内 から 結 果 データを 受 け 取 るのでリスクは 少 ないが 結 果 データを 無 条 件 に 安 全 であると 考 えてはならな い その 他 の Content Provider についても やはり 結 果 データの 安 全 性 を 確 認 する 必 要 がある 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 168 All rights reserved Japan Smartphone Security Association. Content Provider を 作 る 利 用 する

171 4.4. Service を 作 る 利 用 する サンプルコード Service がどのように 利 用 されるかによって Service が 抱 えるリスクや 適 切 な 防 御 手 段 が 異 なる 次 の 判 定 フローに よって 作 成 する Service がどのタイプであるかを 判 断 できる なお 作 成 する Service のタイプによって Service を 利 用 する 側 の 実 装 も 決 まるので 利 用 側 の 実 装 についても 合 わせて 説 明 する はじめ Yes アプリ 内 でのみ 利 用 する No Yes 不 特 定 多 数 の アプリに 利 用 を 認 める No Yes 特 定 他 社 の アプリに 利 用 を 認 める No Private 非 公 開 Service Public 公 開 Service Exclusive パートナー 限 定 Service Proprietary 自 社 限 定 Service 図 Service には 複 数 の 実 装 方 法 があり その 中 から 作 成 する Service のタイプに 合 った 方 法 を 選 択 することになる 下 表 の 縦 の 項 目 が 本 文 書 で 扱 う 実 装 方 法 であり 5 種 類 に 分 類 した 表 中 の 印 は 実 現 可 能 な 組 み 合 わせを 示 し そ の 他 は 実 現 不 可 能 もしくは 困 難 なものを 示 す なお Service の 実 装 方 法 の 詳 細 については Service の 実 装 方 法 について および 各 Service タイプのサ ンプルコード( 表 中 で* 印 の 付 いたもの)を 参 照 すること 表 分 類 非 公 開 Service 公 開 Service パートナー 限 定 Service 自 社 限 定 Service startservice 型 * - IntentService 型 * - local bind 型 Messenger bind 型 - * AIDL bind 型 * 以 下 では 表 中 の* 印 の 組 み 合 わせを 使 って 各 セキュリティタイプの Service のサンプルコードを 示 す All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 169

172 非 公 開 Service を 作 る 利 用 する 非 公 開 Service は 同 一 アプリ 内 でのみ 利 用 される Service であり もっとも 安 全 性 の 高 い Service である また 非 公 開 Service を 利 用 するには クラスを 指 定 する 明 示 的 Intent を 使 えば 誤 って 外 部 アプリに Intent を 送 信 してしまうことがない 以 下 startservice 型 の Service を 使 用 した 例 を 示 す ポイント(Service を 作 る): 1. exported="false"により 明 示 的 に 非 公 開 設 定 する 2. 同 一 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 3. 結 果 を 返 す 場 合 利 用 元 アプリは 同 一 アプリであるから センシティブな 情 報 を 返 送 してよい AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.privateservice" > <application android:allowbackup="false"> <activity android:name=".privateuseractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <!-- 非 公 開 Service --> <!-- ポイント 1 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <service android:name=".privatestartservice" android:exported="false"/> <!-- IntentService を 継 承 した Service --> <!-- 非 公 開 Service --> <!-- ポイント 1 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <service android:name=".privateintentservice" android:exported="false"/> </application> </manifest> PrivateStartService.java package org.jssec.android.service.privateservice; import android.app.service; import android.content.intent; import android.os.ibinder; import android.widget.toast; 170 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

173 public class PrivateStartService extends Service{ // Service が 起 動 するときに1 回 だけ 呼 び 出 される public void oncreate() { Toast.makeText(this, this.getclass().getsimplename() + " - oncreate()", Toast.LENGTH_SHORT).show(); // startservice()が 呼 ばれた 回 数 だけ 呼 び 出 される public int onstartcommand(intent intent, int flags, int startid) { // ポイント 2 同 一 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = intent.getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); // サービスは 明 示 的 に 終 了 させる // stopself や stopservice を 実 行 したときにサービスを 終 了 する // START_NOT_STICKY は メモリが 少 ない 等 で kill された 場 合 に 自 動 的 には 復 帰 しない return Service.START_NOT_STICKY; // Service が 終 了 するときに1 回 だけ 呼 び 出 される public void ondestroy() { Toast.makeText(this, this.getclass().getsimplename() + " - ondestroy()", Toast.LENGTH_SHORT).show(); public IBinder onbind(intent intent) { // このサービスにはバインドしない return null; 次 に 非 公 開 Service を 利 用 する Activity のサンプルコードを 示 す ポイント(Service を 利 用 する): 4. 同 一 アプリ 内 Service はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す 5. 利 用 先 アプリは 同 一 アプリであるから センシティブな 情 報 を 送 信 してもよい 6. 結 果 を 受 け 取 る 場 合 同 一 アプリ 内 Service からの 結 果 情 報 であっても 受 信 データの 安 全 性 を 確 認 する PrivateUserActivity.java package org.jssec.android.service.privateservice; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; public class PrivateUserActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 171

174 Android アプリのセキュア 設 計 セキュアコーディングガイド setcontentview(r.layout.privateservice_activity); // サービス 開 始 public void onstartserviceclick(view v) { // ポイント 4 同 一 アプリ 内 Service はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す Intent intent = new Intent(this, PrivateStartService.class); // ポイント 5 利 用 先 アプリは 同 一 アプリであるから センシティブな 情 報 を 送 信 してもよい intent.putextra("param", "センシティブな 情 報 "); startservice(intent); // サービス 停 止 ボタン public void onstopserviceclick(view v) { dostopservice(); public void onstop(){ super.onstop(); // サービスが 終 了 していない 場 合 は 終 了 する dostopservice(); // サービスを 停 止 する private void dostopservice() { // ポイント 4 同 一 アプリ 内 Service はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す Intent intent = new Intent(this, PrivateStartService.class); stopservice(intent); // IntentService 開 始 ボタン public void onintentserviceclick(view v) { // ポイント 4 同 一 アプリ 内 Service はクラス 指 定 の 明 示 的 Intent で 呼 び 出 す Intent intent = new Intent(this, PrivateIntentService.class); // ポイント 5 利 用 先 アプリは 同 一 アプリであるから センシティブな 情 報 を 送 信 してもよい intent.putextra("param", "センシティブな 情 報 "); startservice(intent); 172 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

175 公 開 Service を 作 る 利 用 する 公 開 Service は 不 特 定 多 数 のアプリに 利 用 されることを 想 定 した Service である マルウェアが 送 信 した 情 報 (Intent など)を 受 信 することがあることに 注 意 が 必 要 である また 公 開 Service を 利 用 するには 送 信 する 情 報 (Intent など)がマルウェアに 受 信 されることがあることに 注 意 が 必 要 である 以 下 IntentService 型 の Service を 使 用 した 例 を 示 す ポイント(Service を 作 る): 1. exported="true"により 明 示 的 に 公 開 設 定 する 2. 受 信 Intent の 安 全 性 を 確 認 する 3. 結 果 を 返 す 場 合 センシティブな 情 報 を 含 めない AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.publicservice" > <application android:allowbackup="false" > <!-- 最 も 標 準 的 な Service --> <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <service android:name=".publicstartservice" android:exported="true"> <intent-filter> <action android:name="org.jssec.android.service.publicservice.action.startservice" /> </intent-filter> </service> <!-- IntentService を 継 承 した Service --> <!-- ポイント 1 exported="true"により 明 示 的 に 公 開 設 定 する --> <service android:name=".publicintentservice" android:exported="true"> <intent-filter> <action android:name="org.jssec.android.service.publicservice.action.intentservice" /> </intent-filter> </service> </application> </manifest> PublicIntentService.java package org.jssec.android.service.publicservice; import android.app.intentservice; import android.content.intent; import android.widget.toast; public class PublicIntentService extends IntentService{ All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 173

176 /** * IntentService を 継 承 した 場 合 引 数 無 しのコンストラクタを 必 ず 用 意 する * これが 無 い 場 合 エラーになる */ public PublicIntentService() { super("creatingtypebservice"); // Service が 起 動 するときに1 回 だけ 呼 び 出 される public void oncreate() { super.oncreate(); Toast.makeText(this, this.getclass().getsimplename() + " - oncreate()", Toast.LENGTH_SHORT).show(); // Service で 行 いたい 処 理 をこのメソッドに 記 述 する protected void onhandleintent(intent intent) { // ポイント 2 受 信 Intent の 安 全 性 を 確 認 する // 公 開 Activity であるため 利 用 元 アプリがマルウェアである 可 能 性 がある // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = intent.getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); // Service が 終 了 するときに1 回 だけ 呼 び 出 される public void ondestroy() { Toast.makeText(this, this.getclass().getsimplename() + " - ondestroy()", Toast.LENGTH_SHORT).show(); 次 に 公 開 Service を 利 用 する Activity のサンプルコードを 示 す ポイント(Service を 利 用 する): 4. センシティブな 情 報 を 送 信 してはならない 5. 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.publicserviceuser" > <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <activity android:name=".publicuseractivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.main" /> 174 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

177 <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> PublicUserActivity.java package org.jssec.android.service.publicserviceuser; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; public class PublicUserActivity extends Activity { ; e"; // 利 用 先 Service 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.service.publicservice"; private static final String TARGET_START_CLASS = "org.jssec.android.service.publicservice.publicstartservice" private static final String TARGET_INTENT_CLASS = "org.jssec.android.service.publicservice.publicintentservic public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.publicservice_activity); // サービス 開 始 public void onstartserviceclick(view v) { Intent intent = new Intent("org.jssec.android.service.publicservice.action.startservice"); // ポイント 4 Service は 明 示 的 Intent で 呼 び 出 す intent.setclassname(target_package, TARGET_START_CLASS); // ポイント 5 センシティブな 情 報 を 送 信 してはならない intent.putextra("param", "センシティブではない 情 報 "); startservice(intent); // ポイント 6 結 果 を 受 け 取 る 場 合 結 果 データの 安 全 性 を 確 認 する // 本 サンプルは startservice()を 使 った Service 利 用 の 例 の 為 結 果 情 報 は 受 け 取 らない // サービス 停 止 ボタン public void onstopserviceclick(view v) { dostopservice(); // IntentService 開 始 ボタン public void onintentserviceclick(view v) { Intent intent = new Intent("org.jssec.android.service.publicservice.action.intentservice"); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 175

178 // ポイント 4 Service は 明 示 的 Intent で 呼 び 出 す intent.setclassname(target_package, TARGET_INTENT_CLASS); // ポイント 5 センシティブな 情 報 を 送 信 してはならない intent.putextra("param", "センシティブではない 情 報 "); startservice(intent); public void onstop(){ super.onstop(); // サービスが 終 了 していない 場 合 は 終 了 する dostopservice(); // サービスを 停 止 する private void dostopservice() { Intent intent = new Intent("org.jssec.android.service.publicservice.action.startservice"); // ポイント 4 Service は 明 示 的 Intent で 呼 び 出 す intent.setclassname(target_package, TARGET_START_CLASS); stopservice(intent); 176 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

179 パートナー 限 定 Service パートナー 限 定 Service は 特 定 のアプリだけから 利 用 できる Service である パートナー 企 業 のアプリと 自 社 アプリ が 連 携 してシステムを 構 成 し パートナーアプリとの 間 で 扱 う 情 報 や 機 能 を 守 るために 利 用 される 以 下 AIDL bind 型 の Service を 使 用 した 例 を 示 す ポイント(Service を 作 る): 1. Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する 2. 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 3. onbind(onstartcommand,onhandleintent)で 呼 び 出 し 元 がパートナーかどうか 判 別 できない 4. パートナーアプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 5. パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい なお ホワイトリストに 指 定 する 利 用 先 アプリの 証 明 書 ハッシュ 値 の 確 認 方 法 は アプリの 証 明 書 のハッシュ 値 を 確 認 する 方 法 を 参 照 すること AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.partnerservice.aidl" > <application android:allowbackup="false"> <!-- AIDL を 利 用 した Service --> <!-- ポイント 1 Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する --> <service android:name="org.jssec.android.service.partnerservice.aidl.partneraidlservice" android:exported="true" /> </application> </manifest> 今 回 の 例 では AIDL ファイルを2つ 作 成 する 1つは Service から Activity にデータを 渡 すためのコールバックイン ターフェースで もう1つは Activity から Service にデータを 渡 し 情 報 を 取 得 するインターフェースである なお AIDL ファイルに 記 述 するパッケージ 名 は java ファイルに 記 述 するパッケージ 名 と 同 様 に AIDL ファイルを 作 成 する ディレクトリ 階 層 に 一 致 させる 必 要 がある IPartnerAIDLServiceCallback.aidl package org.jssec.android.service.partnerservice.aidl; interface IPartnerAIDLServiceCallback { /** * 値 が 変 わった 時 に 呼 び 出 される All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 177

180 Android アプリのセキュア 設 計 セキュアコーディングガイド */ void valuechanged(string info); IPartnerAIDLService.aidl package org.jssec.android.service.partnerservice.aidl; import org.jssec.android.service.partnerservice.aidl.iexclusiveaidlservicecallback; interface IPartnerAIDLService { /** * コールバックを 登 録 する */ void registercallback(ipartneraidlservicecallback cb); /** * 情 報 を 取 得 する */ String getinfo(string param); /** * コールバックを 解 除 する */ void unregistercallback(ipartneraidlservicecallback cb); PartnerAIDLService.java package org.jssec.android.service.partnerservice.aidl; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.service; import android.content.context; import android.content.intent; import android.os.handler; import android.os.ibinder; import android.os.message; import android.os.remotecallbacklist; import android.os.remoteexception; import android.widget.toast; public class PartnerAIDLService extends Service { private static final int REPORT_MSG = 1; private static final int GETINFO_MSG = 2; // Service からクライアントに 通 知 する 値 private int mvalue = 0; // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナーアプリ org.jssec.android.service.partnerservice.aidluser の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.service.partnerservice.aidluser", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 178 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

181 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"partner key"の 証 明 書 ハッシュ 値 "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // 以 下 同 様 に 他 のパートナーアプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); // コールバックを 登 録 するオブジェクト // RemoteCallbackList の 提 供 するメソッドはスレッドセーフになっている private final RemoteCallbackList<IPartnerAIDLServiceCallback> mcallbacks = new RemoteCallbackList<>(); // コールバックに 対 して Service からデータを 送 信 するための Handler protected static class ServiceHandler extends Handler{ private Context mcontext; private RemoteCallbackList<IPartnerAIDLServiceCallback> mcallbacks; private int mvalue = 0; e){ public ServiceHandler(Context context, RemoteCallbackList<IPartnerAIDLServiceCallback> callback, int valu this.mcontext = context; this.mcallbacks = callback; this.mvalue = value; public void handlemessage(message msg) { switch (msg.what) { case REPORT_MSG: { if(mcallbacks == null){ return; // 通 知 を 開 始 する // beginbroadcast()は getbroadcastitem()で 取 得 可 能 なコピーを 作 成 している final int N = mcallbacks.beginbroadcast(); for (int i = 0; i < N; i++) { IPartnerAIDLServiceCallback target = mcallbacks.getbroadcastitem(i); try { // ポイント 5 パートナーアプリに 開 示 してよい 情 報 に 限 り 送 信 してよい target.valuechanged("パートナーアプリに 開 示 してよい 情 報 (callback from Service) No." + (++mvalue)); catch (RemoteException e) { // RemoteCallbackList がコールバックを 管 理 しているので ここでは unregeister しない // RemoteCallbackList.kill()によって 全 て 解 除 される // finishbroadcast()は beginbroadcast()と 対 になる 処 理 mcallbacks.finishbroadcast(); // 10 秒 後 に 繰 り 返 す sendemptymessagedelayed(report_msg, 10000); break; All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 179

182 Android アプリのセキュア 設 計 セキュアコーディングガイド case GETINFO_MSG: { if(mcontext!= null) { Toast.makeText(mContext, (String) msg.obj, Toast.LENGTH_LONG).show(); break; default: super.handlemessage(msg); break; // switch protected final ServiceHandler mhandler = new ServiceHandler(this, mcallbacks, mvalue); // AIDL で 定 義 したインターフェース private final IPartnerAIDLService.Stub mbinder = new IPartnerAIDLService.Stub() { private boolean checkpartner() { Context ctx = PartnerAIDLService.this; if (!PartnerAIDLService.checkPartner(ctx, Utils.getPackageNameFromUid(ctx, getcallinguid()))) { mhandler.post(new Runnable() { public void run() { Toast.makeText(PartnerAIDLService.this, " 利 用 元 アプリはパートナーアプリではない ", Toast. LENGTH_LONG).show(); ); return false; return true; public void registercallback(ipartneraidlservicecallback cb) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner()) { return; if (cb!= null) mcallbacks.register(cb); public String getinfo(string param) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner()) { return null; // ポイント 4 パートナーアプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Message msg = new Message(); msg.what = GETINFO_MSG; msg.obj = String.format("パートナーアプリからのメソッド 呼 び 出 し %s を 受 信 した ", param); PartnerAIDLService.this.mHandler.sendMessage(msg); // ポイント 5 パートナーアプリに 開 示 してよい 情 報 に 限 り 返 送 してよい return "パートナーアプリに 開 示 してよい 情 報 (method from Service)"; public void unregistercallback(ipartneraidlservicecallback cb) { // ポイント 2 利 用 元 アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner()) { return; 180 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

183 ; if (cb!= null) mcallbacks.unregister(cb); public IBinder onbind(intent intent) { // ポイント 3 onbind で 呼 び 出 し 元 がパートナーかどうか 判 別 できない // AIDL で 定 義 したメソッドの 呼 び 出 し 毎 にチェックが 必 要 になる return mbinder; public void oncreate() { Toast.makeText(this, this.getclass().getsimplename() + " - oncreate()", Toast.LENGTH_SHORT).show(); // Service が 実 行 中 の 間 は 定 期 的 にインクリメントした 数 字 を 通 知 する mhandler.sendemptymessage(report_msg); public void ondestroy() { Toast.makeText(this, this.getclass().getsimplename() + " - ondestroy()", Toast.LENGTH_SHORT).show(); // コールバックを 全 て 解 除 する mcallbacks.kill(); mhandler.removemessages(report_msg); PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 181

184 Android アプリのセキュア 設 計 セキュアコーディングガイド public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); 182 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

185 return hexadecimal.tostring(); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 183

186 次 にパートナー 限 定 Service を 利 用 する Activity のサンプルコードを 示 す ポイント(Service を 利 用 する): 6. 利 用 先 パートナー 限 定 Service アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する 7. 利 用 先 パートナー 限 定 アプリに 開 示 してよい 情 報 に 限 り 送 信 してよい 8. 明 示 的 Intent によりパートナー 限 定 Service を 呼 び 出 す 9. パートナー 限 定 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する PartnerAIDLUserActivity.java package org.jssec.android.service.partnerservice.aidluser; import org.jssec.android.service.partnerservice.aidl.ipartneraidlservice; import org.jssec.android.service.partnerservice.aidl.ipartneraidlservicecallback; import org.jssec.android.service.partnerservice.aidl.r; import org.jssec.android.shared.pkgcertwhitelists; import org.jssec.android.shared.utils; import android.app.activity; import android.content.componentname; import android.content.context; import android.content.intent; import android.content.serviceconnection; import android.os.bundle; import android.os.handler; import android.os.ibinder; import android.os.message; import android.os.remoteexception; import android.view.view; import android.widget.toast; public class PartnerAIDLUserActivity extends Activity { private boolean misbound; private Context mcontext; private final static int MGS_VALUE_CHANGED = 1; // ポイント 6 利 用 先 パートナー 限 定 Service アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する private static PkgCertWhitelists swhitelists = null; private static void buildwhitelists(context context) { boolean isdebug = Utils.isDebuggable(context); swhitelists = new PkgCertWhitelists(); // パートナー 限 定 Service アプリ org.jssec.android.service.partnerservice.aidl の 証 明 書 ハッシュ 値 を 登 録 swhitelists.add("org.jssec.android.service.partnerservice.aidl", isdebug? // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // keystore の"my company key"の 証 明 書 ハッシュ 値 "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"); // 以 下 同 様 に 他 のパートナー 限 定 Service アプリを 登 録... private static boolean checkpartner(context context, String pkgname) { if (swhitelists == null) buildwhitelists(context); return swhitelists.test(context, pkgname); 184 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

187 ; // 利 用 先 のパートナー 限 定 Activity に 関 する 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.service.partnerservice.aidl"; private static final String TARGET_CLASS = "org.jssec.android.service.partnerservice.aidl.partneraidlservice" private static class ReceiveHandler extends Handler{ private Context mcontext; public ReceiveHandler(Context context){ this.mcontext = context; public void handlemessage(message msg) { switch (msg.what) { case MGS_VALUE_CHANGED: { String info = (String)msg.obj; Toast.makeText(mContext, String.format("コールバックで %s を 受 信 した ", info), Toast.LENGT H_SHORT).show(); break; default: super.handlemessage(msg); break; // switch private final ReceiveHandler mhandler = new ReceiveHandler(this); // AIDL で 定 義 したインターフェース Service からの 通 知 を 受 け 取 る private final IPartnerAIDLServiceCallback.Stub mcallback = new IPartnerAIDLServiceCallback.Stub() { public void valuechanged(string info) throws RemoteException { Message msg = mhandler.obtainmessage(mgs_value_changed, info); mhandler.sendmessage(msg); ; // AIDL で 定 義 したインターフェース Service へ 通 知 する private IPartnerAIDLService mservice = null; // Service と 接 続 する 時 に 利 用 するコネクション bindservice で 実 装 する 場 合 は 必 要 になる private ServiceConnection mconnection = new ServiceConnection() { // Service に 接 続 された 場 合 に 呼 ばれる public void onserviceconnected(componentname classname, IBinder service) { mservice = IPartnerAIDLService.Stub.asInterface(service); try{ // Service に 接 続 mservice.registercallback(mcallback); catch(remoteexception e){ // Service が 異 常 終 了 した 場 合 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 185

188 Toast.makeText(mContext, "Connected to service", Toast.LENGTH_SHORT).show(); ; // Service が 異 常 終 了 して コネクションが 切 断 された 場 合 に 呼 ばれる public void onservicedisconnected(componentname classname) { Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show(); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.partnerservice_activity); mcontext = this; // サービス 開 始 ボタン public void onstartserviceclick(view v) { // bindservice を 実 行 する dobindservice(); // 情 報 取 得 ボタン public void ongetinfoclick(view v) { getserviceinfo(); // サービス 停 止 ボタン public void onstopserviceclick(view v) { dounbindservice(); public void ondestroy() { super.ondestroy(); dounbindservice(); /** * Service に 接 続 する */ private void dobindservice() { if (!misbound){ // ポイント 6 利 用 先 パートナー 限 定 Service アプリの 証 明 書 がホワイトリストに 登 録 されていることを 確 認 する if (!checkpartner(this, TARGET_PACKAGE)) { Toast.makeText(this, " 利 用 先 Service アプリはホワイトリストに 登 録 されていない ", Toast.LENGTH_LO NG).show(); return; Intent intent = new Intent(); // ポイント 7 利 用 先 パートナー 限 定 アプリに 開 示 してよい 情 報 に 限 り 送 信 してよい intent.putextra("param", "パートナーアプリに 開 示 してよい 情 報 "); 186 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

189 // ポイント 8 明 示 的 Intent によりパートナー 限 定 Service を 呼 び 出 す intent.setclassname(target_package, TARGET_CLASS); bindservice(intent, mconnection, Context.BIND_AUTO_CREATE); misbound = true; /** * Service への 接 続 を 切 断 する */ private void dounbindservice() { if (misbound) { // 登 録 していたレジスタがある 場 合 は 解 除 if(mservice!= null){ try{ mservice.unregistercallback(mcallback); catch(remoteexception e){ // Service が 異 常 終 了 していた 場 合 // サンプルにつき 処 理 は 割 愛 unbindservice(mconnection); Intent intent = new Intent(); // ポイント 8 明 示 的 Intent によりパートナー 限 定 Service を 呼 び 出 す intent.setclassname(target_package, TARGET_CLASS); stopservice(intent); misbound = false; /** * Service から 情 報 を 取 得 する */ void getserviceinfo() { if (misbound && mservice!= null) { String info = null; how(); try { // ポイント 7 利 用 先 パートナー 限 定 アプリに 開 示 してよい 情 報 に 限 り 送 信 してよい info = mservice.getinfo("パートナーアプリに 開 示 してよい 情 報 (method from activity)"); catch (RemoteException e) { e.printstacktrace(); // ポイント 9 パートナー 限 定 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(mContext, String.format("サービスから %s を 取 得 した ", info), Toast.LENGTH_SHORT).s All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 187

190 PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない 188 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

191 Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 189

192 自 社 限 定 Service 自 社 限 定 Service は 自 社 以 外 のアプリから 利 用 されることを 禁 止 する Service である 複 数 の 自 社 製 アプリでシス テムを 構 成 し 自 社 アプリが 扱 う 情 報 や 機 能 を 守 るために 利 用 される 以 下 Messenger bind 型 の Service を 使 用 した 例 を 示 す ポイント(Service を 作 る): 1. 独 自 定 義 Signature Permission を 定 義 する 2. 独 自 定 義 Signature Permission を 要 求 宣 言 する 3. Intent Filter を 定 義 せず exported="true"を 明 示 的 に 設 定 する 4. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 5. 自 社 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する 6. 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい 7. 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.inhouseservice.messengeruser" > <!-- ポイント 8 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.service.inhouseservice.messenger.my_permission" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <activity android:name="org.jssec.android.service.inhouseservice.messengeruser.inhousemessengeruseractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> InhouseMessengerService.java package org.jssec.android.service.inhouseservice.messenger; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import java.util.arraylist; import java.util.iterator; 190 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

193 import android.app.service; import android.content.context; import android.content.intent; import android.os.bundle; import android.os.handler; import android.os.ibinder; import android.os.message; import android.os.messenger; import android.os.remoteexception; import android.widget.toast; public class InhouseMessengerService extends Service{ // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.my_permission "; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; // Service のクライアント(データ 送 信 先 )をリストで 管 理 する private ArrayList<Messenger> mclients = new ArrayList<Messenger>(); // クライアントからのデータを 受 信 するときに 利 用 する Messenger private final Messenger mmessenger = new Messenger(new ServiceSideHandler(mClients)); // クライアントから 受 け 取 った Message を 処 理 する Handler private static class ServiceSideHandler extends Handler{ private ArrayList<Messenger> mclients; public ServiceSideHandler(ArrayList<Messenger> clients){ this.mclients = clients; public void handlemessage(message msg){ switch(msg.what){ case CommonValue.MSG_REGISTER_CLIENT: // クライアントから 受 け 取 った Messenger を 追 加 mclients.add(msg.replyto); break; case CommonValue.MSG_UNREGISTER_CLIENT: mclients.remove(msg.replyto); break; case CommonValue.MSG_SET_VALUE: // クライアントにデータを 送 る sendmessagetoclients(mclients); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 191

194 Android アプリのセキュア 設 計 セキュアコーディングガイド break; default: super.handlemessage(msg); break; /** * クライアントにデータを 送 る */ private static void sendmessagetoclients(arraylist<messenger> mclients){ // ポイント 6 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 返 送 してよい String sendvalue = "センシティブな 情 報 (from Service)"; // 登 録 されているクライアントへ 順 番 に 送 信 する // ループ 途 中 で remove しても 全 てのデータにアクセスしたいので Iterator を 利 用 する Iterator<Messenger> ite = mclients.iterator(); while(ite.hasnext()){ try { Message sendmsg = Message.obtain(null, CommonValue.MSG_SET_VALUE, null); Bundle data = new Bundle(); data.putstring("key", sendvalue); sendmsg.setdata(data); Messenger next = ite.next(); next.send(sendmsg); catch (RemoteException e) { // クライアントが 存 在 しない 場 合 は リストから 取 り 除 く ite.remove(); public IBinder onbind(intent intent) { // ポイント 4 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); return null; // ポイント 5 自 社 アプリからの Intent であっても 受 信 Intent の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String param = intent.getstringextra("param"); Toast.makeText(this, String.format("パラメータ %s を 受 け 取 った ", param), Toast.LENGTH_LONG).show(); return mmessenger.getbinder(); public void oncreate() { Toast.makeText(this, "Service - oncreate()", Toast.LENGTH_SHORT).show(); 192 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

195 public void ondestroy() { Toast.makeText(this, "Service - ondestroy()", Toast.LENGTH_SHORT).show(); SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 193

196 public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 194 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

197 ポイント 7 APK を Export するときに 利 用 元 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 195

198 次 に 自 社 限 定 Service を 利 用 する Activity のサンプルコードを 示 す ポイント(Service を 利 用 する): 8. 独 自 定 義 Signature Permission を 利 用 宣 言 する 9. 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 10. 利 用 先 アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する 11. 利 用 先 アプリは 自 社 アプリであるから センシティブな 情 報 を 送 信 してもよい 12. 明 示 的 Intent により 自 社 限 定 Service を 呼 び 出 す 13. 自 社 アプリからの 結 果 情 報 であっても 受 信 Intent の 安 全 性 を 確 認 する 14. 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.service.inhouseservice.messengeruser" > <!-- ポイント 8 独 自 定 義 Signature Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.service.inhouseservice.messenger.my_permission" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <activity android:name="org.jssec.android.service.inhouseservice.messengeruser.inhousemessengeruseractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> InhouseMessengerUserActivity.java package org.jssec.android.service.inhouseservice.messengeruser; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.componentname; import android.content.context; import android.content.intent; import android.content.serviceconnection; import android.os.bundle; import android.os.handler; import android.os.ibinder; import android.os.message; 196 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

199 import android.os.messenger; import android.os.remoteexception; import android.view.view; import android.widget.toast; public class InhouseMessengerUserActivity extends Activity { private boolean misbound; private Context mcontext; // 利 用 先 の Activity 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.service.inhouseservice.messenger"; private static final String TARGET_CLASS = "org.jssec.android.service.inhouseservice.messenger.inhousemesseng erservice"; "; // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.service.inhouseservice.messenger.my_permission // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; // Service からデータを 受 信 するときに 利 用 する Messenger private Messenger mservicemessenger = null; // Service にデータを 送 信 するときに 利 用 する Messenger private final Messenger mactivitymessenger = new Messenger(new ActivitySideHandler()); // Service から 受 け 取 った Message を 処 理 する Handler private class ActivitySideHandler extends Handler { public void handlemessage(message msg) { switch (msg.what) { case CommonValue.MSG_SET_VALUE: Bundle data = msg.getdata(); String info = data.getstring("key"); // ポイント 13 自 社 アプリからの 結 果 情 報 であっても 値 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 Toast.makeText(mContext, String.format("サービスから %s を 取 得 した ", info), Toast.LENGTH_SHORT).show(); break; default: super.handlemessage(msg); All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 197

200 // Service と 接 続 する 時 に 利 用 するコネクション bindservice で 実 装 する 場 合 は 必 要 になる private ServiceConnection mconnection = new ServiceConnection() { // Service に 接 続 された 場 合 に 呼 ばれる public void onserviceconnected(componentname classname, IBinder service) { mservicemessenger = new Messenger(service); Toast.makeText(mContext, "Connect to service", Toast.LENGTH_SHORT).show(); try { // Service に 自 分 の Messenger を 渡 す Message msg = Message.obtain(null, CommonValue.MSG_REGISTER_CLIENT); msg.replyto = mactivitymessenger; mservicemessenger.send(msg); catch (RemoteException e) { // Service が 異 常 終 了 していた 場 合 ; // Service が 異 常 終 了 して コネクションが 切 断 された 場 合 に 呼 ばれる public void onservicedisconnected(componentname classname) { mservicemessenger = null; Toast.makeText(mContext, "Disconnected from service", Toast.LENGTH_SHORT).show(); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.publicservice_activity); mcontext = this; // サービス 開 始 ボタン public void onstartserviceclick(view v) { // bindservice を 実 行 する dobindservice(); // 情 報 取 得 ボタン public void ongetinfoclick(view v) { getserviceinfo(); // サービス 停 止 ボタン public void onstopserviceclick(view v) { dounbindservice(); protected void ondestroy() { super.ondestroy(); dounbindservice(); /** * Service に 接 続 する */ 198 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

201 void dobindservice() { if (!misbound){ // ポイント 9 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.L ENGTH_LONG).show(); return; // ポイント 10 利 用 先 アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する if (!PkgCert.test(this, TARGET_PACKAGE, mycerthash(this))) { Toast.makeText(this, " 利 用 先 サービスは 自 社 アプリではない ", Toast.LENGTH_LONG).show(); return; Intent intent = new Intent(); // ポイント 11 利 用 先 アプリは 自 社 アプリであるから センシティブな 情 報 を 送 信 してもよい intent.putextra("param", "センシティブな 情 報 "); // ポイント 12 明 示 的 Intent により 自 社 限 定 Service を 呼 び 出 す intent.setclassname(target_package, TARGET_CLASS); bindservice(intent, mconnection, Context.BIND_AUTO_CREATE); misbound = true; /** * Service への 接 続 を 切 断 する */ void dounbindservice() { if (misbound) { unbindservice(mconnection); misbound = false; /** * Service から 情 報 を 取 得 する */ void getserviceinfo() { if (mservicemessenger!= null) { try { // 情 報 の 送 信 を 要 求 する Message msg = Message.obtain(null, CommonValue.MSG_SET_VALUE); mservicemessenger.send(msg); catch (RemoteException e) { // Service が 異 常 終 了 していた 場 合 SigPerm.java package org.jssec.android.shared; All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 199

202 import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); 200 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

203 Android アプリのセキュア 設 計 セキュアコーディングガイド if (pkginfo.signatures.length!= 1) return null; Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; // 複 数 署 名 は 扱 わない private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); ポイント 14 APK を Export するときに 利 用 先 アプリと 同 じ 開 発 者 鍵 で APK を 署 名 する 図 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 201

204 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド Service 実 装 時 には 以 下 のルールを 守 ること 1. アプリ 内 でのみ 使 用 する Service は 非 公 開 設 定 する ( 必 須 ) 2. 受 信 データの 安 全 性 を 確 認 する ( 必 須 ) 3. 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 4. 連 携 するタイミングで Service の 機 能 を 提 供 するかを 判 定 する ( 必 須 ) 5. 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) 6. 利 用 先 Service が 固 定 できる 場 合 は 明 示 的 Intent で Service を 利 用 する ( 必 須 ) 7. 他 社 の 特 定 アプリと 連 携 する 場 合 は 利 用 先 Service を 確 認 する ( 必 須 ) 8. 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) 9. センシティブな 情 報 はできる 限 り 送 らない ( 推 奨 ) アプリ 内 でのみ 使 用 する Service は 非 公 開 設 定 する ( 必 須 ) アプリ 内 (または 同 じ UID)でのみ 使 用 される Service は 非 公 開 設 定 する これにより 他 のアプリから 意 図 せず Intent を 受 け 取 ってしまうことがなくなり アプリの 機 能 を 利 用 される アプリの 動 作 に 異 常 をきたす 等 の 被 害 を 防 ぐこ とができる 実 装 上 は AndroidManifest.xml で Service を 定 義 する 際 に exported 属 性 を false にするだけである AndroidManifest.xml <!-- 非 公 開 Service --> <!-- ポイント 1 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <service android:name=".privatestartservice" android:exported="false"/> また ケースは 少 ないと 思 われるが 同 一 アプリ 内 からのみ 利 用 される Service であり かつ Intent Filter を 設 置 す るような 設 計 はしてはならない Intent Filter の 性 質 上 同 一 アプリ 内 の 非 公 開 Service を 呼 び 出 すつもりでも Intent Filter 経 由 で 呼 び 出 したときに 意 図 せず 他 アプリの 公 開 Service を 呼 び 出 してしまう 場 合 が 存 在 するからであ る AndroidManifest.xml( 非 推 奨 ) <!-- 非 公 開 Service --> <!-- ポイント 1 exported="false"により 明 示 的 に 非 公 開 設 定 する --> <service android:name=".privatestartservice" android:exported="false"> <intent-filter> <action android:name= org.jssec.android.service.open /> </intent-filter> </service> exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Service の 場 合 ) も 参 照 すること 202 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

205 受 信 データの 安 全 性 を 確 認 する ( 必 須 ) Service も Activity と 同 様 に 受 信 Intent のデータを 処 理 する 際 には まず 受 信 Intent の 安 全 性 を 確 認 しなければ ならない Service を 利 用 する 側 も Service からの 結 果 (として 受 信 した) 情 報 の 安 全 性 を 確 認 する 必 要 がある Activity の 受 信 Intent の 安 全 性 を 確 認 する ( 必 須 ) 利 用 先 Activity からの 戻 り Intent の 安 全 性 を 確 認 する ( 必 須 ) も 参 照 すること Service においては Intent 以 外 にもメソッドの 呼 び 出 しや Message によるデータの 送 受 信 などがあるため それぞ れ 注 意 して 実 装 を 行 わなければならない 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 すること 独 自 定 義 Signature Permission は 自 社 アプリが 定 義 したことを 確 認 して 利 用 する ( 必 須 ) 自 社 アプリだけから 利 用 できる 自 社 限 定 Service を 作 る 場 合 独 自 定 義 Signature Permission により 保 護 しなけれ ばならない AndroidManifest.xml での Permission 定 義 Permission 要 求 宣 言 だけでは 保 護 が 不 十 分 であるた め 5.2 Permission と Protection Level の 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 す る 方 法 を 参 照 すること 連 携 するタイミングで Service の 機 能 を 提 供 するかを 判 定 する ( 必 須 ) Intent パラメータの 確 認 や 独 自 定 義 Signature Permission の 確 認 といったセキュリティチェックを oncreate に 入 れてはいけない その 理 由 は Service が 起 動 中 に 新 しい 要 求 を 受 けたときに oncreate の 処 理 が 実 施 されないため で あ る し た が っ て startservice に よ っ て 開 始 さ れ る Service を 実 装 す る 場 合 は onstartcommand (IntentService を 利 用 する 場 合 は onhandleintent)で 判 定 を 行 わなければならない bindservice で 開 始 する Service を 実 装 する 場 合 も 同 様 のことが 言 えるので onbind で 判 定 をしなければならない 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) Service のタイプによって 結 果 情 報 の 返 送 先 (コールバックの 呼 び 出 し 先 や Message の 送 信 先 )アプリの 信 用 度 が 異 なる 返 送 先 がマルウェアである 可 能 性 も 考 慮 して 十 分 に 情 報 漏 洩 に 対 する 配 慮 をしなければならない 詳 細 は Activity の 結 果 情 報 を 返 す 場 合 には 返 送 先 アプリからの 結 果 情 報 漏 洩 に 注 意 する ( 必 須 ) を 参 照 すること 利 用 先 Service が 固 定 できる 場 合 は 明 示 的 Intent で Service を 利 用 する ( 必 須 ) 暗 黙 的 Intent により Service を 利 用 すると Intent Filter の 定 義 が 同 じ 場 合 には 先 にインストールした Service に Intent が 送 信 されてしまう もし 意 図 的 に 同 じ Intent Filter を 定 義 したマルウェアが 先 にインストールされていた 場 合 マルウェアに Intent が 送 信 されてしまい 情 報 漏 洩 が 生 じる 一 方 明 示 的 Intent により Service を 利 用 すると 指 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 203

206 定 した Service 以 外 が Intent を 受 信 することはなく 比 較 的 安 全 である ただし 別 途 考 慮 すべき 点 があるので Activity の 利 用 先 Activity が 固 定 できる 場 合 は 明 示 的 Intent で Activity を 利 用 する ( 必 須 ) を 参 照 すること 他 社 の 特 定 アプリと 連 携 する 場 合 は 利 用 先 Service を 確 認 する ( 必 須 ) 他 社 の 特 定 アプリと 連 携 する 場 合 にはホワイトリストによる 確 認 方 法 がある 自 アプリ 内 に 利 用 先 アプリの 証 明 書 ハ ッシュを 予 め 保 持 しておく 利 用 先 の 証 明 書 ハッシュと 保 持 している 証 明 書 ハッシュが 一 致 するかを 確 認 することで なりすましアプリに Intent を 発 行 することを 防 ぐことができる 具 体 的 な 実 装 方 法 についてはサンプルコードセクショ ン パートナー 限 定 Service を 参 照 すること 資 産 を 二 次 的 に 提 供 する 場 合 には その 資 産 の 従 来 の 保 護 水 準 を 維 持 する ( 必 須 ) Permission により 保 護 されている 情 報 資 産 および 機 能 資 産 を 他 のアプリに 二 次 的 に 提 供 する 場 合 には 提 供 先 ア プリに 対 して 同 一 の Permission を 要 求 するなどして その 保 護 水 準 を 維 持 しなければならない Android の Permission セキュリティモデルでは 保 護 された 資 産 に 対 するアプリからの 直 接 アクセスについてのみ 権 限 管 理 を 行 う この 仕 様 上 の 特 性 により アプリに 取 得 された 資 産 がさらに 他 のアプリに 保 護 のために 必 要 な Permission を 要 求 することなく 提 供 される 可 能 性 がある このことは Permission を 再 委 譲 していることと 実 質 的 に 等 価 なので Permission の 再 委 譲 問 題 と 呼 ばれる Permission の 再 委 譲 問 題 を 参 照 すること センシティブな 情 報 はできる 限 り 送 らない ( 推 奨 ) 不 特 定 多 数 のアプリと 連 携 する 場 合 にはセンシティブな 情 報 を 送 ってはならない センシティブな 情 報 を Service と 受 け 渡 しする 場 合 その 情 報 の 漏 洩 リスクを 検 討 しなければならない 公 開 Service に 送 付 した 情 報 は 必 ず 漏 洩 すると 考 えなければならない またパートナー 限 定 Serviceや 自 社 限 定 Serviceに 送 付 し た 情 報 もそれら Service の 実 装 に 依 存 して 情 報 漏 洩 リスクの 大 小 がある センシティブな 情 報 はできるだけ 送 付 しないように 工 夫 すべきである 送 付 する 場 合 も 利 用 先 Service は 信 頼 できる Service に 限 定 し 情 報 が LogCat などに 漏 洩 しないように 配 慮 しなければならない 204 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

207 アドバンスト exported 設 定 と intent-filter 設 定 の 組 み 合 わせ(Service の 場 合 ) このガイド 文 書 では Service の 用 途 から 非 公 開 Service 公 開 Service パートナー 限 定 Service 自 社 限 定 Service の 4 タイプの Service について 実 装 方 法 を 述 べている 各 タイプに 許 されている AndroidManifest.xml の exported 属 性 と intent-filter 要 素 の 組 み 合 わせを 次 の 表 にまとめた 作 ろうとしている Service のタイプと exported 属 性 および intent-filter 要 素 の 対 応 が 正 しいことを 確 認 すること 表 exported 属 性 の 値 true false 無 指 定 intent-filter 定 義 がある 公 開 ( 使 用 禁 止 ) ( 使 用 禁 止 ) intent-filter 定 義 がない 公 開 パートナー 限 定 非 公 開 ( 使 用 禁 止 ) 自 社 限 定 intent-filter 定 義 がある & exported= false を 使 用 禁 止 にしているのは Android の 振 る 舞 いとして 同 一 ア プリ 内 の 非 公 開 Service を 呼 び 出 したつもりでも 意 図 せず 他 アプリの 公 開 Service を 呼 び 出 してしまう 場 合 が 存 在 するためである 具 体 的 には Android は 以 下 のような 振 る 舞 いをするのでアプリ 設 計 時 に 検 討 が 必 要 である 複 数 の Service で 同 じ 内 容 の intent-filter を 定 義 した 場 合 先 にインストールしたアプリ 内 の Service の 定 義 が 優 先 される 暗 黙 的 Intent を 使 った 場 合 は OS によって 優 先 の Service が 自 動 的 に 選 ばれて 呼 び 出 される 以 下 の 3 つの 図 で Android の 振 る 舞 いによる 意 図 せぬ 呼 び 出 しが 起 こる 仕 組 みを 説 明 する 図 は 同 一 ア プリ 内 からしか 非 公 開 Service(アプリ A)を 暗 黙 的 Intent で 呼 び 出 せない 正 常 な 動 作 の 例 である Intent-filter( 図 中 action="x")を 定 義 しているのが アプリ A しかいないので 意 図 通 りの 動 きとなっている All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 205

208 アプリA 暗 黙 的 Intentを 使 って Serviceを 呼 び 出 す Intent( X ) 非 公 開 Service A-1 exported= false action= X アプリC 暗 黙 的 Intentを 使 って Service を 呼 び 出 す Intent( X ) Service A-1は 非 公 開 Serviceなので ア プリAからしか 呼 び 出 すことはできない Android 端 末 図 図 および 図 は アプリ A に 加 えてアプリ B でも 同 じ intent-filter( 図 中 action="x")を 定 義 している 場 合 である 図 は アプリ A アプリ B の 順 でインストールされた 場 合 である この 場 合 アプリ C が 暗 黙 的 Intent を 送 信 すると 非 公 開 の Service(A-1)を 呼 び 出 そうとして 失 敗 する 一 方 アプリ A は 暗 黙 的 Intent を 使 って 意 図 通 りに 同 一 アプリ 内 の 非 公 開 Service を 呼 び 出 せるので セキュリティの(マルウェア 対 策 の) 面 では 問 題 は 起 こらない アプリA 暗 黙 的 Intentを 使 って Serviceを 呼 び 出 す Intent( X ) 非 公 開 Service A-1 exported= false action= X アプリC 暗 黙 的 Intentを 使 って Service を 呼 び 出 す Intent( X ) アプリB 公 開 Service B-1 exported= true action= X 非 公 開 Serviceを 持 ったアプリAが 先 にイ ンストールされた 場 合 は 非 公 開 Sevice のintent-filterのみが 有 効 になり 外 部 からの 呼 び 出 しは 受 け 付 けない Android 端 末 図 図 は アプリ B アプリ A の 順 でインストールされた 場 合 であり セキュリティ 面 からみて 問 題 がある アプリ A 206 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

209 が 暗 黙 的 Intent を 送 信 して 同 一 アプリ 内 の 非 公 開 Service を 呼 び 出 そうとするが 先 にインストールしたアプリ B の 公 開 Activity(B-1)が 呼 び 出 されてしまう 例 を 示 している これによりアプリ A からアプリ B に 対 してセンシティブな 情 報 を 送 信 する 可 能 性 が 生 じてしまう アプリ B がマルウェアであれば そのままセンシティブな 情 報 の 漏 洩 に 繋 がる アプリB 公 開 Service B-1 exported= true action= X アプリC 暗 黙 的 Intentを 使 って Service を 呼 び 出 す Intent( X ) アプリA 暗 黙 的 Intentを 使 って Serviceを 呼 び 出 す Intent( X ) 非 公 開 Service A-1 exported= false action= X Android 端 末 公 開 Serviceを 持 ったアプリBが 先 にイン ストールされた 場 合 は 公 開 Seviceの intent-filterのみが 有 効 になり アプリA からの 呼 び 出 しで 誤 ってService(B-1) を 呼 び 出 してしまう 図 このように Intent Filter を 用 いた 非 公 開 Service の 暗 黙 的 Intent 呼 び 出 しは 意 図 せぬアプリの 呼 び 出 しや 意 図 せぬアプリへのセンシティブな 情 報 の 送 信 を 避 けるためにも 行 うべきではない Service の 実 装 方 法 について Service の 実 装 方 法 は 多 様 であり サンプルコードで 分 類 したセキュリティ 上 のタイプとの 相 性 もあるため 簡 単 に 特 徴 を 示 す startservice を 利 用 する 場 合 と bindservice を 利 用 する 場 合 とに 大 きく 分 かれるが startservice と bindservice の 両 方 で 利 用 できる Service を 作 成 することも 可 能 である Service の 実 装 方 法 を 決 定 するために 次 のような 項 目 について 検 討 を 行 うことになる Service を 別 アプリに 公 開 するか(Service の 公 開 ) 実 行 中 にデータのやり 取 りを 行 うか(データの 相 互 送 受 信 ) Service を 制 御 するか( 起 動 や 終 了 など) 別 プロセスとして 実 行 するか(プロセス 間 通 信 ) 複 数 の 処 理 を 同 時 に 行 うか( 並 行 処 理 ) 実 装 方 法 の 分 類 と 各 々の 項 目 の 実 現 の 可 否 を 表 にすると 表 のようになる は 実 現 不 可 能 かもしくは 提 供 される 機 能 とは 別 の 枠 組 みが 必 要 な 場 合 を 表 す All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 207

210 表 Service の 実 装 方 法 の 分 類 分 類 Service の 公 開 データの 相 互 送 受 信 Service の 制 御 ( 起 動 終 了 ) プロセス 間 通 信 並 行 処 理 startservice 型 IntentService 型 local bind 型 Messenger bind 型 AIDL bind 型 startservice 型 最 も 基 本 的 な Service である Service クラスを 継 承 し onstartcommand で 処 理 を 行 う Service のことを 指 す 利 用 する 側 は Service を Intent で 指 定 して startservice を 使 用 して 呼 び 出 す Intent の 送 信 元 に 対 して 結 果 な どのデータを 直 接 返 すことはできないため Broadcast など 別 の 方 法 を 組 み 合 わせて 実 現 する 必 要 がある 具 体 的 な 実 装 例 は 非 公 開 Service を 作 る 利 用 する を 参 照 のこと セキュリティ 上 のチェックは onstartcommand で 行 う 必 要 があるが 送 信 元 のパッケージ 名 が 取 得 できないためパ ートナー 限 定 Service には 使 用 できない IntentService 型 IntentService は Service を 継 承 して 作 られているクラスである 呼 び 出 し 方 は startservice 型 と 同 様 である 通 常 の Service(startService 型 )に 比 べて 以 下 の 特 徴 がある Intent の 処 理 は onhandleintent で 行 う (onstartcommand は 使 わない) 別 スレッドで 実 行 される 処 理 がキューイングされる 処 理 が 別 スレッドのため 呼 び 出 しは 即 座 に 返 され キューイング 機 構 によりシーケンシャルに Intent に 対 する 処 理 が 行 われる 各 Intentの 並 行 処 理 はされないが 製 品 の 要 件 によっては 実 装 の 簡 素 化 の 一 つとして 選 択 が 可 能 である Intent の 送 信 元 に 対 して 結 果 などのデータを 直 接 返 すことはできないため Broadcast など 別 の 方 法 を 組 み 合 わ せて 実 現 する 必 要 がある 具 体 的 な 実 装 例 は 公 開 Service を 作 る 利 用 する を 参 照 のこと セキュリティ 上 のチェックは onhandleintent で 行 う 必 要 があるが 送 信 元 のパッケージ 名 が 取 得 できないためパー トナー 限 定 Service には 使 用 できない local bind 型 アプリと 同 じプロセス 内 でのみ 動 くローカル Service を 実 装 するための 方 法 を 指 す Binder クラスから 派 生 したクラス 208 All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する

211 を 定 義 して Service で 実 装 した 機 能 (メソッド)を 呼 び 出 し 元 に 提 供 できるようにする 利 用 する 側 は Service を Intent で 指 定 して bindservice を 使 用 して 呼 び 出 す Service を bind する 方 法 の 中 で は 最 もシンプルな 実 装 であるが 別 プロセスでの 起 動 や Service の 公 開 ができないため 用 途 は 限 定 される 具 体 的 な 実 装 例 は サンプルコードに 含 まれるプロジェクト Service PrivateServiceLocalBind を 参 照 のこと セキュリティ 的 には 非 公 開 Service のみ 実 装 可 能 である Messenger bind 型 Messenger の 仕 組 みを 利 用 して Service との 連 携 を 実 現 する 方 法 を 指 す Service を 利 用 する 側 からも Message の 返 信 先 として Messenger を 渡 すことができるため 双 方 でのデータのやり 取 りが 比 較 的 容 易 に 実 現 可 能 である また 処 理 はキューイングされるため スレッドセーフに 動 作 する 特 徴 がある 各 Message の 並 行 処 理 はされないが 製 品 の 要 件 によっては 実 装 の 簡 素 化 の 一 つとして 選 択 が 可 能 である 利 用 する 側 は Service を Intent で 指 定 して bindservice を 使 用 して 呼 び 出 す 具 体 的 な 実 装 例 は 自 社 限 定 Service を 参 照 のこと セキュリティ 上 のチェックは onbind や Message Handler で 行 う 必 要 があるが 送 信 元 のパッケージ 名 が 取 得 でき ないためパートナー 限 定 Service には 使 用 できない AIDL bind 型 AIDL の 仕 組 みを 利 用 して Service との 連 携 を 実 現 する 方 法 を 指 す AIDL によってインターフェースを 定 義 し Service の 持 つ 機 能 をメソッドとして 提 供 する また AIDL で 定 義 したインターフェースを 利 用 側 で 実 装 することで コ ールバックを 実 現 することもできる マルチスレッド 呼 び 出 しは 可 能 だが 排 他 処 理 はされないので Service 側 で 明 示 的 に 実 装 する 必 要 がある 利 用 する 側 は Service を Intent で 指 定 して bindservice を 使 用 して 呼 び 出 す 具 体 的 な 実 装 例 は パ ートナー 限 定 Service を 参 照 のこと セキュリティ 上 のチェックは 自 社 限 定 Service では onbind で パートナー 限 定 Service では AIDL で 定 義 したインタ ーフェースの 各 メソッドで 行 う 必 要 がある 本 文 書 で 分 類 した 全 セキュリティタイプの Service に 利 用 可 能 である All rights reserved Japan Smartphone Security Association. Service を 作 る 利 用 する 209

212 4.5. SQLite を 使 う Android アプリのセキュア 設 計 セキュアコーディングガイド 本 文 書 では SQLite を 使 用 してデータベースの 作 成 および 操 作 を 行 う 際 にセキュリティ 上 で 注 意 すべき 点 をまとめる 主 なポイントは データベースファイルのアクセス 権 の 適 切 な 設 定 と SQL インジェクションに 対 する 対 策 である ここ では 直 接 外 部 からデータベースファイルの 読 み 書 きを 許 す( 複 数 アプリで 共 有 する)ようなデータベースはここでは 想 定 せず Content Provider のバックエンドやアプリ 単 体 での 使 用 を 前 提 とする また ある 程 度 センシティブな 情 報 を 扱 っていることを 想 定 しているが そうでない 場 合 も 他 アプリからの 想 定 外 の 読 み 書 きを 避 けるためにもここで 挙 げる 対 策 を 適 用 することをお 勧 めする サンプルコード データベースの 作 成 と 操 作 Android のアプリでデータベースを 扱 う 場 合 SQLiteOpenHelper を 使 用 することでデータベースファイルの 適 切 な 配 置 およびアクセス 権 の 設 定 ( 他 のアプリがアクセスできない 設 定 )ができる 5 ここでは アプリ 起 動 時 にデータベー スを 作 成 し UI 上 からデータの 検 索 追 加 変 更 削 除 を 行 う 簡 単 なアプリを 例 に 外 部 からの 入 力 に 対 して 不 正 な SQL が 実 行 されないように SQL インジェクション 対 策 したサンプルコードを 示 す 5 ファイルの 配 置 に 関 しては SQLiteOpenHelper のコンストラクタの 第 2 引 数 (name)にファイルの 絶 対 パスも 指 定 で きる そのため 誤 って SD カードを 直 接 指 定 した 場 合 には 他 のアプリからの 読 み 書 きが 可 能 になるので 注 意 が 必 要 であ る 210 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

213 図 ポイント: 1. データベース 作 成 には SQLiteOpenHelper を 使 用 する 2. SQL インジェクションの 対 策 として 入 力 値 を SQL 文 に 使 用 する 場 合 にはプレースホルダを 利 用 する 3. SQL インジェクションの 保 険 的 な 対 策 としてアプリ 要 件 に 従 って 入 力 値 をチェックする SampleDbOpenHelper.java package org.jssec.android.sqlite; import android.content.context; import android.database.sqlexception; import android.database.sqlite.sqlitedatabase; import android.database.sqlite.sqliteopenhelper; import android.util.log; import android.widget.toast; public class SampleDbOpenHelper extends SQLiteOpenHelper { private SQLiteDatabase msampledb; // 取 り 扱 うデータを 格 納 するデータベース public static SampleDbOpenHelper newhelper(context context) { // ポイント 1 DB 作 成 には SQLiteOpenHelper を 使 用 する return new SampleDbOpenHelper(context); public SQLiteDatabase getdb() { return msampledb; All rights reserved Japan Smartphone Security Association. SQLite を 使 う 211

214 //Writable モードで DB を 開 く public void opendatabasewithhelper() { try { if (msampledb!= null && msampledb.isopen()) { if (!msampledb.isreadonly())// 既 に 読 み 書 き 可 能 でオープン 済 み return; msampledb.close(); msampledb = getwritabledatabase(); //この 段 階 でオープンされる catch (SQLException e) { //データベース 構 築 に 失 敗 した 場 合 ログ 出 力 Log.e(mContext.getClass().toString(), mcontext.getstring(r.string.database_open_error_message)); Toast.makeText(mContext, R.string.DATABASE_OPEN_ERROR_MESSAGE, Toast.LENGTH_LONG).show(); //ReadOnly モードで DB を 開 く public void opendatabasereadonly() { try { if (msampledb!= null && msampledb.isopen()) { if (msampledb.isreadonly())// 既 に ReadOnly でオープン 済 み return; msampledb.close(); SQLiteDatabase.openDatabase(mContext.getDatabasePath(CommonData.DBFILE_NAME).getPath(), null, SQLiteD atabase.open_readonly); catch (SQLException e) { //データベース 構 築 に 失 敗 した 場 合 ログ 出 力 Log.e(mContext.getClass().toString(), mcontext.getstring(r.string.database_open_error_message)); Toast.makeText(mContext, R.string.DATABASE_OPEN_ERROR_MESSAGE, Toast.LENGTH_LONG).show(); //Database Close public void closedatabase() { try { if (msampledb!= null && msampledb.isopen()) { msampledb.close(); catch (SQLException e) { //データベース 構 築 に 失 敗 した 場 合 ログ 出 力 Log.e(mContext.getClass().toString(), mcontext.getstring(r.string.database_close_error_message)); Toast.makeText(mContext, R.string.DATABASE_CLOSE_ERROR_MESSAGE, Toast.LENGTH_LONG).show(); //Context を 覚 えておく private Context mcontext; //テーブル 作 成 コマンド private static final String CREATE_TABLE_COMMANDS = "CREATE TABLE " + CommonData.TABLE_NAME + " (" + "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + "idno INTEGER UNIQUE, " + "name VARCHAR(" + CommonData.TEXT_DATA_LENGTH_MAX + ") NOT NULL, " + "info VARCHAR(" + CommonData.TEXT_DATA_LENGTH_MAX + ")" + ");"; public SampleDbOpenHelper(Context context) { super(context, CommonData.DBFILE_NAME, null, CommonData.DB_VERSION); 212 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

215 Android アプリのセキュア 設 計 セキュアコーディングガイド mcontext = context; public void oncreate(sqlitedatabase db) { try { db.execsql(create_table_commands); //DB 構 築 コマンドの 実 行 catch (SQLException e) { //データベース 構 築 に 失 敗 した 場 合 ログ 出 力 Log.e(this.getClass().toString(), mcontext.getstring(r.string.database_create_error_message)); public void onupgrade(sqlitedatabase arg0, int arg1, int arg2) { // データベースのバージョンアップ 時 に 実 行 される データ 移 行 などの 処 理 を 記 述 する DataSearchTask.java (SQLite Database プロジェクト) package org.jssec.android.sqlite.task; import org.jssec.android.sqlite.commondata; import org.jssec.android.sqlite.datavalidator; import org.jssec.android.sqlite.mainactivity; import org.jssec.android.sqlite.r; import android.database.cursor; import android.database.sqlexception; import android.database.sqlite.sqlitedatabase; import android.os.asynctask; import android.util.log; //データ 検 索 タスク public class DataSearchTask extends AsyncTask<String, Void, Cursor> { private MainActivity mactivity; private SQLiteDatabase msampledb; public DataSearchTask(SQLiteDatabase db, MainActivity activity) { msampledb = db; mactivity = activity; protected Cursor doinbackground(string... params) { String idno = params[0]; String name = params[1]; String info = params[2]; String cols[] = {"_id", "idno","name","info"; Cursor cur; // ポイント 3 アプリ 要 件 に 従 って 入 力 値 をチェックする if (!DataValidator.validateData(idno, name, info)) { return null; All rights reserved Japan Smartphone Security Association. SQLite を 使 う 213

216 Android アプリのセキュア 設 計 セキュアコーディングガイド // 引 数 が 全 部 null だったら 全 件 検 索 する) if ((idno == null idno.length() == 0) && (name == null name.length() == 0) && (info == null info.length() == 0) ) { try { cur = msampledb.query(commondata.table_name, cols, null, null, null, null, null); catch (SQLException e) { Log.e(DataSearchTask.class.toString(), mactivity.getstring(r.string.searching_error_message)); return null; return cur; //No が 指 定 されていたら No で 検 索 if (idno!= null && idno.length() > 0) { String selectionargs[] = {idno; try { // ポイント 2 プレースホルダを 使 用 する cur = msampledb.query(commondata.table_name, cols, "idno =?", selectionargs, null, null, null); catch (SQLException e) { Log.e(DataSearchTask.class.toString(), mactivity.getstring(r.string.searching_error_message)); return null; return cur; //Name が 指 定 されていたら Name で 完 全 一 致 検 索 if (name!= null && name.length() > 0) { String selectionargs[] = {name; try { // ポイント 2 プレースホルダを 使 用 する cur = msampledb.query(commondata.table_name, cols, "name =?", selectionargs, null, null, null); catch (SQLException e) { Log.e(DataSearchTask.class.toString(), mactivity.getstring(r.string.searching_error_message)); return null; return cur; //それ 以 外 の 場 合 は info を 条 件 にして 部 分 一 致 検 索 String argstring = info.replaceall("@", "@@"); // 入 力 として 受 け 取 った info 内 の$をエスケープ argstring = argstring.replaceall("%", "@%"); // 入 力 として 受 け 取 った info 内 の%をエスケープ argstring = argstring.replaceall("_", "@_"); // 入 力 として 受 け 取 った info 内 の_をエスケープ String selectionargs[] = {argstring; try { // ポイント 2 プレースホルダを 使 用 する cur = msampledb.query(commondata.table_name, cols, "info LIKE '%'? '%' ESCAPE '@'", selectiona rgs, null, null, null); catch (SQLException e) { Log.e(DataSearchTask.class.toString(), mactivity.getstring(r.string.searching_error_message)); return null; return cur; 214 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

217 protected void onpostexecute(cursor resultcur) { mactivity.updatecursor(resultcur); DataValidator.java package org.jssec.android.sqlite; public class DataValidator { // 入 力 値 をチェックする // 数 字 チェック public static boolean validateno(string idno) { //null 空 文 字 は OK if (idno == null idno.length() == 0) { return true; // 数 字 であることを 確 認 する try { if (!idno.matches("[1-9][0-9]*")) { // 数 字 以 外 の 時 はエラー return false; catch (NullPointerException e) { //エラーを 検 出 した return false; return true; // 文 字 列 の 長 さを 調 べる public static boolean validatelength(string str, int max_length) { //null 空 文 字 は OK if (str == null str.length() == 0) { return true; // 文 字 列 の 長 さが MAX 以 下 であることを 調 べる try { if (str.length() > max_length) { //MAX より 長 い 時 はエラー return false; catch (NullPointerException e) { //バグ return false; return true; // 入 力 値 チェック public static boolean validatedata(string idno, String name, String info) { if (!validateno(idno)) { return false; if (!validatelength(name, CommonData.TEXT_DATA_LENGTH_MAX)) { All rights reserved Japan Smartphone Security Association. SQLite を 使 う 215

218 Android アプリのセキュア 設 計 セキュアコーディングガイド return false; else if (!validatelength(info, CommonData.TEXT_DATA_LENGTH_MAX)) { return false; return true; 216 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

219 ルールブック SQLite を 使 用 する 際 には 以 下 のルールを 守 ること 1. DB ファイルの 配 置 場 所 アクセス 権 を 正 しく 設 定 する ( 必 須 ) 2. 他 アプリと DB データを 共 有 する 場 合 は Content Provider でアクセス 制 御 する ( 必 須 ) 3. DB 操 作 時 に 可 変 パラメータを 扱 う 場 合 はプレースホルダを 使 用 する ( 必 須 ) DB ファイルの 配 置 場 所 アクセス 権 を 正 しく 設 定 する ( 必 須 ) DB ファイルのデータの 保 護 を 考 えた 場 合 DB ファイルの 配 置 場 所 とアクセス 権 の 設 定 は 合 わせて 考 慮 すべき 重 要 な 要 素 である 例 えば ファイルのアクセス 権 を 正 しく 設 定 したつもりでも SD カードなどアクセス 権 の 設 定 を 行 えない 場 所 に 配 置 し ている 場 合 には 誰 からでもアクセス 可 能 な DB ファイルになってしまう また アプリディレクトリに 配 置 した 場 合 でも アクセス 権 を 正 しく 設 定 しないと 意 図 しないアクセスを 許 してしまうことになる ここでは 配 置 場 所 とアクセス 権 設 定 に ついて 守 るべき 点 を 挙 げた 後 それを 実 現 するための 方 法 について 説 明 する まず 配 置 場 所 とアクセス 権 設 定 については DB ファイル(データ)を 保 護 する 観 点 から 考 えると 以 下 の 2 点 を 実 施 す る 必 要 がある 1. 配 置 場 所 Context#getDatabasePath(String name) で 取 得 で き る フ ァ イ ル パ ス や 場 合 に よ っ て は Context#getFilesDir で 取 得 できるディレクトリの 場 所 に 配 置 する 6 2. アクセス 権 MODE_PRIVATE(=ファイルを 作 成 したアプリのみがアクセス 可 能 )モードに 設 定 する この 2 点 を 実 施 することで 他 のアプリからアクセスできない DB ファイルの 作 成 を 行 うことができる これらを 実 施 す るためには 以 下 の 方 法 が 挙 げられる 1. SQLiteOpenHelper を 使 用 する 2. Context#openOrCreateDatabase を 使 用 する DB ファイルの 作 成 に 際 しては SQLiteDatabase#openOrCreateDatabase を 使 用 することもできる しかし この メソッドを 使 用 した 場 合 Android スマートフォンの 機 種 によっては 他 のアプリから 読 み 取 り 可 能 な DB ファイルが 作 成 されることが 分 かっている そのため このメソッドの 使 用 は 避 けて 他 の 方 法 を 利 用 することを 推 奨 する 上 に 挙 げた 2 つの 方 法 について それぞれの 特 徴 を 以 下 で 説 明 する 6 どちらのメソッドも 該 当 するアプリだけが 読 み 書 き 権 限 を 与 えられ 他 のアプリからはアクセスができないディレクトリ (パッケージディレクトリ)のサブディレクトリ 以 下 のパスが 取 得 できる All rights reserved Japan Smartphone Security Association. SQLite を 使 う 217

220 SQLiteOpenHelper を 使 用 する SQLiteOpenHelper を 使 用 する 場 合 開 発 者 はあまり 多 くのことを 考 えなくてもよい SQLiteOpenHelper を 派 生 し たクラスを 作 成 し コンストラクタの 引 数 に DB の 名 前 (ファイル 名 に 使 われる) 7 を 指 定 すれば 自 動 的 に 上 記 のセキ ュリティ 要 件 を 満 たす DB ファイルを 作 成 してくれる データベースの 作 成 と 操 作 に 具 体 的 な 使 用 方 法 を 示 しているので 参 照 すること Context#openOrCreateDatabase を 使 用 する Context#openOrCreateDatabase メソッドを 使 用 して DB の 作 成 を 行 う 場 合 ファイルのアクセス 権 をオプションで 指 定 する 必 要 があり 明 示 的 に MODE_PRIVATE を 指 定 する ファイルの 配 置 に 関 しては DB 名 (ファイル 名 に 使 用 される)の 指 定 を SQLiteOpenHelper と 同 様 に 行 えるので 自 動 的 に 前 述 のセキュリティ 要 件 を 満 たすファイルパスにファイルが 作 成 される ただし フルパスも 指 定 できるので SD カードなどを 指 定 した 場 合 MODE_PRIVATE を 指 定 しても 他 アプリからアクセス 可 能 になってしまうため 注 意 が 必 要 である DB に 対 して 明 示 的 にアクセス 許 可 設 定 を 行 う 例 :MainActivity.java public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); //データベースの 構 築 try { //MODE_PRIVATE を 設 定 して DB を 作 成 db = Context.openOrCreateDatabase("Sample.db", MODE_PRIVATE, null); catch (SQLException e) { //データベース 構 築 に 失 敗 した 場 合 ログ 出 力 Log.e(this.getClass().toString(), getstring(r.string.database_open_error_message)); return; // 省 略 その 他 の 初 期 化 処 理 なお アクセス 権 の 設 定 は MODE_PRIVATE と 合 わせて 以 下 の 3 種 類 があり MODE_WORLD_READABLE と MODE_WORLD_WRITABLE は OR 演 算 で 同 時 指 定 することもできる ただし MODE_PRIVATE 以 外 は API Level 17 以 降 では deprecated となっており APL Level 15 以 降 を 対 象 とする 場 合 でも 使 用 しないことが 望 ましい どう しても 利 用 する 必 要 がある 場 合 はアプリの 要 件 に 照 らし 合 わせて 慎 重 に 検 討 することをお 勧 めする 7 (ドキュメントに 記 述 はないが)SQLiteOpenHelper の 実 装 では DB の 名 前 にはファイルのフルパスを 指 定 できるので SD カードなどアクセス 権 の 設 定 できない 場 所 のパスが 意 図 せず 入 力 されないように 注 意 が 必 要 である 218 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

221 MODE_PRIVATE 作 成 アプリのみ 読 み 書 き 可 能 MODE_WORLD_READABLE 作 成 アプリは 読 み 書 き 可 能 他 は 読 み 込 みのみ MODE_WORLD_WRITABLE 作 成 アプリは 読 み 書 き 可 能 他 は 書 き 込 みのみ 他 アプリと DB データを 共 有 する 場 合 は Content Provider でアクセス 制 御 する ( 必 須 ) 他 のアプリと DB データを 共 有 する 手 段 として DB ファイルを WORLD_READABLE WORLD_WRITABLE として 作 成 し 他 のアプリから 直 接 アクセスできるようにするという 方 法 がある しかし この 方 法 では DB にアクセスするアプリや DB への 操 作 を 制 限 できないため 意 図 しない 相 手 (アプリ)にデータを 読 み 書 きされることもある 結 果 として データ の 機 密 性 や 整 合 性 に 問 題 が 生 じたり マルウェアの 攻 撃 対 象 となったりする 可 能 性 も 考 えられる 以 上 のことから Android において DB データを 他 のアプリと 共 有 する 場 合 は Content Provider を 使 うことを 強 く お 勧 めする Content Provider を 使 うことにより DB に 対 するアクセス 制 御 を 実 現 できるというセキュリティの 観 点 からのメリットだけでなく DB スキーマ 構 造 を Content Provider 内 に 隠 ぺいできるといった 設 計 観 点 のメリットもあ る DB 操 作 時 に 可 変 パラメータを 扱 う 場 合 はプレースホルダを 使 用 する ( 必 須 ) SQL インジェクションを 防 ぐという 意 味 で 任 意 の 入 力 値 を SQL 文 に 組 み 込 む 時 はプレースホルダを 使 用 するべきで ある プレースホルダを 使 用 した SQL の 実 行 方 法 としては 以 下 の 2 つの 方 法 を 挙 げることができる 1. SQLiteDatabase#compileStatement() を 使 用 し て SQLiteStatement を 取 得 す る そ の 後 SQLiteStatement#bindString() bindlong()などを 使 用 してパラメータをプレースホルダに 配 置 する 2. SQLiteDatabese クラスの execsql() insert() update() delete() query() rawquery() replace()など を 呼 び 出 す 際 にプレースホルダを 持 った SQL 文 を 使 用 する なお SQLiteDatabase#compileStatement()を 使 用 して SELECT コマンドを 実 行 する 場 合 SELECT コマンドの 結 果 として 先 頭 の 1 要 素 (1 行 1 列 目 )しか 取 得 できない という 制 限 があるので 用 途 が 限 られる どちらの 方 式 を 使 う 場 合 でも プレースホルダに 与 えるデータの 内 容 は 事 前 にアプリ 要 件 に 従 ってチェックされている ことが 望 ましい 以 下 で それぞれの 方 法 について 説 明 する SQLiteDatabase#compileStatement()を 使 用 する 場 合 : 以 下 の 手 順 でプレースホルダへデータを 渡 す 1. SQLiteDatabase#compileStatement()を 使 用 してプレースホルダを 含 んだ SQL 文 を SQLiteStatement とし て 取 得 する 2. 作 成 したSQLiteStatement オブジェクトに 対 して bindlong() bindstring()などのメソッドを 使 用 してプレース All rights reserved Japan Smartphone Security Association. SQLite を 使 う 219

222 ホルダに 設 定 する 3. SQLiteStatement オブジェクトの execute()などのメソッドによって SQL を 実 行 する プレースホルダ 使 用 例 :DataInsertTask.java( 抜 粋 ) //データ 追 加 タスク public class DataInsertTask extends AsyncTask<String, Void, Void> { private MainActivity mactivity; private SQLiteDatabase msampledb; public DataInsertTask(SQLiteDatabase db, MainActivity activity) { msampledb = db; mactivity = activity; protected Void doinbackground(string... params) { String idno = params[0]; String name = params[1]; String info = params[2]; // ポイント 3 アプリケーション 要 件 に 従 って 入 力 値 をチェックする if (!DataValidator.validateData(idno, name, info)) { return null; //データ 追 加 処 理 //プレースホルダを 使 用 する String commandstring = "INSERT INTO " + CommonData.TABLE_NAME + " (idno, name, info) VALUES (?,?,?)"; SQLiteStatement sqlstmt = msampledb.compilestatement(commandstring); sqlstmt.bindstring(1, idno); sqlstmt.bindstring(2, name); sqlstmt.bindstring(3, info); try { sqlstmt.executeinsert(); catch (SQLException e) { Log.e(DataInsertTask.class.toString(), mactivity.getstring(r.string.updating_error_message)); finally { sqlstmt.close(); return null; ~ 省 略 ~ あらかじめ 実 行 する SQL 文 をオブジェクトとして 作 成 しておきパラメータを 当 てはめる 形 である 実 行 する 処 理 が 確 定 しているので SQLインジェクションが 発 生 する 余 地 はない また SQLiteStatement オブジェクトを 再 利 用 することで 処 理 効 率 を 高 めることができるというメリットもある SQLiteDatabase が 提 供 する 各 処 理 用 のメソッドを 使 用 する 場 合 : SQLiteDatabase が 提 供 する DB 操 作 メソッドには SQL 文 を 使 用 するものとそうでないものがある SQL 文 を 使 用 す るメソッドに SQLiteDatabase# execsql()/rawquery()などがあり 以 下 の 手 順 で 実 行 する 220 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

223 1. プレースホルダを 含 んだ SQL 文 を 用 意 する 2. プレースホルダに 割 り 当 てるデータを 作 成 する 3. SQL 文 とデータを 引 数 として 渡 して 処 理 用 メソッドを 実 行 する 一 方 SQL 文 を 使 用 しないメソッドには SQLiteDatabase#insert()/update()/delete()/query()/replace()などが ある これらを 使 用 する 場 合 には 以 下 の 手 順 でデータを 渡 す 1. DB に 対 して 挿 入 / 更 新 するデータがある 場 合 には ContentValues に 登 録 する 2. ContentValues を 引 数 として 渡 して 各 処 理 用 メソッド( 以 下 の 例 では SQLiteDatabase#insert())を 実 行 す る 各 処 理 用 メソッド(SQLiteDatabase#insert())を 使 用 する 例 private SQLiteDatabase msampledb; private void adduserdata(string idno, String name, String info) { // 値 の 妥 当 性 ( 型 範 囲 )チェック エスケープ 処 理 if (!validateinsertdata(idno, name, info)) { //バリデーションを 通 過 しなかった 場 合 ログ 出 力 Log.e(this.getClass().toString(), getstring(r.string.validation_error_message)); return // 挿 入 するデータの 準 備 ContentValues insertvalues = new ContentValues(); insertvalues.put("idno", idno); insertvalues.put("name", name); insertvalues.put("info", info); //Insert 実 行 try { msampledb.insert("sampletable", null, insertvalues); catch (SQLException e) { Log.e(this.getClass().toString(), getstring(r.string.db_insert_error_message)); return; この 例 では SQL コマンドを 直 接 記 述 せず SQLiteDatabase が 提 供 する 挿 入 用 のメソッドを 使 用 している SQL コマ ンドを 直 接 使 用 しないため この 方 法 も SQL インジェクションの 余 地 はないと 言 える All rights reserved Japan Smartphone Security Association. SQLite を 使 う 221

224 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド SQL 文 の LIKE 述 語 でワイルドカードを 使 用 する 際 にエスケープ 処 理 を 施 す LIKE 述 語 のワイルドカード(% _)を 含 む 文 字 列 をプレースホルダの 入 力 値 として 使 用 した 場 合 そのままだとワイル ドカードとして 機 能 するため 必 要 に 応 じて 事 前 にエスケープ 処 理 を 施 す 必 要 がある 必 要 なケースとしてはワイルド カードを 単 体 の 文 字 ("%"や"_")として 扱 いたい 場 合 が 当 てはまる 実 際 のエスケープ 処 理 は 以 下 のサンプルコードのように ESCAPE 句 を 使 用 して 行 うことができる LIKE を 利 用 した 場 合 のエスケープ 処 理 の 例 //データ 検 索 タスク public class DataSearchTask extends AsyncTask<String, Void, Cursor> { private MainActivity mactivity; private SQLiteDatabase msampledb; private ProgressDialog mprogressdialog; public DataSearchTask(SQLiteDatabase db, MainActivity activity) { msampledb = db; mactivity = activity; protected Cursor doinbackground(string... params) { String idno = params[0]; String name = params[1]; String info = params[2]; String cols[] = {"_id", "idno","name","info"; Cursor cur; ~ 省 略 ~ //info を 条 件 にして like 検 索 ( 部 分 一 致 ) //ポイント:ワイルドカードに 相 当 する 文 字 はエスケープ 処 理 する String argstring = info.replaceall("@", "@@"); // 入 力 として 受 け 取 った info 内 の$をエスケープ argstring = argstring.replaceall("%", "@%"); // 入 力 として 受 け 取 った info 内 の%をエスケープ argstring = argstring.replaceall("_", "@_"); // 入 力 として 受 け 取 った info 内 の_をエスケープ String selectionargs[] = {argstring; try { //ポイント:プレースホルダを 使 用 する cur = msampledb.query("sampletable", cols, "info LIKE '%'? '%' ESCAPE '@'", selectionargs, null, null, null); catch (SQLException e) { Toast.makeText(mActivity, R.string.SERCHING_ERROR_MESSAGE, Toast.LENGTH_LONG).show(); return null; return cur; protected void onpostexecute(cursor resultcur) { mprogressdialog.dismiss(); mactivity.updatecursor(resultcur); 222 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

225 プレースホルダを 使 用 できない SQL コマンドに 対 して 外 部 入 力 を 使 う テーブルの 作 成 や 削 除 などの DB オブジェクトを 処 理 対 象 とした SQL 文 を 実 行 する 場 合 テーブル 名 などの 値 に 対 し てプレースホルダを 使 うことはできない 基 本 的 には プレースホルダの 使 用 できない 値 に 対 して 外 部 から 入 力 され た 任 意 の 文 字 列 を 使 用 するようなデータベースの 設 計 はすべきでない 仕 様 や 機 能 上 の 制 限 でプレースホルダを 使 用 できない 場 合 は 入 力 値 に 危 険 が 無 いかどうか 実 行 前 に 確 認 し 必 要 な 処 理 を 施 すことが 必 須 となる 基 本 的 には 1. 文 字 列 パラメータとして 使 用 する 場 合 文 字 のエスケープやクォート 処 理 を 施 す 2. 数 値 パラメータとして 使 用 する 場 合 数 字 以 外 の 文 字 が 混 入 していないことを 確 認 する 3. 識 別 子 コマンドとして 使 用 する 場 合 1.に 加 え 使 用 できない 文 字 が 含 まれていないことを 確 認 する を 実 施 する 参 照 : 不 用 意 にデータベースの 書 き 換 えが 行 われないための 対 策 を 行 う SQLiteOpenHelper#getReadableDatabase getwritabledatabase を 使 用 して DB のインスタンスを 取 得 した 場 合 どちらのメソッドを 利 用 しても DB は 読 み 書 き 可 能 な 状 態 でオープンされる 8 また Context#openOrCreateDat abase SQLiteDatabase#openOrCreateDatabase なども 同 様 である これは アプリ 操 作 や 実 装 の 不 具 合 により 意 図 せず DB の 中 身 を 書 き 換 えてしまう( 書 き 換 えられてしまう) 可 能 性 を 意 味 している 基 本 的 にはアプリの 仕 様 と 実 装 の 範 囲 で 対 応 できると 考 えられるが アプリの 検 索 機 能 など 読 み 取 りしか 必 要 のない 機 能 を 実 装 する 場 合 は データベースを 読 み 取 り 専 用 でオープンすることで 設 計 や 検 証 の 簡 素 化 ひいてはアプリ 品 質 の 向 上 に 繋 がる 場 合 があるので 状 況 に 応 じて 検 討 をお 勧 めする 具 体 的 には SQLiteDatabase#openDatabase に OPEN_READONLY を 指 定 してデータベースをオープンする 8 getreabledatabase は 基 本 的 には getwritabledatabase で 取 得 するのと 同 じオブジェクトを 返 す ディスクフルな どの 状 況 で 書 き 込 み 可 能 オブジェクトを 生 成 できない 場 合 にリードオンリーのオブジェクトを 返 すという 仕 様 である (getwritabledatabase はディスクフルなどの 状 況 では 実 行 エラーとなる) All rights reserved Japan Smartphone Security Association. SQLite を 使 う 223

226 読 み 取 り 専 用 でデータベースをオープンする ~ 省 略 ~ // データベースのオープン(データベースは 作 成 済 みとする) SQLiteDatabase db = SQLiteDatabase.openDatabase(SQLiteDatabase.getDatabasePath("Sample.db"), null, OPEN_READONLY); 参 照 : - getreadabledatabase() アプリの 要 件 に 従 って DB の 入 出 力 データの 妥 当 性 をチェックする SQLite は 型 に 寛 容 なデータベースであり DB 上 で Integer として 宣 言 されているカラムに 対 して 文 字 型 のデータを 格 納 することが 可 能 である DB 内 のデータは 数 値 型 を 含 む 全 てのデータが 平 文 の 文 字 データとして DB 内 に 格 納 さ れている このため Integer 型 のカラムに 対 して 文 字 列 型 の 検 索 ( LIKE %123% など)を 行 うことも 可 能 である また VARCHAR(100) のようにデータの 最 大 長 を 記 述 してもそれ 以 上 の 長 さのデータが 入 力 可 能 であるなど SQLi te での 値 の 制 限 ( 正 当 性 確 認 )は 期 待 できない このため SQLite を 使 用 するアプリは このような DB の 特 性 に 注 意 して 予 期 せぬデータを DB に 格 納 したり 取 得 した りしないようにアプリの 要 件 に 従 って 対 処 する 必 要 がある 対 処 の 方 法 としては 次 の 2 つがある 1. データをデータベースに 格 納 する 際 型 や 長 さなどの 条 件 が 一 致 しているか 確 認 する 2. データベースから 値 を 取 得 した 際 データが 想 定 外 の 型 や 長 さでないか 確 認 する 以 下 では 例 として 入 力 値 が 1 以 上 の 数 字 であることを 検 証 するコードを 示 す 例 : 入 力 データが 1 以 上 の 数 字 であることを 確 認 する(MainActivity.java より 抜 粋 ) public class MainActivity extends Activity { ~ 省 略 ~ // 追 加 処 理 private void adduserdata(string idno, String name, String info) { //No のチェック if (!validateno(idno, CommonData.REQUEST_NEW)) { return; //データ 追 加 処 理 DataInsertTask task = new DataInsertTask(mSampleDb, this); task.execute(idno, name, info); ~ 省 略 ~ private boolean validateno(string idno, int request) { if (idno == null idno.length() == 0) { if (request == CommonData.REQUEST_SEARCH) { // 検 索 処 理 の 時 は 未 指 定 を OK にする return true; 224 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

227 else { // 検 索 処 理 以 外 の 時 は null 空 文 字 はエラー Toast.makeText(this, R.string.IDNO_EMPTY_MESSAGE, Toast.LENGTH_LONG).show(); return false; // 数 字 であることを 確 認 する try { // 1 以 上 の 値 if (!idno.matches("[1-9][0-9]*")) { // 数 字 以 外 の 時 はエラー Toast.makeText(this, R.string.IDNO_NOT_NUMERIC_MESSAGE, Toast.LENGTH_LONG).show(); return false; catch (NullPointerException e) { // 今 回 のケースではあり 得 ない return false; return true; ~ 省 略 ~ DB に 格 納 するデータについての 考 察 SQLite では データをファイルに 格 納 する 際 に 以 下 のような 実 装 になっている 数 値 型 を 含 む 全 てのデータが 平 文 の 文 字 データとして DB ファイル 内 に 格 納 される DB に 対 してデータの 削 除 を 行 ってもデータ 自 体 は DB ファイルから 削 除 されない( 削 除 マークが 付 くのみ) データを 更 新 した 場 合 も DB ファイル 内 には 更 新 前 のデータも 削 除 されず 残 っている よって 削 除 された はず の 情 報 が DB ファイル 内 に 残 ったままの 状 態 になっている 可 能 性 がある この 場 合 でも 本 文 書 に 従 って 対 策 を 施 し Android のセキュリティ 機 能 が 有 効 であれば 他 アプリを 含 む 第 三 者 からデータ ファイ ルに 直 接 アクセスされる 心 配 はない ただし root 権 限 を 奪 取 されるなど Android の 保 護 機 構 を 迂 回 してファイルを 抜 き 出 される 可 能 性 を 考 えると ビジネスに 大 きな 影 響 を 与 えるデータが 格 納 されている 場 合 には Android 保 護 機 構 に 頼 らないデータ 保 護 も 検 討 しなければならない これらの 理 由 により 端 末 の root 権 限 が 奪 取 された 場 合 でも 守 る 必 要 があるような 重 要 なデータは SQLite の DB に そのまま 格 納 すべきではない どうしても 重 要 なデータを 格 納 せざるを 得 ない 場 合 には 暗 号 化 したデータを 格 納 する DB 全 体 を 暗 号 化 する などの 対 策 が 必 要 となる 実 際 に 暗 号 化 が 必 要 な 場 合 暗 号 化 に 使 う 鍵 の 扱 いやコードの 難 読 化 など 本 文 書 の 範 囲 を 超 える 課 題 が 多 いので 現 時 点 でビジネスインパクトの 大 きなデータを 扱 うアプリの 開 発 には 専 門 家 への 相 談 をお 勧 めする 参 考 として [ 参 考 ]SQLite データベースを 暗 号 化 する(SQLCipher for Android) に データベースを 暗 号 All rights reserved Japan Smartphone Security Association. SQLite を 使 う 225

228 化 するライブラリを 紹 介 しておく [ 参 考 ]SQLite データベースを 暗 号 化 する(SQLCipher for Android) SQLCipher は データベースファイルの 透 過 的 な 256 ビット AES の 暗 号 化 を 提 供 する SQLite 拡 張 である 現 在 は オープンソース(BSD ライセンス) 化 され Zetetic LLC によって 維 持 管 理 されている モバイルの 世 界 では SQLCipher は ノキア/QT アップルの ios で 広 く 使 用 されている SQLCipher for Android プロジェクトは Android 環 境 における SQLite データベースの 標 準 の 統 合 化 された 暗 号 化 をサポートすることを 目 的 としている 標 準 の SQLite の API を SQLCipher 用 に 作 成 することで 開 発 者 は 通 常 と 同 じコーディングで 暗 号 化 されたデータベースを 利 用 できるようになっている 参 照 : 使 い 方 アプリ 開 発 者 は 以 下 の3つの 作 業 をすることで SQLCipher の 利 用 が 可 能 になる 1. アプリの lib ディレクトリに sqlcipher.jar および libdatabase_sqlcipher.so libsqlcipher_android.so libstlport_shared.so を 配 置 する 2. 全 て の ソ ー ス フ ァ イ ル に つ い て import で 指 定 さ れ て い る android.database.sqlite.* を 全 て info.guardianproject.database.sqlite.* に 変 更 する なお android.database.cursor はそのまま 使 用 可 能 である 3. oncreate()の 中 でデータベースを 初 期 化 し データベースをオープンする 際 にパスワードを 設 定 する 簡 単 なコード 例 SQLiteDatabase.loadLibs(this); //まず ライブラリを Context を 使 用 して 初 期 化 する SQLiteOpenHelper.getWritableDatabase(passwoed): // 引 数 はパスワード(String 型 セキュアに 取 得 したものと 仮 定 ) SQLCipher for Android は 執 筆 時 点 でバージョン であり 版 が 開 発 進 行 中 で RC4 が 公 開 されている 状 況 である Android における 使 用 実 績 や API の 安 定 性 という 点 で 今 後 検 証 が 必 要 となるが 現 時 点 で Android で 利 用 可 能 な SQLite の 暗 号 化 ソリューションとして 検 討 する 余 地 はある ライブラリ 構 成 SQLCipher を 使 用 するためには SDK として 含 まれている 以 下 のファイルが 必 要 となる assets/icudt46l.zip 2,252KB 端 末 の /system/usr/icu/ 以 下 に icudt46l.dat が 存 在 しない 場 合 に 必 要 となる icudt46l.dat が 見 つからない 場 合 この zip が 解 凍 されて 使 用 される 226 All rights reserved Japan Smartphone Security Association. SQLite を 使 う

229 libs/armeabi/libdatabase_sqlcipher.so 44KB libs/armeabi/libsqlcipher_android.so 1,117KB libs/armeabi/libstlport_shared.so 555KB Native ライブラリ SQLCipher の 初 期 ロード 時 (SQLiteDatabase#loadLibs() 呼 び 出 し 時 )に 読 み 込 まれる libs/commons-codec.jar 46KB libs/guava-r09.jar 1,116KB libs/sqlcipher.jar 102KB Native ライブラリを 呼 び 出 す Java ライブラリ sqlcipher.jar がメイン あとは sqlcipher.jar から 参 照 されている 合 計 : 約 5.12MB ただし icudt46l.zip は 解 凍 されると 7MB 程 度 になる All rights reserved Japan Smartphone Security Association. SQLite を 使 う 227

230 4.6. ファイルを 扱 う Android アプリのセキュア 設 計 セキュアコーディングガイド Android のセキュリティ 設 計 思 想 に 従 うと ファイルは 情 報 を 永 続 化 又 は 一 時 保 存 (キャッシュ)する 目 的 にのみ 利 用 し 原 則 非 公 開 にするべきである アプリ 間 の 情 報 交 換 はファイルを 直 接 アクセスさせるのではなく ファイル 内 の 情 報 を Content Provider や Service といったアプリ 間 連 携 の 仕 組 みによって 交 換 するべきである これによりアプリ 間 のアクセス 制 御 も 実 現 できる SD カード 等 の 外 部 記 憶 デバイスは 十 分 なアクセス 制 御 ができないため 容 量 の 大 きなファイルを 扱 う 場 合 や 別 の 場 所 (PC など)への 情 報 の 移 動 目 的 など 機 能 上 どうしても 必 要 な 場 合 のみに 使 用 を 限 定 するべきである 基 本 的 に 外 部 記 憶 デバイス 上 にはセンシティブな 情 報 を 含 んだファイルを 配 置 してはならない もしセンシティブな 情 報 を 外 部 記 憶 デバイス 上 のファイルに 保 存 しなければならない 場 合 は 暗 号 化 等 の 対 策 が 必 要 になるが ここでは 言 及 しない サンプルコード 前 述 のようにファイルは 原 則 非 公 開 にするべきである しかしながらさまざまな 事 情 によって 他 のアプリにファイルを 直 接 読 み 書 きさせるべきときもある セキュリティの 観 点 から 分 類 したファイルの 種 類 と 比 較 を 表 に 示 す ファ イルの 格 納 場 所 や 他 アプリへのアクセス 許 可 の 組 み 合 わせにより 4 種 類 のファイルに 分 類 している 以 降 ではこのフ ァイルの 分 類 ごとにサンプルコードを 示 し 説 明 を 加 えていく 表 セキュリティ 観 点 によるファイルの 分 類 と 比 較 ファイルの 分 類 他 アプリへの 格 納 場 所 概 要 アクセス 許 可 非 公 開 ファイル なし アプリディレクトリ 内 アプリ 内 でのみ 読 み 書 きできる センシティブな 情 報 を 扱 うことができる ファイルは 原 則 このタイプにするべき 読 み 取 り 公 開 ファイル 読 み 取 り アプリディレクトリ 内 他 アプリおよびユーザーも 読 み 取 り 可 能 アプリ 外 部 に 公 開 ( 閲 覧 ) 可 能 な 情 報 を 扱 う 読 み 書 き 公 開 ファイル 読 み 取 り 書 き 込 み アプリディレクトリ 内 他 アプリおよびユーザーも 読 み 書 き 可 能 セキュリティの 観 点 からもアプリ 設 計 の 観 点 か らも 使 用 は 避 けるべき 外 部 記 憶 ファイル 読 み 取 り SD カードなどの 外 アクセス 権 のコントロールができない ( 読 み 書 き 公 開 ) 書 き 込 み 部 記 憶 装 置 他 アプリやユーザーによるファイルの 読 み 書 き 削 除 が 常 に 可 能 使 用 は 必 要 最 小 限 にするべき 比 較 的 容 量 の 大 きなファイルを 扱 うことができ る 228 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

231 非 公 開 ファイルを 扱 う 同 一 アプリ 内 でのみ 読 み 書 きされるファイルを 扱 う 場 合 であり 安 全 なファイルの 使 い 方 である ファイルに 格 納 する 情 報 が 公 開 可 能 かどうかに 関 わらず できるだけファイルは 非 公 開 の 状 態 で 保 持 し 他 アプリとの 必 要 な 情 報 のやり 取 りは 別 の Android の 仕 組 み(Content Provider Service)を 利 用 して 行 うことを 原 則 とする ポイント: 1. ファイルは アプリディレクトリ 内 に 作 成 する 2. ファイルのアクセス 権 は 他 のアプリが 利 用 できないようにプライベートモードにする 3. センシティブな 情 報 を 格 納 することができる 4. ファイルに 格 納 する(された) 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する PrivateFileActivity.java package org.jssec.android.file.privatefile; import java.io.file; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.textview; public class PrivateFileActivity extends Activity { private TextView mfileview; private static final String FILE_NAME = "private_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.file); mfileview = (TextView) findviewbyid(r.id.file_view); /** * ファイルの 作 成 処 理 * view */ public void oncreatefileclick(view view) { FileOutputStream fos = null; try { // ポイント 1 ファイルは アプリディレクトリ 内 に 作 成 する // ポイント 2 ファイルのアクセス 権 は 他 のアプリが 利 用 できないようにプライベートモードにする fos = openfileoutput(file_name, MODE_PRIVATE); // ポイント 3 センシティブな 情 報 を 格 納 することができる // ポイント 4 ファイルに 格 納 する 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 229

232 // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 fos.write(new String("センシティブな 情 報 (File Activity)\n").getBytes()); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("privatefileactivity", "ファイルの 作 成 に 失 敗 しました"); finally { if (fos!= null) { try { fos.close(); catch (IOException e) { android.util.log.e("privatefileactivity", "ファイルの 終 了 に 失 敗 しました"); finish(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { fis = openfileinput(file_name); byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("privatefileactivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.e("privatefileactivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 削 除 処 理 * view */ public void ondeletefileclick(view view) { File file = new File(this.getFilesDir() + "/" + FILE_NAME); file.delete(); 230 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

233 mfileview.settext(r.string.file_view); PrivateUserActivity.java package org.jssec.android.file.privatefile; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.textview; public class PrivateUserActivity extends Activity { private TextView mfileview; private static final String FILE_NAME = "private_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user); mfileview = (TextView) findviewbyid(r.id.file_view); private void callfileactivity() { Intent intent = new Intent(); intent.setclass(this, PrivateFileActivity.class); startactivity(intent); /** * ファイル Activity の 呼 び 出 し 処 理 * view */ public void oncallfileactivityclick(view view) { callfileactivity(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { fis = openfileinput(file_name); All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 231

234 byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); // ポイント 4 ファイルに 格 納 された 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.d("privatefileactivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.d("privatefileactivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 追 記 処 理 * view */ public void onwritefileclick(view view) { FileOutputStream fos = null; try { // ポイント 1 ファイルは アプリケーションディレクトリ 内 に 作 成 する // ポイント 2 ファイルのアクセス 権 は 他 のアプリが 利 用 できないようにプライベートモードにする fos = openfileoutput(file_name, MODE_APPEND); // ポイント 3 センシティブな 情 報 を 格 納 することができる // ポイント 4 ファイルに 格 納 する 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 fos.write(new String("センシティブな 情 報 (User Activity)\n").getBytes()); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.d("privatefileactivity", "ファイルの 作 成 に 失 敗 しました"); finally { if (fos!= null) { try { fos.close(); catch (IOException e) { android.util.log.d("privatefileactivity", "ファイルの 終 了 に 失 敗 しました"); callfileactivity(); 232 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

235 読 み 取 り 公 開 ファイルを 扱 う 不 特 定 多 数 のアプリに 対 して 内 容 を 公 開 するためのファイルである 以 下 のポイントに 気 を 付 けて 実 装 すれば 比 較 的 安 全 なファイルの 使 い 方 になる ただし 公 開 ファイルを 作 成 するための MODE_WORLD_READABLE 変 数 は API Level17 以 降 では deprecated となっており Content Provider によるファイル 共 有 方 法 が 望 ましい ポイント: 1. ファイルは アプリディレクトリ 内 に 作 成 する 2. ファイルのアクセス 権 は 他 のアプリに 対 しては 読 み 取 り 専 用 モードにする 3. センシティブな 情 報 は 格 納 しない 4. ファイルに 格 納 する(された) 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する PublicFileActivity.java package org.jssec.android.file.publicfile.readonly; import java.io.file; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.textview; public class PublicFileActivity extends Activity { private TextView mfileview; private static final String FILE_NAME = "public_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.file); mfileview = (TextView) findviewbyid(r.id.file_view); /** * ファイルの 作 成 処 理 * view */ public void oncreatefileclick(view view) { FileOutputStream fos = null; try { // ポイント 1 ファイルは アプリディレクトリ 内 に 作 成 する // ポイント 2 ファイルのアクセス 権 は 他 のアプリに 対 しては 読 み 取 り 専 用 モードにする // ( 読 み 取 り 専 用 モードの MODE_WORLDREADABLE は API LEVEL 17 で deplicated となったため // 極 力 使 用 せず ContentProvider などによるデータのやり 取 りをすること) fos = openfileoutput(file_name, MODE_WORLD_READABLE); All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 233

236 // ポイント 3 センシティブな 情 報 は 格 納 しない // ポイント 4 ファイルに 格 納 する 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 fos.write(new String("センシティブでない 情 報 (Public File Activity)\n").getBytes()); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("publicfileactivity", "ファイルの 作 成 に 失 敗 しました"); finally { if (fos!= null) { try { fos.close(); catch (IOException e) { android.util.log.e("publicfileactivity", "ファイルの 終 了 に 失 敗 しました"); finish(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { fis = openfileinput(file_name); byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("publicfileactivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.e("publicfileactivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 削 除 処 理 * view */ public void ondeletefileclick(view view) { 234 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

237 File file = new File(this.getFilesDir() + "/" + FILE_NAME); file.delete(); mfileview.settext(r.string.file_view); PublicUserActivity.java package org.jssec.android.file.publicuser.readonly; import java.io.file; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.app.activity; import android.content.activitynotfoundexception; import android.content.context; import android.content.intent; import android.content.pm.packagemanager.namenotfoundexception; import android.os.bundle; import android.view.view; import android.widget.textview; public class PublicUserActivity extends Activity { private TextView mfileview; private static final String TARGET_PACKAGE = "org.jssec.android.file.publicfile.readonly"; private static final String TARGET_CLASS = "org.jssec.android.file.publicfile.readonly.publicfileactivity"; private static final String FILE_NAME = "public_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user); mfileview = (TextView) findviewbyid(r.id.file_view); private void callfileactivity() { Intent intent = new Intent(); intent.setclassname(target_package, TARGET_CLASS); try { startactivity(intent); catch (ActivityNotFoundException e) { mfileview.settext("(file Activity がありませんでした)"); /** * ファイル Activity の 呼 び 出 し 処 理 * view */ All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 235

238 public void oncallfileactivityclick(view view) { callfileactivity(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { File file = new File(getFilesPath(FILE_NAME)); fis = new FileInputStream(file); byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); // ポイント 4 ファイルに 格 納 された 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { android.util.log.e("publicuseractivity", "ファイルがありません"); catch (IOException e) { android.util.log.e("publicuseractivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.e("publicuseractivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 追 記 処 理 * view */ public void onwritefileclick(view view) { FileOutputStream fos = null; boolean exception = false; try { File file = new File(getFilesPath(FILE_NAME)); // 書 き 込 みは 失 敗 する FileNotFoundException が 発 生 fos = new FileOutputStream(file, true); fos.write(new String("センシティブでない 情 報 (Public User Activity)\n").getBytes()); catch (IOException e) { mfileview.settext(e.getmessage()); exception = true; finally { if (fos!= null) { try { 236 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

239 fos.close(); catch (IOException e) { exception = true; if (!exception) callfileactivity(); private String getfilespath(string filename) { String path = ""; try { Context ctx = createpackagecontext(target_package, Context.CONTEXT_RESTRICTED); File file = new File(ctx.getFilesDir(), filename); path = file.getpath(); catch (NameNotFoundException e) { android.util.log.e("publicuseractivity", "ファイルがありません"); return path; All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 237

240 読 み 書 き 公 開 ファイルを 扱 う 不 特 定 多 数 のアプリに 対 して 読 み 書 き 権 限 を 許 可 するファイルの 使 い 方 である 不 特 定 多 数 のアプリが 読 み 書 き 可 能 ということは マルウェアも 当 然 内 容 の 書 き 換 えが 可 能 であり データの 信 頼 性 も 安 全 性 も 全 く 保 証 されない また 悪 意 のない 場 合 でもファイル 内 のデータの 形 式 や 書 き 込 みを 行 うタイミングなど 制 御 が 困 難 であり そのようなファイルは 機 能 面 からも 実 用 性 が 無 いに 等 しい 以 上 のように セキュリティの 観 点 からもアプリ 設 計 の 観 点 からも 読 み 書 き 公 開 ファイルを 安 全 に 運 用 することは 不 可 能 であり 読 み 書 き 公 開 ファイルの 使 用 は 避 けなければならない ポイント: 1. 他 アプリから 読 み 書 き 可 能 なアクセス 権 を 設 定 したファイルは 作 らない 238 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

241 外 部 記 憶 ( 読 み 書 き 公 開 )ファイルを 扱 う SD カードのような 外 部 記 憶 デバイス 上 にファイルを 格 納 する 場 合 である 比 較 的 容 量 の 大 きな 情 報 を 格 納 する (Web からダウンロードしたファイルを 置 くなどの) 場 合 や 外 部 に 情 報 を 持 ち 出 す(バックアップなどの) 場 合 に 利 用 す ることが 想 定 される 外 部 記 憶 ( 読 み 書 き 公 開 )ファイル は 不 特 定 多 数 のアプリに 対 して 読 み 取 り 公 開 ファイル と 同 等 の 性 質 を 持 つ さらに android.permission.write_external_storage Permission を 利 用 宣 言 している 不 特 定 多 数 のア プリに 対 しては 読 み 書 き 公 開 ファイル と 同 等 の 性 質 を 持 つ そのため 外 部 記 憶 ( 読 み 書 き 公 開 )ファイルの 使 用 は 必 要 最 小 限 にとどめるべきである Android アプリの 慣 例 として バックアップファイルは 外 部 記 憶 デバイス 上 に 作 成 されることが 多 い しかし 外 部 記 憶 デバイス 上 のファイルは 前 述 のようにマルウェアを 含 む 他 のアプリから 改 ざんや 削 除 されてしまうリスクがある ゆえにバックアップを 出 力 するアプリでは バックアップファイルは 速 やかに PC 等 の 安 全 な 場 所 にコピーしてくださ い といった 警 告 表 示 をするなど アプリの 仕 様 や 設 計 面 でのリスク 最 小 化 の 工 夫 も 必 要 となる ポイント: 1. センシティブな 情 報 は 格 納 しない 2. アプリ 毎 にユニークなディレクトリにファイルを 配 置 する 3. ファイルに 格 納 する(された) 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する 4. 利 用 側 のアプリで 書 き 込 みを 行 わない 仕 様 にする AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.file.externalfile" > <!-- android.permission.write_external_storage Permission を 利 用 宣 言 する --> <!-- Android 4.4 (API Level 19) 以 降 では 外 部 ストレージのアプリデータ 領 域 を 読 み 書 きする 際 に Permission が 不 要 なため maxsdkversion を 宣 言 する --> <uses-permission android:name="android.permission.write_external_storage" android:maxsdkversion="18"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <activity android:name=".externalfileactivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 239

242 ExternalFileActivity.java package org.jssec.android.file.externalfile; import java.io.file; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.fileoutputstream; import java.io.ioexception; import android.app.activity; import android.os.bundle; import android.view.view; import android.widget.textview; public class ExternalFileActivity extends Activity { private TextView mfileview; private static final String TARGET_TYPE = "external"; private static final String FILE_NAME = "external_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.file); mfileview = (TextView) findviewbyid(r.id.file_view); /** * ファイルの 作 成 処 理 * view */ public void oncreatefileclick(view view) { FileOutputStream fos = null; try { // ポイント 1 センシティブな 情 報 は 格 納 しない // ポイント 2 アプリ 毎 にユニークなディレクトリにファイルを 配 置 する File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME); fos = new FileOutputStream(file, false); // ポイント 3 ファイルに 格 納 する 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 fos.write(new String("センシティブでない 情 報 (External File Activity)\n").getBytes()); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("externalfileactivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fos!= null) { try { fos.close(); catch (IOException e) { android.util.log.e("externaluseractivity", "ファイルの 終 了 に 失 敗 しました"); 240 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

243 finish(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME); fis = new FileInputStream(file); byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); // ポイント 3 ファイルに 格 納 された 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("externalfileactivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.e("externalfileactivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 削 除 処 理 * view */ public void ondeletefileclick(view view) { File file = new File(getExternalFilesDir(TARGET_TYPE), FILE_NAME); file.delete(); mfileview.settext(r.string.file_view); 利 用 側 のサンプルコード ExternalFileUser.java package org.jssec.android.file.externaluser; import java.io.file; All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 241

244 import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.ioexception; import android.app.activity; import android.app.alertdialog; import android.content.activitynotfoundexception; import android.content.context; import android.content.dialoginterface; import android.content.intent; import android.content.pm.packagemanager.namenotfoundexception; import android.os.bundle; import android.view.view; import android.widget.textview; public class ExternalUserActivity extends Activity { private TextView mfileview; private static final String TARGET_PACKAGE = "org.jssec.android.file.externalfile"; private static final String TARGET_CLASS = "org.jssec.android.file.externalfile.externalfileactivity"; private static final String TARGET_TYPE = "external"; private static final String FILE_NAME = "external_file.dat"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user); mfileview = (TextView) findviewbyid(r.id.file_view); private void callfileactivity() { Intent intent = new Intent(); intent.setclassname(target_package, TARGET_CLASS); try { startactivity(intent); catch (ActivityNotFoundException e) { mfileview.settext("(file Activity がありませんでした)"); /** * ファイル Activity の 呼 び 出 し 処 理 * view */ public void oncallfileactivityclick(view view) { callfileactivity(); /** * ファイルの 読 み 込 み 処 理 * view */ public void onreadfileclick(view view) { FileInputStream fis = null; try { 242 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

245 File file = new File(getFilesPath(FILE_NAME)); fis = new FileInputStream(file); byte[] data = new byte[(int) fis.getchannel().size()]; fis.read(data); // ポイント 3 ファイルに 格 納 された 情 報 に 対 しては その 入 手 先 に 関 わらず 内 容 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String str = new String(data); mfileview.settext(str); catch (FileNotFoundException e) { mfileview.settext(r.string.file_view); catch (IOException e) { android.util.log.e("externaluseractivity", "ファイルの 読 込 に 失 敗 しました"); finally { if (fis!= null) { try { fis.close(); catch (IOException e) { android.util.log.e("externaluseractivity", "ファイルの 終 了 に 失 敗 しました"); /** * ファイルの 追 記 処 理 * view */ public void onwritefileclick(view view) { // ポイント 4 利 用 側 のアプリで 書 き 込 みを 行 わない 仕 様 にする // ただし 悪 意 のあるアプリが 上 書 き 削 除 などを 行 うことを 想 定 してアプリの 設 計 を 行 うこと final AlertDialog.Builder alertdialogbuilder = new AlertDialog.Builder( this); alertdialogbuilder.settitle("ポイント 4"); alertdialogbuilder.setmessage(" 利 用 側 のアプリで 書 き 込 みを 行 わないこと"); alertdialogbuilder.setpositivebutton("ok", new DialogInterface.OnClickListener() { ); public void onclick(dialoginterface dialog, int which) { callfileactivity(); alertdialogbuilder.create().show(); private String getfilespath(string filename) { String path = ""; try { Context ctx = createpackagecontext(target_package, Context.CONTEXT_IGNORE_SECURITY); All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 243

246 Android アプリのセキュア 設 計 セキュアコーディングガイド File file = new File(ctx.getExternalFilesDir(TARGET_TYPE), filename); path = file.getpath(); catch (NameNotFoundException e) { android.util.log.e("externaluseractivity", "ファイルがありません"); return path; AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.file.externaluser" > <!-- Android (API Level 14) 以 降 では 外 部 ストレージを 読 むための Permission が 定 義 されたので 利 用 宣 言 をする 実 際 には Android 4.4(API Level 19) 以 降 で 他 のアプリデータ 領 域 を 読 む 場 合 に 必 須 となる --> <uses-permission android:name="android.permission.read_external_storage"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:allowbackup="false" > <activity android:name=".externaluseractivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> 244 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

247 ルールブック ファイルを 扱 う 場 合 には 以 下 のルールを 守 ること 1. ファイルは 原 則 非 公 開 ファイルとして 作 成 する ( 必 須 ) 2. 他 のアプリから 読 み 書 き 権 限 でアクセス 可 能 なファイルは 作 成 しない ( 必 須 ) 3. SD カードなど 外 部 記 憶 デバイスに 格 納 するファイルの 利 用 は 必 要 最 小 限 にする ( 必 須 ) 4. ファイルの 生 存 期 間 を 考 慮 してアプリの 設 計 を 行 う ( 必 須 ) ファイルは 原 則 非 公 開 ファイルとして 作 成 する ( 必 須 ) 4.6 ファイルを 扱 う 非 公 開 ファイルを 扱 う で 述 べたように 格 納 する 情 報 の 内 容 に 関 わらずファイルは 原 則 非 公 開 にするべきである Android のセキュリティ 設 計 の 観 点 からも 情 報 のやり 取 りとそのアクセス 制 御 は Content Provider や Service などの Android の 仕 組 みの 中 で 行 うべきであり できない 事 情 がある 場 合 のみファ イルのアクセス 権 で 代 用 することを 検 討 することになる 各 ファイルタイプのサンプルコードや 以 下 のルールの 項 も 参 照 のこと 他 のアプリから 読 み 書 き 権 限 でアクセス 可 能 なファイルは 作 成 しない ( 必 須 ) 読 み 書 き 公 開 ファイルを 扱 う で 述 べたように 他 のアプリに 対 してファイルの 読 み 書 きを 許 可 すると ファイ ルに 格 納 される 情 報 の 制 御 ができない そのため セキュリティ 的 な 観 点 からも 機 能 設 計 的 な 観 点 からも 読 み 書 き 公 開 ファイルを 利 用 した 情 報 の 共 有 を 考 えるべきではない SD カードなど 外 部 記 憶 デバイスに 格 納 するファイルの 利 用 は 必 要 最 小 限 にする ( 必 須 ) 外 部 記 憶 ( 読 み 書 き 公 開 )ファイルを 扱 う で 述 べたように SD カードをはじめとする 外 部 記 憶 デバイスにファ イルを 置 くことは セキュリティおよび 機 能 の 両 方 の 観 点 から 潜 在 的 な 問 題 を 抱 えることに 繋 がる 一 方 で SD カード はアプリディレクトリより 生 存 期 間 の 長 いファイルを 扱 え アプリ 外 部 にデータを 持 ち 出 すのに 常 時 使 える 唯 一 のスト レージなので アプリの 仕 様 によっては 使 用 せざるを 得 ないケースも 多 いと 考 えられる 外 部 記 憶 デバイスにファイルを 格 納 する 場 合 不 特 定 多 数 のアプリおよびユーザーが 読 み 書 き 削 除 できることを 考 慮 して サンプルコードで 述 べたポイントを 含 めて 以 下 のようなポイントに 気 をつけてアプリの 設 計 を 行 う 必 要 がある なお 暗 号 化 や 電 子 署 名 などの 暗 号 技 術 については 本 ガイドの 今 後 の 版 で 別 途 記 事 を 設 ける 予 定 である 原 則 としてセンシティブな 情 報 は 外 部 記 憶 デバイス 上 のファイルに 保 存 しない もしセンシティブな 情 報 を 外 部 記 憶 デバイス 上 のファイルに 保 存 する 場 合 は 暗 号 化 する 他 アプリやユーザーに 改 ざんされては 困 る 情 報 を 外 部 記 憶 デバイス 上 のファイルに 保 存 する 場 合 は 電 子 署 名 も 一 緒 に 保 存 する 外 部 記 憶 デバイス 上 のファイルを 読 み 込 む 場 合 読 み 込 むデータの 安 全 性 を 確 認 してからデータを 利 用 する 他 のアプリやユーザーによって 外 部 記 憶 デバイス 上 のファイルはいつでも 削 除 されることを 想 定 してアプリを 設 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 245

248 計 しなければならない ファイルの 生 存 期 間 を 考 慮 してアプリの 設 計 を 行 う ( 必 須 ) も 参 照 すること ファイルの 生 存 期 間 を 考 慮 してアプリの 設 計 を 行 う ( 必 須 ) アプリディレクトリに 保 存 されたデータは 以 下 のユーザー 操 作 により 消 去 される アプリの 生 存 期 間 と 一 致 する また はアプリの 生 存 期 間 より 短 いのが 特 徴 である アプリのアンインストール 各 アプリのデータおよびキャッシュの 消 去 ( 設 定 アプリケーション アプリケーションの 管 理 ) SD カード 等 の 外 部 記 憶 デバイス 上 に 保 存 されたファイルは アプリの 生 存 期 間 よりファイルの 生 存 期 間 が 長 いことが 特 徴 である さらに 次 の 状 況 も 想 定 する 必 要 がある ユーザーによるファイルの 消 去 SD カードの 抜 き 取 り 差 し 替 え アンマウント マルウェアによるファイルの 消 去 このようにファイルの 保 存 場 所 によってファイルの 生 存 期 間 が 異 なるため 本 節 で 説 明 したようなセンシティブな 情 報 を 保 護 する 観 点 だけでなく アプリとして 正 しい 動 作 を 実 現 する 観 点 でもファイルの 保 存 場 所 を 正 しく 選 択 する 必 要 が ある 246 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

249 アドバンスト ファイルディスクリプタ 経 由 のファイル 共 有 他 のアプリに 公 開 ファイルを 直 接 アクセスさせるのではなく ファイルディスクリプタ 経 由 でファイル 共 有 する 方 法 があ る Content Provider と Service でこの 方 法 が 使 える Content Provider や Service の 中 で 非 公 開 ファイルをオ ープンし そのファイルディスクリプタを 相 手 のアプリに 渡 す 相 手 アプリはファイルディスクリプタ 経 由 でファイルを 読 み 書 きできる 他 のアプリにファイルを 直 接 アクセスさせるファイル 共 有 方 法 とファイルディスクリプタ 経 由 のファイル 共 有 方 法 の 比 較 を 表 に 示 す アクセス 権 のバリエーションとアクセス 許 可 するアプリの 範 囲 でメリットがある 特 にアクセス を 許 可 するアプリを 細 かく 制 御 できるところがセキュリティ 観 点 ではメリットが 大 きい 表 アプリ 間 ファイル 共 有 方 法 の 比 較 ファイル 共 有 方 法 アクセス 権 設 定 のバリエーション アクセスを 許 可 するアプリの 範 囲 他 のアプリにファイルを 直 接 アクセス させるファイル 共 有 ファイルディスクリプタ 経 由 のファイ ル 共 有 読 み 取 り 書 き 込 み 読 み 取 り+ 書 き 込 み 読 み 取 り 書 き 込 み 追 記 のみ 読 み 取 り+ 書 き 込 み 読 み 取 り+ 追 記 のみ すべてのアプリに 対 して 一 律 アクセス 許 可 してしまう Content Provider や Service にアク セスしてくるアプリに 対 して 個 別 に 一 時 的 にアクセス 許 可 不 許 可 を 制 御 できる 上 記 ファイル 共 有 方 法 のどちらにも 共 通 することであるが 他 のアプリにファイルの 書 き 込 みを 許 可 するとファイル 内 容 の 完 全 性 が 保 証 しづらくなる 特 に 複 数 のアプリから 同 時 に 書 き 込 みが 行 われると ファイル 内 容 のデータ 構 造 が 壊 れてしまいアプリが 正 常 に 動 作 しなくなるリスクがある 他 のアプリとのファイル 共 有 においては 読 み 込 み 権 限 だ けを 許 可 するのが 望 ましい 以 下 では Content Provider でファイルを 共 有 する 実 装 例 ( 非 公 開 Provider の 場 合 )をサンプルコードとして 掲 載 す る ポイント 1. 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 保 存 してよい 2. 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 認 する InhouseProvider.java package org.jssec.android.file.inhouseprovider; import java.io.file; import java.io.filenotfoundexception; import java.io.fileoutputstream; All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 247

250 import java.io.ioexception; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.content.contentprovider; import android.content.contentvalues; import android.content.context; import android.database.cursor; import android.net.uri; import android.os.parcelfiledescriptor; public class InhouseProvider extends ContentProvider { private static final String FILENAME = "sensitive.txt"; // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.file.inhouseprovider.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; public boolean oncreate() { File dir = getcontext().getfilesdir(); FileOutputStream fos = null; try { fos = new FileOutputStream(new File(dir, FILENAME)); // ポイント 1 利 用 元 アプリは 自 社 アプリであるから センシティブな 情 報 を 保 存 してよい fos.write(new String("センシティブな 情 報 ").getbytes()); catch (IOException e) { android.util.log.e("inhouseprovider", "ファイル 保 存 に 失 敗 しました"); finally { try { fos.close(); catch (IOException e) { android.util.log.e("inhouseprovider", "ファイル 終 了 に 失 敗 しました"); return true; public ParcelFileDescriptor openfile(uri uri, String mode) throws FileNotFoundException { 248 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

251 // 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(getContext(), MY_PERMISSION, mycerthash(getcontext()))) { throw new SecurityException( " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); File dir = getcontext().getfilesdir(); File file = new File(dir, FILENAME); // サンプルのため 読 み 取 り 専 用 を 常 に 返 す int modebits = ParcelFileDescriptor.MODE_READ_ONLY; return ParcelFileDescriptor.open(file, modebits); public String gettype(uri uri) { return ""; public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { return null; public Uri insert(uri uri, ContentValues values) { return null; public int update(uri uri, ContentValues values, String selection, String[] selectionargs) { return 0; public int delete(uri uri, String selection, String[] selectionargs) { return 0; InhouseUserActivity.java package org.jssec.android.file.inhouseprovideruser; import java.io.fileinputstream; import java.io.filenotfoundexception; import java.io.ioexception; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.content.pm.packagemanager; All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 249

252 import android.content.pm.providerinfo; import android.net.uri; import android.os.bundle; import android.os.parcelfiledescriptor; import android.view.view; import android.widget.textview; public class InhouseUserActivity extends Activity { // 利 用 先 の Content Provider 情 報 private static final String AUTHORITY = "org.jssec.android.file.inhouseprovider"; // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.file.inhouseprovider.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; // 利 用 先 Content Provider のパッケージ 名 を 取 得 private static String providerpkgname(context context, String authority) { String pkgname = null; PackageManager pm = context.getpackagemanager(); ProviderInfo pi = pm.resolvecontentprovider(authority, 0); if (pi!= null) pkgname = pi.packagename; return pkgname; public void onreadfileclick(view view) { logline("[readfile]"); // 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { logline(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; // 利 用 先 Content Provider アプリの 証 明 書 が 自 社 の 証 明 書 であることを 確 認 する String pkgname = providerpkgname(this, AUTHORITY); if (!PkgCert.test(this, pkgname, mycerthash(this))) { logline(" 利 用 先 Content Provider は 自 社 アプリではない "); return; // 自 社 限 定 Content Provider アプリに 開 示 してよい 情 報 に 限 りリクエストに 含 めてよい ParcelFileDescriptor pfd = null; 250 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

253 try { pfd = getcontentresolver().openfiledescriptor( Uri.parse("content://" + AUTHORITY), "r"); catch (FileNotFoundException e) { android.util.log.e("inhouseuseractivity", "ファイルがありません"); if (pfd!= null) { FileInputStream fis = new FileInputStream(pfd.getFileDescriptor()); 認 する if (fis!= null) { try { byte[] buf = new byte[(int) fis.getchannel().size()]; fis.read(buf); // ポイント 2 自 社 限 定 Content Provider アプリからの 結 果 であっても 結 果 データの 安 全 性 を 確 // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 logline(new String(buf)); catch (IOException e) { android.util.log.e("inhouseuseractivity", "ファイルの 読 み 込 みに 失 敗 しました"); finally { try { fis.close(); catch (IOException e) { android.util.log.e("inhouseuseractivity", "ファイルの 終 了 に 失 敗 しました"); try { pfd.close(); catch (IOException e) { android.util.log.e("inhouseuseractivity", "ファイルディスクリプタの 終 了 に 失 敗 しました"); else { logline(" null file descriptor"); private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mlogview = (TextView) findviewbyid(r.id.logview); private void logline(string line) { mlogview.append(line); mlogview.append("\n"); All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 251

254 ディレクトリのアクセス 権 設 定 これまでファイルに 着 目 してセキュリティの 考 慮 点 を 説 明 してきた ファイルのコンテナであるディレクトリについてもセ キュリティの 考 慮 が 必 要 である ここではディレクトリのアクセス 権 設 定 についてセキュリティ 上 の 考 慮 ポイントを 説 明 する Android には アプリディレクトリ 内 にサブディレクトリを 取 得 作 成 するメソッドがいくつか 用 意 されている 主 なもの を 表 に 示 す 表 アプリディレクトリ 配 下 のサブディレクトリ 取 得 作 成 メソッド 他 アプリに 対 する ユーザーによる 削 除 アクセス 権 の 指 定 Context#getFilesDir() 不 可 ( 実 行 権 限 のみ) 設 定 アプリ アプリケーションを 選 択 データを 消 去 Context#getCacheDir() 不 可 ( 実 行 権 限 のみ) 設 定 アプリ アプリケーションを 選 択 キャッシュを 消 去 データを 消 去 でも 削 除 される Context#getDir(String name, int mode) mode に 以 下 を 指 定 可 能 MODE_PRIVATE MODE_WORLD_READABLE MODE_WORLD_WRITABLE 設 定 アプリ アプリケーションを 選 択 データを 消 去 ここで 特 に 気 を 付 けるのは Context#getDir()によるアクセス 権 の 設 定 である ファイルの 作 成 でも 説 明 しているよう に Android のセキュリティ 設 計 の 観 点 からディレクトリも 基 本 的 には 非 公 開 にするべきであり アクセス 権 の 設 定 に よって 情 報 の 共 有 を 行 うと 思 わぬ 副 作 用 があるので 情 報 の 共 有 には 他 の 手 段 を 考 えるべきである MODE_WORLD_READABLE すべてのアプリに 対 してディレクトリの 読 み 取 り 権 限 を 与 えるフラグである すべてのアプリがディレクトリ 内 のファイル 一 覧 や 個 々のファイルの 属 性 情 報 を 取 得 可 能 になる このディレクトリ 配 下 に 秘 密 のファイルを 配 置 することはできな いので このフラグの 使 用 には 十 分 な 注 意 が 必 要 である なお 本 フラグは API Level17 以 降 では deprecated とな っており 通 常 はこのフラグを 使 用 しないこと MODE_WORLD_WRITABLE 他 アプリに 対 してディレクトリの 書 き 込 み 権 限 を 与 えるフラグである すべてのアプリがディレクトリ 内 のファイルを 作 成 移 動 9 リネーム 削 除 が 可 能 になる これらの 操 作 はファイル 自 体 のアクセス 権 設 定 ( 読 み 取 り 書 き 込 み 実 行 )と は 無 関 係 であり ディレクトリの 書 き 込 み 権 限 があるだけで 可 能 となる 操 作 であることに 注 意 が 必 要 だ 他 のアプリか ら 勝 手 にファイルを 削 除 されたり 置 き 換 えられたりするので 通 常 はこのフラグを 使 用 してはならない なお 本 フラグ 9 内 部 ストレージから 外 部 記 憶 装 置 (SD カードなど)への 移 動 などマウントポイントを 超 えた 移 動 はできない そのため 読 み 取 り 権 限 のない 内 部 ストレージファイルが 外 部 記 憶 装 置 に 移 動 されて 読 み 書 き 可 能 になるようなことはない 252 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

255 は API Level17 以 降 では deprecated となっている 表 の ユーザーによる 削 除 に 関 しては ファイルの 生 存 期 間 を 考 慮 してアプリの 設 計 を 行 う ( 必 須 ) を 参 照 のこと Shared Preference やデータベースファイルのアクセス 権 設 定 Shared Preference やデータベースもファイルで 構 成 される アクセス 権 設 定 についてはファイルと 同 じことが 言 える したがって Shared Preference もデータベースもファイルと 同 様 に 基 本 的 には 非 公 開 ファイルとして 作 成 し 内 容 の 共 有 は Android のアプリ 間 連 携 の 仕 組 みによって 実 現 するべきである Shared Preference の 使 用 例 を 次 に 示 す MODE_PRIVATE により 非 公 開 ファイルとして Shared Preference を 作 成 している Shared Preference ファイルにアクセス 制 限 を 設 定 する 例 import android.content.sharedpreferences; import android.content.sharedpreferences.editor; ~ 省 略 ~ // Shared Preference を 取 得 する(なければ 作 成 される) // ポイント: 基 本 的 に MODE_PRIVATE モードを 指 定 する SharedPreferences preference = getsharedpreferences( PREFERENCE_FILE_NAME, MODE_PRIVATE); // 値 が 文 字 列 のプリファレンスを 書 き 込 む 例 Editor editor = preference.edit(); editor.putstring("prep_key", "prep_value");// key:"prep_key", value:"prep_value" editor.commit(); データベースについては 4.5 SQLite を 使 う を 参 照 すること Android 4.4 (API Level 19)における 外 部 ストレージへのアクセスに 関 する 仕 様 変 更 について Android 4.4 (API Level 19) 以 降 の 端 末 において 外 部 ストレージへのアクセスに 関 して 以 下 のように 仕 様 が 変 更 さ れた (1) 外 部 ストレージ 上 のアプリ 固 有 ディレクトリに 読 み 書 きする 場 合 は WRITE_EXTERNAL_STORAGE/READ_EX TERNAL_STORAGE Permission が 不 要 である( 変 更 箇 所 ) (2) 外 部 ストレージ 上 のアプリ 固 有 ディレクトリ 以 外 の 場 所 にあるファイルを 読 み 込 む 場 合 は READ_EXTERNAL_S TORAGE Permission が 必 要 である( 変 更 箇 所 ) (3) プライマリ 外 部 ストレージ 上 のアプリ 固 有 ディレクトリ 以 外 の 場 所 にファイルを 書 き 込 む 場 合 は WRITE_EXTERN AL_STORAGE Permission が 必 要 である (4) セカンダリ 以 降 の 外 部 ストレージにはアプリ 固 有 ディレクトリ 以 外 の 場 所 に 書 き 込 みは 出 来 ない All rights reserved Japan Smartphone Security Association. ファイルを 扱 う 253

256 この 仕 様 では Android OS のバージョンによって Permission の 利 用 宣 言 の 要 不 要 が 変 わっているため Andro id 4.4 (API Level 19)をまたいで 端 末 のサポートが 必 要 なアプリの 場 合 は インストールする 端 末 のバージョンに よって 不 要 な Permission をユーザーに 要 求 することになり 好 ましい 状 況 とは 言 えない よって 上 記 仕 様 (1)のみ に 該 当 するアプリの 場 合 は <uses-permission>タグの maxsdkversion 属 性 を 以 下 のように 記 述 して 対 応 する ことをお 薦 めする AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.file.externalfile" > <!-- android.permission.write_external_storage Permission を 利 用 宣 言 する --> <!-- Android 4.4 (API Level 19) 以 降 では 外 部 ストレージのアプリデータ 領 域 を 読 み 書 きする 際 に Permission が 不 要 なため maxsdkversion を 宣 言 する --> <uses-permission android:name="android.permission.write_external_storage" android:maxsdkversion="18"/> <application android:allowbackup="false" > <activity android:name=".externalfileactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> 254 All rights reserved Japan Smartphone Security Association. ファイルを 扱 う

257 4.7. Browsable Intent を 利 用 する ブラウザから Web ページのリンクに 対 応 して 起 動 するようにアプリを 作 ることができる Browsable Intent という 機 能 である アプリは URI スキームを Manifest ファイルで 指 定 することで その URI スキームを 持 つリンクへの 移 動 (ユーザーのタップなど)に 反 応 し リンクをパラメータとして 起 動 することが 可 能 になる また URI スキームを 利 用 することでブラウザから 対 応 するアプリを 起 動 する 方 法 は Android のみならず ios 他 の プラットフォームでも 対 応 しており Web アプリとの 外 部 アプリ 連 携 などに 一 般 的 に 使 われている 例 えば Twitter ア プリや Facebook アプリでは 次 のような URI スキームが 定 義 されており Android でも ios でもブラウザから 対 応 す るアプリが 起 動 するようになっている 表 URI スキーム 対 応 するアプリ fb:// Facebook twitter:// Twitter このように 連 携 や 利 便 性 を 考 えた 便 利 な 機 能 であるが 悪 意 ある 第 三 者 に 悪 用 される 危 険 性 も 潜 んでいる 悪 意 の ある Web サイトを 用 意 してリンクの URL に 不 正 なパラメータを 仕 込 むことでアプリの 機 能 を 悪 用 したり 同 じ URI スキ ームに 対 応 したマルウェアをインストールさせて URL に 含 まれる 情 報 を 横 取 りしたりするなどが 考 えられる このような 危 険 性 に 対 応 するために 利 用 する 際 にはいくつかのポイントに 気 をつけなければならない サンプルコード 以 下 に Browsable Intent を 利 用 したアプリのサンプルコードを 示 す ポイント: 1. (Web ページ 側 ) 対 応 する URI スキーマを 使 ったリンクのパラメータにセンシティブな 情 報 を 含 めない 2. URL のパラメータを 利 用 する 前 に 値 の 安 全 性 を 確 認 する Starter.html <html> <body> <!-- ポイント 1 URL にセンシティブな 情 報 を 含 めない --> <!-- URL パラメータとして 渡 す 文 字 列 は UTF-8 で かつ URI エンコードしておくこと --> <a href="secure://jssec?user=user_id"> Login </a> </body> </html> AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.browsableintent" > <application android:icon="@drawable/ic_launcher" All rights reserved Japan Smartphone Security Association. Browsable Intent を 利 用 する 255

258 android:allowbackup="false" > <activity android:name=".browsableintentactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.view" /> // 暗 黙 的 intent を 受 け 付 ける <category android:name="android.intent.category.default" /> // Browsable intent を 受 け 付 ける <category android:name="android.intent.category.browsable" /> // URI 'secure://jssec' を 受 け 付 ける <data android:scheme="secure" android:host="jssec"/> </intent-filter> </activity> </application> </manifest> BrowsableIntentActivity.java package org.jssec.android.browsableintent; import android.app.activity; import android.content.intent; import android.net.uri; import android.os.bundle; import android.widget.textview; public class BrowsableIntentActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_browsable_intent); Intent intent = getintent(); Uri uri = intent.getdata(); if (uri!= null) { // URL パラメータで 渡 されたユーザーID を 取 得 する // ポイント 2 URL のパラメータを 利 用 する 前 に 値 の 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String userid = "User ID = " + uri.getqueryparameter("user"); TextView tv = (TextView)findViewById(R.id.text_userid); tv.settext(userid); 256 All rights reserved Japan Smartphone Security Association. Browsable Intent を 利 用 する

259 ルールブック Browsable Intent を 利 用 する 場 合 には 以 下 のルールを 守 ること 1. (Web ページ 側 ) 対 応 するリンクのパラメータにセンシティブな 情 報 を 含 めない ( 必 須 ) 2. URL のパラメータを 利 用 する 前 に 値 の 安 全 性 を 確 認 する ( 必 須 ) (Web ページ 側 ) 対 応 するリンクのパラメータにセンシティブな 情 報 を 含 めない ( 必 須 ) ブラウザ 上 でリンクをタップした 際 data(intent#getdata にて 取 得 )に URL の 値 が 入 った Intent が 発 行 され シス テムにより 該 当 する Intent Filter を 持 つアプリが 起 動 する この 時 同 じ URI スキームを 受 け 付 けるよう Intent Filter が 設 定 されたアプリが 複 数 存 在 する 場 合 は 通 常 の 暗 黙 的 Intent による 起 動 と 同 様 にアプリ 選 択 のダイアログが 表 示 され ユーザーの 選 択 したアプリが 起 動 することになる 仮 に アプリ 選 択 画 面 の 選 択 肢 としてマルウェアが 存 在 していた 場 合 は ユーザーが 誤 ってマルウェアを 起 動 させて しまう 危 険 性 があり パラメータがマルウェアに 渡 ることになる このように Web ページのリンク URL に 含 めたパラメータはすべてマルウェアに 渡 る 可 能 性 があるので 一 般 の Web ページのリンクを 作 るときと 同 様 に URL のパラメータに 直 接 センシティブな 情 報 を 含 めることは 避 けなければならな い URL にユーザーID とパスワードが 入 っている 例 insecure://sample/login?userid=12345&password=abcdef また URL のパラメータがユーザーID などセンシティブでない 情 報 のみの 場 合 でも アプリ 起 動 時 のパスワード 入 力 をアプリ 側 でさせるような 仕 様 では ユーザーが 気 付 かずにマルウェアを 起 動 してしまい マルウェアに 対 してパスワ ードを 入 力 してしまう 危 険 性 もある そのため 一 連 のログイン 処 理 自 体 はアプリ 側 で 完 結 するような 仕 様 を 検 討 すべ きである Browsable Intent によるアプリ 起 動 はあくまで 暗 黙 的 Intent によるアプリ 起 動 であり 意 図 したアプリが 起 動 される 保 証 がないことを 念 頭 に 置 いたアプリ サービス 設 計 を 心 がける 必 要 がある URL のパラメータを 利 用 する 前 に 値 の 安 全 性 を 確 認 する ( 必 須 ) URI スキーマに 合 わせたリンクは アプリ 開 発 者 に 限 らず 誰 でも 作 成 可 能 なので アプリに 渡 された URL のパラメータ が 正 規 の Web ページから 送 られてくるとは 限 らない また 渡 された URL のパラメータが 正 規 の Web ページから 送 られてきたかどうかを 調 べる 方 法 もない そのため 渡 された URL のパラメータを 利 用 する 前 に パラメータに 想 定 しない 値 が 入 っていないかなど 値 の 安 全 性 を 確 認 する 必 要 がある All rights reserved Japan Smartphone Security Association. Browsable Intent を 利 用 する 257

260 4.8. LogCat にログ 出 力 する AndroidはLogCat と 呼 ばれるシステムログ 機 構 があり システムのログ 情 報 だけでなくアプリのログ 情 報 も LogCat に 出 力 される LogCat のログ 情 報 は 同 じ 端 末 内 の 他 のアプリからも 読 み 取 り 可 能 10であるため センシティブな 情 報 を LogCat にログ 出 力 してしまうアプリには 情 報 漏 洩 の 脆 弱 性 があるとされる LogCat にはセンシティブな 情 報 をロ グ 出 力 すべきではない セキュリティ 観 点 ではリリース 版 アプリでは 一 切 ログ 出 力 しないことが 望 ましい しかし 様 々な 理 由 によりリリース 版 ア プリでもログ 出 力 するケースがある ここではリリース 版 アプリにおいてもログ 出 力 しつつ センシティブな 情 報 はログ 出 力 しない 方 法 を 紹 介 する また リリース 版 アプリにおけるログ 出 力 の 2 つの 考 え 方 も 参 照 すること サンプルコード ここでは ProGuard を 利 用 してリリース 版 アプリでの LogCat へのログ 出 力 を 制 御 する 方 法 を 紹 介 する ProGuard は 使 用 されていないメソッド 等 実 質 的 に 不 要 なコードを 自 動 削 除 する 最 適 化 ツールの 一 つである Android の android.util.log クラスには 5 種 類 のログ 出 力 メソッド Log.e() Log.w() Log.i() Log.d() Log.v() がある ログ 情 報 は リリース 版 アプリで 出 力 することを 意 図 したログ 情 報 ( 以 下 運 用 ログ 情 報 と 呼 ぶ)と リリース 版 アプリで 出 力 してはならない(たとえばデバッグ 用 の)ログ 情 報 ( 以 下 開 発 ログ 情 報 と 呼 ぶ)を 区 別 するべきである 運 用 ログ 出 力 のためには Log.e()/w()/i()を 使 用 し 開 発 ログ 出 力 のためには Log.d()/v()を 使 用 するとよい 5 種 類 のログ 出 力 メソッドの 使 い 分 けの 詳 細 については ログレベルとログ 出 力 メソッドの 選 択 基 準 を 参 照 するこ と また DEBUG ログと VERBOSE ログは 自 動 的 に 削 除 されるわけではない も 参 照 すること 次 ページ 以 降 で Log.d()/v()で 出 力 する 開 発 ログ 情 報 を 開 発 版 アプリではログ 出 力 し リリース 版 アプリではログ 出 力 しないサンプルコードを 紹 介 する このサンプルコードでは Log.d()/v() 呼 び 出 しコードを 自 動 削 除 するために ProGuard を 使 用 している 10 LogCat に 出 力 されたログ 情 報 は READ_LOGS Permission を 利 用 宣 言 したアプリであれば 読 み 取 り 可 能 である ただし Android 4.1 以 降 では LogCat に 出 力 された 他 のアプリのログ 情 報 は 読 み 取 り 不 可 となった また スマートフ ォンユーザーであれば ADB 経 由 で LogCat のログ 情 報 を 参 照 することも 可 能 である 258 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

261 ポイント: 1. センシティブな 情 報 は Log.e()/w()/i() System.out/err で 出 力 しない 2. センシティブな 情 報 をログ 出 力 する 場 合 は Log.d()/v()で 出 力 する 3. Log.d()/v()の 呼 び 出 しでは 戻 り 値 を 使 用 しない( 代 入 や 比 較 ) 4. リリースビルドでは Log.d()/v()の 呼 び 出 しが 自 動 削 除 される 仕 組 みを 導 入 する 5. リリース 版 アプリの APK ファイルはリリースビルドで 作 成 する ProGuardActivity.java package org.jssec.android.log.proguard; import android.app.activity; import android.os.bundle; import android.util.log; public class ProGuardActivity extends Activity { final static String LOG_TAG = "ProGuardActivity"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_proguard); // ポイント 1 センシティブな 情 報 は Log.e()/w()/i() System.out/err で 出 力 しない Log.e(LOG_TAG, "センシティブではない 情 報 (ERROR)"); Log.w(LOG_TAG, "センシティブではない 情 報 (WARN)"); Log.i(LOG_TAG, "センシティブではない 情 報 (INFO)"); // ポイント 2 センシティブな 情 報 をログ 出 力 する 場 合 は Log.d()/v()で 出 力 する // ポイント 3 Log.d()/v()の 呼 び 出 しでは 戻 り 値 を 使 用 しない( 代 入 や 比 較 ) Log.d(LOG_TAG, "センシティブな 情 報 (DEBUG)"); Log.v(LOG_TAG, "センシティブな 情 報 (VERBOSE)"); proguard-project.txt # クラス 名 メソッド 名 等 の 変 更 を 防 ぐ -dontobfuscate # ポイント 4 リリースビルドでは Log.d()/v()の 呼 び 出 しが 自 動 削 除 される 仕 組 みを 導 入 する -assumenosideeffects class android.util.log { public static int d(...); public static int v(...); All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 259

262 ポイント 5 リリース 版 アプリの APK ファイルはリリースビルドで 作 成 する 図 リリース 版 アプリを 作 成 する 方 法 (Export する) 開 発 版 アプリ(デバッグビルド)とリリース 版 アプリ(リリースビルド)の LogCat 出 力 の 違 いを 図 に 示 す 開 発 版 アプリ(デバッグビルド) リリース 版 アプリ(リリースビルド) 図 Log メソッドの 開 発 版 アプリとリリース 版 アプリの LogCat 出 力 の 違 い 260 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

263 ルールブック LogCat にログを 出 力 する 際 は 以 下 のルールを 守 ること 1. 運 用 ログ 情 報 にセンシティブな 情 報 を 含 めない ( 必 須 ) 2. 開 発 ログ 情 報 を 出 力 するコードをリリースビルド 時 に 自 動 削 除 する 仕 組 みを 導 入 する ( 推 奨 ) 3. Throwable オブジェクトをログ 出 力 するときは Log.d()/v()メソッドを 使 う ( 推 奨 ) 4. ログ 出 力 には android.util.log クラスのメソッドのみ 使 用 する ( 推 奨 ) 運 用 ログ 情 報 にセンシティブな 情 報 を 含 めない ( 必 須 ) LogCat に 出 力 したログは 他 のアプリから 読 むことができるので リリース 版 アプリがユーザーのログイン 情 報 などの センシティブな 情 報 をログ 出 力 することがあってはならない 開 発 中 にセンシティブな 情 報 をログ 出 力 するコードを 書 かないようにするか あるいは リリース 前 にそのようなコードを 全 て 削 除 することが 必 要 である このルールを 順 守 するためには 運 用 ログ 情 報 にセンシティブな 情 報 を 含 めないこと さらに センシティブな 情 報 を 出 力 するコードをリリースビルド 時 に 削 除 する 仕 組 みを 導 入 することを 強 く 推 奨 する 開 発 ログ 情 報 を 出 力 するコードをリリースビルド 時 に 自 動 削 除 する 仕 組 みを 導 入 する ( 推 奨 ) を 参 照 すること 開 発 ログ 情 報 を 出 力 するコードをリリースビルド 時 に 自 動 削 除 する 仕 組 みを 導 入 する ( 推 奨 ) アプリ 開 発 中 は 複 雑 なロジックの 処 理 過 程 の 中 間 的 な 演 算 結 果 プログラム 内 部 の 状 態 情 報 通 信 プロトコルの 通 信 データ 構 造 など 処 理 内 容 の 確 認 やデバッグ 用 でセンシティブな 情 報 をログ 出 力 させたいことがある アプリ 開 発 時 にセンシティブな 情 報 をデバッグログとして 出 力 するのは 構 わないが この 場 合 は 運 用 ログ 情 報 にセン シティブな 情 報 を 含 めない ( 必 須 ) で 述 べたように リリース 前 に 必 ず 該 当 するログ 出 力 コードを 削 除 するこ と リリースビルド 時 に 開 発 ログ 情 報 を 出 力 するコードを 確 実 に 削 除 するために 何 らかのツールを 用 いてコード 削 除 を 自 動 化 する 仕 組 みを 導 入 すべきである そのためのツールに で 紹 介 した ProGuard がある 以 下 では ProGuard を 使 ったコード 削 除 の 仕 組 みを 導 入 する 際 の 注 意 を 説 明 する ここでは ログレベルとログ 出 力 メソッドの 選 択 基 準 に 準 拠 し 開 発 ログ 情 報 を Log.d()/v()のいずれかのみで 出 力 しているアプリに 対 して 仕 組 み を 適 用 することを 想 定 している ProGuard は 使 用 さ れ て い な い メ ソ ッ ド 等 実 質 的 に 不 要 な コ ー ド を 自 動 削 除 す る Log.d()/v() を -assumenosideeffects オプションの 引 数 に 指 定 することにより Log.d() Log.v()の 呼 び 出 しが 実 質 的 に 不 要 なコ ードとみなされ 自 動 削 除 される Log.d()/v()を-assumenosideeffects と 指 定 することで 自 動 削 除 の 対 象 にする -assumenosideeffects class android.util.log { public static int d(...); public static int v(...); All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 261

264 この 自 動 削 除 の 仕 組 みを 利 用 する 場 合 は Log.v(), Log.d()の 戻 り 値 を 使 用 してしまうと Log.v()/d()のコードが 削 除 されない 点 に 注 意 が 必 要 である よって Log.v(), Log.d()の 戻 り 値 を 使 用 してはならない たとえば 次 の 実 験 コー ドにおいては Log.v()が 削 除 されない 削 除 指 定 した Log.v()が 削 除 されない 実 験 コード int i = android.util.log.v("tag", "message"); System.out.println(String.format( Log.v()が%d を 返 した, i)); // 実 験 のため Log.v()の 戻 り 値 を 使 用 また 上 記 ProGuard 設 定 により Log.d() 及 び Log.v()が 自 動 削 除 されることを 前 提 としたソースコードがあったとす る もしそのソースコードを ProGuard 設 定 がされていない 他 のプロジェクトで 再 利 用 してしまうと Log.d() 及 び Log.v()が 削 除 されないため センシティブな 情 報 が 漏 洩 してしまう 危 険 性 がある ソースコードを 再 利 用 する 際 は ProGuard 設 定 を 含 めたプロジェクト 環 境 の 整 合 性 を 確 保 すること Throwable オブジェクトをログ 出 力 するときは Log.d()/v()メソッドを 使 う ( 推 奨 ) サンプルコード および ログレベルとログ 出 力 メソッドの 選 択 基 準 に 示 した 通 り Log.e()/w()/i() ではセンシティブな 情 報 をログ 出 力 してはならない 一 方 で 開 発 者 がプログラムの 異 常 を 詳 細 にログ 出 力 するため に 例 外 発 生 時 に Log.e(, Throwable tr)/w(, Throwable tr)/i(, Throwable tr)でスタックトレースを LogCat にログ 出 力 しているケースがみられる しかしながら スタックトレースはプログラムの 内 部 構 造 を 詳 細 に 出 力 してしまうので アプリケーションによってはセンシティブな 情 報 が 含 まれてしまう 場 合 がある 例 えば SQLiteException をそのまま 出 力 してしまうと どのような SQL ステートメントが 発 行 されたかが 明 らかになるので SQL インジェクション 攻 撃 の 手 がかりを 与 えてしまうことがある よって Throwable オブジェクトをログ 出 力 する 際 に は Log.d()/Log.v()メソッドのみを 使 用 することを 推 奨 する ログ 出 力 には android.util.log クラスのメソッドのみ 使 用 する ( 推 奨 ) 開 発 中 にアプリが 想 定 通 りに 動 作 していることを 確 認 するために System.out/err でログを 出 力 することがあるだろ う もちろん System.out/err の print()/println()メソッドでも LogCat にログを 出 力 することは 可 能 だが 以 下 の 理 由 からログ 出 力 には android.util.log クラスのメソッドのみを 使 用 することを 強 く 推 奨 する ログを 出 力 するときは 一 般 には 情 報 の 緊 急 度 に 応 じて 出 力 メソッドを 使 い 分 け 出 力 を 制 御 する たとえば 深 刻 な エラー 警 告 単 なるアプリ 情 報 通 知 などの 区 分 が 使 われる この 区 分 を System.out/err に 適 用 する 手 段 の 一 つに は エラーと 警 告 は System.err それ 以 外 は System.out で 出 力 する 方 法 がある しかし この 場 合 リリース 時 に も 出 力 する 必 要 のあるセンシティブでない 情 報 ( 運 用 ログ 情 報 )とセンシティブな 情 報 が 含 まれている 可 能 性 のある 情 報 ( 開 発 ログ 情 報 )が 同 じメソッドによって 出 力 されてしまう よって センシティブな 情 報 を 出 力 するコードを 削 除 する 際 に 削 除 漏 れが 発 生 するおそれがある また ログ 出 力 に android.util.log と System.out/err を 使 う 場 合 は android.util.log のみを 使 う 場 合 と 比 べて ログ 出 力 コードを 削 除 する 際 に 考 慮 することが 増 えるため 削 除 漏 れなどのミスが 生 じるおそれがある 262 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

265 上 記 のようなミスが 生 じる 危 険 を 減 らすために android.util.log クラスのメソッドのみ 使 用 することを 推 奨 する All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 263

266 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド リリース 版 アプリにおけるログ 出 力 の 2 つの 考 え 方 リリース 版 Android アプリにおけるログ 出 力 の 考 え 方 には 大 きく 分 けて 一 切 ログ 出 力 すべきではないという 考 え 方 と 後 の 解 析 のために 必 要 な 情 報 をログ 出 力 すべきという 考 え 方 の 2 つがある セキュリティ 観 点 ではリリース 版 アプ リでは 一 切 ログ 出 力 しないことが 望 ましい しかし 様 々な 理 由 によりリリース 版 アプリでもログ 出 力 するケースがある ここでは 両 者 のそれぞれの 考 え 方 について 述 べる 1 つ 目 は リリース 版 アプリにおいてログ 出 力 することにはあまり 価 値 がなく しかもセンシティブな 情 報 を 漏 洩 してし まうリスクがあるので 一 切 ログ 出 力 すべきではない という 考 え 方 である この 考 え 方 は 多 くの Web アプリ 運 用 環 境 などと 違 い Android アプリ 運 用 環 境 ではリリース 後 のアプリのログ 情 報 を 開 発 者 が 収 集 する 手 段 が 用 意 され ていないことによるものである この 考 え 方 に 基 づくと 開 発 中 に 使 用 したログ 出 力 コードを 最 終 版 のソースコードから 削 除 してリリース 版 アプリを 作 成 するという 運 用 がなされる 2 つ 目 は カスタマーサポート 等 でアプリの 不 具 合 解 析 を 行 う 最 終 手 段 として 後 の 解 析 のために 必 要 な 情 報 をログ 出 力 すべき という 考 え 方 である この 考 え 方 に 基 づくと リリース 版 アプリではセンシティブな 情 報 を 誤 ってログ 出 力 してしまわないよう 細 心 の 注 意 が 必 要 となるため サンプルコードセクションで 紹 介 したような 人 為 的 ミスを 排 除 する 運 用 が 必 要 となる なお 下 記 の Google の Code Style Guideline も 2 つ 目 の 考 え 方 に 基 づいている Code Style Guidelines for Contributors / Log Sparingly ログレベルとログ 出 力 メソッドの 選 択 基 準 Android の android.util.log クラスには ERROR WARN INFO DEBUG,VERBOSE の 5 段 階 のログレベルが 定 義 されている 出 力 したいログ 情 報 のログレベルに 応 じて 適 切 な android.util.log クラスのログ 出 力 メソッドを 選 択 する 必 要 がある 選 択 基 準 を 表 にまとめた 表 ログレベルとログ 出 力 メソッドの 選 択 基 準 ログレベル メソッド 出 力 するログ 情 報 の 趣 旨 アプリリリース 時 の 注 意 ERROR WARN INFO DEBUG Log.e() アプリが 致 命 的 な 状 況 に 陥 ったときに 出 力 するログ 情 報 Log.w() アプリが 深 刻 な 予 期 せぬ 状 況 に 遭 遇 した ときに 出 力 するログ 情 報 Log.i() 上 記 以 外 で アプリの 注 目 すべき 状 態 の 変 化 や 結 果 を 知 らせる 目 的 で 出 力 するロ グ 情 報 Log.d() アプリ 開 発 時 に 特 定 のバグの 原 因 究 明 の ために 一 時 的 にログ 出 力 したいプログラ 左 記 のログ 情 報 はユーザーも 参 照 する ことが 想 定 される 情 報 であるため 開 発 版 アプリとリリース 版 アプリの 両 方 でロ グ 出 力 されるべき 情 報 である そのた めこのログレベルではセンシティブな 情 報 をログ 出 力 してはならない 左 記 のログ 情 報 はアプリ 開 発 者 専 用 の 情 報 であるため リリース 版 アプリでは 264 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

267 ム 内 部 の 状 態 情 報 VERBOSE Log.v() 以 上 のいずれにも 該 当 しないログ 情 報 アプリ 開 発 者 がさまざまな 目 的 で 出 力 す るログ 情 報 が 該 当 する サーバーとの 通 信 データをダンプ 出 力 したい 場 合 など ログ 出 力 されてはならない 情 報 である 開 発 版 アプリではセンシティブな 情 報 を 出 力 しても 構 わないが リリース 版 アプ リでは 絶 対 にセンシティブな 情 報 をログ 出 力 してはならない より 詳 細 なログ 出 力 の 作 法 については 下 記 URL を 参 照 すること Code Style Guidelines for Contributors / Log Sparingly DEBUG ログと VERBOSE ログは 自 動 的 に 削 除 されるわけではない Developer Reference の android.util.log クラスの 解 説 11には 次 のような 記 載 がある The order in terms of verbosity, from least to most is ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled into an application except during development. Debug logs are compiled in but stripped at runtime. Error, warning and info logs are always kept. 開 発 者 の 中 には この 文 章 から Log クラスの 動 作 を 次 のように 誤 った 解 釈 をしている 人 がいる Log.v() 呼 び 出 しはリリースビルド 時 にはコンパイルされず VERBOSE ログが 出 力 されることがなくなる Log.d() 呼 び 出 しはコンパイルされるが 実 行 時 には DEBUG ログが 出 力 されることはない しかし 実 際 には Log クラスはこのようには 動 作 せず デバッグビルド リリースビルドを 問 わず 全 てのログを 出 力 して しまう よく 読 んでみるとわかるが この 英 文 は Log クラスの 動 作 について 語 っているのではなく ログ 情 報 とはこうあ るべきということを 説 明 しているだけである この 記 事 のサンプルコードでは ProGuard を 使 って 上 記 英 文 のような 動 作 を 実 現 する 方 法 を 紹 介 している BuildConfig.DEBUG は ADT 21 以 降 で 使 う Eclipse 用 ADT プラグインでは 次 のような BuildConfig.java ファイルが 自 動 生 成 される ADT プラグインによって 下 記 の DEBUG 定 数 (BuildConfig.DEBUG)が リリースビルドでは false デバッグビルドでは true に 自 動 設 定 さ れる BuildConfig.java /** Automatically generated file. DO NOT MODIFY */ 11 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 265

268 package com.example.buildconfig; public final class BuildConfig { public final static boolean DEBUG = true; 下 記 のように BuildConfig.DEBUG を 用 いると リリースビルド 時 にログ 出 力 を 抑 制 することができる if (BuildConfig.DEBUG) android.util.log.d(tag, "ログ 出 力 情 報 "); 残 念 ながら ADT 20 以 前 ではバグがあり リリースビルドでも DEBUG 定 数 が true になる 場 合 があった ADT 21 以 降 ではバグが 修 正 されているので BuildConfig.DEBUG を 使 う 場 合 は ADT 21 以 降 を 使 う 必 要 がある ログ 情 報 の 組 み 立 て 処 理 を 削 除 する 下 記 ソースコードを ProGuard でリリースビルドして Log.d()を 削 除 した 場 合 Log.d()の 呼 び 出 し 処 理 ( 下 記 コードの 2 行 目 )は 削 除 されるものの その 前 段 でセンシティブな 情 報 を 組 み 立 てる 処 理 ( 下 記 コードの 1 行 目 )は 削 除 されな いことに 注 意 が 必 要 である String debug_info = String.format("%s:%s", "センシティブな 情 報 1", "センシティブな 情 報 2"); if (BuildConfig.DEBUG) android.util.log.d(tag, debug_info); 上 記 ソースコードをリリースビルドした APK ファイルを 逆 アセンブルすると 次 のようになる 確 かに Log.d()の 呼 び 出 し 処 理 は 存 在 しないが センシティブな 情 報 1 といった 文 字 列 定 数 定 義 と String#format()メソッドの 呼 び 出 し 処 理 が 削 除 されず 残 っていることが 分 かる const-string v1, "%s:%s" const/4 v2, 0x2 new-array v2, v2, [Ljava/lang/Object; const/4 v3, 0x0 const-string v4, "センシティブな 情 報 1" aput-object v4, v2, v3 const/4 v3, 0x1 const-string v4, "センシティブな 情 報 2" aput-object v4, v2, v3 invoke-static {v1, v2, Ljava/lang/String;->format(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; move-result-object v0 実 際 には APK ファイルを 逆 アセンブルして 上 記 のようにログ 出 力 情 報 を 組 み 立 てている 箇 所 を 発 見 するのは 容 易 なことではない しかし 非 常 に 機 密 度 の 高 い 情 報 を 扱 っているアプリにおいては このような 処 理 が APK ファイルに 残 ってしまってはならない 場 合 もあり 得 る 266 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

269 もし 上 記 のようなログ 出 力 情 報 の 組 み 立 て 処 理 も 削 除 してしまいたい 場 合 には 次 のように 記 述 するとよい 12 リリー スビルド 時 にはコンパイラの 最 適 化 処 理 によって 下 記 サンプルコードの 処 理 は 丸 ごと 削 除 される ただし BuildConfig.DEBUG は ADT 21 以 降 で 使 う の 注 意 が 必 要 である if (BuildConfig.DEBUG) { String debug_info = String.format("%s:%s", "センシティブな 情 報 1", "センシティブな 情 報 2"); if (BuildConfig.DEBUG) android.util.log.d(tag, debug_info); なお 下 記 ソースコードに ProGuard を 適 用 した 場 合 も 同 様 にログ 情 報 の 組 み 立 て 処 理 ("result:" + value の 部 分 ) が 残 ってしまう Log.d(TAG, "result:" + value); この 場 合 も 下 記 のように 対 処 すればよい if (BuildConfig.DEBUG) Log.d(TAG, "result:" + value); Intent の 内 容 が LogCat に 出 力 される Activity を 利 用 する 際 に ActivityManager が Intent の 内 容 を LogCat に 出 力 するため 注 意 が 必 要 である Activity 利 用 時 のログ 出 力 について を 参 照 すること System.out/err に 出 力 されるログの 抑 制 System.out/err の 出 力 先 は LogCat である System.out/err に 出 力 されるのは 開 発 者 がデバッグのために 出 力 したログに 限 らない 例 えば 次 の 場 合 スタックトレースは System.err に 出 力 される Exception#printStackTrace()を 使 った 場 合 暗 黙 的 に System.err に 出 力 される 場 合 ( 例 外 をアプリでキャッチしていない 場 合 システムが Exception#printStackTrace()に 渡 すため ) スタックトレースにはアプリ 固 有 の 情 報 が 含 まれるため 例 外 は 開 発 者 が 正 しくハンドリングすべきである 保 険 的 対 策 として System.out/err の 出 力 先 を LogCat 以 外 に 変 更 する 方 法 がある 以 下 に リリースビルド 時 に System.out/err の 出 力 先 を 変 更 し どこにもログ 出 力 しないようにする 実 装 例 を 挙 げる ただし この 対 応 は System.out/err の 出 力 先 をアプリの 実 行 時 に 一 時 的 に 書 き 換 えるので アプリやシステムの 誤 動 作 に 繋 がらない かどうかを 充 分 に 検 討 する 必 要 がある また この 対 策 はアプリ 自 身 のプロセスには 有 効 であるが システムプロセ 12 前 述 のサンプルコードを 条 件 式 に BuildConfig.DEBUG を 用 いた if 文 で 囲 った Log.d() 呼 び 出 し 前 の if 文 は 不 要 であ るが 前 述 のサンプルコードと 対 比 させるため そのまま 残 した All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 267

270 スが 生 成 するエラーログを 抑 制 することはできない すべてのエラーを 抑 制 できるわけではないことに 注 意 すること OutputRedirectApplication.java package org.jssec.android.log.outputredirection; import java.io.ioexception; import java.io.outputstream; import java.io.printstream; import android.app.application; public class OutputRedirectApplication extends Application { // どこにも 出 力 しない PrintStream private final PrintStream emptystream = new PrintStream(new OutputStream() { public void write(int onebyte) throws IOException { // do nothing ); public void oncreate() { // リリースビルド 時 に System.out/err をどこにも 出 力 しない PrintStream にリダイレクトする // System.out/err の 本 来 のストリームを 退 避 する PrintStream savedout = System.out; PrintStream savederr = System.err; // 一 旦 System.out/err をどこにも 出 力 しない PrintStream にリダイレクトする System.setOut(emptyStream); System.setErr(emptyStream); // デバッグ 時 のみ 本 来 のストリームに 戻 す(リリースビルドでは 下 記 1 行 が ProGuard により 削 除 される) resetstreams(savedout, savederr); // リリース 時 は ProGuard により 下 記 メソッドがまるごと 削 除 される private void resetstreams(printstream savedout, PrintStream savederr) { System.setOut(savedOut); System.setErr(savedErr); AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.android.log.outputredirection" > <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:name=".outputredirectapplication" android:allowbackup="false" > <activity android:name=".logactivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> 268 All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する

271 <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> proguard-project.txt # クラス 名 メソッド 名 等 の 変 更 を 防 ぐ -dontobfuscate # リリースビルド 時 に Log.d()/v()の 呼 び 出 しを 自 動 的 に 削 除 する -assumenosideeffects class android.util.log { public static int d(...); public static int v(...); # リリースビルド 時 に resetstreams()を 自 動 的 に 削 除 する -assumenosideeffects class org.jssec.android.log.outputredirection.outputredirectapplication { private void resetstreams(...); 開 発 版 アプリ(デバッグビルド)とリリース 版 アプリ(リリースビルド)の LogCat 出 力 の 違 いを 図 に 示 す 開 発 版 アプリ(デバッグビルド) リリース 版 アプリ(リリースビルド) 図 System.out/err の 開 発 版 アプリとリリース 版 アプリの LogCat 出 力 の 違 い All rights reserved Japan Smartphone Security Association. LogCat にログ 出 力 する 269

272 4.9. WebView を 使 う Web サイトや HTML ファイルを 閲 覧 する 機 能 を 実 装 する 方 法 として WebView を 使 用 することができる WebView は HTML をレンダリングする JavaScript を 実 行 するなど この 目 的 のために 有 用 な 機 能 を 提 供 する サンプルコード WebView を 使 用 することにより 容 易 に Web サイト HTML ファイル 閲 覧 機 能 を 実 現 することができるが アクセスす るコンテンツの 特 性 によって WebView が 抱 えるリスクや 適 切 な 防 衛 手 段 が 異 なってくる 特 に 気 をつけなければいけないのは JavaScript の 使 用 である WebView のデフォルト 設 定 では JavaScript の 機 能 が 無 効 になっているが WebSettings#setJavaScriptEnabled()メソッドにより 有 効 にすることが 可 能 である JavaScript を 使 用 することでインタラクティブなコンテンツの 表 示 が 可 能 になるが 悪 意 のある 第 三 者 により 端 末 の 情 報 を 取 得 される あるいは 端 末 を 操 作 されるという 被 害 が 発 生 する 可 能 性 がある WebView を 用 いてコンテンツにアクセスするアプリを 開 発 する 際 は 次 の 原 則 に 従 うこと 13 (1) 自 社 が 管 理 しているコンテンツにのみアクセスする 場 合 に 限 り JavaScript を 有 効 にしてよい (2) 上 記 以 外 の 場 合 には JavaScript を 有 効 にしてはならない 開 発 しているアプリがアクセスするコンテンツの 特 性 を 踏 まえ 図 に 従 いサンプルコードを 選 択 することが 必 要 である はじめ Yes アプリがアクセスする コンテンツは 自 社 管 理 のコンテンツ に 限 定 されるか? No Yes アプリがアクセスする コンテンツはアプリ 内 のコンテンツ に 限 定 されるか? No assetsまたはresディレクトリに 配 置 した コンテンツのみを 表 示 する インターネット 上 の 自 社 管 理 コンテンツを 表 示 する 自 社 管 理 以 外 のコンテンツを 表 示 する 図 WebView のサンプルコードを 選 択 するフローチャート 13 厳 密 に 言 えば 安 全 性 を 保 証 できるコンテンツであれば JavaScript を 有 効 にしてよい 自 社 管 理 のコンテンツであれば 自 社 の 努 力 で 安 全 性 を 確 保 できるし 責 任 も 取 れる では 信 頼 できる 提 携 会 社 のコンテンツは 安 全 だろうか?これは 会 社 間 の 信 頼 関 係 により 決 まる 信 頼 できる 提 携 会 社 のコンテンツを 安 全 であると 信 頼 して JavaScript を 有 効 にしてもよい が 万 一 の 場 合 は 自 社 責 任 も 伴 うため ビジネス 責 任 者 の 判 断 が 必 要 となる 270 All rights reserved Japan Smartphone Security Association. WebView を 使 う

273 assets または res ディレクトリに 配 置 したコンテンツのみを 表 示 する 端 末 内 のローカルコンテンツを WebView で 表 示 するアプリに 関 しては アプリの APK に 含 まれる assets あるいは res ディレクトリ 内 のコンテンツにアクセスする 場 合 に 限 り JavaScript を 有 効 にしてもよい 以 下 に WebView を 使 用 して assets ディレクトリ 内 にある HTML ファイルを 表 示 するサンプルコードを 示 す ポイント: 1. assets と res ディレクトリ 以 外 の 場 所 に 配 置 したファイルへのアクセスを 禁 止 にする 2. JavaScript を 有 効 にしてよい WebViewAssetsActivity.java package org.jssec.webview.assets; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class WebViewAssetsActivity extends Activity { /** * assets 内 のコンテンツを 表 示 する */ public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings websettings = webview.getsettings(); // ポイント 1 assets と res ディレクトリ 以 外 の 場 所 に 配 置 したファイルへのアクセスを 禁 止 にする websettings.setallowfileaccess(false); // ポイント 2 JavaScript を 有 効 にしてよい websettings.setjavascriptenabled(true); // assets 内 に 配 置 したコンテンツを 表 示 する webview.loadurl("file:///android_asset/sample/index.html"); All rights reserved Japan Smartphone Security Association. WebView を 使 う 271

274 インターネット 上 の 自 社 管 理 コンテンツを 表 示 する 自 社 の 管 理 するサービス 上 のコンテンツを 表 示 する 場 合 サービス 側 アプリ 側 の 双 方 で 適 切 な 対 策 を 施 し 安 全 が 確 保 できるならば JavaScript を 有 効 にしてもよい サービス 側 の 対 策 図 に 示 したように サービス 側 に 用 意 するコンテンツは 自 社 の 管 理 していないコンテンツを 参 照 してはな らない 加 えて サービスに 適 切 なセキュリティ 対 策 が 施 されていることも 必 要 である その 理 由 は サービスを 構 成 するコンテンツへの 攻 撃 コードの 埋 め 込 みや 改 ざんを 防 止 することにある JavaScript を 有 効 に するのはコンテンツを 自 社 が 管 理 している 場 合 に 限 定 する ( 必 須 ) を 参 照 すること アプリ 側 の 対 策 次 にアプリ 側 での 対 策 を 述 べる アプリ 側 では 接 続 先 が 自 社 管 理 サービスであることを 確 認 することが 必 要 で ある そのために 通 信 プロトコルは HTTPS を 使 用 し 証 明 書 が 信 頼 できる 場 合 のみ 接 続 するように 実 装 する 以 下 では アプリ 側 での 実 装 の 例 として WebView を 使 って 自 社 管 理 コンテンツを 表 示 する Activity の 例 を 示 す アプリ Android 端 末 自 社 管 理 サービス コンテンツ コンテンツの 参 照 関 係 アプリが 読 み 込 んでよい コンテンツ アプリが 読 み 込 んではならない コンテンツ アプリによる 読 み 込 み 不 許 可 自 社 管 理 サービス 以 外 のサービス インターネット 上 の サービス コンテンツ 図 アプリが 読 み 込 んでよい 自 社 管 理 コンテンツ 272 All rights reserved Japan Smartphone Security Association. WebView を 使 う

275 ポイント: 1. WebView の SSL 通 信 エラーを 適 切 にハンドリングする 2. WebView の JavaScript を 有 効 にしてもよい 3. WebView で 表 示 する URL を HTTPS プロトコルだけに 限 定 する 4. WebView で 表 示 する URL を 自 社 管 理 コンテンツだけに 限 定 する WebViewTrustedContentsActivity.java package org.jssec.webview.trustedcontents; import android.app.activity; import android.app.alertdialog; import android.content.dialoginterface; import android.net.http.sslcertificate; import android.net.http.sslerror; import android.os.bundle; import android.webkit.sslerrorhandler; import android.webkit.webview; import android.webkit.webviewclient; import java.text.simpledateformat; public class WebViewTrustedContentsActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); WebView webview = (WebView) findviewbyid(r.id.webview); webview.setwebviewclient(new WebViewClient() { public void onreceivedsslerror(webview view, SslErrorHandler handler, SslError error) { // ポイント 1 WebView の SSL 通 信 エラーを 適 切 にハンドリングする // SSL エラーが 発 生 した 場 合 には SSL エラーが 発 生 した 旨 をユーザに 通 知 する AlertDialog dialog = createsslerrordialog(error); dialog.show(); ); // ポイント 1 WebView の SSL 通 信 エラーを 適 切 にハンドリングする // SSL エラーが 発 生 した 場 合 有 効 期 限 切 れなど 証 明 書 に 不 備 があるか // もしくは 中 間 者 攻 撃 を 受 けている 可 能 性 があるので 安 全 のために 接 続 を 中 止 する handler.cancel(); // ポイント 2 WebView の JavaScript を 有 効 にしてもよい // 以 下 のコードでは loadurl()で 自 社 管 理 コンテンツを 読 みこむことを 想 定 している webview.getsettings().setjavascriptenabled(true); // ポイント 3 WebView で 表 示 する URL を HTTPS プロトコルだけに 限 定 する // ポイント 4 WebView で 表 示 する URL を 自 社 管 理 コンテンツだけに 限 定 する webview.loadurl(" private AlertDialog createsslerrordialog(sslerror error) { // ダイアログに 表 示 するエラーメッセージ String errormsg = createerrormessage(error); All rights reserved Japan Smartphone Security Association. WebView を 使 う 273

276 Android アプリのセキュア 設 計 セキュアコーディングガイド // ダイアログの OK ボタン 押 下 時 の 挙 動 DialogInterface.OnClickListener onclickok = new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { setresult(result_ok); ; // ダイアログの 作 成 AlertDialog dialog = new AlertDialog.Builder( WebViewTrustedContentsActivity.this).setTitle("SSL 接 続 エラー").setMessage(errorMsg).setPositiveButton("OK", onclickok).create(); return dialog; private String createerrormessage(sslerror error) { SslCertificate cert = error.getcertificate(); SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); StringBuilder result = new StringBuilder().append("サイトのセキュリティ 証 明 書 が 信 頼 できません 接 続 を 終 了 しました \n\n エラーの 原 因 \n"); switch (error.getprimaryerror()) { case SslError.SSL_EXPIRED: result.append(" 証 明 書 の 有 効 期 限 が 切 れています \n\n 終 了 時 刻 =").append(dateformat.format(cert.getvalidnotafterdate())); return result.tostring(); case SslError.SSL_IDMISMATCH: result.append("ホスト 名 が 一 致 しません \n\ncn=").append(cert.getissuedto().getcname()); return result.tostring(); case SslError.SSL_NOTYETVALID: result.append(" 証 明 書 はまだ 有 効 ではありません\n\n 開 始 時 刻 =").append(dateformat.format(cert.getvalidnotbeforedate())); return result.tostring(); case SslError.SSL_UNTRUSTED: result.append(" 証 明 書 を 発 行 した 認 証 局 が 信 頼 できません\n\n 認 証 局 \n").append(cert.getissuedby().getdname()); return result.tostring(); default: result.append(" 原 因 不 明 のエラーが 発 生 しました"); return result.tostring(); 274 All rights reserved Japan Smartphone Security Association. WebView を 使 う

277 自 社 管 理 以 外 のコンテンツを 表 示 する 自 社 で 管 理 していないコンテンツを WebView で 接 続 表 示 する 場 合 は JavaScript を 有 効 にしてはならない 攻 撃 者 が 用 意 したコンテンツに 接 続 する 可 能 性 があるからである 以 下 のサンプルコードは WebView を 使 用 して 自 社 管 理 以 外 のコンテンツを 表 示 するアプリである このアプリは ア ドレスバーに 入 力 した URL の 指 す HTML ファイルなどのコンテンツを 読 み 込 み 画 面 に 表 示 する 安 全 の 確 保 のため に JavaScript を 無 効 化 しているほか HTTPS で 通 信 していて SSL エラーが 発 生 した 場 合 は 接 続 を 中 止 する 実 装 とな っている SSL エラーは インターネット 上 の 自 社 管 理 コンテンツを 表 示 する と 同 様 の 方 法 によりハンドリン グしている HTTPS 通 信 についての 詳 細 は 5.4 HTTPS で 通 信 する を 参 照 すること ポイント: 1. HTTPS 通 信 の 場 合 には SSL 通 信 のエラーを 適 切 にハンドリングする 2. JavaScript を 有 効 にしない WebViewUntrustActivity.java package org.jssec.webview.untrust; import android.app.activity; import android.app.alertdialog; import android.content.dialoginterface; import android.graphics.bitmap; import android.net.http.sslcertificate; import android.net.http.sslerror; import android.os.bundle; import android.view.view; import android.webkit.sslerrorhandler; import android.webkit.webview; import android.webkit.webviewclient; import android.widget.button; import android.widget.edittext; import java.text.simpledateformat; public class WebViewUntrustActivity extends Activity { /* * 自 社 管 理 以 外 のコンテンツを 表 示 する ( 簡 易 ブラウザとして 機 能 するサンプルプログラム) */ private EditText texturl; private Button buttongo; private WebView webview; // この Activity が 独 自 に URL リクエストをハンドリングできるようにするために 定 義 private class WebViewUnlimitedClient extends WebViewClient { public boolean shouldoverrideurlloading(webview webview, String url) { webview.loadurl(url); texturl.settext(url); return true; All rights reserved Japan Smartphone Security Association. WebView を 使 う 275

278 // Web ページの 読 み 込 み 開 始 処 理 public void onpagestarted(webview webview, String url, Bitmap favicon) { buttongo.setenabled(false); texturl.settext(url); // SSL 通 信 で 問 題 があるとエラーダイアログを 表 示 し // 接 続 を 中 止 する public void onreceivedsslerror(webview webview, SslErrorHandler handler, SslError error) { // ポイント 1 HTTPS 通 信 の 場 合 には SSL 通 信 のエラーを 適 切 にハンドリングする AlertDialog errordialog = createsslerrordialog(error); errordialog.show(); handler.cancel(); texturl.settext(webview.geturl()); buttongo.setenabled(true); // Web ページの load が 終 わったら 表 示 されたページの URL を EditText に 表 示 させる public void onpagefinished(webview webview, String url) { texturl.settext(url); buttongo.setenabled(true); public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); webview = (WebView) findviewbyid(r.id.webview); webview.setwebviewclient(new WebViewUnlimitedClient()); // ポイント 2 JavaScript を 有 効 にしない // デフォルトの 設 定 で JavaScript 無 効 となっているが 明 示 的 に 無 効 化 する webview.getsettings().setjavascriptenabled(false); webview.loadurl(getstring(r.string.texturl)); texturl = (EditText) findviewbyid(r.id.texturl); buttongo = (Button) findviewbyid(r.id.go); public void onclickbuttongo(view v) { webview.loadurl(texturl.gettext().tostring()); private AlertDialog createsslerrordialog(sslerror error) { // ダイアログに 表 示 するエラーメッセージ String errormsg = createerrormessage(error); // ダイアログの OK ボタン 押 下 時 の 挙 動 DialogInterface.OnClickListener onclickok = new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int which) { setresult(result_ok); ; 276 All rights reserved Japan Smartphone Security Association. WebView を 使 う

279 // ダイアログの 作 成 AlertDialog dialog = new AlertDialog.Builder( WebViewUntrustActivity.this).setTitle("SSL 接 続 エラー").setMessage(errorMsg).setPositiveButton("OK", onclickok).create(); return dialog; private String createerrormessage(sslerror error) { SslCertificate cert = error.getcertificate(); SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); StringBuilder result = new StringBuilder().append("サイトのセキュリティ 証 明 書 が 信 頼 できません 接 続 を 終 了 しました \n\n エラーの 原 因 \n"); switch (error.getprimaryerror()) { case SslError.SSL_EXPIRED: result.append(" 証 明 書 の 有 効 期 限 が 切 れています \n\n 終 了 時 刻 =").append(dateformat.format(cert.getvalidnotafterdate())); return result.tostring(); case SslError.SSL_IDMISMATCH: result.append("ホスト 名 が 一 致 しません \n\ncn=").append(cert.getissuedto().getcname()); return result.tostring(); case SslError.SSL_NOTYETVALID: result.append(" 証 明 書 はまだ 有 効 ではありません\n\n 開 始 時 刻 =").append(dateformat.format(cert.getvalidnotbeforedate())); return result.tostring(); case SslError.SSL_UNTRUSTED: result.append(" 証 明 書 を 発 行 した 認 証 局 が 信 頼 できません\n\n 認 証 局 \n").append(cert.getissuedby().getdname()); return result.tostring(); default: result.append(" 原 因 不 明 のエラーが 発 生 しました"); return result.tostring(); All rights reserved Japan Smartphone Security Association. WebView を 使 う 277

280 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド WebView を 使 用 する 際 には 以 下 のルールを 守 ること 1. JavaScript を 有 効 にするのはコンテンツを 自 社 が 管 理 している 場 合 に 限 定 する ( 必 須 ) 2. 自 社 管 理 サービスとの 通 信 には HTTPS を 使 用 する ( 必 須 ) 3. Intent 経 由 など 他 から 受 け 取 った URL は JavaScript が 有 効 な WebView には 表 示 しない ( 必 須 ) 4. SSL 通 信 のエラーを 適 切 にハンドリングする ( 必 須 ) JavaScript を 有 効 にするのはコンテンツを 自 社 が 管 理 している 場 合 に 限 定 する ( 必 須 ) WebView を 用 いてコンテンツやサービスにアクセスするアプリを 開 発 する 際 に セキュリティの 面 で 最 も 注 意 しなけ ればならない 点 は JavaScript を 有 効 にするかどうかである 原 則 的 には 自 社 が 管 理 しているサービスにのみアプリ がアクセスする 場 合 に 限 り JavaScript を 有 効 にしてもよい しかし そうでないサービスにアクセスする 可 能 性 が 少 し でもある 場 合 には JavaScript を 有 効 にしてはならない 自 社 で 管 理 しているサービス 自 社 で 作 成 あるいは 運 用 管 理 に 責 任 を 持 つサービスは 自 社 が 安 全 を 保 証 できる 例 として 自 社 管 理 サーバー 上 の 自 社 開 発 コンテンツにアプリがアクセスする 場 合 を 考 える 各 コンテンツがサーバー 内 部 のコンテンツのみを 参 照 しており かつ 自 社 管 理 サーバーに 対 して 適 切 なセキュリティ 対 策 が 施 されているならば このサービスは 自 社 以 外 が 内 容 を 書 き 換 えていることはないとみなせる この 場 合 自 社 管 理 サービスにアクセスするアプリの JavaScript を 有 効 にしてもよい インターネット 上 の 自 社 管 理 コンテンツを 表 示 する を 参 照 すること また 他 のアプリ による 書 き 換 えが 不 可 能 な 端 末 内 コンテンツ(APK の assets または res ディレクトリ 内 に 配 置 されたコンテンツやアプ リディレクトリ 下 のコンテンツ)にアクセスするアプリの 場 合 も 同 様 に 考 え JavaScript を 有 効 にしてもよい assets または res ディレクトリに 配 置 したコンテンツのみを 表 示 する を 参 照 すること 自 社 で 管 理 していないサービス 自 社 で 管 理 していないコンテンツ サービスは 自 社 が 安 全 を 保 証 できると 考 えてはならない それゆえ アプリの JavaScript を 無 効 にしなければならない 自 社 管 理 以 外 のコンテンツを 表 示 する を 参 照 すること 加 えて SD カードのような 端 末 の 外 部 記 憶 装 置 に 配 置 されたコンテンツは 他 のアプリによる 書 き 換 えが 可 能 なので 自 社 が 管 理 しているとは 言 えない そのようなコンテンツにアクセスするアプリについても JavaScript を 無 効 にしなければな らない 自 社 管 理 サービスとの 通 信 には HTTPS を 使 用 する ( 必 須 ) 自 社 管 理 サービスにアクセスするアプリは 悪 意 ある 第 三 者 によるサービスのなりすましによる 被 害 を 防 ぎ 対 象 サ ービスへ 確 実 に 接 続 する 必 要 がある そのためには サービスとの 通 信 に HTTPS を 使 用 する 詳 細 は SSL 通 信 のエラーを 適 切 にハンドリングする ( 必 須 ) 5.4 HTTPS で 通 信 する を 参 照 するこ と 278 All rights reserved Japan Smartphone Security Association. WebView を 使 う

281 Intent 経 由 など 他 から 受 け 取 った URL は JavaScript が 有 効 な WebView には 表 示 しない ( 必 須 ) 他 のアプリから Intent を 受 信 し その Intent のパラメータで 渡 された URL を WebView に 表 示 する 実 装 が 多 くのア プリで 見 られる ここで WebView の JavaScript が 有 効 である 場 合 悪 意 ある Web ページの URL を WebView で 表 示 してしまい 悪 意 ある JavaScript が WebView 上 で 実 行 されて 何 らかの 被 害 が 生 じる 可 能 性 がある この 実 装 の 問 題 点 は 安 全 を 保 証 できない 不 特 定 の URL を JavaScript が 有 効 な WebView で 表 示 してしまうことである サンプルコード インターネット 上 の 自 社 管 理 コンテンツを 表 示 する では 固 定 URL 文 字 列 定 数 で 自 社 管 理 コンテンツを 指 定 することで WebView で 表 示 するコンテンツを 自 社 管 理 コンテンツに 限 定 し 安 全 を 確 保 している もし Intent 等 で 受 け 取 った URL を JavaScript が 有 効 な WebView で 表 示 したい 場 合 は その URL が 自 社 管 理 コン テンツであることを 保 証 しなければならない あらかじめアプリ 内 に 自 社 管 理 コンテンツ URL のホワイトリストを 正 規 表 現 等 で 保 持 しておき このホワイトリストと 照 合 して 合 致 した URL だけを WebView で 表 示 することで 安 全 を 確 保 する ことができる この 場 合 も ホワイトリスト 登 録 する URL は HTTPS でなければならないことにも 注 意 が 必 要 だ SSL 通 信 のエラーを 適 切 にハンドリングする ( 必 須 ) HTTPS 通 信 で SSL エラーが 発 生 した 場 合 は エラーが 発 生 した 旨 をダイアログ 表 示 するなどの 方 法 でユーザーに 通 知 して 通 信 を 終 了 しなければならない SSL エラーの 発 生 は サーバー 証 明 書 に 不 備 がある 可 能 性 あるいは 中 間 者 攻 撃 を 受 けている 可 能 性 を 示 唆 する しかし WebView には サービスとの 通 信 時 に 発 生 した SSL エラーに 関 する 情 報 をユーザーに 通 知 する 仕 組 みが 備 わっていない そこで SSL エラーが 発 生 した 場 合 にはその 旨 をダイアログなどで 表 示 することで 脅 威 にさらされてい る 可 能 性 があることをユーザーに 通 知 する 必 要 がある エラー 通 知 の 例 は インターネット 上 の 自 社 管 理 コンテンツを 表 示 する のサンプルコードあるいは 自 社 管 理 以 外 のコンテンツを 表 示 する のサンプルコー ドを 参 照 すること また エラーの 通 知 に 加 えて アプリはサービスとの 通 信 を 終 了 しなければならない 特 に 次 のような 実 装 を 行 って はならない 発 生 したエラーを 無 視 してサービスとの 通 信 を 継 続 する HTTP などの 非 暗 号 化 通 信 を 使 ってサービスと 改 めて 通 信 する HTTP 通 信 /HTTPS 通 信 の 詳 細 は 5.4 HTTPS で 通 信 する を 参 照 すること SSL エラーが 発 生 した 際 には 対 象 のサーバーと 接 続 を 行 わないことが WebView のデフォルトの 挙 動 である よって WebView のデフォルトの 挙 動 に SSL エラーの 通 知 機 能 を 実 装 することで 適 切 に 通 信 エラーを 取 り 扱 うことができる All rights reserved Japan Smartphone Security Association. WebView を 使 う 279

282 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド Android 4.2 未 満 の 端 末 における addjavascriptinterface()に 起 因 する 脆 弱 性 について Android 4.2(API Level 17) 未 満 の 端 末 には addjavascriptinterface()に 起 因 する 脆 弱 性 があり JavaScript か ら Java のリフレクションを 行 うことにより 任 意 の Java メソッドが 実 行 できてしまう 問 題 が 存 在 する そのため JavaScript を 有 効 にするのはコンテンツを 自 社 が 管 理 している 場 合 に 限 定 する ( 必 須 ) で 解 説 した 通 り 自 社 で 管 理 していないコンテンツ サービスにアクセスする 可 能 性 がある 場 合 は JavaScript を 無 効 にす る 必 要 がある Android 4.2(API Level 17) 以 降 の 端 末 では Java のソースコード 上 で@JavascriptInterface というアノテーション が 指 定 されたメソッドしか JavaScript から 操 作 できないように API が 仕 様 変 更 され 脆 弱 性 の 対 策 がされた ただし 自 社 で 管 理 していないコンテンツ サービスにアクセスする 可 能 性 がある 場 合 は コンテンツ サービス 提 供 者 が 悪 意 ある JavaScript を 送 信 する 恐 れがあるため JavaScript を 無 効 化 する 対 策 は 引 き 続 き 必 要 である file スキームに 起 因 する 問 題 について WebView をデフォルト 設 定 で 使 用 している 場 合 file スキームを 利 用 してアクセスすると 当 該 アプリがアクセス 可 能 なすべてのファイルにアクセスすることが 可 能 になる この 動 作 を 悪 用 された 場 合 例 えば JavaScript から file スキ ーム 使 ったリクエストすることで アプリの 専 用 フォルダに 保 存 したファイル 等 を 攻 撃 者 に 取 得 されてしまう 可 能 性 があ る 対 策 としては JavaScript を 有 効 にするのはコンテンツを 自 社 が 管 理 している 場 合 に 限 定 する ( 必 須 ) で 解 説 した 通 り 自 社 で 管 理 していないコンテンツ サービスにアクセスする 可 能 性 がある 場 合 は JavaScript を 無 効 にする この 対 策 により 意 図 しない file スキームによるリクエストが 送 信 されないようにする ま た Android 4.1 ( API Level 16 ) 以 降 の 場 合 setallowfileaccessfromfileurls() お よ び setallowuniversalaccessfromfileurls()を 利 用 することで file スキームによるアクセスを 禁 止 することができる file スキームの 無 効 化 webview = (WebView) findviewbyid(r.id.webview); webview.setwebviewclient(new WebViewUnlimitedClient()); WebSettings settings = webview.getsettings(); settings.setallowuniversalaccessfromfileurls(false); settings.setallowfileaccessfromfileurls(false); Web Messaging 利 用 時 の 送 信 先 オリジン 指 定 について Android 6.0(API Level 23)において HTML5 Web Messaging を 実 現 するための API が 追 加 された Web Messaging は 異 なるブラウジング コンテキスト 間 でデータを 送 受 信 するための 仕 組 みであり HTML5 で 定 義 されて 280 All rights reserved Japan Smartphone Security Association. WebView を 使 う

283 いる 14 WebView クラスに 追 加 された postwebmessage()は Web Messaging で 定 義 されている Cross-domain messaging によるデータ 送 信 を 処 理 するメソッドである このメソッドは 第 一 引 数 で 指 定 されたメッセージオブジェクト を WebView に 読 み 込 んでいるブラウジング コンテキストに 対 して 送 信 するのだが その 際 第 二 引 数 として 送 信 先 の オリジンを 指 定 する 必 要 がある 指 定 されたオリジン 15 が 送 信 先 コンテキストのオリジンと 一 致 しない 限 りメッセージは 送 信 されない 送 信 先 オリジンを 制 限 することで 意 図 しない 送 信 先 にメッセージを 渡 してしまうことを 防 いでいるので ある ただし postwebmessage()メソッドではオリジンとしてワイルドカードを 指 定 できることに 注 意 が 必 要 である 16 ワイ ルドカードを 指 定 するとメッセージの 送 信 先 オリジンがチェックされず どのようなオリジンに 対 してもメッセージを 送 信 してしまう もし WebView に 悪 意 のあるコンテンツが 読 み 込 まれている 状 況 でオリジンの 制 限 なしに 重 要 なメッセー ジを 送 信 してしまうと 何 らかの 被 害 につながる 可 能 性 も 生 じる WebView を 用 いて Web messaging を 行 う 際 は postwebmessage()メソッドに 特 定 のオリジンを 明 示 的 に 指 定 するべきである オリジンとは URL のスキーム ホスト 名 ポート 番 号 の 組 み 合 わせのこと 詳 細 な 定 義 は を 参 照 16 Uri.EMPTY および Uri.parse("")がワイルドカードとして 機 能 する( 執 筆 時 ) All rights reserved Japan Smartphone Security Association. WebView を 使 う 281

284 4.10. Notification を 使 用 する Android にはエンドユーザーへのメッセージを 通 知 する Notification 機 能 がある Notification を 使 うと 画 面 上 部 のステータスバーと 呼 ばれる 領 域 に アイコンやメッセージを 表 示 することができる 図 Notifcation の 表 示 例 Notification の 通 知 機 能 は Android 5.0(API Level 21)で 強 化 され アプリやユーザー 設 定 によって 画 面 がロッ クされている 状 態 であっても Notification による 通 知 を 表 示 することが 可 能 になった ただし Notification の 使 い 方 を 誤 ると 端 末 ユーザー 本 人 にのみ 見 せるべきプライベートな 情 報 が 第 三 者 の 目 に 触 れる 恐 れがある したがって プライバシーやセキュリティを 考 慮 して 適 切 に 実 装 を 行 うことが 重 要 である 282 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

285 なお Visibility が 取 り 得 る 値 と Notification の 振 る 舞 いは 以 下 の 通 りである Visibility の 値 Public Private Notification の 振 る 舞 い すべてのロック 画 面 上 で Notification が 表 示 される すべてのロック 画 面 上 で Notifcation が 表 示 されるが パスワード 等 で 保 護 された ロック 画 面 (セキュアロック) 上 では Notification のタイトルやテキスト 等 が 隠 され る(プライベート 情 報 が 隠 された 公 開 可 能 な 文 に 置 き 換 わる) Secret パスワード 等 で 保 護 されたロック 画 面 (セキュアロック) 上 では Notification が 表 示 されなくなる(セキュアロック 以 外 のロック 画 面 では Notification は 表 示 される) サンプルコード Notification に 端 末 ユーザーのプライベートな 情 報 を 含 む 場 合 プライベート 情 報 を 取 り 除 いた 通 知 を 画 面 ロック 時 の 表 示 用 に 作 成 し 加 えておくこと All rights reserved Japan Smartphone Security Association. Notification を 使 用 する 283

286 図 ロック 画 面 上 の Notification プライベート 情 報 を 含 んだ 通 知 を 行 うサンプルコードを 以 下 に 示 す ポイント: 1. プライベート 情 報 を 含 んだ 通 知 を 行 う 場 合 は 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の Notification を 用 意 する 2. 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の Notification にはプライベート 情 報 を 含 めない 3. Visibility を 明 示 的 に Private に 設 定 して Notification を 作 成 する 4. Visibility が Private の 場 合 プライベート 情 報 を 含 めて 通 知 してもよい VisibilityPrivateNotificationActivity.java package org.jssec.notification.visibilityprivate; 284 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

287 import android.app.activity; import android.app.notification; import android.app.notificationmanager; import android.content.context; import android.os.build; import android.os.bundle; import android.view.view; public class VisibilityPrivateNotificationActivity extends Activity { /** * Private な Notification を 表 示 する */ private final int mnotificationid = 0; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); public void onsendnotificationclick(view view) { // ポイント 1 プライベート 情 報 を 含 んだ 通 知 を 行 う 場 合 は 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の Notification を 用 意 する Notification.Builder publicnotificationbuilder = new Notification.Builder(this).setContentTitle("Notifica tion : Public"); if (Build.VERSION.SDK_INT >= 21) publicnotificationbuilder.setvisibility(notification.visibility_public); // ポイント 1 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の Notification にはプライベート 情 報 を 含 めない publicnotificationbuilder.setcontenttext("visibility Public : Omitting sensitive data."); publicnotificationbuilder.setsmallicon(r.drawable.ic_launcher); Notification publicnotification = publicnotificationbuilder.build(); // プライベート 情 報 を 含 む Notification を 作 成 する Notification.Builder privatenotificationbuilder = new Notification.Builder(this).setContentTitle("Notific ation : Private"); // ポイント 3 明 示 的 に Visibility を Private に 設 定 して Notification を 作 成 する if (Build.VERSION.SDK_INT >= 21) privatenotificationbuilder.setvisibility(notification.visibility_private); // ポイント 4 Visibility が Private の 場 合 プライベート 情 報 を 含 めて 通 知 してもよい privatenotificationbuilder.setcontenttext("visibility Private : Including user info."); privatenotificationbuilder.setsmallicon(r.drawable.ic_launcher); // Visibility が Private の Notification を 利 用 する 場 合 Visibility を Public にした 公 開 用 の Notification を 合 わせて 設 定 する if (Build.VERSION.SDK_INT >= 21) privatenotificationbuilder.setpublicversion(publicnotification); Notification privatenotification = privatenotificationbuilder.build(); // 本 サンプルでは 実 装 していないが Notification では setcontentintent(pendingintent intent) を 使 い // Notification をクリックした 際 に Intent が 送 信 されるように 実 装 することが 多 い // このときに 設 定 する Intent は 呼 び 出 すコンポーネントの 種 類 に 合 わせて // 安 全 な 方 法 で 呼 び 出 すことが 必 要 である( 例 えば 明 示 的 Intent を 使 うなど) // 各 コンポーネントの 安 全 な 呼 び 出 し 方 法 は 以 下 の 項 目 を 参 照 のこと // 4.1. Activity を 作 る 利 用 する // 4.2. Broadcast を 受 信 する 送 信 する // 4.4. Service を 作 る 利 用 する All rights reserved Japan Smartphone Security Association. Notification を 使 用 する 285

288 NotificationManager notificationmanager = (NotificationManager) this.getsystemservice(context.notificatio N_SERVICE); notificationmanager.notify(mnotificationid, privatenotification); 286 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

289 ルールブック Notification を 利 用 する 際 には 以 下 のルールを 守 ること 1. Visibility の 設 定 に 依 らず Notification にはセンシティブな 情 報 を 含 めない(プライベート 情 報 は 例 外 )( 必 須 ) 2. Visibility Public の Notification には プライベート 情 報 を 含 めない ( 必 須 ) 3. ( 特 に Visibility Private にする 場 合 )Visibility は 明 示 的 に 設 定 する ( 必 須 ) 4. Visibility が Private の Notification を 利 用 する 場 合 Visibility を Public にした 公 開 用 の Notification を 併 せて 設 定 する ( 推 奨 ) Visibility の 設 定 に 依 らず Notification にはセンシティブな 情 報 を 含 めない(プライベート 情 報 は 例 外 ) ( 必 須 ) Android4.3(API Level 18) 以 降 の 端 末 では 設 定 画 面 からユーザーが Notification の 読 み 取 り 許 可 をアプリに 与 えることができる 許 可 されたアプリは 全 ての Notification の 情 報 を 読 み 取 ることが 可 能 になるため センシティブ な 情 報 を Notification に 含 めてはならない (ただし プライベート 情 報 は Visibility の 設 定 によっては Notification に 含 めて 良 い) Notification に 含 まれた 情 報 は 通 常 は Notification を 送 信 したアプリを 除 き 他 のアプリから 読 み 取 ることはでき ない しかし ユーザーが 明 示 的 に 許 可 を 与 えることで ユーザーが 指 定 したアプリは 全 ての Notification の 情 報 を 読 み 取 ることが 可 能 になる ユーザーが 許 可 を 与 えたアプリのみが Notification の 情 報 を 読 み 取 れることから ユー ザー 自 身 のプライベート 情 報 を Notification に 含 めることは 問 題 ない 一 方 で ユーザーのプライベート 情 報 以 外 の センシティブな 情 報 ( 例 えば アプリ 開 発 者 のみが 知 り 得 る 機 密 情 報 )を Notification に 含 めると ユーザー 自 身 が Notification に 含 まれた 情 報 を 読 みとろうとして Notification への 閲 覧 をアプリに 許 可 する 可 能 性 があるため 利 用 者 のプライベート 情 報 以 外 のセンシティブな 情 報 を 含 めることは 問 題 となる 具 体 的 な 方 法 と 条 件 は ユーザー 許 可 による Notification の 閲 覧 について を 参 照 の 事 Visibility Public の Notification には プライベート 情 報 を 含 めない ( 必 須 ) Visibility が Public に 設 定 された Notification によって 通 知 を 行 う 場 合 ユーザーのプライベート 情 報 を Notification に 含 めてはならない Visibility が Public に 設 定 された Notification は 画 面 ロック 中 にも Notification の 情 報 が 表 示 され 端 末 に 物 理 的 に 接 近 できる 第 三 者 がプライベート 情 報 を 盗 み 見 るリスクにつなが るためである VisibilityPrivateNotificationActivity.java // 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の センシティブな 情 報 を 持 たない Notification を 用 意 する Notification.Builder publicnotificationbuilder = new Notification.Builder(this).setContentTitle("Notification : P ublic"); publicnotificationbuilder.setvisibility(notification.visibility_public); // 公 開 用 ( 画 面 ロック 時 の 表 示 用 )の Notification にはプライベート 情 報 を 含 めない All rights reserved Japan Smartphone Security Association. Notification を 使 用 する 287

290 publicnotificationbuilder.setcontenttext("visibility Public : センシティブな 情 報 は 含 めずに 通 知 "); publicnotificationbuilder.setsmallicon(r.drawable.ic_launcher); プライベート 情 報 の 典 型 例 としては ユーザー 宛 てに 送 信 されたメールやユーザーの 位 置 情 報 など 5.5. プライバ シー 情 報 を 扱 う で 言 及 されている 情 報 が 挙 げられる ( 特 に Visibility Private にする 場 合 )Visibility は 明 示 的 に 設 定 する ( 必 須 ) Visibility Public の Notification には プライベート 情 報 を 含 めない ( 必 須 ) の 通 り Android 5.0(API Level 21) 以 降 の 端 末 では 画 面 ロック 中 にも Notification が 表 示 されるため Visibility の 設 定 が 重 要 で あり デフォルト 値 に 頼 らず 明 示 的 に 設 定 すること 現 状 では Notification の Visibility のデフォルト 値 は Private に 設 定 されており 明 示 的 に Public を 指 定 しない 限 りプライベート 情 報 が 盗 み 見 られるリスクは 発 生 しない しかし Visibility のデフォルト 値 が 将 来 変 更 になる 可 能 性 も あり 含 める 情 報 の 取 り 扱 いを 常 に 意 識 するためにも たとえ Visiblity を Private にする 場 合 であっても Notification の Visibility は 明 示 的 に 設 定 することを 必 須 としている VisibilityPrivateNotificationActivity.java // プライベート 情 報 を 含 む Notification を 作 成 する Notification.Builder priavtenotificationbuilder = new Notification.Builder(this).setContentTitle("Notific ation : Private"); // ポイント 明 示 的 に Visibility を Private に 設 定 して Notification を 作 成 する priavtenotificationbuilder.setvisibility(notification.visibility_private); Visibility が Private の Notification を 利 用 する 場 合 Visibility を Public にした 公 開 用 の Notification を 併 せて 設 定 する ( 推 奨 ) Visibility が Private に 設 定 された Notification を 使 って 通 知 する 場 合 画 面 ロック 中 に 表 示 される 情 報 を 制 御 する ため Visibility を Public にした 公 開 用 の Notification を 併 せて 設 定 することが 望 ましい Visibility が Private に 設 定 された Notification に 公 開 用 の Notification を 設 定 しない 場 合 画 面 ロック 中 にはシ ステムで 用 意 されたデフォルトの 文 言 が 表 示 されるためセキュリティ 上 の 問 題 はない しかし Notification に 含 める 情 報 の 取 り 扱 いを 常 に 意 識 するためにも 公 開 用 の Notification を 明 示 的 に 用 意 し 設 定 することを 推 奨 する VisibilityPrivateNotificationActivity.java // プライベート 情 報 を 含 む Notification を 作 成 する Notification.Builder privatenotificationbuilder = new Notification.Builder(this).setContentTitle("Notific ation : Private"); // ポイント 明 示 的 に Visibility を Private に 設 定 して Notification を 作 成 する if (Build.VERSION.SDK_INT >= 21) privatenotificationbuilder.setvisibility(notification.visibility_private); // ポイント Visibility が Private の 場 合 プライベート 情 報 を 含 めて 通 知 してもよい privatenotificationbuilder.setcontenttext("visibility Private : Including user info."); privatenotificationbuilder.setsmallicon(r.drawable.ic_launcher); // Visibility が Private の Notification を 利 用 する 場 合 Visibility を Public にした 公 開 用 の Notification を 合 288 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

291 わせて 設 定 する if (Build.VERSION.SDK_INT >= 21) privatenotificationbuilder.setpublicversion(publicnotification); All rights reserved Japan Smartphone Security Association. Notification を 使 用 する 289

292 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド ユーザー 許 可 による Notification の 閲 覧 について Visibility の 設 定 に 依 らず Notification にはセンシティブな 情 報 を 含 めない(プライベート 情 報 は 例 外 ) ( 必 須 ) で 述 べたように Android4.3(API Level 18) 以 降 の 端 末 では ユーザーが 許 可 を 与 えた 場 合 指 定 されたアプリは 全 ての Notification の 情 報 を 読 み 取 ることが 可 能 になる ただし ユーザー 許 可 の 対 象 となるために は アプリが NotificationListenerService を 継 承 した Service を 実 装 しておく 必 要 がある 図 Notification の 読 み 取 りを 設 定 する 通 知 へのアクセス 画 面 290 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

293 NotificationListenerService を 使 ったサンプルコードを 以 下 に 示 す AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.notification.notificationlistenerservice"> <application android:allowbackup="false" > <service android:name=".mynotificationlistenerservice" android:permission="android.permission.bind_notification_listener_service"> <intent-filter> <action android:name= "android.service.notification.notificationlistenerservice" /> </intent-filter> </service> </application> </manifest> MyNotificationListenerService.java package org.jssec.notification.notificationlistenerservice; import android.app.notification; import android.service.notification.notificationlistenerservice; import android.service.notification.statusbarnotification; import android.util.log; public class MyNotificationListenerService extends NotificationListenerService { public void onnotificationposted(statusbarnotification sbn) { // Notification is posted. outputnotificationdata(sbn, "Notification Posted : "); public void onnotificationremoved(statusbarnotification sbn) { // Notification is deleted. outputnotificationdata(sbn, "Notification Deleted : "); private void outputnotificationdata(statusbarnotification sbn, String prefix) { Notification notification = sbn.getnotification(); int notificationid = sbn.getid(); String packagename = sbn.getpackagename(); long PostTime = sbn.getposttime(); String message = prefix + "Visibility :" + notification.visibility + " ID : " + notificationid; message += " Package : " + packagename + " PostTime : " + PostTime; Log.d("NotificationListen", message); All rights reserved Japan Smartphone Security Association. Notification を 使 用 する 291

294 上 記 の 通 り NotificationListenerService を 使 い ユーザーの 許 可 を 得 ることで Notification を 読 み 取 ること が 可 能 になるが Notification に 含 まれる 情 報 には 端 末 のプライベート 情 報 が 含 まれることが 多 いため 取 り 扱 いに は 十 分 な 注 意 が 必 要 である 292 All rights reserved Japan Smartphone Security Association. Notification を 使 用 する

295 5. セキュリティ 機 能 の 使 い 方 暗 号 や 電 子 署 名 Permission など Android にはさまざまなセキュリティ 機 能 が 用 意 されている これらのセキュリ ティ 機 能 は 取 り 扱 いを 間 違 えるとセキュリティ 機 能 が 十 分 に 発 揮 されず 抜 け 道 ができてしまう この 章 では 開 発 者 が セキュリティ 機 能 を 活 用 するシーンを 想 定 した 記 事 を 扱 う 5.1. パスワード 入 力 画 面 を 作 る サンプルコード パスワード 入 力 画 面 を 作 る 際 セキュリティ 上 考 慮 すべきポイントについて 述 べる ここではパスワードの 入 力 に 関 す る 内 容 のみとする パスワードの 保 存 方 法 については 今 後 の 版 にて 別 途 記 事 を 設 ける 予 定 である 図 ポイント: 1. 入 力 したパスワードはマスク 表 示 ( で 表 示 )する 2. パスワードを 平 文 表 示 するオプションを 用 意 する 3. パスワード 平 文 表 示 時 の 危 険 性 を 注 意 喚 起 する ポイント: 前 回 入 力 したパスワードを 扱 う 場 合 には 上 記 ポイントに 加 え 下 記 ポイントにも 気 を 付 けること 4. Activity 初 期 表 示 時 に 前 回 入 力 したパスワードがある 場 合 前 回 入 力 パスワードの 桁 数 を 推 測 されないよう 固 定 桁 数 の 文 字 でダミー 表 示 する 5. 前 回 入 力 パスワードをダミー 表 示 しているとき パスワードを 表 示 した 場 合 前 回 入 力 パスワードをクリアして 新 規 にパスワードを 入 力 できる 状 態 とする 6. 前 回 入 力 パスワードをダミー 表 示 しているとき ユーザーがパスワードを 入 力 しようとした 場 合 前 回 入 力 パスワ All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 293

296 ードをクリアし ユーザーの 入 力 を 新 たなパスワードとして 扱 う password_activity.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:padding="10dp" > <!-- パスワード 項 目 のラベル --> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" /> <!-- パスワード 入 力 項 目 --> <!-- ポイント 1 入 力 したパスワードはマスク 表 示 ( で 表 示 )する --> <EditText android:id="@+id/password_edit" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="@string/hint_password" android:inputtype="textpassword" /> <!-- ポイント 2 パスワードを 平 文 表 示 するオプションを 用 意 する --> <CheckBox android:id="@+id/password_display_check" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/display_password" /> <!-- ポイント 3 パスワード 平 文 表 示 時 の 危 険 性 を 注 意 喚 起 する --> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/alert_password" /> <!-- キャンセル OK ボタン --> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_margintop="50dp" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onclick="onclickcancelbutton" android:text="@android:string/cancel" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:onclick="onclickokbutton" android:text="@android:string/ok" /> 294 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

297 </LinearLayout> </LinearLayout> 次 の PasswordActivity.java の 最 後 に 配 置 した 3 つのメソッドは 用 途 に 合 わせて 実 装 内 容 を 調 整 すること private String getpreviouspassword() private void onclickcancelbutton(view view) private void onclickokbutton(view view) PasswordActivity.java package org.jssec.android.password.passwordinputui; import android.app.activity; import android.os.bundle; import android.text.editable; import android.text.inputtype; import android.text.textwatcher; import android.view.view; import android.view.windowmanager; import android.widget.checkbox; import android.widget.compoundbutton; import android.widget.compoundbutton.oncheckedchangelistener; import android.widget.edittext; import android.widget.toast; public class PasswordActivity extends Activity { // 状 態 保 存 用 のキー private static final String KEY_DUMMY_PASSWORD = "KEY_DUMMY_PASSWORD"; // Activity 内 の View private EditText mpasswordedit; private CheckBox mpassworddisplaycheck; // パスワードがダミー 表 示 かを 表 すフラグ private boolean misdummypassword; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.password_activity); // スクリーンキャプチャを 無 効 化 する getwindow().addflags(windowmanager.layoutparams.flag_secure); // View の 取 得 mpasswordedit = (EditText) findviewbyid(r.id.password_edit); mpassworddisplaycheck = (CheckBox) findviewbyid(r.id.password_display_check); // 前 回 入 力 パスワードがあるか if (getpreviouspassword()!= null) { // ポイント 4 Activity 初 期 表 示 時 に 前 回 入 力 したパスワードがある 場 合 // 前 回 入 力 パスワードの 桁 数 を 推 測 されないよう 固 定 桁 数 の 文 字 でダミー 表 示 する // 表 示 はダミーパスワードにする All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 295

298 Android アプリのセキュア 設 計 セキュアコーディングガイド mpasswordedit.settext("**********"); // パスワード 入 力 時 にダミーパスワードをクリアするため テキスト 変 更 リスナーを 設 定 mpasswordedit.addtextchangedlistener(new PasswordEditTextWatcher()); // ダミーパスワードフラグを 設 定 する misdummypassword = true; // パスワードを 表 示 するオプションのチェック 変 更 リスナーを 設 定 mpassworddisplaycheck.setoncheckedchangelistener(new OnPasswordDisplayCheckedChangeListener()); public void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); // 画 面 の 縦 横 変 更 で Activity が 再 生 成 されないよう 指 定 した 場 合 には 不 要 // Activity の 状 態 保 存 outstate.putboolean(key_dummy_password, misdummypassword); public void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); // 画 面 の 縦 横 変 更 で Activity が 再 生 成 されないよう 指 定 した 場 合 には 不 要 // Activity の 状 態 の 復 元 misdummypassword = savedinstancestate.getboolean(key_dummy_password); /** * パスワードを 入 力 した 場 合 の 処 理 */ private class PasswordEditTextWatcher implements TextWatcher { public void beforetextchanged(charsequence s, int start, int count, int after) { // 未 使 用 public void ontextchanged(charsequence s, int start, int before, int count) { // ポイント 6 前 回 入 力 パスワードをダミー 表 示 しているとき ユーザーがパスワードを 入 力 しようと // した 場 合 前 回 入 力 パスワードをクリアし ユーザーの 入 力 を 新 たなパスワードとして 扱 う if (misdummypassword) { // ダミーパスワードフラグを 設 定 する misdummypassword = false; // パスワードを 入 力 した 文 字 だけにする CharSequence work = s.subsequence(start, start + count); mpasswordedit.settext(work); // カーソル 位 置 が 最 初 に 戻 るので 最 後 にする mpasswordedit.setselection(work.length()); public void aftertextchanged(editable s) { // 未 使 用 296 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

299 /** * パスワードの 表 示 オプションチェックを 変 更 した 場 合 の 処 理 */ private class OnPasswordDisplayCheckedChangeListener implements OnCheckedChangeListener { public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { // ポイント 5 前 回 入 力 パスワードをダミー 表 示 しているとき パスワードを 表 示 した 場 合 // 前 回 入 力 パスワードをクリアして 新 規 にパスワードを 入 力 できる 状 態 とする if (misdummypassword && ischecked) { // ダミーパスワードフラグを 設 定 する misdummypassword = false; // パスワードを 空 表 示 にする mpasswordedit.settext(null); // カーソル 位 置 が 最 初 に 戻 るので 今 のカーソル 位 置 を 記 憶 する int pos = mpasswordedit.getselectionstart(); // ポイント 2 パスワードを 平 文 表 示 するオプションを 用 意 する // InputType の 作 成 int type = InputType.TYPE_CLASS_TEXT; if (ischecked) { // チェック ON 時 は 平 文 表 示 type = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; else { // チェック OFF 時 はマスク 表 示 type = InputType.TYPE_TEXT_VARIATION_PASSWORD; // パスワード EditText に InputType を 設 定 mpasswordedit.setinputtype(type); // カーソル 位 置 を 設 定 する mpasswordedit.setselection(pos); // 以 下 のメソッドはアプリに 合 わせて 実 装 すること /** * 前 回 入 力 パスワードを 取 得 する * 前 回 入 力 パスワード */ private String getpreviouspassword() { // 保 存 パスワードを 復 帰 させたい 場 合 にパスワード 文 字 列 を 返 す // パスワードを 保 存 しない 用 途 では null を 返 す return "hirake5ma"; /** * キャンセルボタンの 押 下 処 理 * view */ public void onclickcancelbutton(view view) { All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 297

300 Android アプリのセキュア 設 計 セキュアコーディングガイド // Activity を 閉 じる finish(); /** * OK ボタンの 押 下 処 理 * view */ public void onclickokbutton(view view) { // password を 保 存 するとか 認 証 に 使 うとか 必 要 な 処 理 を 行 う String password = null; if (misdummypassword) { // 最 後 までダミーパスワード 表 示 だった 場 合 は 前 回 入 力 パスワードを 確 定 パスワードとする password = getpreviouspassword(); else { // ダミーパスワード 表 示 じゃない 場 合 はユーザー 入 力 パスワードを 確 定 パスワードとする password = mpasswordedit.gettext().tostring(); // パスワードを Toast 表 示 する Toast.makeText(this, "password is \"" + password + "\"", Toast.LENGTH_SHORT).show(); // Activity を 閉 じる finish(); 298 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

301 ルールブック パスワード 入 力 画 面 を 作 る 際 には 以 下 のルールを 守 ること 1. パスワードを 入 力 するときにはマスク 表 示 ( で 表 示 する) 機 能 を 用 意 する ( 必 須 ) 2. パスワードを 平 文 表 示 するオプションを 用 意 する ( 必 須 ) 3. Activity 起 動 時 はパスワードをマスク 表 示 にする ( 必 須 ) 4. 前 回 入 力 したパスワードを 表 示 する 場 合 ダミーパスワードを 表 示 する ( 必 須 ) パスワードを 入 力 するときにはマスク 表 示 ( で 表 示 する) 機 能 を 用 意 する ( 必 須 ) スマートフォンは 電 車 やバス 等 の 人 混 みで 利 用 されることが 多 く 第 三 者 にパスワードを 盗 み 見 られるリスクが 大 きい アプリの 仕 様 として パスワードをマスク 表 示 する 機 能 が 必 要 である パスワードを 入 力 する EditText をマスク 表 示 する 方 法 には 静 的 にレイアウト XML で 指 定 する 方 法 と 動 的 にプロ グラム 上 で 切 り 替 える 方 法 の 2 種 類 がある 前 者 は android:inputtype 属 性 に"textPassword"を 指 定 すること で 実 現 できる これは android:password 属 性 でも 実 現 できるが API Level 3 以 降 では 後 者 は EditText クラ スの setinputtype()メソッドで EditText の 入 力 タイプに InputType.TYPE_TEXT_VARIATION_PASSWORD を 追 加 することで 実 装 できる 以 下 それぞれのサンプルコードを 示 す レイアウト XML で 指 定 する 方 法 password_activity.xml <!-- パスワード 入 力 項 目 --> <!-- android:password を true に 設 定 する --> <EditText android:id="@+id/password_edit" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="@string/hint_password" android:inputtype="textpassword" /> Activity 内 で 指 定 する 方 法 PasswordActivity.java // パスワード 表 示 タイプを 設 定 // InputType に TYPE_TEXT_VARIATION_PASSWORD を 設 定 する EditText passwordedit = (EditText) findviewbyid(r.id.password_edit); int type = InputType.TYPE_CLASS_TEXT InputType.TYPE_TEXT_VARIATION_PASSWORD; passwordedit.setinputtype(type); All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 299

302 パスワードを 平 文 表 示 するオプションを 用 意 する ( 必 須 ) スマートフォンにおけるパスワード 入 力 はタッチパネルでの 入 力 となるため PC でキーボード 入 力 する 場 合 と 比 較 す ると 誤 入 力 が 生 じやすい その 入 力 の 煩 わしさからユーザーは 単 純 なパスワードを 利 用 してしまう 可 能 性 があり か えって 危 険 である また 複 数 回 のパスワード 入 力 失 敗 によりアカウントをロックするなどのポリシーがある 場 合 誤 入 力 はできるだけ 避 けるようにする 必 要 もある それらの 解 決 策 として パスワードを 平 文 表 示 できるオプションを 用 意 することで 安 全 なパスワードを 利 用 してもらえるようになる ただし パスワードを 平 文 表 示 した 際 に 覗 き 見 される 可 能 性 もあるため そのオプションを 使 う 際 に ユーザーに 背 後 からの 覗 き 見 への 注 意 を 促 す 必 要 がある また 平 文 表 示 するオプションをつけた 場 合 平 文 表 示 の 時 間 を 設 定 する など 平 文 表 示 の 自 動 解 除 を 行 う 仕 組 みも 用 意 する 必 要 がある パスワードの 平 文 表 示 の 制 限 については 今 後 の 版 にて 別 途 記 事 を 設 ける 予 定 である そのため この 版 のサンプルコードにはパスワードの 平 文 表 示 の 制 限 は 含 めて いない 表 示 チェックオン 図 EditText の InputType 指 定 で マスク 表 示 と 平 文 表 示 を 切 り 替 えることができる PasswordActivity.java /** * パスワードの 表 示 オプションチェックを 変 更 した 場 合 の 処 理 */ private class OnPasswordDisplayCheckedChangeListener implements OnCheckedChangeListener { public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { // ポイント 5 ダミー 表 示 時 は 空 表 示 にする if (misdummypassword && ischecked) { // ダミーパスワードフラグを 設 定 する 300 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

303 misdummypassword = false; // パスワードを 空 表 示 にする mpasswordedit.settext(null); // カーソル 位 置 が 最 初 に 戻 るので 今 のカーソル 位 置 を 記 憶 する int pos = mpasswordedit.getselectionstart(); // ポイント 2 チェックに 応 じてパスワードを 平 文 表 示 する // InputType の 作 成 int type = InputType.TYPE_CLASS_TEXT; if (ischecked) { // チェック ON 時 は 平 文 表 示 type = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; else { // チェック OFF 時 はマスク 表 示 type = InputType.TYPE_TEXT_VARIATION_PASSWORD; // パスワード EditText に InputType を 設 定 mpasswordedit.setinputtype(type); // カーソル 位 置 を 設 定 する mpasswordedit.setselection(pos); Activity 起 動 時 はパスワードをマスク 表 示 にする ( 必 須 ) 意 図 せずパスワード 表 示 してしまい 第 三 者 に 見 られることを 防 ぐため Activity 起 動 時 にパスワードを 表 示 するオプ ションのデフォルト 値 はオフにするべきである デフォルト 値 は 安 全 側 に 定 めるのが 基 本 である 前 回 入 力 したパスワードを 表 示 する 場 合 ダミーパスワードを 表 示 する ( 必 須 ) 前 回 入 力 したパスワードを 指 定 する 場 合 第 三 者 にパスワードのヒントを 与 えないように 固 定 文 字 数 のマスク 文 字 ( など)でダミー 表 示 するべきである また ダミー 表 示 時 に パスワードを 表 示 する とした 場 合 は パスワードをクリ アしてから 平 文 表 示 モードにする これにより スマートフォンが 盗 難 される 等 によって 第 三 者 の 手 に 渡 ったとしても 前 回 入 力 したパスワードが 盗 み 見 られる 危 険 性 を 低 く 抑 えることができる なお ダミー 表 示 時 にユーザーがパスワード を 入 力 しようとした 場 合 には ダミー 表 示 を 解 除 して 通 常 の 入 力 状 態 に 戻 す 必 要 がある 前 回 入 力 したパスワードを 表 示 する 場 合 ダミーパスワードを 表 示 する PasswordActivity.java public void oncreate(bundle savedinstancestate) { ~ 省 略 ~ // 前 回 入 力 パスワードがあるか if (getpreviouspassword()!= null) { // ポイント 4 前 回 入 力 パスワードがある 場 合 はダミーパスワードを 表 示 する All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 301

304 // 表 示 はダミーパスワードにする mpasswordedit.settext("**********"); // パスワード 入 力 時 にダミーパスワードをクリアするため テキスト 変 更 リスナーを 設 定 mpasswordedit.addtextchangedlistener(new PasswordEditTextWatcher()); // ダミーパスワードフラグを 設 定 する misdummypassword = true; ~ 省 略 ~ /** * 前 回 入 力 パスワードを 取 得 する * 前 回 入 力 パスワード */ private String getpreviouspassword() { // 保 存 パスワードを 復 帰 させたい 場 合 にパスワード 文 字 列 を 返 す // パスワードを 保 存 しない 用 途 では null を 返 す return "hirake5ma"; ダミー 表 示 時 は パスワードを 表 示 するオプションをオンにすると 表 示 内 容 をクリアする PasswordActivity.java /** * パスワードの 表 示 オプションチェックを 変 更 した 場 合 の 処 理 */ private class OnPasswordDisplayCheckedChangeListener implements OnCheckedChangeListener { public void oncheckedchanged(compoundbutton buttonview, boolean ischecked) { // ポイント 5 ダミー 表 示 時 は 空 表 示 にする if (misdummypassword && ischecked) { // ダミーパスワードフラグを 設 定 する misdummypassword = false; // パスワードを 空 表 示 にする mpasswordedit.settext(null); ~ 省 略 ~ ダミー 表 示 時 にユーザーがパスワードを 入 力 した 場 合 には ダミー 表 示 を 解 除 する PasswordActivity.java // 状 態 保 存 用 のキー private static final String KEY_DUMMY_PASSWORD = "KEY_DUMMY_PASSWORD"; ~ 省 略 ~ 302 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

305 // パスワードがダミー 表 示 かを 表 すフラグ private boolean misdummypassword; public void oncreate(bundle savedinstancestate) { ~ 省 略 ~ // 前 回 入 力 パスワードがあるか if (getpreviouspassword()!= null) { // ポイント 4 前 回 入 力 パスワードがある 場 合 はダミーパスワードを 表 示 する // 表 示 はダミーパスワードにする mpasswordedit.settext("**********"); // パスワード 入 力 時 にダミーパスワードをクリアするため テキスト 変 更 リスナーを 設 定 mpasswordedit.addtextchangedlistener(new PasswordEditTextWatcher()); // ダミーパスワードフラグを 設 定 する misdummypassword = true; ~ 省 略 ~ public void onsaveinstancestate(bundle outstate) { super.onsaveinstancestate(outstate); // 画 面 の 縦 横 変 更 で Activity が 再 生 成 されないよう 指 定 した 場 合 には 不 要 // Activity の 状 態 保 存 outstate.putboolean(key_dummy_password, misdummypassword); public void onrestoreinstancestate(bundle savedinstancestate) { super.onrestoreinstancestate(savedinstancestate); // 画 面 の 縦 横 変 更 で Activity が 再 生 成 されないよう 指 定 した 場 合 には 不 要 // Activity の 状 態 の 復 元 misdummypassword = savedinstancestate.getboolean(key_dummy_password); /** * パスワードを 入 力 した 場 合 の 処 理 */ private class PasswordEditTextWatcher implements TextWatcher { public void beforetextchanged(charsequence s, int start, int count, int after) { // 未 使 用 public void ontextchanged(charsequence s, int start, int before, int count) { // ポイント 6 ダミー 表 示 時 にパスワードを 再 入 力 した 場 合 は 入 力 内 容 に 応 じた 表 示 にする if (misdummypassword) { // ダミーパスワードフラグを 設 定 する misdummypassword = false; // パスワードを 入 力 した 文 字 だけにする CharSequence work = s.subsequence(start, start + count); All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 303

306 Android アプリのセキュア 設 計 セキュアコーディングガイド mpasswordedit.settext(work); // カーソル 位 置 が 最 初 に 戻 るので 最 後 にする mpasswordedit.setselection(work.length()); public void aftertextchanged(editable s) { // 未 使 用 304 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

307 アドバンスト ログイン 処 理 について パスワード 入 力 が 求 められる 場 面 の 代 表 例 はログイン 処 理 である ログイン 処 理 で 気 を 付 けるポイントをいくつか 紹 介 する ログイン 失 敗 時 のエラーメッセージ ログイン 処 理 では ID(アカウント)とパスワードの 2 つの 情 報 を 入 力 する ログイン 失 敗 時 には ID が 存 在 しない 場 合 と ID は 存 在 するがパスワードが 間 違 っている 場 合 の 2 つがある ログイン 失 敗 のメッセージでこの 2 つの 場 合 を 区 別 し て 表 示 すると 攻 撃 者 は 指 定 した ID が 存 在 するか 否 か を 推 測 できてしまう このような 推 測 を 許 さないためにも ロ グイン 失 敗 時 のメッセージでは 上 記 2 つの 場 合 を 区 別 せずに 下 記 のように 表 示 すべきである メッセージ 例 : ログイン ID または パスワード が 間 違 っています 自 動 ログイン 機 能 一 度 ログイン 処 理 が 成 功 すると 次 回 以 降 はログイン ID とパスワードの 入 力 を 省 略 して 自 動 的 にログインを 行 う 機 能 がある 自 動 ログイン 機 能 は 煩 わしい 入 力 が 省 略 できるので 利 便 性 が 高 まるが その 反 面 スマートフォンが 盗 難 さ れた 場 合 に 第 三 者 に 悪 用 されるリスクが 伴 う 第 三 者 に 悪 用 された 場 合 の 被 害 が 受 け 入 れられる 用 途 か 十 分 なセキュリティ 対 策 が 可 能 な 場 合 にのみ 自 動 ログ イン 機 能 は 利 用 することができる 例 えば オンラインバンキングアプリの 場 合 第 三 者 に 端 末 を 操 作 されると 金 銭 的 な 被 害 が 出 るので 自 動 ログイン 機 能 に 合 わせてセキュリティ 対 策 が 必 須 となる 対 策 としては 決 済 処 理 などの 金 銭 的 な 処 理 が 発 生 する 直 前 にはパスワードの 再 入 力 を 求 める 自 動 ログイン 設 定 時 にユーザーに 対 して 十 分 に 注 意 を 喚 起 し 確 実 な 端 末 のロックを 促 す などいくつか 考 えられる 自 動 ログインの 利 用 にあたっては これらの 対 策 を 前 提 に 利 便 性 とリスクを 勘 案 して 慎 重 な 検 討 を 行 うべきである パスワード 変 更 について 一 度 設 定 したパスワードを 別 のパスワードに 変 更 する 場 合 以 下 の 入 力 項 目 を 画 面 上 に 用 意 すべきである 現 在 のパスワード 新 しいパスワード 新 しいパスワード( 入 力 確 認 用 ) 自 動 ログイン 機 能 がついている 場 合 第 三 者 がアプリを 利 用 できる 可 能 性 がある その 場 合 勝 手 にパスワードを 変 更 されないよう 現 在 のパスワードの 入 力 を 求 める 必 要 がある また 新 しいパスワードが 入 力 ミスで 使 用 不 能 に 陥 る 危 険 を 減 らすため 新 しいパスワードは 2 回 入 力 を 求 める 必 要 がある All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 305

308 システムの パスワードを 表 示 設 定 メニューについて Android の 設 定 メニューの 中 に パスワードを 表 示 という 設 定 がある 設 定 >セキュリティ > パスワードを 表 示 Android 5.0 より 前 のバージョンまで この 手 順 で 設 定 できる ただし Android 5.0 以 降 では パスワードを 表 示 はチェックボックスからトグルボタンに 変 更 されている 図 パスワードを 表 示 設 定 をオンにすると 最 後 に 入 力 した1 文 字 が 平 文 表 示 となる 一 定 時 間 (2 秒 程 度 ) 経 過 後 また は 次 の 文 字 が 入 力 されると 平 文 表 示 されていた 文 字 はマスク 表 示 される オフにすると 入 力 直 後 からマスク 表 示 と なる これはシステム 全 体 に 影 響 する 設 定 であり EditText のパスワード 表 示 機 能 を 使 用 しているすべてのアプリに 適 用 される 306 All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る

309 一 定 時 間 経 過 または 次 の 文 字 を 入 力 図 スクリーンキャプチャの 無 効 化 パスワード 入 力 画 面 ではパスワードなどの 個 人 情 報 が 画 面 上 に 表 示 される 可 能 性 がある そのような 画 面 では 第 三 者 によってスクリーンキャプチャから 個 人 情 報 が 流 出 してしまう 恐 れがある よってパスワード 入 力 画 面 などの 個 人 情 報 が 表 示 されてしまう 恐 れのある 画 面 ではスクリーンキャプチャを 無 効 にしておく 必 要 がある スクリーンキャプチャの 無 効 化 は WindowManager に addflag で FLAG_SECURE を 設 定 することで 実 現 できる All rights reserved Japan Smartphone Security Association. パスワード 入 力 画 面 を 作 る 307

310 5.2. Permission と Protection Level Permission の Protection Level には normal, dangerous, signature, signatureorsystem の 4 種 類 がある その 他 に development system appop も 存 在 するが 一 般 的 なアプリでは 使 用 しないので 本 章 での 説 明 は 省 略 する Permission はどの Protection Level であるかによってそれぞれ Normal Permission, Dangerous Permission, Signature Permission, SignatureOrSystem Permission と 呼 ばれる 以 下 このような 名 称 を 使 う サンプルコード Android OS 既 定 の Permission を 利 用 する 方 法 Android OS は 電 話 帳 や GPS などのユーザー 資 産 をマルウェアから 保 護 するための Permission というセキュリティ の 仕 組 みがある Android OS が 保 護 対 象 としている こうした 情 報 や 機 能 にアクセスするアプリは 明 示 的 にそれら にアクセスするための 権 限 (Permission)を 利 用 宣 言 しなければならない ユーザー 確 認 が 必 要 な Permission では その Permission を 利 用 宣 言 したアプリがインストールされるときに 次 のようなユーザー 確 認 画 面 が 表 示 される 17 図 この 確 認 画 面 により ユーザーはそのアプリがどのような 機 能 や 情 報 にアクセスしようとしているのかを 知 ることがで きる もし アプリの 動 作 に 明 らかに 不 必 要 な 機 能 や 情 報 にアクセスしようとしている 場 合 は そのアプリは 悪 意 があ るアプリの 可 能 性 が 高 い ゆえに 自 分 のアプリがマルウェアであると 疑 われないためにも 利 用 宣 言 する 17 Android 6.0(API Level 23) 以 降 では ユーザー 確 認 と 権 限 の 付 与 はインストール 時 に 行 われず アプリの 実 行 中 に 権 限 の 利 用 を 要 求 する 仕 様 に 変 更 された 詳 細 は Android 6.0 以 降 で Dangerous Permission を 利 用 す る 方 法 および Android 6.0 以 降 の Permission モデルの 仕 様 変 更 について を 参 照 すること 308 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

311 Permission は 最 小 限 にしなければならない ポイント: 1. 利 用 する Permission を AndroidManifest.xml に uses-permission で 利 用 宣 言 する 2. 不 必 要 な Permission は 利 用 宣 言 しない AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.permission.usespermission"> <!-- ポイント 1 アプリで 利 用 する Permission を 利 用 宣 言 する --> <!-- インターネットにアクセスする Permission --> <uses-permission android:name="android.permission.internet"/> <!-- ポイント 2 不 必 要 な Permission は 利 用 宣 言 しない --> <!-- アプリの 動 作 に 不 必 要 な Permission を 利 用 宣 言 していると ユーザーに 不 信 感 を 与 えてしまう --> <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".mainactivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> All rights reserved Japan Smartphone Security Association. Permission と Protection Level 309

312 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 Android OS が 定 義 する 既 定 の Permission の 他 に アプリが 独 自 に Permission を 定 義 することができる 独 自 定 義 の Permission( 正 確 には 独 自 定 義 の Signature Permission)を 使 えば 自 社 アプリだけが 連 携 できる 仕 組 みを 作 ることができる 複 数 の 自 社 製 アプリをインストールした 場 合 に それぞれのアプリの 単 機 能 に 加 え アプリ 間 連 携 による 複 合 機 能 を 提 供 することで 複 数 の 自 社 製 アプリをシリーズ 販 売 して 収 益 を 上 げる といった 用 途 がある サンプルプログラム 独 自 定 義 Signature Permission(UserApp) はサンプルプログラム 独 自 定 義 Signature Permission(ProtectedApp) に startactivity()する 両 アプリは 同 じ 開 発 者 鍵 で 署 名 されている 必 要 がある もし 署 名 した 開 発 者 鍵 が 異 なる 場 合 は UserApp は Intent を 送 信 せず ProtectedApp は 受 信 した Intent を 処 理 しな い またアドバンストセクションで 説 明 しているインストール 順 序 による Signature Permission 回 避 の 問 題 にも 対 処 している Componentを 利 用 するアプリ Componentを 提 供 するアプリ 図 ポイント:Component を 提 供 するアプリ 1. 独 自 Permission を protectionlevel="signature"で 定 義 する 2. Component には permission 属 性 で 独 自 Permission 名 を 指 定 する 3. Component が Activity の 場 合 には intent-filter を 定 義 しない 4. ソースコード 上 で 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 5. Component を 利 用 するアプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.permission.protectedapp"> <!-- ポイント 1 独 自 Permission を protectionlevel="signature"で 定 義 する --> <permission android:name="org.jssec.android.permission.protectedapp.my_permission" android:protectionlevel="signature" /> <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > 310 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

313 <!-- ポイント 2 Component には permission 属 性 で 独 自 Permission 名 を 指 定 する --> <activity android:name=".protectedactivity" android:exported="true" android:label="@string/app_name" android:permission="org.jssec.android.permission.protectedapp.my_permission" > <!-- ポイント 3 Component が Activity の 場 合 には intent-filter を 定 義 しない --> </activity> </application> </manifest> ProtectedActivity.java package org.jssec.android.permission.protectedapp; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.os.bundle; import android.widget.textview; public class ProtectedActivity extends Activity { // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; private TextView mmessageview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); mmessageview = (TextView) findviewbyid(r.id.messageview); 認 する // ポイント 4 ソースコード 上 で 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { mmessageview.settext(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない "); return; All rights reserved Japan Smartphone Security Association. Permission と Protection Level 311

314 // ポイント 4 証 明 書 が 一 致 する 場 合 にのみ 処 理 を 続 行 する mmessageview.settext(" 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 できた "); SigPerm.java package org.jssec.android.shared; import android.content.context; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.permissioninfo; public class SigPerm { public static boolean test(context ctx, String sigpermname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, sigpermname)); public static String hash(context ctx, String sigpermname) { if (sigpermname == null) return null; try { // sigpermname を 定 義 したアプリのパッケージ 名 を 取 得 する PackageManager pm = ctx.getpackagemanager(); PermissionInfo pi; pi = pm.getpermissioninfo(sigpermname, PackageManager.GET_META_DATA); String pkgname = pi.packagename; // 非 Signature Permission の 場 合 は 失 敗 扱 い if (pi.protectionlevel!= PermissionInfo.PROTECTION_SIGNATURE) return null; // sigpermname を 定 義 したアプリの 証 明 書 のハッシュ 値 を 返 す return PkgCert.hash(ctx, pkgname); catch (NameNotFoundException e) { return null; PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { 312 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

315 public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); All rights reserved Japan Smartphone Security Association. Permission と Protection Level 313

316 ポイント 5 Android Studio からメニュー:Build -> Generated Signed APK と 選 択 し Component を 提 供 するアプリと 同 じ 開 発 者 鍵 で 署 名 する 図 ポイント:Component を 利 用 するアプリ 6. 独 自 定 義 Signature Permission は 定 義 しない 7. uses-permission により 独 自 Permission を 利 用 宣 言 する 8. ソースコード 上 で 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する 9. 利 用 先 アプリが 自 社 アプリであることを 確 認 する 10. 利 用 先 Component が Activity の 場 合 明 示 的 Intent を 使 う 11. Component を 提 供 するアプリと 同 じ 開 発 者 鍵 で APK を 署 名 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.permission.userapp"> <!-- ポイント 6 独 自 定 義 Signature Permission は 定 義 しない --> <!-- ポイント 7 uses-permission により 独 自 Permission を 利 用 宣 言 する --> <uses-permission android:name="org.jssec.android.permission.protectedapp.my_permission" /> <application android:allowbackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".useractivity" android:label="@string/app_name" android:exported="true" > <intent-filter> 314 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

317 <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> UserActivity.java package org.jssec.android.permission.userapp; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.sigperm; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.toast; public class UserActivity extends Activity { // 利 用 先 の Activity 情 報 private static final String TARGET_PACKAGE = "org.jssec.android.permission.protectedapp"; private static final String TARGET_ACTIVITY = "org.jssec.android.permission.protectedapp.protectedactivity"; // 自 社 の Signature Permission private static final String MY_PERMISSION = "org.jssec.android.permission.protectedapp.my_permission"; // 自 社 の 証 明 書 のハッシュ 値 private static String smycerthash = null; private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); public void onsendbuttonclicked(view view) { // ポイント 8 ソースコード 上 で 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていることを 確 認 する if (!SigPerm.test(this, MY_PERMISSION, mycerthash(this))) { Toast.makeText(this, " 独 自 定 義 Signature Permission が 自 社 アプリにより 定 義 されていない ", Toast.LENGT H_LONG).show(); All rights reserved Japan Smartphone Security Association. Permission と Protection Level 315

318 Android アプリのセキュア 設 計 セキュアコーディングガイド return; // ポイント 9 利 用 先 アプリが 自 社 アプリであることを 確 認 する if (!PkgCert.test(this, TARGET_PACKAGE, mycerthash(this))) { Toast.makeText(this, " 利 用 先 アプリは 自 社 アプリではない ", Toast.LENGTH_LONG).show(); return; // ポイント 10 利 用 先 Component が Activity の 場 合 明 示 的 Intent を 使 う try { Intent intent = new Intent(); intent.setclassname(target_package, TARGET_ACTIVITY); startactivity(intent); catch(exception e) { Toast.makeText(this, String.format(" 例 外 発 生 :%s", e.getmessage()), Toast.LENGTH_LONG).show(); PkgCertWhitelists.java package org.jssec.android.shared; import java.util.hashmap; import java.util.map; import android.content.context; public class PkgCertWhitelists { private Map<String, String> mwhitelists = new HashMap<String, String>(); public boolean add(string pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceall(" ", ""); if (sha256.length()!= 64) return false; // SHA-256 は 32 バイト sha256 = sha256.touppercase(); if (sha256.replaceall("[0-9a-f]+", "").length()!= 0) return false; // 0-9A-F 以 外 の 文 字 がある mwhitelists.put(pkgname, sha256); return true; public boolean test(context ctx, String pkgname) { // pkgname に 対 応 する 正 解 のハッシュ 値 を 取 得 する String correcthash = mwhitelists.get(pkgname); // pkgname の 実 際 のハッシュ 値 と 正 解 のハッシュ 値 を 比 較 する return PkgCert.test(ctx, pkgname, correcthash); 316 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

319 PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); All rights reserved Japan Smartphone Security Association. Permission と Protection Level 317

320 ポイント 11 Android Studio からメニュー:Build -> Generated Signed APK と 選 択 し Component を 提 供 するアプリと 同 じ 開 発 者 鍵 で 署 名 する 図 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

321 アプリの 証 明 書 のハッシュ 値 を 確 認 する 方 法 このガイド 文 書 の 各 所 で 出 てくるアプリの 証 明 書 のハッシュ 値 を 確 認 する 方 法 を 紹 介 する 厳 密 には APK を 署 名 す るときに 使 った 開 発 者 鍵 の 公 開 鍵 証 明 書 の SHA256 ハッシュ 値 を 確 認 する 方 法 である Keytool により 確 認 する 方 法 JDK に 付 属 する keytool というプログラムを 利 用 すると 開 発 者 鍵 の 公 開 鍵 証 明 書 のハッシュ 値 ( 証 明 書 のフィンガー プリントとも 言 う)を 求 めることができる ハッシュ 値 にはハッシュアルゴリズムの 違 いにより MD5 や SHA1 SHA256 など 様 々なものがあるが このガイド 文 書 では 暗 号 ビット 長 の 安 全 性 を 考 慮 して SHA256 の 利 用 を 推 奨 している 残 念 なことに Android SDK で 利 用 されている JDK6 に 付 属 する keytool は SHA256 でのハッシュ 値 出 力 に 対 応 して おらず JDK7 に 付 属 する keytool を 使 う 必 要 がある Android のデバッグ 証 明 書 の 内 容 を keytool で 出 力 する 例 > keytool -list -v -keystore <キーストアファイル> -storepass <パスワード> キーストアのタイプ: JKS キーストア プロバイダ: SUN キーストアには 1 エントリが 含 まれます 別 名 : androiddebugkey 作 成 日 : 2012/01/11 エントリ タイプ: PrivateKeyEntry 証 明 書 チェーンの 長 さ: 1 証 明 書 [1]: 所 有 者 : CN=Android Debug, O=Android, C=US 発 行 者 : CN=Android Debug, O=Android, C=US シリアル 番 号 : 4f0cef98 有 効 期 間 の 開 始 日 : Wed Jan 11 11:10:32 JST 2012 終 了 日 : Fri Jan 03 11:10:32 JST 2042 証 明 書 のフィンガプリント: MD5: 9E:89:53:18:06:B2:E3:AC:B4:24:CD:6A:56:BF:1E:A1 SHA1: A8:1E:5D:E5:68:24:FD:F6:F1:ED:2F:C3:6E:0F:09:A3:07:F8:5C:0C SHA256: FB:75:E9:B9:2E:9E:6B:4D:AB:3F:94:B2:EC:A1:F0:33:09:74:D8:7A:CF:42:58:22:A2:56:85:1B:0F:85:C6:35 署 名 アルゴリズム 名 : SHA1withRSA バージョン: 3 ******************************************* ******************************************* All rights reserved Japan Smartphone Security Association. Permission と Protection Level 319

322 JSSEC 証 明 書 ハッシュ 値 チェッカーにより 確 認 する 方 法 JDK7 をインストールしなくても JSSEC 証 明 書 ハッシュ 値 チェッカーを 使 えば 簡 単 に 証 明 書 ハッシュ 値 を 確 認 できる 図 これは 端 末 にインストールされているアプリの 証 明 書 ハッシュ 値 を 一 覧 表 示 する Android アプリである 上 図 中 sha-256 の 右 に 表 示 されている 16 進 数 文 字 列 64 文 字 が 証 明 書 ハッシュ 値 である このガイド 文 書 と 一 緒 に 配 布 しているサンプルコードの JSSEC CertHash Checker フォルダがそのソースコード 一 式 である ビルドして 活 用 して いただきたい Android 6.0 以 降 で Dangerous Permission を 利 用 する 方 法 アプリに 対 する Permission 付 与 のタイミングについて Android 6.0(API Level 23)でアプリの 実 装 にかかわる 仕 様 変 更 が 行 われた Android 5.1(API Level 22) 以 前 の Permission モデル( Android 6.0 以 降 の Permission モデルの 仕 様 変 更 について 参 照 )では アプリが 利 用 宣 言 している Permission は 全 てアプリのインストール 時 に 付 与 される し かし Android 6.0 以 降 では Dangerous Permission についてはアプリが 適 切 なタイミングで Permission を 要 求 するよう アプリ 開 発 者 が 明 示 的 に 実 装 しなければならない アプリが Permission を 要 求 すると Android OS はユ ーザーに 対 して 下 記 のような 確 認 画 面 を 表 示 し その Permission の 利 用 を 許 可 するかどうかの 判 断 を 求 めることに なる ユーザーが Permission の 利 用 を 許 可 すれば アプリはその Permission を 必 要 とする 処 理 を 実 行 することが できる 320 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

323 図 Permission を 付 与 する 単 位 にも 変 更 が 加 えられている 従 来 はすべての Permission が 一 括 して 付 与 されていた が Android 6.0(API Level 23) 以 降 Permission(Permission Group) 毎 に 個 別 に 付 与 される これに 伴 いユー ザー 確 認 画 面 も 個 別 に 表 示 され ユーザーは Permission 利 用 の 可 不 可 について 従 来 よりも 柔 軟 に 判 断 できるよう になった アプリ 開 発 者 は Permission の 付 与 が 拒 否 された 場 合 も 考 慮 して アプリの 仕 様 や 設 計 を 見 直 す 必 要 が ある Android 6.0 以 降 の Permission モデルについての 詳 細 は Android 6.0 以 降 の Permission モデルの 仕 様 変 更 について を 参 照 すること ポイント 1. アプリで 利 用 する Permission を 利 用 宣 言 する 2. 不 必 要 な Permission は 利 用 宣 言 しない 3. Permission がアプリに 付 与 されているか 確 認 する 4. Permission を 要 求 する(ユーザーに 許 可 を 求 めるダイアログを 表 示 する) 5. Permission の 利 用 が 許 可 されていない 場 合 の 処 理 を 実 装 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.permission.permissionrequestingpermissionatruntime" > <!-- ポイント 1 アプリで 利 用 する Permission を 利 用 宣 言 する --> <!-- 連 絡 先 情 報 を 読 み 取 る Permission (Protection Level: dangerous) --> <uses-permission android:name="android.permission.read_contacts" /> All rights reserved Japan Smartphone Security Association. Permission と Protection Level 321

324 <!-- ポイント 2 不 必 要 な Permission は 利 用 宣 言 しない --> <application android:allowbackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsrtl="true" android:theme="@style/apptheme" > <activity android:name=".mainactivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <activity android:name=".contactlistactivity" android:exported="false"> </activity> </application> </manifest> MainActivity.java package org.jssec.android.permission.permissionrequestingpermissionatruntime; import android.manifest; import android.content.intent; import android.content.pm.packagemanager; import android.os.bundle; import android.support.v4.app.activitycompat; import android.support.v4.content.contextcompat; import android.support.v7.app.appcompatactivity; import android.view.view; import android.widget.button; import android.widget.toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final int REQUEST_CODE_READ_CONTACTS = 0; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); Button button = (Button)findViewById(R.id.button); button.setonclicklistener(this); public void onclick(view v) { readcontacts(); private void readcontacts() { // ポイント 3 Permission がアプリに 付 与 されているか 確 認 する if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.READ_CONTACTS)!= Pack 322 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

325 agemanager.permission_granted) { // Permission が 付 与 されていない // ポイント 4 Permission を 要 求 する(ユーザーに 許 可 を 求 めるダイアログを 表 示 する) ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS, REQUEST_CODE _READ_CONTACTS); else { // Permission がすでに 付 与 されている showcontactlist(); // ユーザー 選 択 の 結 果 を 受 けるコールバックメソッド public void onrequestpermissionsresult(int requestcode, String[] permissions, int[] grantresults) { switch (requestcode) { case REQUEST_CODE_READ_CONTACTS: if (grantresults.length > 0 && grantresults[0] == PackageManager.PERMISSION_GRANTED) { // Permission の 利 用 が 許 可 されているので 連 絡 先 情 報 を 利 用 する 処 理 を 実 行 できる showcontactlist(); else { // Permission の 利 用 が 許 可 されていないため 連 絡 先 情 報 を 利 用 する 処 理 は 実 行 できない // ポイント 5 Permission の 利 用 が 許 可 されていない 場 合 の 処 理 を 実 装 する Toast.makeText(this, String.format(" 連 絡 先 の 利 用 が 許 可 されていません"), Toast.LENGTH_LONG).sh ow(); return; // 連 絡 先 一 覧 を 表 示 private void showcontactlist() { // ContactListActivity を 起 動 Intent intent = new Intent(); intent.setclass(getapplicationcontext(), ContactListActivity.class); startactivity(intent); All rights reserved Japan Smartphone Security Association. Permission と Protection Level 323

326 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド 独 自 Permission 利 用 時 には 以 下 のルールを 守 ること 1. Android OS 規 定 の Dangerous Permission はユーザーの 資 産 を 保 護 するためにだけ 利 用 する ( 必 須 ) 2. 独 自 定 義 の Dangerous Permission は 利 用 してはならない ( 必 須 ) 3. 独 自 定 義 Signature Permission は Component の 提 供 側 アプリでのみ 定 義 する ( 必 須 ) 4. 独 自 定 義 Signature Permission は 自 社 アプリにより 定 義 されていることを 確 認 する ( 必 須 ) 5. 独 自 定 義 の Normal Permission は 利 用 してはならない ( 推 奨 ) 6. 独 自 定 義 の Permission 名 はアプリのパッケージ 名 を 拡 張 した 文 字 列 にする ( 推 奨 ) Android OS 規 定 の Dangerous Permission はユーザーの 資 産 を 保 護 するためにだけ 利 用 する( 必 須 ) 独 自 定 義 の Dangerous Permission の 利 用 は 非 推 奨 ( 独 自 定 義 の Dangerous Permission は 利 用 し てはならない ( 必 須 ) 参 照 )のため ここでは Android OS 規 定 の Dangerous Permission を 前 提 に 話 をする Dangerous Permission は 他 の 3 つの Permission と 異 なり アプリにその 権 限 を 付 与 するかどうかをユーザーに 判 断 を 求 める 機 能 がある Dangerous Permission を 利 用 宣 言 しているアプリを 端 末 にインストールするとき 次 の ような 画 面 が 表 示 される これにより そのアプリがどのような 権 限 (Dangerous Permission および Normal Permission)を 利 用 しようとしているのかをユーザーが 知 ることができる ユーザーが インストール をタップすること で そのアプリは 利 用 宣 言 した 権 限 が 付 与 され インストールされるようになっている 図 アプリの 中 には ユーザーの 資 産 とアプリ 開 発 者 が 保 護 したい 資 産 がある それらのうち Dangerous Permission で 保 護 できるのはユーザーの 資 産 だけであることに 注 意 が 必 要 である なぜなら 権 限 の 付 与 がユーザーの 判 断 に 委 ねられているためである 一 方 アプリ 開 発 者 が 保 護 したい 資 産 については この 方 法 では 保 護 できない 例 えば 自 社 アプリだけと 連 携 する Component は 他 社 アプリからのアクセスを 禁 止 したい 場 合 を 考 える このような 324 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

327 Component を Dangerous Permission により 保 護 するように 実 装 したとする 他 社 アプリがインストールされると きに ユーザーの 判 断 により 他 社 アプリに 対 して 権 限 の 付 与 を 許 可 してしまうと 保 護 すべき 自 社 の 資 産 が 他 社 アプ リに 悪 用 される 危 険 が 生 じる このような 場 合 に 自 社 の 資 産 を 保 護 するためには 独 自 定 義 の Signature Permission を 使 うとよい 独 自 定 義 の Dangerous Permission は 利 用 してはならない ( 必 須 ) 独 自 定 義 の Dangerous Permission を 使 用 しても インストール 時 に ユーザーに 権 限 の 許 可 を 求 める 画 面 が 表 示 されない 場 合 がある つまり Dangerous Permission の 特 徴 であるユーザーに 判 断 を 求 める 機 能 が 働 かないこと があるのだ よって 本 ガイドでは 独 自 定 義 の Dangerous Permission を 利 用 しない ことをルールとする まず 説 明 のために 2 つのアプリを 想 定 する 1 つは 独 自 の Dangerous Permission を 定 義 し この Permission に より 保 護 した Component を 公 開 するアプリである これを ProtectedApp とする もう 1 つは ProtectedApp の Component を 悪 用 しようとする 別 のアプリでこれを AttackerApp とする ここで AttackerApp は ProtectedApp が 定 義 した Permission の 利 用 宣 言 とともに 同 じ Permission の 定 義 も 行 っているものとする AttackerApp がユーザーの 許 可 なしに ProtectedApp の Component を 利 用 できてしまうケースは 以 下 のような 場 合 に 起 きる 1. ユーザーがまず AttackerApp をインストールすると Dangerous Permission の 利 用 許 可 を 求 める 画 面 は 表 示 されずに そのままインストールが 完 了 してしまう 2. 次 に ProtectedApp をインストールすると ここでも 特 に 警 告 もなくインストールできてしまう 3. その 後 ユーザーが AttackerApp を 起 動 すると AttackerApp はユーザーの 気 づかぬうちに ProtectedApp の Component にアクセスできてしまい 場 合 によっては 被 害 に 繋 がる このケースの 原 因 は 次 の 通 りである 先 に AttackerApp をインストールしようとすると uses-permission により 利 用 宣 言 された Permission はまだその 端 末 上 では 定 義 されていない このとき Android OS はエラーとすることもなく インストールを 続 行 してしまう Dangerous Permission のユーザー 確 認 はインストール 時 だけしか 実 施 されないた め 一 度 インストールされたアプリは 権 限 を 許 可 されたものとして 扱 われる したがって 後 からインストールされるアプ リの Component を 同 名 の Dangerous Permission で 保 護 していた 場 合 ユーザーの 許 可 なく 先 にインストールさ れたアプリからその Component が 利 用 できてしまうのである なお Android OS 既 定 の Dangerous Permission はアプリがインストールされるときにはその 存 在 が 保 証 されて いるので uses-permission しているアプリがインストールされるときには 必 ずユーザー 確 認 画 面 が 表 示 される 独 自 定 義 の Dangerous Permission の 場 合 にだけこの 問 題 は 生 じる 現 在 このケースで Component へのアクセスを 防 止 するよい 方 法 は 見 つかっていない したがって 独 自 定 義 の Dangerous Permission は 利 用 してはならない All rights reserved Japan Smartphone Security Association. Permission と Protection Level 325

328 独 自 定 義 Signature Permission は Component の 提 供 側 アプリでのみ 定 義 する ( 必 須 ) 自 社 アプリ 間 で 連 携 する 場 合 実 行 時 に Signature Permission をチェックすることでセキュリティを 担 保 できることを 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 で 例 示 した この 仕 組 みを 利 用 する 際 に は Protection Level が Signature の 独 自 Permission の 定 義 は Component 提 供 側 ア プ リ の AndroidManifest.xml でのみ 行 い 利 用 側 アプリでは 独 自 の Signature Permission を 定 義 してはならない なお signatureorsystem Permission についても 同 様 である 以 下 がその 理 由 となる 提 供 側 アプリより 先 にインストールされた 利 用 側 アプリが 複 数 あり どの 利 用 側 アプリも 独 自 定 義 Permission の 利 用 宣 言 とともに Permission の 定 義 もしている 場 合 を 考 える この 状 況 で 提 供 側 アプリをインストールすると すべて の 利 用 側 アプリから 提 供 側 アプリにアクセスすることが 可 能 になる 次 に 最 初 にインストールした 利 用 側 アプリをア ンインストールすると Permission の 定 義 が 削 除 され Permission が 未 定 義 となる そのため 残 った 利 用 側 アプ リからの 提 供 側 アプリの 利 用 が 不 可 能 となってしまう このように 利 用 側 アプリで Permission の 定 義 を 行 うと 思 わぬ Permission の 未 定 義 状 態 が 発 生 するので Permission の 定 義 は 保 護 する Component の 提 供 側 アプリのみ 行 い 利 用 側 アプリで Permission を 定 義 するの は 避 けなければならない こうすることで 提 供 側 アプリのインストール 時 に 権 限 付 与 が 行 われ かつ アンインストール 時 に Permission が 未 定 義 となり 提 供 側 アプリと Permission の 定 義 の 存 在 期 間 が 必 ず 一 致 するので 適 正 な Component の 提 供 と 保 護 が 可 能 である なお 独 自 定 義 Signature Permission に 関 しては 連 携 するアプリのインストール 順 によらず 利 用 側 アプリに Permission 利 用 権 限 が 付 与 されるため この 議 論 が 成 り 立 つことに 注 意 独 自 定 義 Signature Permission は 自 社 アプリにより 定 義 されていることを 確 認 する ( 必 須 ) AnroidManifest.xml で Signature Permission を 宣 言 し Component をその Permission で 保 護 しただけでは 実 は 保 護 が 十 分 ではない この 詳 細 はアドバンストセクションの 独 自 定 義 Signature Permission を 回 避 できる Android OS の 特 性 とその 対 策 を 参 照 すること 以 下 独 自 定 義 Signature Permission を 安 全 に 正 しく 使 う 手 順 である まず AndroidManifest.xml にて 次 を 行 う 1. 保 護 したい Component のあるアプリの AndroidManifest.xml にて 独 自 Signature Permission を 定 義 す る(Permission の 定 義 ) 例 : <permission android:name= xxx android:protectionlevel= signature /> 18 Normal/Dangerous Permission を 利 用 する 場 合 には Permission が 未 定 義 のまま 利 用 側 アプリが 先 にインスト ールされると 利 用 側 アプリへの 権 限 の 付 与 が 行 われず 提 供 側 アプリがインストールされた 後 もアクセスができない 326 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

329 2. 保 護 したい Component のある AndroidManifest.xml にて その Component の 定 義 タグの permission 属 性 で 独 自 定 義 Signature Permission を 指 定 する(Permission の 要 求 宣 言 ) 例 : <activity android:permission= xxx > </activity> 3. 保 護 したい Component にアクセスする 連 携 アプリの AndroidManifest.xml にて uses-permission タグに 独 自 定 義 Signature Permission を 指 定 する(Permission の 利 用 宣 言 ) 例 : <uses-permission android:name= xxx /> 続 いて ソースコード 上 にて 次 を 実 装 する 4. 保 護 したい Component でリクエストを 処 理 する 前 に 独 自 定 義 した Signature Permission が 自 社 アプリによ り 定 義 されたものかどうかを 確 認 し そうでなければリクエストを 無 視 する(Component 提 供 側 による 保 護 ) 5. 保 護 したい Component にアクセスする 前 に 独 自 定 義 した Signature Permission が 自 社 アプリにより 定 義 さ れたものかどうかを 確 認 し そうでなければ Component にアクセスしない(Component 利 用 側 による 保 護 ) 最 後 に Android Studio の 署 名 機 能 にて 次 を 行 う 6. 連 携 するすべてのアプリの APK を 同 じ 開 発 者 鍵 で 署 名 する ここで 独 自 定 義 した Signature Permission が 自 社 アプリにより 定 義 されたものかどうかを 確 認 する 必 要 がある が 具 体 的 な 実 装 方 法 についてはサンプルコードセクションの 独 自 定 義 の Signature Permission で 自 社 アプリ 連 携 する 方 法 を 参 照 すること なお signatureorsystem Permission についても 同 様 である 独 自 定 義 の Normal Permission は 利 用 してはならない ( 推 奨 ) Normal Permission を 利 用 するアプリは Android Manifest.xml に uses-permission で 利 用 宣 言 するだけでそ の 権 限 を 得 ることができる そのため 一 度 インストールされてしまったマルウェアから Component を 保 護 するよう な 目 的 に Normal Permission は 利 用 できない さらに 独 自 定 義 Normal Permission を 用 いてアプリ 間 連 携 を 行 う 場 合 連 携 する 各 アプリへの Permission の 付 与 はインストール 順 に 依 存 する 例 えば Permission を 定 義 したコンポーネントを 持 つアプリよりも 先 にその Permission を 利 用 宣 言 したアプリをインストールすると Permission を 定 義 したアプリをインストールした 後 も 利 用 側 アプリは Permission で 保 護 されたコンポーネントにアクセスすることができない インストール 順 によりアプリ 間 連 携 ができなくなる 問 題 を 回 避 する 方 法 として 連 携 する 全 てのアプリに Permission を 定 義 することも 考 えられる そうすることにより 最 初 に 利 用 側 アプリがインストールされた 場 合 でも 全 ての 利 用 側 ア プリが 提 供 側 アプリにアクセスすることが 可 能 となる しかし 最 初 にインストールした 利 用 側 アプリがアンインストー ルされた 際 に Permission が 未 定 義 な 状 態 となり 他 に 利 用 側 アプリが 存 在 していても それらのアプリから 提 供 側 アプリにアクセスすることができなくなってしまうのである 以 上 のようにアプリの 可 用 性 が 損 なわれる 恐 れがあることから 独 自 定 義 Normal Permission の 利 用 は 控 えるべ All rights reserved Japan Smartphone Security Association. Permission と Protection Level 327

330 きである Android アプリのセキュア 設 計 セキュアコーディングガイド 独 自 定 義 の Permission 名 はアプリのパッケージ 名 を 拡 張 した 文 字 列 にする ( 推 奨 ) 複 数 のアプリが 同 じ 名 前 で Permission を 定 義 する 場 合 先 にインストールされたアプリが 定 義 する Protection Level が 適 用 される 先 にインストールされたアプリが Normal Permission を 定 義 し 後 にインストールされたアプリ が 同 じ 名 前 で Signature Permission を 定 義 した 場 合 Signature Permission による 保 護 がまったく 効 かない 悪 意 がない 場 合 でも 複 数 のアプリにおいて Permission 名 が 衝 突 して 意 図 しない Protection Level で 動 作 する 可 能 性 がある このような 事 故 を 防 ぐため Permission 名 にはアプリのパッケージ 名 を 入 れた 方 が 良 い (パッケージ 名 ).permission.( 識 別 する 文 字 列 ) 例 えば org.jssec.android.sample というパッケージに READ アクセスの Permission を 定 義 するならば 次 の 様 な 命 名 が 好 ましい org.jssec.android.sample.permission.read 328 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

331 アドバンスト 独 自 定 義 Signature Permission を 回 避 できる Android OS の 特 性 とその 対 策 独 自 定 義 Signature Permission は 同 じ 開 発 者 鍵 で 署 名 されたアプリ 間 だけでアプリ 間 連 携 を 実 現 する Permission である 開 発 者 鍵 はプライベート 鍵 であり 絶 対 に 公 開 してはならないものであるため Signature Permission による 保 護 は 自 社 アプリだけで 連 携 する 場 合 に 使 われることが 多 い まずは Android の Dev Guide( 説 明 されている 独 自 定 義 Signature Permission の 基 本 的 な 使 い 方 を 紹 介 する ただし 後 述 するように この 使 い 方 には Permission 回 避 の 問 題 があることが 分 かっており 本 ガイドに 掲 載 した 対 策 が 必 要 となる 以 下 独 自 定 義 Signature Permission の 基 本 的 な 使 い 方 である 1. 保 護 したい Component のあるアプリの AndroidManifest.xml にて 独 自 Signature Permission を 定 義 す る 例 : <permission android:name= xxx android:protectionlevel= signature /> 2. 保 護 したい Component を 持 つアプリの AndroidManifest.xml で 保 護 したい Component に android:pe rmission 属 性 を 指 定 し 1.で 定 義 した Signature Permission を 要 求 する 例 : <activity android:permission= xxx > </activity> 3. 保 護 したい Component にアクセスしたい 連 携 アプリの AndroidManifest.xml にて 独 自 定 義 Signature P ermission を 利 用 宣 言 する 例 : <uses-permission android:name= xxx /> 4. 連 携 するすべてのアプリの APK を 同 じ 開 発 者 鍵 で 署 名 する 実 は この 使 い 方 だけでは 次 の 条 件 が 成 立 すると Signature Permission 回 避 の 抜 け 道 ができてしまう 説 明 のために 独 自 定 義 の Signature Permission で 保 護 したアプリを ProtectedApp とし ProtectedApp とは 異 なる 開 発 者 鍵 で 署 名 したアプリを AttackerApp とする ここで Signature Permission 回 避 の 抜 け 道 とは AttackerApp は 署 名 が 一 致 していないにもかかわらず ProtectedApp の Component にアクセス 可 能 になること である 条 件 1. AttackerApp も ProtectedApp が 独 自 定 義 し た Signature Permission と 同 じ 名 前 で Normal Permission を 定 義 する( 厳 密 には Signature Permission でも 構 わない) 例 : <permission android:name=" xxx " android:protectionlevel="normal" /> 条 件 2. AttackerApp は 独 自 定 義 した Normal Permission を uses-permission で 利 用 宣 言 する 例 : <uses-permission android:name="xxx" /> 条 件 3. AttackerApp を ProtectedApp より 先 に 端 末 にインストールする All rights reserved Japan Smartphone Security Association. Permission と Protection Level 329

332 条 件 3 ProtectedAppよりも 先 にインストール ProtectedApp AttackerApp AndroidManifest.xml AndroidManifest.xml 条 件 1 <permission android:name= xxx android:protectionlevel= signature /> <permission android:name= xxx android:protectionlevel= normal /> <activity android:permission= xxx > </activity> アクセス 可 能 <uses-permission android:name= xxx /> 条 件 2 ProtectedAppとは 異 なる 開 発 者 鍵 で 署 名 図 条 件 1 および 条 件 2 の 成 立 に 必 要 な ProtectedApp 独 自 定 義 の Permission 名 は APK ファイルから AndroidManifest.xml を 取 り 出 せば 攻 撃 者 にとって 容 易 に 知 ることができる 条 件 3 もユーザーを 騙 すなどの 方 法 により 攻 撃 者 にある 程 度 制 御 の 余 地 がある このように 独 自 定 義 の Signature Permission には 基 本 的 な 使 い 方 だけでは 保 護 を 回 避 されてしまう 危 険 性 があり 抜 け 道 をふさぐような 対 策 が 必 要 である 具 体 的 にはルールセクションの 独 自 定 義 Signature Permission は 自 社 アプリにより 定 義 されていることを 確 認 する ( 必 須 ) に 掲 載 している 方 法 で 対 処 できるので そち らを 参 照 のこと ユーザーが AndroidManifest.xml を 改 ざんする 独 自 Permission の Protection Level が 意 図 しないものになるケースは 既 に 説 明 した そのことによる 不 具 合 を 防 ぐために Java のソースコード 側 で 何 らかの 対 応 を 実 施 する 必 要 があった ここでは AndroidManifest.xml が 改 ざんされるという 視 点 から ソースコード 側 の 対 応 について 述 べる 改 ざんを 検 知 する 簡 易 な 実 装 例 を 提 示 するが 犯 罪 意 識 をもって 改 ざんを 行 うプロのハッカーに 対 してはほとんど 効 果 がない 方 法 であることに 注 意 すること この 節 はアプリの 改 ざんに 関 するものであり ユーザー 自 身 が 悪 意 を 持 っているケースである 本 来 はガイドラインの 範 囲 外 であるが Permission に 関 する 事 これを 行 うツールがアプリとして 公 開 されている 事 から プロでないハッ カーに 対 する 簡 易 な 対 策 として 述 べておくことにした Android ア プ リ は root 権 限 無 し に 改 ざ ん で き る こ と を 頭 に 置 い て お く 必 要 が あ る な ぜ な ら 330 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

333 AndroidManifest.xml を 変 更 して APK ファイルを 再 生 成 署 名 するツールが 配 布 されているためである このツー ルを 使 用 する 事 で 誰 でも 任 意 のアプリから Permission を 削 除 することが 可 能 になっている 事 例 としては INTERNET Permission を 取 り 除 いた AndroidManifest.xml から 別 署 名 の APK を 生 成 し アプリに 組 み 込 まれた 広 告 モジュールが 動 作 しないようにするケースが 多 いようである 個 人 情 報 がどこかに 送 信 されている かもしれない 等 の 不 安 が 払 拭 されるということで この 種 のツールの 存 在 を 評 価 しているユーザーも 存 在 する このよ うな 行 為 は アプリに 組 み 込 まれた 広 告 が 機 能 しなくなるため 広 告 収 入 を 期 待 している 開 発 者 に 対 して 金 銭 的 被 害 を 与 える 行 動 であるとも 言 える ユーザーのほとんどは 罪 の 意 識 無 くこれらの 行 為 を 行 っていると 思 われる インターネット Permission を uses-permission で 宣 言 しているアプリが 実 行 時 に 自 身 の AndroidManifest.xml に 記 載 されている Permission を 確 認 する 実 装 例 を 次 に 示 す public class CheckPermissionActivity extends Activity { public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // AndroidManifest.xml に 定 義 した Permission を 取 得 List<String> list = getdefinedpermissionlist(); // 改 ざんを 検 知 する if( checkpermissions(list) ){ // OK Log.d("dbg", "OK."); else{ Log.d("dbg", "manifest file is stale."); finish(); /** * AndroidManifest.xml に 定 義 した Permission をリストで 取 得 する */ private List<String> getdefinedpermissionlist(){ List<String> list = new ArrayList<String>(); list.add("android.permission.internet"); return list; /** * Permission が 変 更 されていないことを 確 認 する permissionlist */ private boolean checkpermissions(list<string> permissionlist){ try { PackageInfo packageinfo = getpackagemanager().getpackageinfo( getpackagename(), PackageManager.GET_PERMISSIONS); String[] permissionarray = packageinfo.requestedpermissions; if (permissionarray!= null) { for (String permission : permissionarray) { All rights reserved Japan Smartphone Security Association. Permission と Protection Level 331

334 if(! permissionlist.remove(permission)){ // 意 図 しない Permission が 付 加 されている return false; if(permissionlist.size() == 0){ // OK return true; catch (NameNotFoundException e) { return false; APK の 改 ざんを 検 出 する ユーザーが AndroidManifest.xml を 改 ざんする ではユーザーによる Permission 改 ざんの 検 出 につ いて 説 明 した しかし アプリの 改 ざんは Permission に 限 らず リソースを 差 し 替 えて 別 のアプリとしてマーケットで 配 布 するなど ソースコードを 変 更 することなく 改 ざんし 流 用 する 事 例 が 多 様 に 存 在 する ここでは APK ファイルが 改 ざんされたことを 検 出 するためのより 汎 用 的 な 方 法 を 紹 介 する APK の 改 ざんを 行 うには APK ファイルを 一 度 展 開 し 内 容 を 改 変 した 後 に 再 び APK ファイルとして 再 構 成 する 必 要 がある その 際 に 改 ざん 者 は 元 の 開 発 者 の 鍵 を 持 ち 得 ないので 改 ざん 者 自 身 の 鍵 で APK を 署 名 することになる このように APKの 改 ざんには 署 名 ( 証 明 書 )の 変 更 を 伴 うため アプリ 起 動 時 に APK の 証 明 書 と 予 めソースコードに 埋 め 込 んだ 開 発 者 の 証 明 書 を 比 較 することで 改 ざんの 有 無 を 検 出 することができる 以 下 にサンプルコードを 示 す なお 実 装 例 のままではプロのハッカーであれば 改 ざん 検 出 の 無 効 化 が 容 易 である あくまで 簡 易 な 実 装 例 であることを 念 頭 においてアプリへの 適 用 を 検 討 するべきである ポイント: 1. 主 要 な 処 理 を 行 うまでの 間 に アプリの 証 明 書 が 開 発 者 の 証 明 書 であることを 確 認 する SignatureCheckActivity.java package org.jssec.android.permission.signcheckactivity; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.utils; import android.app.activity; import android.content.context; import android.os.bundle; import android.widget.toast; public class SignatureCheckActivity extends Activity { // 自 己 証 明 書 のハッシュ 値 private static String smycerthash = null; 332 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

335 private static String mycerthash(context context) { if (smycerthash == null) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 smycerthash = "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 smycerthash = "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; return smycerthash; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.main); // ポイント 1 主 要 な 処 理 を 行 うまでの 間 に アプリの 証 明 書 が 開 発 者 の 証 明 書 であることを 確 認 する if (!PkgCert.test(this, this.getpackagename(), mycerthash(this))) { Toast.makeText(this, " 自 己 署 名 の 照 合 NG", Toast.LENGTH_LONG).show(); finish(); return; Toast.makeText(this, " 自 己 署 名 の 照 合 OK", Toast.LENGTH_LONG).show(); PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); All rights reserved Japan Smartphone Security Association. Permission と Protection Level 333

336 Android アプリのセキュア 設 計 セキュアコーディングガイド catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); Permission の 再 委 譲 問 題 アプリが Android OS に 保 護 されている 電 話 帳 や GPS といった 情 報 や 機 能 にアクセスするためには Permission を 利 用 宣 言 しなければならない Permission を 利 用 宣 言 し 許 可 されると そのアプリにはその Permission が 委 譲 さ れたことになり その Permission により 保 護 された 情 報 や 機 能 にアクセスできるようになる プログラムの 組 み 方 によっては Permission を 委 譲 された( 許 可 された)アプリはPermission で 保 護 されたデータを 取 得 し そのデータを 別 のアプリに 何 の Permission も 要 求 せずに 提 供 することもできてしまう これは Permission を 持 たないアプリが Permission で 保 護 されたデータにアクセスできることに 他 ならない 実 質 的 に Permission を 再 委 譲 していることと 等 価 になるので これを Permission の 再 委 譲 問 題 と 呼 ぶ このように Android の Permission セキュリティモデルでは 保 護 されたデータへのアプリからの 直 接 アクセスだけしか 権 限 管 理 ができないという 仕 様 上 の 性 質 がある 具 体 例 を 図 に 示 す 中 央 のアプリは android.permission.read_contacts を 利 用 宣 言 したアプリが 連 絡 先 情 報 を 読 み 取 って 自 分 の DB に 蓄 積 している 何 の 制 限 もなく Content Provider 経 由 で 蓄 積 した 情 報 を 他 のア プリに 提 供 した 場 合 に Permission の 再 委 譲 問 題 が 生 じる 334 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

337 Android 端 末 uses-permissionを 宣 言 したアプリ Content Provider Contacts AAA AAA BBB Permission 利 用 宣 言 の 無 いアプリ AAA BBB CCC BBB CCC CCC Content Providerに 適 切 なPermission 設 定 が ないと uses-permissionを 宣 言 していないアプリが 権 限 なしに 連 絡 先 データを 取 得 できてしまう 図 Permission を 持 たないアプリが 連 絡 先 情 報 を 取 得 する 同 様 の 例 として android.permission.call_phone を 利 用 宣 言 したアプリが 同 Permission を 利 用 宣 言 してい ない 他 のアプリからの 任 意 の 電 話 番 号 を 受 け 付 け ユーザーの 確 認 もなくその 番 号 に 電 話 を 掛 けることができるなら ば Permission の 再 委 譲 問 題 がある Permission の 利 用 宣 言 をして 得 た 情 報 資 産 機 能 資 産 をほぼそのままの 形 で 他 のアプリに 二 次 提 供 する 場 合 には 提 供 先 アプリに 対 し 同 じ Permission を 要 求 するなどして 元 の 保 護 水 準 を 維 持 しなければならない また 情 報 資 産 機 能 資 産 の 一 部 分 のみを 他 のアプリに 二 次 提 供 する 場 合 には その 情 報 資 産 機 能 資 産 の 一 部 分 が 悪 用 されたとき の 被 害 度 合 に 応 じた 適 切 な 保 護 が 必 要 である たとえば 前 述 と 同 様 に 同 じ Permission を 要 求 したり ユーザーへ 利 用 許 諾 を 確 認 したり 非 公 開 Activity を 作 る 利 用 する 自 社 限 定 Activity を 作 る 利 用 する などを 利 用 して 対 象 アプリの 制 限 を 設 けるなどの 保 護 施 策 がある このような 再 委 譲 問 題 は Permission に 限 ったことではない Android アプリでは アプリに 必 要 な 情 報 機 能 を 他 の アプリやネットワーク 記 憶 媒 体 から 調 達 することが 一 般 に 行 われている 提 供 元 が Android アプリであれば Permission ネットワークであればログイン 記 憶 媒 体 であればアクセス 制 限 といったように それぞれ 調 達 する 際 に 必 要 な 権 限 や 制 限 が 存 在 することも 多 い こうして 調 達 した 情 報 や 機 能 をその 所 有 者 であるユーザーから 二 次 的 に 他 のアプリに 提 供 したり ネットワークや 記 憶 媒 体 に 転 送 する 際 には ユーザーの 意 図 に 反 した 利 用 がないように 慎 重 に 検 討 してアプリに 対 策 を 施 すべきである 必 要 に 応 じて Permission の 例 と 同 様 に 提 供 先 に 対 して 権 限 の 要 求 や 使 用 の 制 限 を 行 わなければならない ユーザーへの 利 用 許 諾 もその 一 環 である 以 下 では READ_CONTACTS Permission を 利 用 して 連 絡 先 DB から 一 覧 を 取 得 したアプリが 情 報 提 供 先 のア プリに 対 して 同 じ READ_CONTACTS Permission を 要 求 する 例 を 示 す All rights reserved Japan Smartphone Security Association. Permission と Protection Level 335

338 ポイント Android アプリのセキュア 設 計 セキュアコーディングガイド 1. Manifest で 提 供 元 と 同 じ Permission を 要 求 する AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android=" package="org.jssec.android.permission.transferpermission" > <uses-permission android:name="android.permission.read_contacts"/> <application android:allowbackup="false" > <activity android:name=".transferpermissionactivity" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> <provider android:name=".transferpermissioncontentprovider" <!-- ポイント 1 Manifest で 提 供 元 と 同 じ Permission を 要 求 する --> android:authorities="org.jssec.android.permission.transferpermission" android:enabled="true" android:exported="true" android:readpermission="android.permission.read_contacts" > </provider> </application> </manifest> アプリが 複 数 の Permission を 要 求 する 必 要 がある 場 合 は 上 記 の 方 法 では 解 決 することができない ソースコード 上 で Context#checkCallingPermission()や PackageManager#checkPermission()を 使 用 して 呼 び 出 し 元 の アプリが Manifest ですべての Permission の 利 用 宣 言 を 行 っているかどうかを 確 認 することになる Activity の 場 合 public void oncreate(bundle savedinstancestate) { ~ 省 略 ~ if (checkcallingpermission("android.permission.read_contacts") == PackageManager.PERMISSION_GRANTED && checkcallingpermission("android.permission.write_contacts") == PackageManager.PERMISSION_GRANTED) { // 呼 び 出 し 元 が 正 しく Permission を 利 用 宣 言 していた 時 の 処 理 return; finish(); 336 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

339 独 自 定 義 Permission の 署 名 チェック 機 構 について (Android 5.0 以 降 ) Android 5.0(API Level 21) 以 降 の 端 末 では 独 自 の Permission を 定 義 したアプリにおいて 以 下 のような 条 件 に 合 致 するとインストールに 失 敗 するように 仕 様 変 更 された 条 件 1. 既 に 同 名 の Permission を 定 義 したアプリがインストールされている 条 件 2. そのインストール 済 みのアプリと 署 名 が 一 致 しない この 仕 様 変 更 により 保 護 対 象 の 機 能 (Component)の 提 供 側 アプリと 利 用 側 アプリの 双 方 で Permission を 定 義 した 場 合 には 同 じ Permission を 定 義 した 署 名 の 異 なる 他 社 アプリが 両 アプリと 同 時 にインストールされるのを 防 ぐ ことができる しかしながら 独 自 定 義 Signature Permission は Component の 提 供 側 アプリでのみ 定 義 する ( 必 須 ) で 言 及 した 通 り アプリのアンインストール 操 作 などによって 双 方 のアプリに Permission を 定 義 するとその Permission が 意 図 せず 未 定 義 状 態 になる 場 合 があるため この 仕 様 を 自 社 の 定 義 した Signature Permission が 他 アプリに 定 義 されていないことのチェックに 活 用 することはできないことが 分 かっている 結 果 として 自 社 限 定 アプリで 独 自 定 義 Signature Permission を 利 用 する 場 合 は 引 き 続 き 独 自 定 義 Signature Permission は Component の 提 供 側 アプリでのみ 定 義 する ( 必 須 ) 独 自 定 義 Signature Permission は 自 社 アプリにより 定 義 されていることを 確 認 する ( 必 須 ) のルールを 順 守 する 必 要 がある Android 6.0 以 降 の Permission モデルの 仕 様 変 更 について Android 6.0(API Level 23)においてアプリの 仕 様 や 設 計 にも 影 響 を 及 ぼす Permission モデルの 仕 様 変 更 が 行 わ れた 本 節 では Android 6.0 以 降 の Permission モデルの 概 要 を 解 説 する 権 限 の 付 与 取 り 消 しのタイミング ユーザー 確 認 が 必 要 な 権 限 (Dangerous Permission)をアプリが 利 用 宣 言 している 場 合 ( Android OS 既 定 の Permission を 利 用 する 方 法 参 照 ) Android 5.1(API Level 22) 以 前 の 仕 様 では アプリのインストール 時 にその 権 限 一 覧 が 表 示 され ユーザーがすべての 権 限 を 許 可 することでインストールが 行 われる この 時 点 で ア プリが 利 用 宣 言 している(Dangerous Permission 以 外 の 権 限 を 含 め) 全 ての 権 限 はアプリに 付 与 され 一 度 付 与 さ れた 権 限 はアプリが 端 末 からアンインストールされるまで 有 効 である しかし Android 6.0 以 降 の 仕 様 では 権 限 の 付 与 はアプリの 実 行 時 に 行 う 仕 様 となり アプリのインストール 時 には 権 限 の 付 与 もユーザーへの 確 認 も 行 われない アプリは Dangerous Permission を 必 要 とする 処 理 を 実 行 する 際 事 前 にその 権 限 がアプリに 付 与 されているかどうかを 確 認 し 権 限 が 付 与 されていない 場 合 には Android OS に 確 認 画 面 を 表 示 させ ユーザーに 権 限 利 用 の 許 可 を 求 める 必 要 がある 19 ユーザーが 確 認 画 面 で 許 可 することでその 19 Normal Permission および Signature Permission は Android OS により 自 動 的 に 付 与 されるため ユーザー 確 認 を 行 う 必 要 はない All rights reserved Japan Smartphone Security Association. Permission と Protection Level 337

340 権 限 はアプリに 付 与 される ただし ユーザーは 一 度 アプリに 許 可 した 権 限 (Dangerous Permission)を 設 定 メニュ ーを 通 じて 任 意 のタイミングで 取 り 消 すことができる( 図 )ため 権 限 がアプリに 付 与 されておらず 必 要 な 情 報 や 機 能 にアクセスすることができない 状 況 においても アプリが 異 常 な 動 作 を 起 こすことがないよう 適 切 な 処 理 を 実 装 する 必 要 がある 図 権 限 の 付 与 取 り 消 しの 単 位 いくつかの Permission はその 機 能 や 関 連 する 情 報 の 種 類 に 応 じて Permission Group と 呼 ばれる 単 位 でグルー プ 化 されている 例 えば カレンダー 情 報 の 読 み 取 りに 必 要 な Permission である android.permission.read_ca LENDAR と カレンダー 情 報 の 書 き 込 みに 必 要 な Permission である android.permission.write_calendar は どちらも android.permission-group.calendar という Permission Group に 属 している Android 6.0 以 降 の 新 しい Permission モデルにおいて 権 限 の 付 与 や 取 り 消 しはこの Permission Group を 単 位 として 行 われる つまり アプリの 実 行 時 に android.permission.read_calendar の 要 求 が 行 われ ユーザ ーがこれを 許 可 すると Android OS は android.permission.read_calendar と android.permission.writ E_CALENDAR の 利 用 が 両 方 とも 許 可 されたとみなし その 後 android.permission.write_calendar が 要 求 さ れても ユーザーに 確 認 ダイアログが 表 示 されることなく 即 時 に 権 限 が 付 与 されるのである 20 Permission Group の 分 類 については Developer Reference ( de/topics/security/permissions.html#perm-groups)を 参 照 すること 20 この 場 合 も アプリによる android.permission.read_calendar と android.permission.write_calendar の 利 用 宣 言 はともに 必 要 である 338 All rights reserved Japan Smartphone Security Association. Permission と Protection Level

341 仕 様 変 更 の 影 響 範 囲 アプリの 実 行 時 に Permission 要 求 が 必 要 なのは 端 末 が Android 6.0 以 降 で 動 作 していることに 加 え アプリの maxsdkversion が 23 以 上 に 設 定 されている 場 合 に 限 られる 端 末 が Android 5.1 以 前 で 動 作 している 場 合 や アプリの maxsdkversion が 23 未 満 である 場 合 権 限 は 従 来 通 りアプリのインストール 時 にまとめて 付 与 される ただし アプリの targetsdkversion が 23 未 満 であっても 端 末 が Android 6.0 以 降 であれば インストールされ たアプリの Permission をユーザーが 任 意 のタイミングで 取 り 消 すことができるため 意 図 しないアプリの 異 常 終 了 が 起 きる 可 能 性 がある 早 急 に 仕 様 変 更 に 対 応 するか アプリの maxsdkversion を 22 以 前 に 設 定 して Android 6.0(API Level 23) 以 降 の 端 末 にイントールされないようにするなどの 対 応 が 必 要 である( 表 5.2-1) 表 端 末 の Android OS バー ア プ リ の アプリへの 権 限 付 与 のタイ ユーザーによる 権 限 制 御 ジョン targetsdkversion ミング アプリ 実 行 時 あり <23 インストール 時 あり( 早 急 な 対 応 が 必 要 ) 23 インストール 時 なし <23 インストール 時 なし ただし maxsdkversion の 効 果 は 限 定 的 であることに 注 意 が 必 要 である maxsdkversion を22 以 前 に 設 定 した 場 合 アプリを Google Play 経 由 で 配 布 したときには Android 6.0(API Level 23) 以 降 の 端 末 が 対 象 アプリのイン ストール 可 能 端 末 としてリスト 表 示 されなくなる 一 方 Google Play 以 外 のマーケットプレイスでは maxsdkversion の 値 がチェックされないことがあるため Android 6.0(API Level 23) 以 降 の 端 末 に 対 象 アプリをインストールできる 場 合 がある このように maxsdkversion の 効 果 は 限 定 的 であること さらに Google が maxsdkversion の 使 用 を 推 奨 してい ないことを 踏 まえ 早 急 に 仕 様 変 更 に 対 応 することをお 勧 めする なお 以 下 のネットワーク 通 信 に 関 する Permission は Android 6.0 以 降 Protection Level が dangerous から normal に 変 更 されている つまり これらの Permission は 利 用 宣 言 していても ユーザーの 明 示 的 な 許 可 を 必 要 と しないため 今 回 の 仕 様 変 更 の 影 響 を 受 けない android.permission.bluetooth android.permission.bluetooth_admin android.permission.change_wifi_multicast_state android.permission.change_wifi_state android.permission.change_wimax_state android.permission.disable_keyguard android.permission.internet android.permission.nfc All rights reserved Japan Smartphone Security Association. Permission と Protection Level 339

342 5.3. Account Manager に 独 自 アカウントを 追 加 する Account Manager はアプリがオンラインサービスへアクセスするために 必 要 となるアカウント 情 報 (アカウント 名 パ スワード)および 認 証 トークンを 一 元 管 理 する Android OS の 仕 組 みである 21 ユーザーは 事 前 にアカウント 情 報 を Account Manager に 登 録 しておき アプリがオンラインサービスにアクセスしようとしたときにユーザーの 許 可 を 得 て Account Manager がアプリに 認 証 トークンを 自 動 提 供 する 仕 組 みである パスワードという 極 めてセンシティブ な 情 報 をアプリが 扱 わなくて 済 むことが Account Manager の 利 点 である Account Manager を 使 用 したアカウント 管 理 機 能 は 図 のような 構 成 となる 利 用 アプリ は 認 証 トークンの 提 供 を 受 けてオンラインサービスにアクセスするアプリであり 前 述 のアプリのことである 一 方 Authenticator ア プリ は Account Manager の 機 能 拡 張 であり Authenticator と 呼 ばれるオブジェクトを Account Manager に 提 供 することにより Account Manager がそのオンラインサービスのアカウント 情 報 および 認 証 トークンを 一 元 管 理 で きるようになる 利 用 アプリと Authenticator アプリは 別 のアプリである 必 要 はなく 一 つのアプリとして 実 装 すること もできる Android 端 末 Webサーバー 利 用 アプリ 認 証 トークン 取 得 アカウント 追 加 など Android Framework Account Manager Authenticatorアプリ オンラインサービスA の Authenticator オンラインサービスA アカウント 管 理 機 能 認 証 トークンを 使 ってサービスの 各 種 機 能 を 利 用 サービスの 各 種 機 能 図 5.3-1Account Manager を 使 用 したアカウント 管 理 機 能 の 構 成 本 来 利 用 アプリと Authenticator アプリは 開 発 者 の 署 名 鍵 が 異 なっていてもよい しかし Android 4.0.x の 端 末 に 限 り Android Framework のバグがあり 利 用 アプリと Authenticator アプリの 署 名 鍵 が 異 なっていると 利 用 アプ リで 例 外 が 発 生 してしまい 独 自 アカウントが 利 用 できない ここで 紹 介 するサンプルコードはこの 不 具 合 には 対 応 で きていない 詳 しくは Android 4.0.x では 利 用 アプリと Authenticator アプリの 署 名 鍵 が 異 なると 例 外 が 発 生 する を 参 照 すること サンプルコード Authenticator アプリのサンプルとして 独 自 アカウントを 作 る を 利 用 アプリのサンプルとして 独 自 アカウントを 利 用 する を 用 意 した JSSEC の Web サイトで 配 布 しているサンプルコード 一 式 ではそれぞれ AccountManager Authenticator および AccountManager User に 対 応 している 21 Account Manager はオンラインサービスとの 同 期 の 仕 組 みも 提 供 するが 本 節 では 扱 っていない 340 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

343 独 自 アカウントを 作 る ここでは Account Manager が 独 自 アカウントを 扱 えるようにする Authenticator アプリのサンプルコードを 紹 介 す る このアプリはホーム 画 面 から 起 動 できる Activity は 存 在 しない もう 一 つのサンプルアプリ 独 自 アカウ ントを 利 用 する から Account Manager 経 由 で 間 接 的 に 呼 び 出 されることに 注 意 してほしい ポイント: 1. Authenticator を 提 供 する Service は 非 公 開 Service とする 2. ログイン 画 面 Activity は Authenticator アプリで 実 装 する 3. ログイン 画 面 Acitivity は 公 開 Activity とする 4. KEY_INTENT には ログイン 画 面 Activity のクラス 名 を 指 定 した 明 示 的 Intent を 与 える 5. アカウント 情 報 や 認 証 トークンなどのセンシティブな 情 報 はログ 出 力 しない 6. Account Manager にパスワードを 保 存 しない 7. Authenticator とオンラインサービスとの 通 信 は HTTPS で 行 う AndroidManifest.xml にて Authenticator の IBinder を Account Manager に 提 供 するサービスを 定 義 meta-data にて Authenticator を 記 述 したリソース XML ファイルを 指 定 AccountManager Authenticator/AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.android.accountmanager.authenticator" xmlns:tools=" <!-- Authenticator を 実 装 するのに 必 要 な Permission --> <uses-permission android:name="android.permission.get_accounts" /> <uses-permission android:name="android.permission.authenticate_accounts" /> <application android:allowbackup="false" > <!-- Authenticator の IBinder を AccountManager に 提 供 するサービス --> <!-- ポイント 1 Authenticator を 提 供 する Service は 非 公 開 Service とする --> <service android:name=".authenticationservice" android:exported="false" > <!-- intent-filter と meta-data はお 決 まりのパターン --> <intent-filter> <action android:name="android.accounts.accountauthenticator" /> </intent-filter> <meta-data android:name="android.accounts.accountauthenticator" android:resource="@xml/authenticator" /> </service> <!-- アカウントを 追 加 するときなどに 表 示 されるログイン 画 面 用 の Activity --> <!-- ポイント 2 ログイン 画 面 Activity は Authenticator アプリで 実 装 する --> <!-- ポイント 3 ログイン 画 面 Activity は 公 開 Activity とする --> <activity android:name=".loginactivity" All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 341

344 android:exported="true" tools:ignore="exportedactivity" /> </application> </manifest> XML ファイルで Authenticator を 定 義 独 自 アカウントのアカウントタイプ 等 を 指 定 する res/xml/authenticator.xml <account-authenticator xmlns:android=" android:accounttype="org.jssec.android.accountmanager" android:customtokens="true" /> Authenticator のインスタンスをAccount Managerに 提 供 するサービス このサンプルで 実 装 するAuthenticator である JssecAuthenticator クラスのインスタンスを onbind()で return するだけの 簡 単 な 実 装 でよい AuthenticationService.java package org.jssec.android.accountmanager.authenticator; import android.app.service; import android.content.intent; import android.os.ibinder; public class AuthenticationService extends Service { private JssecAuthenticator mauthenticator; public void oncreate() { mauthenticator = new JssecAuthenticator(this); public IBinder onbind(intent intent) { return mauthenticator.getibinder(); 342 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

345 このサンプルで 実 装 する Authenticator である JssecAuthenticator AbstractAccountAuthenticator を 継 承 し て abstract メソッドをすべて 実 装 する これらのメソッドは Account Manager から 呼 ばれる addaccount()および getauthtoken()では オンラインサービスから 認 証 トークンを 取 得 するための LoginActivity を 起 動 する intent を Account Manager に 返 している JssecAuthenticator.java package org.jssec.android.accountmanager.authenticator; import android.accounts.abstractaccountauthenticator; import android.accounts.account; import android.accounts.accountauthenticatorresponse; import android.accounts.accountmanager; import android.accounts.networkerrorexception; import android.content.context; import android.content.intent; import android.os.bundle; public class JssecAuthenticator extends AbstractAccountAuthenticator { public static final String JSSEC_ACCOUNT_TYPE = "org.jssec.android.accountmanager"; public static final String JSSEC_AUTHTOKEN_TYPE = "webservice"; public static final String JSSEC_AUTHTOKEN_LABEL = "JSSEC Web Service"; public static final String RE_AUTH_NAME = "reauth_name"; protected final Context mcontext; public JssecAuthenticator(Context context) { super(context); mcontext = context; public Bundle addaccount(accountauthenticatorresponse response, String accounttype, String authtokentype, String[] requiredfeatures, Bundle options) throws NetworkErrorException { AccountManager am = AccountManager.get(mContext); Account[] accounts = am.getaccountsbytype(jssec_account_type); Bundle bundle = new Bundle(); if (accounts.length > 0) { // 本 サンプルコードではアカウントが 既 に 存 在 する 場 合 はエラーとする bundle.putstring(accountmanager.key_error_code, String.valueOf(-1)); bundle.putstring(accountmanager.key_error_message, mcontext.getstring(r.string.error_account_exists)); else { // ポイント 2 ログイン 画 面 Activity は Authenticator アプリで 実 装 する // ポイント 4 KEY_INTENT には ログイン 画 面 Activity のクラス 名 を 指 定 した 明 示 的 Intent を 与 える Intent intent = new Intent(mContext, LoginActivity.class); intent.putextra(accountmanager.key_account_authenticator_response, response); bundle.putparcelable(accountmanager.key_intent, intent); return bundle; public Bundle getauthtoken(accountauthenticatorresponse response, Account account, String authtokentype, Bundle options) throws NetworkErrorException { All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 343

346 Bundle bundle = new Bundle(); if (accountexist(account)) { // ポイント 4 KEY_INTENT には ログイン 画 面 Activity のクラス 名 を 指 定 した 明 示 的 Intent を 与 える Intent intent = new Intent(mContext, LoginActivity.class); intent.putextra(re_auth_name, account.name); bundle.putparcelable(accountmanager.key_intent, intent); else { // 指 定 されたアカウントが 存 在 しない 場 合 はエラーとする bundle.putstring(accountmanager.key_error_code, String.valueOf(-2)); bundle.putstring(accountmanager.key_error_message, mcontext.getstring(r.string.error_account_not_exists)); return bundle; public String getauthtokenlabel(string authtokentype) { return JSSEC_AUTHTOKEN_LABEL; public Bundle confirmcredentials(accountauthenticatorresponse response, Account account, Bundle options) throws NetworkErrorException { return null; public Bundle editproperties(accountauthenticatorresponse response, String accounttype) { return null; public Bundle updatecredentials(accountauthenticatorresponse response, Account account, String authtokentype, Bundle options) throws NetworkErrorException { return null; public Bundle hasfeatures(accountauthenticatorresponse response, Account account, String[] features) throws NetworkErrorException { Bundle result = new Bundle(); result.putboolean(accountmanager.key_boolean_result, false); return result; private boolean accountexist(account account) { AccountManager am = AccountManager.get(mContext); Account[] accounts = am.getaccountsbytype(jssec_account_type); for (Account ac : accounts) { if (ac.equals(account)) { return true; return false; 344 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

347 オンラインサービスにアカウント 名 パスワードを 送 信 してログイン 認 証 を 行 い その 結 果 として 認 証 トークンを 取 得 す る LoginActivity 新 規 アカウント 追 加 および 認 証 トークン 再 取 得 の 場 合 に 表 示 される オンラインサービスへの 実 際 のアクセスは WebService クラス 内 で 実 装 されるものとしている LoginActivity.java package org.jssec.android.accountmanager.authenticator; import org.jssec.android.accountmanager.webservice.webservice; import android.accounts.account; import android.accounts.accountauthenticatoractivity; import android.accounts.accountmanager; import android.content.intent; import android.os.bundle; import android.text.inputtype; import android.text.textutils; import android.util.log; import android.view.view; import android.view.window; import android.widget.edittext; public class LoginActivity extends AccountAuthenticatorActivity { private static final String TAG = AccountAuthenticatorActivity.class.getSimpleName(); private String mreauthname = null; private EditText mnameedit = null; private EditText mpassedit = null; public void oncreate(bundle icicle) { super.oncreate(icicle); // アラートアイコン 表 示 requestwindowfeature(window.feature_left_icon); setcontentview(r.layout.login_activity); getwindow().setfeaturedrawableresource(window.feature_left_icon, android.r.drawable.ic_dialog_alert); // widget を 見 つけておく mnameedit = (EditText) findviewbyid(r.id.username_edit); mpassedit = (EditText) findviewbyid(r.id.password_edit); // ポイント 3 ログイン 画 面 Activity は 公 開 Activity として 他 のアプリからの 攻 撃 アクセスを 想 定 する // 外 部 入 力 は Intent#extras の String 型 の RE_AUTH_NAME だけしか 扱 わない // この 外 部 入 力 String は TextEdit#setText() WebService#login() new Account()に // 引 数 として 渡 されるが どんな 文 字 列 が 与 えられても 問 題 が 起 きないことを 確 認 している mreauthname = getintent().getstringextra(jssecauthenticator.re_auth_name); if (mreauthname!= null) { // ユーザー 名 指 定 で LoginActivity が 呼 び 出 されたので ユーザー 名 を 編 集 不 可 とする mnameedit.settext(mreauthname); mnameedit.setinputtype(inputtype.type_null); mnameedit.setfocusable(false); mnameedit.setenabled(false); // ログインボタン 押 下 時 に 実 行 される public void handlelogin(view view) { String name = mnameedit.gettext().tostring(); All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 345

348 String pass = mpassedit.gettext().tostring(); if (TextUtils.isEmpty(name) TextUtils.isEmpty(pass)) { // 入 力 値 が 不 正 である 場 合 の 処 理 setresult(result_canceled); finish(); // 入 力 されたアカウント 情 報 によりオンラインサービスにログインする WebService web = new WebService(); String authtoken = web.login(name, pass); if (TextUtils.isEmpty(authToken)) { // 認 証 が 失 敗 した 場 合 の 処 理 setresult(result_canceled); finish(); // 以 下 ログイン 成 功 時 の 処 理 // ポイント 5 アカウント 情 報 や 認 証 トークンなどのセンシティブな 情 報 はログ 出 力 しない Log.i(TAG, "WebService login succeeded"); if (mreauthname == null) { // ログイン 成 功 したアカウントを AccountManager に 登 録 する // ポイント 6 Account Manager にパスワードを 保 存 しない AccountManager am = AccountManager.get(this); Account account = new Account(name, JssecAuthenticator.JSSEC_ACCOUNT_TYPE); am.addaccountexplicitly(account, null, null); am.setauthtoken(account, JssecAuthenticator.JSSEC_AUTHTOKEN_TYPE, authtoken); Intent intent = new Intent(); intent.putextra(accountmanager.key_account_name, name); intent.putextra(accountmanager.key_account_type, JssecAuthenticator.JSSEC_ACCOUNT_TYPE); setaccountauthenticatorresult(intent.getextras()); setresult(result_ok, intent); else { // 認 証 トークンを 返 却 する Bundle bundle = new Bundle(); bundle.putstring(accountmanager.key_account_name, name); bundle.putstring(accountmanager.key_account_type, JssecAuthenticator.JSSEC_ACCOUNT_TYPE); bundle.putstring(accountmanager.key_authtoken, authtoken); setaccountauthenticatorresult(bundle); setresult(result_ok); finish(); 346 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

349 実 際 には WebService クラスはダミー 実 装 となっており 常 に 認 証 が 成 功 し 固 定 文 字 列 を 認 証 トークンとして 返 すサ ンプル 実 装 になっている WebService.java package org.jssec.android.accountmanager.webservice; public class WebService { /** * オンラインサービスのアカウント 管 理 機 能 にアクセスする 想 定 * username アカウント 名 文 字 列 password パスワード 文 字 列 認 証 トークンを 返 す */ public String login(string username, String password) { // ポイント 7 Authenticator とオンラインサービスとの 通 信 は HTTPS で 行 う // 実 際 には サーバーとの 通 信 処 理 を 実 装 するが サンプルにつき 割 愛 return getauthtoken(username, password); private String getauthtoken(string username, String password) { // 実 際 にはサーバーから ユニーク 性 と 推 測 不 可 能 性 を 保 証 された 値 を 取 得 するが // サンプルにつき 通 信 は 行 わずに 固 定 値 を 返 す return "c2f981bda5f34f90c0419e171f60f45c"; All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 347

350 独 自 アカウントを 利 用 する 独 自 アカウントの 追 加 と 認 証 トークンの 取 得 を 行 うアプリのサンプルコードを 以 下 に 示 す もう 一 つのサンプルアプリ 独 自 アカウントを 作 る が 端 末 にインストールされているときに 独 自 アカウントの 追 加 や 認 証 トークンの 取 得 ができる アクセスリクエスト 画 面 は 両 アプリの 署 名 鍵 が 異 なる 場 合 にだけ 表 示 される 図 サンプルアプリ AccountManager User の 動 作 画 面 ポイント: 1. Authenticator が 正 規 のものであることを 確 認 してからアカウント 処 理 を 実 施 する 利 用 アプリの AndroidManifest.xml 必 要 な Permission を 利 用 宣 言 必 要 な Permission については Account Manager の 利 用 と Permission を 参 照 AccountManager User/AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.android.accountmanager.user" > <uses-permission android:name="android.permission.get_accounts" /> <uses-permission android:name="android.permission.manage_accounts" /> <uses-permission android:name="android.permission.use_credentials" /> <application android:allowbackup="false" > <activity android:name=".useractivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> 348 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

351 </manifest> 利 用 アプリの Activity 画 面 上 のボタンをタップすると addacount()または getauthtoken()が 実 行 される 指 定 の アカウントタイプに 対 応 した Authenticator が 偽 物 であるケースがあるので 正 規 の Authenticator であることを 確 認 してからアカウント 処 理 を 始 めていることに 注 意 UserActivity.java package org.jssec.android.accountmanager.user; import java.io.ioexception; import org.jssec.android.shared.pkgcert; import org.jssec.android.shared.utils; import android.accounts.account; import android.accounts.accountmanager; import android.accounts.accountmanagercallback; import android.accounts.accountmanagerfuture; import android.accounts.authenticatordescription; import android.accounts.authenticatorexception; import android.accounts.operationcanceledexception; import android.app.activity; import android.content.context; import android.os.bundle; import android.view.view; import android.widget.textview; public class UserActivity extends Activity { // 利 用 する Authenticator の 情 報 private static final String JSSEC_ACCOUNT_TYPE = "org.jssec.android.accountmanager"; private static final String JSSEC_TOKEN_TYPE = "webservice"; private TextView mlogview; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.user_activity); mlogview = (TextView)findViewById(R.id.logview); public void addaccount(view view) { logline(); logline(" 新 しいアカウントを 追 加 します"); // ポイント 1 Authenticator が 正 規 のものであることを 確 認 してからアカウント 処 理 を 実 施 する if (!checkauthenticator()) return; AccountManager am = AccountManager.get(this); am.addaccount(jssec_account_type, JSSEC_TOKEN_TYPE, null, null, this, new AccountManagerCallback<Bundle>() { public void run(accountmanagerfuture<bundle> future) { try { Bundle result = future.getresult(); All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 349

352 Android アプリのセキュア 設 計 セキュアコーディングガイド, null); String type = result.getstring(accountmanager.key_account_type); String name = result.getstring(accountmanager.key_account_name); if (type!= null && name!= null) { logline(" 以 下 のアカウントを 追 加 しました:"); logline(" アカウント 種 別 : %s", type); logline(" アカウント 名 : %s", name); else { String code = result.getstring(accountmanager.key_error_code); String msg = result.getstring(accountmanager.key_error_message); logline("アカウントが 追 加 できませんでした"); logline(" エラーコード %s: %s", code, msg); catch (OperationCanceledException e) { catch (AuthenticatorException e) { catch (IOException e) { public void getauthtoken(view view) { logline(); logline("トークンを 取 得 します"); // ポイント 1 Authenticator が 正 規 のものであることを 確 認 してからアカウント 処 理 を 実 施 する if (!checkauthenticator()) return; AccountManager am = AccountManager.get(this); Account[] accounts = am.getaccountsbytype(jssec_account_type); if (accounts.length > 0) { Account account = accounts[0]; am.getauthtoken(account, JSSEC_TOKEN_TYPE, null, this, new AccountManagerCallback<Bundle>() { public void run(accountmanagerfuture<bundle> future) { try { Bundle result = future.getresult(); String name = result.getstring(accountmanager.key_account_name); String authtoken = result.getstring(accountmanager.key_authtoken); logline(" %s さんのトークン:", name); if (authtoken!= null) { logline(" %s", authtoken); else { logline(" 取 得 できませんでした"); catch (OperationCanceledException e) { logline(" 例 外 : %s",e.getclass().getname()); catch (AuthenticatorException e) { logline(" 例 外 : %s",e.getclass().getname()); catch (IOException e) { logline(" 例 外 : %s",e.getclass().getname());, null); else { logline("アカウントが 登 録 されていません"); 350 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

353 // ポイント 1 Authenticator が 正 規 のものであることを 確 認 する private boolean checkauthenticator() { AccountManager am = AccountManager.get(this); String pkgname = null; for (AuthenticatorDescription ad : am.getauthenticatortypes()) { if (JSSEC_ACCOUNT_TYPE.equals(ad.type)) { pkgname = ad.packagename; break; if (pkgname == null) { logline("authenticator が 見 つかりません"); return false; logline(" アカウントタイプ: %s", JSSEC_ACCOUNT_TYPE); logline(" Authenticator のパッケージ 名 :"); logline(" %s", pkgname); if (!PkgCert.test(this, pkgname, gettrustedcertificatehash(this))) { logline(" 正 規 の Authenticator ではありません( 証 明 書 不 一 致 )"); return false; logline(" 正 規 の Authenticator です"); return true; // 正 規 の Authenticator アプリの 証 明 書 ハッシュ 値 // サンプルアプリ JSSEC CertHash Checker で 証 明 書 ハッシュ 値 は 確 認 できる private String gettrustedcertificatehash(context context) { if (Utils.isDebuggable(context)) { // debug.keystore の"androiddebugkey"の 証 明 書 ハッシュ 値 return "0EFB A BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; else { // keystore の"my company key"の 証 明 書 ハッシュ 値 return "D397D343 A5CBC10F 4EDDEB7C A10062DE F 1FB9E88B D7B3A7C2 42E142CA"; private void log(string str) { mlogview.append(str); private void logline(string line) { log(line + "\n"); private void logline(string fmt, Object... args) { logline(string.format(fmt, args)); private void logline() { log("\n"); All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 351

354 PkgCert.java package org.jssec.android.shared; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import android.content.context; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.content.pm.signature; public class PkgCert { public static boolean test(context ctx, String pkgname, String correcthash) { if (correcthash == null) return false; correcthash = correcthash.replaceall(" ", ""); return correcthash.equals(hash(ctx, pkgname)); public static String hash(context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getpackagemanager(); PackageInfo pkginfo = pm.getpackageinfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length!= 1) return null; // 複 数 署 名 は 扱 わない Signature sig = pkginfo.signatures[0]; byte[] cert = sig.tobytearray(); byte[] sha256 = computesha256(cert); return byte2hex(sha256); catch (NameNotFoundException e) { return null; private static byte[] computesha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); catch (NoSuchAlgorithmException e) { return null; private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(string.format("%02x", b)); return hexadecimal.tostring(); 352 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

355 ルールブック Authenticator アプリを 実 装 する 際 には 以 下 のルールを 守 ること 1. Authenticator を 提 供 する Service は 非 公 開 Service とする ( 必 須 ) 2. ログイン 画 面 Activity は Authenticator アプリで 実 装 する ( 必 須 ) 3. ログイン 画 面 Activity は 公 開 Activity として 他 のアプリからの 攻 撃 アクセスを 想 定 する ( 必 須 ) 4. KEY_INTENT には ログイン 画 面 Activity のクラス 名 を 指 定 した 明 示 的 Intent を 与 える ( 必 須 ) 5. アカウント 情 報 や 認 証 トークンなどのセンシティブな 情 報 はログ 出 力 しない ( 必 須 ) 6. Account Manager にパスワードを 保 存 しない ( 推 奨 ) 7. Authenticator とオンラインサービスとの 通 信 は HTTPS で 行 う ( 必 須 ) 利 用 アプリを 実 装 する 際 には 以 下 のルールを 守 ること 1. Authenticator が 正 規 のものであることを 確 認 してからアカウント 処 理 を 実 施 する ( 必 須 ) Authenticator を 提 供 する Service は 非 公 開 Service とする ( 必 須 ) Authenticator を 提 供 する Service は Account Manager から 利 用 されることを 前 提 としており 他 のアプリがアク セスできてはならない 非 公 開 Service とすることにより 他 のアプリからのアクセスを 排 除 することができる また Account Manager は system 権 限 で 動 作 しているので 非 公 開 Service であってもアクセスできる ログイン 画 面 Activity は Authenticator アプリで 実 装 する ( 必 須 ) 新 規 アカウント 追 加 および 認 証 トークン 再 取 得 の 場 合 に 表 示 されるログイン 画 面 は Authenticator アプリで 実 装 す べきである 利 用 アプリ 側 で 独 自 にログイン 画 面 を 用 意 してはならない この 記 事 の 冒 頭 で パスワードという 極 めて センシティブな 情 報 をアプリが 扱 わなくて 済 むことが Account Manager の 利 点 である と 呼 べた もし 利 用 アプリ 側 でログイン 画 面 を 用 意 してしまうと 利 用 アプリがパスワードを 扱 ってしまうことになり Account Manager の 思 想 か ら 逸 脱 した 設 計 となってしまう Authenticator アプリがログイン 画 面 を 用 意 することにより ログイン 画 面 を 操 作 できるのは 端 末 のユーザーだけに 限 定 される これは 悪 意 あるアプリが 直 接 ログインを 試 みたり アカウントを 作 成 したりといったアカウント 攻 撃 をする 手 段 がないということである ログイン 画 面 Activity は 公 開 Activity として 他 のアプリからの 攻 撃 アクセスを 想 定 する ( 必 須 ) ログイン 画 面 Activity は 利 用 アプリの 権 限 で 起 動 する 仕 組 みとなっている 利 用 アプリと Authenticator アプリの 署 名 鍵 が 異 なる 場 合 にもログイン 画 面 Activity が 表 示 されるためには ログイン 画 面 Activity は 公 開 Activity として 実 装 しなければならない ログイン 画 面 Activity が 公 開 Activity であるということは 悪 意 あるアプリからも 起 動 される 可 能 性 があるということ である 入 力 データは 一 切 信 用 してはならない したがって 3.2 入 力 データの 安 全 性 を 確 認 する で 述 べたような 対 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 353

356 策 が 必 要 となる Android アプリのセキュア 設 計 セキュアコーディングガイド KEY_INTENT には ログイン 画 面 Activity のクラス 名 を 指 定 した 明 示 的 Intent を 与 える ( 必 須 ) Authenticator がログイン 画 面 Activity を 開 きたいときには Account Manager に 返 す Bundle の 中 にログイン 画 面 Activity を 起 動 する Intent を KEY_INTENT で 与 えることになっている ここで 与 える Intent はログイン 画 面 Activity をクラス 名 で 指 定 する 明 示 的 Intent でなければならない もしアクション 名 指 定 の 暗 黙 的 Intent を 指 定 して しまうと Authenticator アプリが 自 ら 用 意 したログイン 画 面 Activity ではなく 他 のアプリが 用 意 した Activity が 起 動 される 可 能 性 が 生 じてしまうからだ 悪 意 あるアプリが 正 規 のログイン 画 面 に 似 せたログイン 画 面 を 用 意 していた 場 合 偽 のログイン 画 面 でユーザーがパスワードを 入 力 してしまう 危 険 がある アカウント 情 報 や 認 証 トークンなどのセンシティブな 情 報 はログ 出 力 しない ( 必 須 ) オンラインサービスに 接 続 するアプリは その 開 発 時 だけでなく 運 用 時 においても オンラインサービスにうまく 接 続 で きないトラブルに 悩 まされることがある 接 続 できない 原 因 は 多 岐 に 渡 り ネットワーク 環 境 の 整 備 不 足 通 信 プロトコ ルの 実 装 ミス Permission 不 足 認 証 エラーなど 様 々である こうした 原 因 の 切 り 分 けを 目 的 として プログラム 内 部 で 得 られた 情 報 をログ 出 力 する 実 装 もよくみられる パスワードや 認 証 トークンなどのセンシティブな 情 報 は 決 してログ 出 力 してはならない ログ 情 報 は 他 のアプリからも 読 み 取 ることができるため 情 報 漏 洩 の 原 因 となりかねないからだ アカウント 名 も 漏 洩 も 被 害 につながる 場 合 にはロ グ 出 力 してはならない Account Manager にパスワードを 保 存 しない ( 推 奨 ) Account Manager に 登 録 するアカウントには パスワードと 認 証 トークンの 2 つの 認 証 情 報 を 保 存 することができる これらの 情 報 は 次 のディレクトリの accounts.db の 中 に 平 文 で(つまり 暗 号 化 されず) 保 存 される Android 4.1 以 前 /data/system/accounts.db Android 4.2 以 降 /data/system/users/0/accounts.db Android 4.2 以 降 はマルチユーザー 機 能 がサポートされているため ユーザーに 合 わせたディレクトリへ 保 存 され るように 変 更 されている この accounts.db の 内 容 を 読 み 取 るためには root 権 限 または system 権 限 が 必 要 であり 市 販 の Android 端 末 では 読 み 取 ることができない もし 攻 撃 者 に root 権 限 や system 権 限 が 奪 われてしまう 脆 弱 性 が Android OS に ある 場 合 には accounts.db の 中 に 保 存 された 認 証 情 報 が 危 険 にさらされることになる この 記 事 で 紹 介 している Authenticator アプリは Account Manager に 認 証 トークンは 保 存 するが ユーザーのパ スワードは 保 存 しない 設 計 としている 一 定 の 期 間 以 内 にオンラインサービスに 継 続 的 に 接 続 していれば 認 証 トー クンの 有 効 期 間 が 延 長 されるのが 一 般 的 であるため パスワードを 保 存 しない 設 計 で 十 分 であることが 多 い 354 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

357 認 証 トークンは 一 般 にパスワードよりも 有 効 期 限 が 短 く いつでも 無 効 化 できる 特 徴 がある いわば 使 い 捨 ての 認 証 情 報 である 万 一 認 証 トークンが 漏 洩 したとしても 認 証 トークンを 無 効 化 することができるため 認 証 トークンはパ スワードに 比 べ 安 全 性 が 高 いとされている 認 証 トークンが 無 効 化 された 場 合 には ユーザーはもう 一 度 パスワード を 入 力 して 新 しい 認 証 トークンを 取 得 すればよい パスワードが 漏 洩 した 場 合 パスワードを 無 効 化 してしまうと そのユーザーはオンラインサービスを 利 用 できなくなっ てしまう このような 場 合 コールセンター 対 応 等 が 必 要 となってしまうため 大 きなコストが 発 生 する ゆえに Account Manager にパスワードを 保 存 する 設 計 はできるだけ 避 けるべきである どうしてもパスワードを 保 存 する 設 計 をしな ければならない 場 合 は パスワードを 暗 号 化 して 暗 号 化 の 鍵 を 難 読 化 するなど 高 度 なリバースエンジニアリング 対 策 を 実 施 することになる Authenticator とオンラインサービスとの 通 信 は HTTPS で 行 う ( 必 須 ) パスワードや 認 証 トークンはいわゆる 認 証 情 報 といい これを 第 三 者 に 奪 われてしまうと 第 三 者 がユーザーになり すましできることになる Authenticator はオンラインサービスとこうした 認 証 情 報 を 送 受 信 することになるので HTTPS 等 の 安 全 性 の 確 立 した 暗 号 化 通 信 方 式 で 通 信 しなければならない Authenticator が 正 規 のものであることを 確 認 してからアカウント 処 理 を 実 施 する ( 必 須 ) 端 末 に 同 一 のアカウントタイプを 定 義 した Authenticator が 複 数 存 在 する 場 合 先 にインストールされた Authenticator が 有 効 になる 自 分 の Authenticator が 後 にインストールされた 場 合 には 利 用 されないということで ある もし 先 にインストールされた Authenticator がマルウェアによる 偽 装 であった 場 合 には ユーザーが 入 力 したアカウ ント 情 報 がマルウェアに 奪 われてしまう 恐 れがある 利 用 アプリはアカウント 操 作 を 行 うアカウントタイプについて 正 規 の Authenticator がそのアカウントタイプに 割 り 当 てられていることを 確 認 してから アカウント 操 作 を 実 施 しなけ ればならない あるアカウントタイプに 割 り 当 てられている Authenticator が 正 規 のものであるかは その Authenticator を 含 むパ ッケージの 証 明 書 ハッシュ 値 を 事 前 に 確 認 している 正 規 の 証 明 書 ハッシュ 値 と 一 致 するかどうかで 確 認 できる もし 証 明 書 ハッシュ 値 が 一 致 しないことが 判 明 した 場 合 そのアカウントタイプに 割 り 当 てられている 意 図 しない Authenticator を 含 むパッケージをアンインストールするようユーザーを 促 すといった 対 処 を 施 すことが 望 ましい All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 355

358 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド Account Manager の 利 用 と Permission AccountManager クラスの 各 メソッドを 利 用 するためには アプリの AndroidManifest.xml にそれぞれ 適 正 な Permission の 利 用 宣 言 をする 必 要 がある Permission とメソッドの 対 応 を 表 に 示 す 表 Account Manager の 機 能 と Permission Account Manager が 提 供 する 機 能 Permission メソッド 説 明 AUTHENTICATE_ACCOUNTS (Authenticator と 同 じ 鍵 で 署 名 された Package のみ 利 用 可 能 ) getpassword() getuserdata() addaccountexplicitly() peekauthtoken() setauthtoken() setpassword() setuserdata() renameaccount() パスワードの 取 得 利 用 者 情 報 の 取 得 アカウントの DB への 追 加 キャッシュされたトークンの 取 得 認 証 トークンの 登 録 パスワードの 変 更 利 用 者 情 報 の 設 定 アカウント 名 の 変 更 GET_ACCOUNTS getaccounts() すべてのアカウントの 一 覧 取 得 getaccountsbytype() アカウントタイプが 同 じアカウントの 一 覧 取 得 getaccountsbytypeandfeatures() 指 定 した 機 能 を 持 ったアカウントの 一 覧 取 得 addonaccountsupdatedlistener() hasfeatures() イベントリスナーの 登 録 指 定 した 機 能 の 有 無 MANAGE_ACCOUNTS getauthtokenbyfeatures() 指 定 した 機 能 を 持 つアカウントの 認 証 トークンの 取 得 addaccount() removeaccount() clearpassword() updatecredentials() editproperties() confirmcredentials() ユーザーへのアカウント 追 加 要 請 アカウントの 削 除 パスワードの 初 期 化 ユーザーへのパスワード 変 更 要 請 Authenticator の 設 定 変 更 ユーザーへのパスワード 再 入 力 要 USE_CREDENTIALS getauthtoken() 認 証 トークンの 取 得 請 MANAGE_ACCOUNTS または USE_CREDENTIALS blockinggetauthtoken() invalidateauthtoken() 認 証 トークンの 取 得 キャッシュされたトークンの 削 除 356 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する

359 ここで AUTHENTICATE_ACCOUNTS Permission が 必 要 なメソッド 群 を 使 う 場 合 には Permission に 加 えてパッ ケージの 署 名 鍵 に 関 する 制 限 が 設 けられている 具 体 的 には Authenticator を 提 供 するパッケージの 署 名 に 使 う 鍵 とメソッドを 使 うアプリのパッケージの 署 名 に 使 う 鍵 が 同 じでなければならない そのため Authenticator 以 外 に AUTHENTICATE_ACCOUNTS Permission が 必 要 なメソッド 群 を 使 うアプリを 配 布 する 際 には Authenticator と 同 じ 鍵 で 署 名 を 施 すことになる Android Studio での 開 発 の 際 には 設 定 した 署 名 鍵 が 固 定 で 使 われるため 鍵 のことを 意 識 せずに Permission だ けで 実 装 や 動 作 確 認 が 出 来 てしまう 特 にアプリによって 署 名 鍵 を 使 い 分 けている 開 発 者 は この 制 限 を 考 慮 してア プリに 使 う 鍵 を 選 定 する 必 要 があるので 注 意 をすること また Account Manager から 取 得 するデータにはセンシテ ィブな 情 報 が 含 まれるため 漏 洩 や 不 正 利 用 などのリスクを 減 らすように 扱 いには 十 分 注 意 すること Android 4.0.x では 利 用 アプリと Authenticator アプリの 署 名 鍵 が 異 なると 例 外 が 発 生 する Authenticator を 含 む Authenticator アプリと 異 なる 開 発 者 鍵 で 署 名 された 利 用 アプリから 認 証 トークンの 取 得 機 能 が 要 求 された 場 合 Account Manager は 認 証 トークン 使 用 許 諾 画 面 (GrantCredentialsPermissionActivity) を 表 示 してユーザーに 認 証 トークンの 使 用 可 否 を 確 認 する しかし Android 4.0.x の Android Framework には 不 具 合 があり Account Manager によってこの 画 面 が 開 かれた 途 端 例 外 が 発 生 し アプリが 強 制 終 了 してしまう ( 図 5.3-3) 不 具 合 の 詳 細 は に 記 載 され ている Android 4.1.x 以 降 ではこの 不 具 合 はない Android 4.0.x Android 4.1.x 図 5.3-3Android 標 準 の 認 証 トークン 使 用 許 諾 画 面 を 表 示 した 場 合 All rights reserved Japan Smartphone Security Association. Account Manager に 独 自 アカウントを 追 加 する 357

360 5.4. HTTPS で 通 信 する スマートフォンアプリはインターネット 上 の Web サーバーと 通 信 するものが 多 い その 通 信 方 式 として 当 ガイドでは HTTP と HTTPS の 2 方 式 に 着 目 する この 2 方 式 のうち セキュリティの 観 点 では HTTPS による 通 信 が 望 ましい 近 年 Google や Facebook など 大 手 の Web サービスは HTTPS による 接 続 を 基 本 とするように 変 わってきた 2012 年 には Android アプリの HTTPS 通 信 の 実 装 方 法 における 欠 陥 が 多 く 指 摘 された これは 信 頼 できる 第 三 者 認 証 局 から 発 行 されたサーバー 証 明 書 ではなく 私 的 に 発 行 されたサーバー 証 明 書 ( 以 降 プライベート 証 明 書 と 呼 ぶ)により 運 用 されているテスト 用 Web サーバーに 接 続 するために 実 装 された 欠 陥 であると 推 察 される この 記 事 では HTTP および HTTPS 通 信 の 方 法 について 説 明 する HTTPS 通 信 の 方 法 には プライベート 証 明 書 で 運 用 されている Web サーバーに 安 全 に 接 続 する 方 法 も 含 む サンプルコード 開 発 しているアプリの 通 信 処 理 の 特 性 を 踏 まえ 図 に 従 いサンプルコードを 選 択 すること はじめ センシティブな 情 報 を 送 信 または 受 信 するか? Yes No 接 続 先 サーバーを 認 証 するか? Yes No サーバーでは CAが 発 行 したサーバー 証 明 書 を 使 うか? No Yes HTTP 通 信 する HTTPS 通 信 する 私 的 証 明 書 でHTTPS 通 信 する 図 HTTP/HTTPS のサンプルコードを 選 択 するフローチャート センシティブな 情 報 を 送 受 信 する 場 合 は SSL/TLS で 通 信 経 路 が 暗 号 化 される HTTPS 通 信 を 用 いる HTTPS 通 信 を 358 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

361 用 いたほうが 良 いセンシティブな 情 報 としては 以 下 のようなものがある Web サービスへのログイン ID パスワード 認 証 状 態 を 維 持 するための 情 報 (セッション ID トークン Cookie の 情 報 など) その 他 Web サービスの 特 性 に 応 じた 重 要 情 報 秘 密 情 報 ( 個 人 情 報 やクレジットカード 情 報 など) ここで 対 象 となっているスマートフォンアプリは サーバーと 通 信 を 行 うことで 連 携 し 構 築 されるシステムの 一 部 を 担 っている 従 って 通 信 のどの 部 分 を HTTP もしくは HTTPS とするのかについては システム 全 体 を 考 慮 して 適 切 な セキュア 設 計 セキュアコーディングを 施 すこと HTTP と HTTPS の 通 信 方 式 の 違 いは 表 を 参 考 にすること またサンプルコードの 違 いについては 表 を 参 考 にすること 表 HTTP 通 信 方 式 HTTPS 通 信 方 式 の 比 較 HTTP HTTPS 特 徴 URL 始 まる 始 まる 通 信 内 容 の 暗 号 化 なし あり 通 信 内 容 の 改 ざん 検 知 不 可 可 接 続 先 サーバーの 認 証 不 可 可 被 害 攻 撃 者 による 通 信 内 容 の 読 み 取 り 高 低 リスク 攻 撃 者 による 通 信 内 容 の 書 き 換 え 高 低 アプリの 偽 サーバーへの 接 続 高 低 表 HTTP/HTTPS 通 信 のサンプルコードの 説 明 サンプルコード 通 信 センシティブな 情 サーバー 証 明 書 報 の 送 受 信 HTTP 通 信 する HTTP - HTTPS 通 信 する HTTPS Cybertrust や VeriSign 等 の 第 三 者 認 証 局 により 発 行 されたサーバー 証 明 書 プライベート 証 明 書 で HTTPS 通 信 する HTTPS プライベート 証 明 書 イントラサーバーやテストサーバーで 良 くみられ る 運 用 形 態 なお Android が サ ポ ー ト し 現 在 広 く 使 わ れ て い る HTTP/HTTPS 通 信 用 API は Java SDK 由 来 の java.net.httpurlconnection/javax.net.ssl.httpsurlconnection である Apache HTTPComponent 由 来 の Apache HttpClient ライブラリについては Android 6.0(API Level 23)でサポートが 打 ち 切 られている All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 359

362 HTTP 通 信 する Android アプリのセキュア 設 計 セキュアコーディングガイド HTTP 通 信 で 送 受 信 する 情 報 はすべて 攻 撃 者 に 盗 聴 改 ざんされる 可 能 性 があることを 前 提 としなければならない また 接 続 先 サーバーも 攻 撃 者 が 用 意 した 偽 物 のサーバーに 接 続 することがあることも 前 提 としなければならない こ のような 前 提 においても 被 害 が 生 じない または 許 容 範 囲 に 収 まる 用 途 のアプリにおいてのみ HTTP 通 信 を 利 用 で きる こうした 前 提 を 受 け 入 れられないアプリについては HTTPS 通 信 する や プライベート 証 明 書 で HTTPS 通 信 する を 参 照 すること 以 下 のサンプルコードは Web サーバー 上 で 画 像 検 索 を 行 い 検 索 画 像 を 取 得 して 表 示 するアプリである 1 回 の 検 索 でサーバーと HTTP 通 信 を 2 回 行 う 1 回 目 の 通 信 で 画 像 検 索 を 実 施 し 2 回 目 の 通 信 で 画 像 を 取 得 する UI ス レッドでの 通 信 を 避 けるために AsyncTask を 利 用 して 通 信 処 理 用 のワーカースレッドを 作 成 している Web サーバ ーとの 通 信 で 送 受 信 する 情 報 は 画 像 の 検 索 文 字 列 画 像 の URL 画 像 データだが どれもセンシティブな 情 報 はな いとみなしている そのため 受 信 データである 画 像 の URL と 画 像 データは 攻 撃 者 が 用 意 した 攻 撃 用 のデータであ る 可 能 性 がある 簡 単 のため サンプルコードでは 受 信 データが 攻 撃 データであっても 許 容 されるとして 対 策 を 施 して いない また 同 様 の 理 由 により JSON パース 時 や 画 像 データを 表 示 する 時 に 発 生 する 可 能 性 のある 例 外 に 対 する 例 外 処 理 を 省 略 している アプリの 仕 様 に 応 じて 適 切 に 処 理 を 実 装 する 必 要 があることに 注 意 すること ポイント: 1. 送 信 データにセンシティブな 情 報 を 含 めない 2. 受 信 データが 攻 撃 者 からの 送 信 データである 場 合 を 想 定 する HttpImageSearch.java package org.jssec.android.https.imagesearch; import android.os.asynctask; import org.json.jsonexception; import org.json.jsonobject; import java.io.bufferedinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.net.httpurlconnection; import java.net.url; public abstract class HttpImageSearch extends AsyncTask<String, Void, Object> { protected Object doinbackground(string... params) { byte[] responsearray; // // 通 信 1 回 目 : 画 像 検 索 する // // ポイント 1 送 信 データにセンシティブな 情 報 を 含 めない // 画 像 検 索 文 字 列 を 送 信 する StringBuilder s = new StringBuilder(); for (String param : params){ s.append(param); s.append('+'); 360 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

363 s.deletecharat(s.length() - 1); String search_url = " + s.tostring(); responsearray = getbytearray(search_url); if (responsearray == null) { return null; // ポイント 2 受 信 データが 攻 撃 者 からの 送 信 データである 場 合 を 想 定 する // サンプルにつき 検 索 結 果 が 攻 撃 者 からのデータである 場 合 の 処 理 は 割 愛 // サンプルにつき JSON パース 時 の 例 外 処 理 は 割 愛 String image_url; try { String json = new String(responseArray); image_url = new JSONObject(json).getJSONObject("responseData").getJSONArray("results").getJSONObject(0).getString("url"); catch(jsonexception e) { return e; // // 通 信 2 回 目 : 画 像 を 取 得 する // // ポイント 1 送 信 データにセンシティブな 情 報 を 含 めない if (image_url!= null ) { responsearray = getbytearray(image_url); if (responsearray == null) { return null; // ポイント 2 受 信 データが 攻 撃 者 からの 送 信 データである 場 合 を 想 定 する return responsearray; private byte[] getbytearray(string strurl) { byte[] buff = new byte[1024]; byte[] result = null; HttpURLConnection response; BufferedInputStream inputstream = null; ByteArrayOutputStream responsearray = null; int length; try { URL url = new URL(strUrl); response = (HttpURLConnection) url.openconnection(); response.setrequestmethod("get"); response.connect(); checkresponse(response); inputstream = new BufferedInputStream(response.getInputStream()); responsearray = new ByteArrayOutputStream(); while ((length = inputstream.read(buff))!= -1) { if (length > 0) { responsearray.write(buff, 0, length); All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 361

364 Android アプリのセキュア 設 計 セキュアコーディングガイド result = responsearray.tobytearray(); catch (IOException e) { e.printstacktrace(); finally { if (inputstream!= null) { try { inputstream.close(); catch (IOException e) { // 例 外 処 理 は 割 愛 if (responsearray!= null) { try { responsearray.close(); catch (IOException e) { // 例 外 処 理 は 割 愛 return result; private void checkresponse(httpurlconnection response) throws IOException { int statuscode = response.getresponsecode(); if (HttpURLConnection.HTTP_OK!= statuscode) { throw new IOException("HttpStatus: " + statuscode); ImageSearchActivity.java package org.jssec.android.https.imagesearch; import android.app.activity; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.os.asynctask; import android.os.bundle; import android.view.view; import android.widget.edittext; import android.widget.imageview; import android.widget.textview; public class ImageSearchActivity extends Activity { private EditText mquerybox; private TextView mmsgbox; private ImageView mimgbox; private AsyncTask<String, Void, Object> masynctask ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mquerybox = (EditText)findViewById(R.id.querybox); mmsgbox = (TextView)findViewById(R.id.msgbox); mimgbox = (ImageView)findViewById(R.id.imageview); 362 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

365 Android アプリのセキュア 設 計 セキュアコーディングガイド protected void onpause() { // このあと Activity が 破 棄 される 可 能 性 があるので 非 同 期 処 理 をキャンセルしておく if (masynctask!= null) masynctask.cancel(true); super.onpause(); public void onhttpsearchclick(view view) { String query = mquerybox.gettext().tostring(); mmsgbox.settext(" + query); mimgbox.setimagebitmap(null); // 直 前 の 非 同 期 処 理 が 終 わってないこともあるのでキャンセルしておく if (masynctask!= null) masynctask.cancel(true); // UI スレッドで 通 信 してはならないので AsyncTask によりワーカースレッドで 通 信 する masynctask = new HttpImageSearch() { protected void onpostexecute(object result) { // UI スレッドで 通 信 結 果 を 処 理 する if (result == null) { mmsgbox.append("\n 例 外 発 生 \n"); else if (result instanceof Exception) { Exception e = (Exception)result; mmsgbox.append("\n 例 外 発 生 \n" + e.tostring()); else { // サンプルにつき 画 像 表 示 の 際 の 例 外 処 理 は 割 愛 byte[] data = (byte[])result; Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); mimgbox.setimagebitmap(bmp);.execute(query); // 検 索 文 字 列 を 渡 して 非 同 期 処 理 を 開 始 public void onhttpssearchclick(view view) { String query = mquerybox.gettext().tostring(); mmsgbox.settext(" + query); mimgbox.setimagebitmap(null); // 直 前 の 非 同 期 処 理 が 終 わってないこともあるのでキャンセルしておく if (masynctask!= null) masynctask.cancel(true); // UI スレッドで 通 信 してはならないので AsyncTask によりワーカースレッドで 通 信 する masynctask = new HttpsImageSearch() { protected void onpostexecute(object result) { // UI スレッドで 通 信 結 果 を 処 理 する if (result instanceof Exception) { Exception e = (Exception)result; mmsgbox.append("\n 例 外 発 生 \n" + e.tostring()); else { byte[] data = (byte[])result; Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); mimgbox.setimagebitmap(bmp);.execute(query); // 検 索 文 字 列 を 渡 して 非 同 期 処 理 を 開 始 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 363

366 Android アプリのセキュア 設 計 セキュアコーディングガイド AndroidManifest.xml <manifest xmlns:android=" package="org.jssec.android.https.imagesearch" android:versioncode="1" android:versionname="1.0"> <uses-permission android:name="android.permission.internet"/> <application android:allowbackup="false" > <activity android:name=".imagesearchactivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.main" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity> </application> </manifest> 364 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

367 HTTPS 通 信 する HTTPS 通 信 では 送 受 信 するデータが 暗 号 化 されるだけでなく 接 続 先 サーバーが 本 物 かどうかの 検 証 も 行 われる そのために HTTPS 通 信 開 始 時 のハンドシェイク 処 理 において サーバーから 送 られてくるサーバー 証 明 書 に 対 して Android の HTTPS ライブラリ 内 部 で 次 のような 観 点 で 検 証 が 行 われる 第 三 者 認 証 局 により 署 名 されたサーバー 証 明 書 であること サーバー 証 明 書 の 期 限 等 が 有 効 であること サーバー 証 明 書 の Subject の CN(Common Name)または SAN(Subject Altername Names)の DNS 名 が 接 続 先 サーバーのホスト 名 と 一 致 していること これらの 検 証 に 失 敗 するとサーバー 証 明 書 検 証 エラー(SSLException)が 発 生 する サーバー 証 明 書 に 不 備 がある 場 合 もしくは 攻 撃 者 が 中 間 者 攻 撃 22をしている 場 合 にこのエラーが 発 生 する エラーが 発 生 した 場 合 には アプリの 仕 様 に 応 じて 適 切 な 処 理 を 実 行 する 必 要 がある 点 に 注 意 すること ここでは 第 三 者 認 証 局 から 発 行 されたサーバー 証 明 書 で 運 用 されている Web サーバーに 接 続 する HTTPS 通 信 の サンプルコードを 示 す 第 三 者 認 証 局 から 発 行 されたサーバー 証 明 書 ではなく 私 的 に 発 行 したサーバー 証 明 書 で HTTPS 通 信 を 実 現 したい 場 合 には プライベート 証 明 書 で HTTPS 通 信 する を 参 照 すること 以 下 のサンプルコードは Web サーバー 上 で 画 像 検 索 を 行 い 検 索 画 像 を 取 得 して 表 示 するアプリである 1 回 の 検 索 でサーバーと HTTPS 通 信 を 2 回 行 う 1 回 目 の 通 信 で 画 像 検 索 を 実 施 し 2 回 目 の 通 信 で 画 像 を 取 得 する UI スレッドでの 通 信 を 避 けるために AsyncTask を 利 用 して 通 信 処 理 用 のワーカースレッドを 作 成 している Web サー バーとの 通 信 で 送 受 信 する 情 報 は 画 像 の 検 索 文 字 列 画 像 の URL 画 像 データで 全 てセンシティブな 情 報 とみな している なお 簡 単 のため SSLException に 対 してはユーザーへの 通 知 などの 例 外 処 理 を 行 っていないが アプ リの 仕 様 に 応 じて 適 切 な 処 理 を 実 装 する 必 要 がある また 以 下 のサンプルコードでは SSLv3 を 用 いた 通 信 が 許 容 さ れている SSLv3 の 脆 弱 性 ( 通 称 POODLE)に 対 する 攻 撃 を 回 避 するためには 接 続 先 サーバーにおいて SSLv3 を 無 効 化 する 設 定 を 施 すことをお 勧 めする ポイント: 1. URI は 始 める 2. 送 信 データにセンシティブな 情 報 を 含 めてよい 3. HTTPS 接 続 したサーバーからのデータであっても 受 信 データの 安 全 性 を 確 認 する 4. SSLException に 対 してアプリに 適 した 例 外 処 理 を 行 う HttpsImageSearch.java package org.jssec.android.https.imagesearch; import org.json.jsonexception; import org.json.jsonobject; 22 中 間 者 攻 撃 については 次 のページを 参 照 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 365

368 import android.os.asynctask; import java.io.bufferedinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.net.httpurlconnection; import java.net.url; public abstract class HttpsImageSearch extends AsyncTask<String, Void, Object> { protected Object doinbackground(string... params) { byte[] responsearray; // // 通 信 1 回 目 : 画 像 検 索 する // // ポイント 1 URI は 始 める // ポイント 2 送 信 データにセンシティブな 情 報 を 含 めてよい StringBuilder s = new StringBuilder(); for (String param : params){ s.append(param); s.append('+'); s.deletecharat(s.length() - 1); String search_url = " + s.tostring(); responsearray = getbytearray(search_url); if (responsearray == null) { return null; // ポイント 3 HTTPS 接 続 したサーバーからのデータであっても 受 信 データの 安 全 性 を 確 認 する // サンプルにつき 割 愛 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 String image_url; try { String json = new String(responseArray); image_url = new JSONObject(json).getJSONObject("responseData").getJSONArray("results").getJSONObject(0).getString("url"); catch(jsonexception e) { return e; // // 通 信 2 回 目 : 画 像 を 取 得 する // // ポイント 1 URI は 始 める // ポイント 2 送 信 データにセンシティブな 情 報 を 含 めてよい if (image_url!= null ) { responsearray = getbytearray(image_url); if (responsearray == null) { return null; return responsearray; 366 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

369 private byte[] getbytearray(string strurl) { byte[] buff = new byte[1024]; byte[] result = null; HttpURLConnection response; BufferedInputStream inputstream = null; ByteArrayOutputStream responsearray = null; int length; try { URL url = new URL(strUrl); response = (HttpURLConnection) url.openconnection(); response.setrequestmethod("get"); response.connect(); checkresponse(response); inputstream = new BufferedInputStream(response.getInputStream()); responsearray = new ByteArrayOutputStream(); while ((length = inputstream.read(buff))!= -1) { if (length > 0) { responsearray.write(buff, 0, length); result = responsearray.tobytearray(); catch (IOException e) { e.printstacktrace(); finally { if (inputstream!= null) { try { inputstream.close(); catch (IOException e) { // 例 外 処 理 は 割 愛 if (responsearray!= null) { try { responsearray.close(); catch (IOException e) { // 例 外 処 理 は 割 愛 return result; private void checkresponse(httpurlconnection response) throws IOException { int statuscode = response.getresponsecode(); if (HttpURLConnection.HTTP_OK!= statuscode) { throw new IOException("HttpStatus: " + statuscode); サンプルコードの 他 のファイルについては HTTP 通 信 する と 共 用 しているので HTTP 通 信 する も 参 照 すること All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 367

370 プライベート 証 明 書 で HTTPS 通 信 する ここでは 第 三 者 認 証 局 から 発 行 されたサーバー 証 明 書 ではなく 私 的 に 発 行 したサーバー 証 明 書 (プライベート 証 明 書 )で HTTPS 通 信 をするサンプルコードを 示 す プライベート 認 証 局 のルート 証 明 書 とプライベート 証 明 書 の 作 成 方 法 および Web サーバーの HTTPS 設 定 については プライベート 証 明 書 の 作 成 方 法 とサーバー 設 定 を 参 考 にすること またサンプルプログラムの assets 中 の cacert.crt ファイルはプライベート 認 証 局 のルート 証 明 書 ファ イルである 以 下 のサンプルコードは Web サーバー 上 の 画 像 を 取 得 して 表 示 するアプリである Web サーバーとは HTTPS を 用 いた 通 信 を 行 う UI スレッドでの 通 信 を 避 けるために AsyncTask を 利 用 して 通 信 処 理 用 のワーカースレッドを 作 成 している Web サーバーとの 通 信 で 送 受 信 する 情 報 は 画 像 の URL と 画 像 データで このサンプルではどちらもセンシ ティブな 情 報 とみなしている また 簡 単 のため SSLException に 対 してはユーザーへの 通 知 などの 例 外 処 理 を 行 っていないが アプリの 仕 様 に 応 じて 適 切 な 処 理 を 実 装 する 必 要 がある ポイント: 1. プライベート 認 証 局 のルート 証 明 書 でサーバー 証 明 書 を 検 証 する 2. URI は 始 める 3. 送 信 データにセンシティブな 情 報 を 含 めてよい 4. 受 信 データを 接 続 先 サーバーと 同 じ 程 度 に 信 用 してよい 5. SSLException に 対 しユーザーに 通 知 する 等 の 適 切 な 例 外 処 理 をする PrivateCertificateHttpsGet.java package org.jssec.android.https.privatecertificate; import java.io.bufferedinputstream; import java.io.bytearrayoutputstream; import java.io.ioexception; import java.net.httpurlconnection; import java.net.url; import java.security.keystore; import java.security.securerandom; import javax.net.ssl.hostnameverifier; import javax.net.ssl.httpsurlconnection; import javax.net.ssl.sslcontext; import javax.net.ssl.sslexception; import javax.net.ssl.sslsession; import javax.net.ssl.trustmanagerfactory; import android.content.context; import android.os.asynctask; public abstract class PrivateCertificateHttpsGet extends AsyncTask<String, Void, Object> { private Context mcontext; public PrivateCertificateHttpsGet(Context context) { mcontext = context; 368 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

371 protected Object doinbackground(string... params) { TrustManagerFactory trustmanager; BufferedInputStream inputstream = null; ByteArrayOutputStream responsearray = null; byte[] buff = new byte[1024]; int length; try { URL url = new URL(params[0]); // ポイント 1 プライベート 証 明 書 でサーバー 証 明 書 を 検 証 する // assets に 格 納 しておいたプライベート 証 明 書 だけを 含 む KeyStore を 設 定 KeyStore ks = KeyStoreUtil.getEmptyKeyStore(); KeyStoreUtil.loadX509Certificate(ks, mcontext.getresources().getassets().open("cacert.crt")); // ホスト 名 の 検 証 を 行 う HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { public boolean verify(string hostname, SSLSession session) { if (!hostname.equals(session.getpeerhost())) { return false; return true; ); // ポイント 2 URI は 始 める // ポイント 3 送 信 データにセンシティブな 情 報 を 含 めてよい trustmanager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustmanager.init(ks); SSLContext sslcon = SSLContext.getInstance("TLS"); sslcon.init(null, trustmanager.gettrustmanagers(), new SecureRandom()); HttpURLConnection con = (HttpURLConnection)url.openConnection(); HttpsURLConnection response = (HttpsURLConnection)con; response.setdefaultsslsocketfactory(sslcon.getsocketfactory()); response.setsslsocketfactory(sslcon.getsocketfactory()); checkresponse(response); // ポイント 4 受 信 データを 接 続 先 サーバーと 同 じ 程 度 に 信 用 してよい inputstream = new BufferedInputStream(response.getInputStream()); responsearray = new ByteArrayOutputStream(); while ((length = inputstream.read(buff))!= -1) { if (length > 0) { responsearray.write(buff, 0, length); return responsearray.tobytearray(); catch(sslexception e) { // ポイント 5 SSLException に 対 しユーザーに 通 知 する 等 の 適 切 な 例 外 処 理 をする // サンプルにつき 例 外 処 理 は 割 愛 return e; catch(exception e) { return e; finally { if (inputstream!= null) { try { inputstream.close(); All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 369

372 Android アプリのセキュア 設 計 セキュアコーディングガイド catch (Exception e) { // 例 外 処 理 は 割 愛 if (responsearray!= null) { try { responsearray.close(); catch (Exception e) { // 例 外 処 理 は 割 愛 private void checkresponse(httpurlconnection response) throws IOException { int statuscode = response.getresponsecode(); if (HttpURLConnection.HTTP_OK!= statuscode) { throw new IOException("HttpStatus: " + statuscode); KeyStoreUtil.java package org.jssec.android.https.privatecertificate; import java.io.ioexception; import java.io.inputstream; import java.security.keystore; import java.security.keystoreexception; import java.security.nosuchalgorithmexception; import java.security.cert.certificate; import java.security.cert.certificateexception; import java.security.cert.certificatefactory; import java.security.cert.x509certificate; import java.util.enumeration; public class KeyStoreUtil { public static KeyStore getemptykeystore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore ks = KeyStore.getInstance("BKS"); ks.load(null); return ks; public static void loadandroidcastore(keystore ks) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore aks = KeyStore.getInstance("AndroidCAStore"); aks.load(null); Enumeration<String> aliases = aks.aliases(); while (aliases.hasmoreelements()) { String alias = aliases.nextelement(); Certificate cert = aks.getcertificate(alias); ks.setcertificateentry(alias, cert); public static void loadx509certificate(keystore ks, InputStream is) throws CertificateException, KeyStoreException { 370 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

373 try { CertificateFactory factory = CertificateFactory.getInstance("X509"); X509Certificate x509 = (X509Certificate)factory.generateCertificate(is); String alias = x509.getsubjectdn().getname(); ks.setcertificateentry(alias, x509); finally { try { is.close(); catch (IOException e) { /* 例 外 処 理 は 割 愛 */ PrivateCertificateHttpsActivity.java package org.jssec.android.https.privatecertificate; import android.app.activity; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.os.asynctask; import android.os.bundle; import android.view.view; import android.widget.edittext; import android.widget.imageview; import android.widget.textview; public class PrivateCertificateHttpsActivity extends Activity { private EditText murlbox; private TextView mmsgbox; private ImageView mimgbox; private AsyncTask<String, Void, Object> masynctask ; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); murlbox = (EditText)findViewById(R.id.urlbox); mmsgbox = (TextView)findViewById(R.id.msgbox); mimgbox = (ImageView)findViewById(R.id.imageview); protected void onpause() { // このあと Activity が 破 棄 される 可 能 性 があるので 非 同 期 処 理 をキャンセルしておく if (masynctask!= null) masynctask.cancel(true); super.onpause(); public void onclick(view view) { String url = murlbox.gettext().tostring(); mmsgbox.settext(url); mimgbox.setimagebitmap(null); // 直 前 の 非 同 期 処 理 が 終 わってないこともあるのでキャンセルしておく if (masynctask!= null) masynctask.cancel(true); // UI スレッドで 通 信 してはならないので AsyncTask によりワーカースレッドで 通 信 する masynctask = new PrivateCertificateHttpsGet(this) { All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 371

374 Android アプリのセキュア 設 計 セキュアコーディングガイド protected void onpostexecute(object result) { // UI スレッドで 通 信 結 果 を 処 理 する if (result instanceof Exception) { Exception e = (Exception)result; mmsgbox.append("\n 例 外 発 生 \n" + e.tostring()); else { byte[] data = (byte[])result; Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); mimgbox.setimagebitmap(bmp);.execute(url); // URL を 渡 して 非 同 期 処 理 を 開 始 372 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

375 ルールブック HTTP 通 信 HTTPS 通 信 する 場 合 には 以 下 のルールを 守 ること 2. センシティブな 情 報 は HTTPS 通 信 で 送 受 信 する ( 必 須 ) 3. HTTP 通 信 では 受 信 データの 安 全 性 を 確 認 する ( 必 須 ) 4. SSLException に 対 しユーザーに 通 知 する 等 の 適 切 な 例 外 処 理 をする ( 必 須 ) 5. 独 自 の TrustManager を 作 らない ( 必 須 ) 6. 独 自 の HostnameVerifier は 作 らない ( 必 須 ) センシティブな 情 報 は HTTPS 通 信 で 送 受 信 する ( 必 須 ) HTTP を 使 った 通 信 では 送 受 信 する 情 報 の 盗 聴 改 ざん または 接 続 先 サーバーのなりすましが 起 こる 可 能 性 が ある センシティブな 情 報 は HTTPS 通 信 で 送 受 信 すること HTTP 通 信 では 受 信 データの 安 全 性 を 確 認 する ( 必 須 ) HTTP 通 信 における 受 信 データは 攻 撃 者 が 制 御 可 能 であるため コード 脆 弱 性 を 狙 った 攻 撃 データを 受 信 する 可 能 性 がある あらゆる 値 形 式 のデータを 受 信 することを 想 定 して 受 信 データを 処 理 するコードに 脆 弱 性 がないように 気 を 付 けてコーディングする 必 要 がある また HTTPS 通 信 における 受 信 データについても 受 信 データを 無 条 件 に 安 全 であると 考 えてはならない HTTPS 接 続 先 のサーバーが 攻 撃 者 によって 用 意 されたものである 場 合 や 受 信 デ ータが 接 続 先 サーバーとは 別 の 場 所 で 生 成 されたデータである 場 合 もあるためである 3.2 入 力 データの 安 全 性 を 確 認 する も 参 照 すること SSLException に 対 しユーザーに 通 知 する 等 の 適 切 な 例 外 処 理 をする ( 必 須 ) HTTPS 通 信 ではサーバー 証 明 書 の 検 証 時 に SSLException が 発 生 することがある SSLException はサーバー 証 明 書 の 不 備 が 原 因 となって 発 生 する 証 明 書 の 不 備 は 攻 撃 者 による 中 間 者 攻 撃 によって 発 生 している 可 能 性 がある ので SSLException に 対 しては 適 切 な 例 外 処 理 を 実 装 することが 必 要 である 例 外 処 理 の 例 としては SSLExceptionによる 通 信 失 敗 をユーザーに 通 知 すること あるいはログに 記 録 することが 考 えられる その 一 方 で アプリによってはユーザーに 対 する 特 別 な 通 知 は 必 要 とされないこともありうる このように 実 装 すべき 処 理 はアプ リの 仕 様 や 特 性 によって 異 なるので それらを 十 分 に 検 討 した 上 で 決 定 しなければならない 加 えて SSLException が 発 生 した 場 合 には 中 間 者 攻 撃 を 受 けている 可 能 性 があるので HTTP などの 非 暗 号 化 通 信 によってセンシティブな 情 報 の 送 受 信 を 再 度 試 みるような 実 装 してはならない 独 自 の TrustManager を 作 らない ( 必 須 ) 自 己 署 名 証 明 書 などのプライベート 証 明 書 で HTTPS 通 信 するためには サーバー 証 明 書 検 証 に 使 う KeyStore を 変 更 するだけで 済 む しかしながら 証 明 書 検 証 を 無 効 化 する 危 険 なコード で 説 明 しているように インター All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 373

376 ネット 上 で 公 開 されているサンプルコードには 危 険 な TrustManager を 実 装 する 例 を 紹 介 しているものが 多 くある これらのサンプルを 参 考 にして 実 装 されたアプリは 脆 弱 性 を 作 りこむ 可 能 性 がある プライベート 証 明 書 で HTTPS 通 信 をしたい 場 合 には プライベート 証 明 書 で HTTPS 通 信 する の 安 全 なサ ンプルコードを 参 照 すること 本 来 ならば 独 自 の TrustManager を 安 全 に 実 装 することも 可 能 であるが 暗 号 処 理 や 暗 号 通 信 に 十 分 な 知 識 をもっ た 技 術 者 でなければミスを 作 り 込 む 危 険 性 があるため このルールはあえて 必 須 とした 独 自 の HostnameVerifier は 作 らない ( 必 須 ) 自 己 署 名 証 明 書 などのプライベート 証 明 書 で HTTPS 通 信 するためには サーバー 証 明 書 検 証 に 使 う KeyStore を 変 更 するだけで 済 む しかしながら 証 明 書 検 証 を 無 効 化 する 危 険 なコード で 説 明 しているように インター ネット 上 で 公 開 されているサンプルコードには 危 険 な HostnameVerifier を 利 用 する 例 を 紹 介 しているものが 多 くあ る これらのサンプルを 参 考 にして 実 装 したアプリは 脆 弱 性 を 作 りこむ 可 能 性 がある プライベート 証 明 書 で HTTPS 通 信 をしたい 場 合 には プライベート 証 明 書 で HTTPS 通 信 する の 安 全 なサ ンプルコードを 参 照 すること 本 来 ならば 独 自 の HostnameVerifier を 安 全 に 実 装 することも 可 能 であるが 暗 号 処 理 や 暗 号 通 信 に 十 分 な 知 識 を もった 技 術 者 でなければミスを 作 り 込 む 危 険 性 があるため このルールはあえて 必 須 とした 374 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

377 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド プライベート 証 明 書 の 作 成 方 法 とサーバー 設 定 ここでは Ubuntu や CentOS などの Linux 環 境 におけるプライベート 証 明 書 の 作 成 方 法 とサーバー 設 定 について 説 明 する プライベート 証 明 書 は 私 的 に 発 行 されたサーバー 証 明 書 のことである Cybertrust や VeriSign などの 第 三 者 認 証 局 から 発 行 されたサーバー 証 明 書 と 区 別 してプライベート 証 明 書 と 呼 ばれる プライベート 認 証 局 の 作 成 まずプライベート 証 明 書 を 発 行 するためのプライベート 認 証 局 を 作 成 する Cybertrust や VeriSign などの 第 三 者 認 証 局 と 区 別 してプライベート 認 証 局 と 呼 ばれる 1 つのプライベート 認 証 局 で 複 数 のプライベート 証 明 書 を 発 行 できる プライベート 認 証 局 を 作 成 した PC は 限 られた 信 頼 できる 人 物 しかアクセスできないように 厳 重 に 管 理 されなければ ならない プライベート 認 証 局 を 作 成 するには 下 記 のシェルスクリプト newca.sh および 設 定 ファイル openssl.cnf を 作 成 し 実 行 する シェルスクリプト 中 の CASTART および CAEND は 認 証 局 の 有 効 期 間 CASUBJ は 認 証 局 の 名 称 であるので 作 成 する 認 証 局 に 合 わせて 変 更 すること シェルスクリプト 実 行 の 際 には 認 証 局 アクセスのためのパスワードが 合 計 3 回 聞 かれるので 同 じパスワードを 入 力 すること newca.sh プライベート 認 証 局 を 作 成 するシェルスクリプト #!/bin/bash umask 0077 CONFIG=openssl.cnf CATOP=./CA CAKEY=cakey.pem CAREQ=careq.pem CACERT=cacert.pem CAX509=cacert.crt CASTART= Z # 2013/01/01 00:00:00 GMT CAEND= Z # 2023/01/01 00:00:00 GMT CASUBJ="/CN=JSSEC Private CA/O=JSSEC/ST=Tokyo/C=JP" mkdir -p ${CATOP mkdir -p ${CATOP/certs mkdir -p ${CATOP/crl mkdir -p ${CATOP/newcerts mkdir -p ${CATOP/private touch ${CATOP/index.txt openssl req -new -newkey rsa:2048 -sha256 -subj "${CASUBJ" \ -keyout ${CATOP/private/${CAKEY -out ${CATOP/${CAREQ openssl ca -selfsign -md sha256 -create_serial -batch \ -keyfile ${CATOP/private/${CAKEY \ -startdate ${CASTART -enddate ${CAEND -extensions v3_ca \ -in ${CATOP/${CAREQ -out ${CATOP/${CACERT \ -config ${CONFIG openssl x509 -in ${CATOP/${CACERT -outform DER -out ${CATOP/${CAX509 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 375

378 openssl.cnf - 2 つのシェルスクリプトが 共 通 に 参 照 する openssl コマンドの 設 定 ファイル [ ca ] default_ca = CA_default # The default ca section [ CA_default ] dir =./CA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. #unique_subject = no # Set to 'no' to allow creation of # several ctificates with same subject. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/cacert.pem # The CA certificate serial = $dir/serial # The current serial number crlnumber = $dir/crlnumber # the current crl number # must be commented out to leave a V1 CRL crl = $dir/crl.pem # The current CRL private_key = $dir/private/cakey.pem# The private key RANDFILE = $dir/private/.rand # private random number file x509_extensions = usr_cert # The extentions to add to the cert name_opt = ca_default # Subject Name options cert_opt = ca_default # Certificate field options policy = policy_match [ policy_match ] countryname = match stateorprovincename = match organizationname = supplied organizationalunitname = optional commonname = supplied address = optional [ usr_cert ] basicconstraints=ca:false nscomment = "OpenSSL Generated Certificate" subjectkeyidentifier=hash authoritykeyidentifier=keyid,issuer [ v3_ca ] subjectkeyidentifier=hash authoritykeyidentifier=keyid:always,issuer basicconstraints = CA:true 上 記 シェルスクリプトを 実 行 すると 作 業 ディレクトリ 直 下 に CA というディレクトリが 作 成 される この CA ディレクトリ がプライベート 認 証 局 である CA/cacert.crt ファイルがプライベート 認 証 局 のルート 証 明 書 であり プライ ベート 証 明 書 で HTTPS 通 信 する の assets に 使 用 されたり Android OS の 証 明 書 ストアにプライベート 認 証 局 のルート 証 明 書 をインストールする で Android 端 末 にインストールされたりする プライベート 証 明 書 の 作 成 プライベート 証 明 書 を 作 成 するには 下 記 のシェルスクリプト newsv.sh を 作 成 し 実 行 する シェルスクリプト 中 の SVSTART および SVEND はプライベート 証 明 書 の 有 効 期 間 SVSUBJ は Web サーバーの 名 称 であるので 対 象 Web サーバーに 合 わせて 変 更 すること 特 に SVSUBJ の/CN で 指 定 する Web サーバーのホスト 名 はタイプミスがな 376 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

379 いように 気 を 付 けること シェルスクリプトを 実 行 すると 認 証 局 アクセスのためのパスワードが 聞 かれるので プライベ ート 認 証 局 を 作 成 するときに 指 定 したパスワードを 入 力 すること その 後 合 計 2 回 の y/n を 聞 かれるので y を 入 力 すること newsv.sh - プライベート 証 明 書 を 発 行 するシェルスクリプト #!/bin/bash umask 0077 CONFIG=openssl.cnf CATOP=./CA CAKEY=cakey.pem CACERT=cacert.pem SVKEY=svkey.pem SVREQ=svreq.pem SVCERT=svcert.pem SVX509=svcert.crt SVSTART= Z # 2013/01/01 00:00:00 GMT SVEND= Z # 2023/01/01 00:00:00 GMT SVSUBJ="/CN=selfsigned.jssec.org/O=JSSEC Secure Coding Group/ST=Tokyo/C=JP" openssl genrsa -out ${SVKEY 2048 openssl req -new -key ${SVKEY -subj "${SVSUBJ" -out ${SVREQ openssl ca -md sha256 \ -keyfile ${CATOP/private/${CAKEY -cert ${CATOP/${CACERT \ -startdate ${SVSTART -enddate ${SVEND \ -in ${SVREQ -out ${SVCERT -config ${CONFIG openssl x509 -in ${SVCERT -outform DER -out ${SVX509 上 記 シェルスクリプトを 実 行 すると 作 業 ディレクトリ 直 下 に Web サーバー 用 のプライベートキーファイル svkey.pem およびプライベート 証 明 書 ファイル svcert.pem が 生 成 される Web サーバーが Apache である 場 合 には 設 定 ファイル 中 に 上 で 作 成 した prikey.pem と cert.pem を 次 のように 指 定 するとよい SSLCertificateFile /path/to/svcert.pem SSLCertificateKeyFile /path/to/svkey.pem All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 377

380 Android OS の 証 明 書 ストアにプライベート 認 証 局 のルート 証 明 書 をインストールする プライベート 証 明 書 で HTTPS 通 信 する のサンプルコードは 1 つのアプリにプライベート 認 証 局 のルート 証 明 書 を 持 たせることで プライベート 証 明 書 で 運 用 する Web サーバーに HTTPS 接 続 する 方 法 を 紹 介 した ここで は Android OS にプライベート 認 証 局 のルート 証 明 書 をインストールすることで すべてのアプリがプライベート 証 明 書 で 運 用 する Web サーバーに HTTPS 接 続 する 方 法 を 紹 介 する インストールしてよいのは 信 頼 できる 認 証 局 の 発 行 した 証 明 書 に 限 ることに 注 意 すること まずプライベート 認 証 局 のルート 証 明 書 ファイル cacert.crt を Android 端 末 の 内 部 ストレージにコピーする なおサ ンプルコードで 使 用 しているルート 証 明 書 ファイルは からも 取 得 できる 次 に Android の 設 定 メニューのセキュリティを 開 き 下 図 のような 手 順 を 進 めることで Android OS にルート 証 明 書 をインストールすることができる 図 プライベート 認 証 局 のルート 証 明 書 のインストール 手 順 378 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

381 図 ルート 証 明 書 がインストールされていることの 確 認 Android OS にプライベート 認 証 局 のルート 証 明 書 をインストールすると その 認 証 局 から 発 行 されたプライベート 証 明 書 を す べ て の ア プ リ で 正 し く 証 明 書 検 証 で き る よ う に な る 下 図 は Chrome ブ ラ ウ ザ で を 表 示 した 場 合 の 例 である ルート 証 明 書 を インストールする サーバー 証 明 書 検 証 エラーが 発 生 正 しく 安 全 に 通 信 可 能 図 ルート 証 明 書 のインストール 後 はプライベート 証 明 書 を 正 しく 検 証 できるようになる All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 379

382 この 方 法 を 使 えば HTTPS 通 信 する のサンプルコードでもプライベート 証 明 書 で 運 用 する Web サーバーに HTTPS 接 続 できるようになる 証 明 書 検 証 を 無 効 化 する 危 険 なコード インターネット 上 にはサーバー 証 明 書 検 証 エラーを 無 視 して HTTPS 通 信 をするサンプルコードが 多 数 掲 載 されている これらのサンプルコードはプライベート 証 明 書 を 使 って HTTPS 通 信 を 実 現 する 方 法 として 紹 介 されているため そうし たサンプルコードをコピー&ペーストして 利 用 しているアプリが 多 数 存 在 している 残 念 ながらこうしたサンプルコード は 中 間 者 攻 撃 に 脆 弱 なものであることが 多 く この 記 事 の 冒 頭 で 2012 年 には Android アプリの HTTPS 通 信 の 実 装 方 法 における 欠 陥 が 多 く 指 摘 された と 述 べたように こうしたインターネット 上 の 脆 弱 なサンプルコードを 利 用 し てしまったと 思 われる 多 くの 脆 弱 な Android アプリが 報 告 されている ここではこうした 脆 弱 な HTTPS 通 信 のサンプルコードの 断 片 を 紹 介 する こうしたサンプルコードを 見 かけた 場 合 に は プライベート 証 明 書 で HTTPS 通 信 する のサンプルコードに 置 き 換 えるなどしていただきたい 危 険 : 空 っぽの TrustManager を 作 るケース TrustManager tm = new X509TrustManager() { public void checkclienttrusted(x509certificate[] chain, String authtype) throws CertificateException { // 何 もしない どんな 証 明 書 でも 受 付 ける public void checkservertrusted(x509certificate[] chain, String authtype) throws CertificateException { // 何 もしない どんな 証 明 書 でも 受 付 ける ; public X509Certificate[] getacceptedissuers() { return null; 危 険 : 空 っぽの HostnameVerifier を 作 るケース HostnameVerifier hv = new HostnameVerifier() { public boolean verify(string hostname, SSLSession session) { // 常 に true を 返 す どんなホスト 名 でも 受 付 ける return true; ; 危 険 :ALLOW_ALL_HOSTNAME_VERIFIER を 使 っているケース SSLSocketFactory sf; sf.sethostnameverifier(sslsocketfactory.allow_all_hostname_verifier); 380 All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

383 HTTP リクエストヘッダを 設 定 する 際 の 注 意 点 HTTP および HTTPS 通 信 において 独 自 の HTTP リクエストヘッダを 設 定 したい 場 合 は URLConnection クラスの setrequestproperty()メソッド もしくは addrequestproperty()メソッドを 使 用 する これらメソッドの 引 数 に 外 部 か らの 入 力 データを 用 いる 場 合 は HTTP ヘッダ インジェクションの 対 策 が 必 要 となる HTTP ヘッダ インジェクション による 攻 撃 の 最 初 のステップとなるのは HTTP ヘッダの 区 切 り 文 字 である 改 行 コードを 入 力 データに 含 めることであ るため 入 力 データから 改 行 コードを 排 除 するようにしなければならない HTTP リクエストヘッダを 設 定 する public byte[] openconnection(string strurl, String strlanguage, String strcookie) { // HttpURLConnection は URLConnection の 派 生 クラス HttpURLConnection connection; try { URL url = new URL(strUrl); connection = (HttpURLConnection) url.openconnection(); connection.setrequestmethod("get"); // ポイント HTTP リクエストヘッダに 入 力 値 を 使 用 する 場 合 は アプリケーション 要 件 に 従 って // 入 力 データをチェックする( 3.2 入 力 データの 安 全 性 を 確 認 する を 参 照 ) if (strlanguage.matches("^[a-za-z,-]+$")) { connection.addrequestproperty("accept-language", strlanguage); else { throw new IllegalArgumentException("Invalid Language : " + strlanguage); // ポイント もしくは 入 力 データを URL エンコードする(というアプリケーション 要 件 にする) connection.setrequestproperty("cookie", URLEncoder.encode(strCookie, "UTF-8")); connection.connect(); ~ 省 略 ~ ピンニングによる 検 証 の 注 意 点 と 実 装 例 アプリが HTTPS 通 信 を 行 う 際 は 通 信 開 始 時 のハンドシェイク 処 理 において 接 続 先 サーバーから 送 られてくる 証 明 書 が 第 三 者 認 証 局 により 署 名 されているかどうかの 検 証 が 行 われる しかし 攻 撃 者 が 第 三 者 認 証 局 から 不 正 な 証 明 書 を 入 手 したり 認 証 局 の 署 名 鍵 を 入 手 して 不 正 な 証 明 書 を 作 成 したりした 場 合 その 攻 撃 者 により 不 正 なサーバ ーへの 誘 導 や 中 間 者 攻 撃 が 行 われても アプリはそれらの 攻 撃 をハンドシェイク 処 理 で 検 出 することができず 結 果 として 被 害 につながってしまう 可 能 性 がある このような 不 正 な 第 三 者 認 証 局 の 証 明 書 を 用 いた 中 間 者 攻 撃 に 対 しては ピンニングによる 検 証 が 有 効 である こ れは あらかじめ 接 続 先 サーバーの 証 明 書 や 公 開 鍵 をアプリ 内 に 保 持 しておき それらの 情 報 をハンドシェイク 処 理 で 用 いたり ハンドシェイク 処 理 後 に 再 検 証 したりする 方 法 である ピンニングによる 検 証 は 公 開 鍵 基 盤 (PKI)の 基 礎 である 第 三 者 認 証 局 の 信 頼 性 が 損 なわれた 場 合 に 備 え 通 信 の 安 全 性 を 補 填 する 目 的 で 用 いられる 開 発 者 は 自 身 のアプリが 扱 う 資 産 レベルに 応 じて この 検 証 を 行 うかどうか 検 討 してほしい All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 381

384 アプリ 内 に 保 持 した 証 明 書 公 開 鍵 をハンドシェイク 処 理 で 使 用 する アプリ 内 に 保 持 しておいた 接 続 先 サーバーの 証 明 書 や 公 開 鍵 の 情 報 をハンドシェイク 処 理 で 用 いるためには それ らの 情 報 を 含 めた 独 自 の KeyStore を 作 成 して 通 信 に 用 いる これにより 上 記 のような 不 正 な 第 三 者 認 証 局 の 証 明 書 を 用 いた 中 間 者 攻 撃 が 行 われても ハンドシェイク 処 理 において 不 正 を 検 出 することができるようになる 独 自 の K eystore を 設 定 して HTTPS 通 信 を 行 う 具 体 的 な 方 法 は プライベート 証 明 書 で HTTPS 通 信 する で 紹 介 し たサンプルコードを 参 照 すること アプリ 内 に 保 持 した 証 明 書 公 開 鍵 を 用 いてハンドシェイク 処 理 後 に 再 検 証 する ハンドシェイク 処 理 が 行 われた 後 に 接 続 先 を 再 検 証 するためには まずハンドシェイク 処 理 で 検 証 されシステムに 信 頼 された 証 明 書 チェーンを 取 得 し その 証 明 書 チェーンを あらかじめアプリ 内 に 保 持 しておいた 情 報 と 照 合 する 照 合 の 結 果 保 持 しておいた 情 報 と 一 致 するものが 含 まれていれば 通 信 を 許 可 し 含 まれていなければ 通 信 処 理 を 中 断 させればよい ただし ハンドシェイク 処 理 でシステムに 信 頼 された 証 明 書 チェーンを 取 得 する 際 に 以 下 のメソッドを 使 用 すると 期 待 通 りの 証 明 書 チェーンが 得 られず 結 果 としてピンニングによる 検 証 が 正 常 に 機 能 しなくなってしまう 危 険 がある 23 javax.net.ssl.sslsession.getpeercertificates() javax.net.ssl.sslsession.getpeercertificatechain() これらのメソッドが 返 すのは ハンドシェイク 処 理 でシステムに 信 頼 された 証 明 書 チェーンではなく アプリが 通 信 相 手 から 受 け 取 った 証 明 書 チェーンそのものである そのため 中 間 者 攻 撃 により 不 正 な 第 三 者 認 証 局 の 証 明 書 が 証 明 書 チェーンに 付 け 加 えられても 上 記 のメソッドはハンドシェイク 処 理 でシステムが 信 用 した 証 明 書 だけでなく 本 来 ア プリが 接 続 しようとしていたサーバーの 証 明 書 も 一 緒 に 返 してしまう この 本 来 アプリが 接 続 しようとしていたサーバ ーの 証 明 書 は ピンニングによる 検 証 のためアプリ 内 にあらかじめ 保 持 しておいたものと 同 等 の 証 明 書 なので 再 検 証 を 行 っても 不 正 を 検 出 することができない このような 理 由 から ハンドシェイク 処 理 後 の 再 検 証 を 実 装 する 際 に 上 記 のメソッドを 使 用 することは 避 けるべきである Android 4.2(API Level 17) 以 上 であれば 上 記 のメソッドの 代 わりに net.http.x509trustmanagerextensio ns の checkservertrusted()を 使 用 することで ハンドシェイク 処 理 でシステムに 信 頼 された 証 明 書 チェーンのみを 取 得 することができる X509TrustManagerExtensions を 用 いたピンニング 検 証 の 例 // 正 しい 通 信 先 サーバーの 証 明 書 に 含 まれる 公 開 鍵 の SHA-256 ハッシュ 値 を 保 持 (ピンニング) private static final Set<String> PINS = new HashSet<>(Arrays.asList( new String[] { "d9b1a68fceaa460ac492fb8452ce13bd8c78c6013f989b76f186b1cbba1315c1", 23 この 危 険 性 については 以 下 の 記 事 で 詳 しく 説 明 されている All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する

385 )); Android アプリのセキュア 設 計 セキュアコーディングガイド "cd13bb83c426551c67fabcff38d4496e094d50a20c7c15e886c151deb8531cdc" // AsyncTask のワーカースレッドで 通 信 する protected Object doinbackground(string... strings) { ~ 省 略 ~ // ハンドシェイク 時 の 検 証 によりシステムに 信 頼 された 証 明 書 チェーンを 取 得 する X509Certificate[] chain = (X509Certificate[]) connection.getservercertificates(); X509TrustManagerExtensions trustmanagerext = new X509TrustManagerExtensions((X509TrustManager) (trustmanagerfa ctory.gettrustmanagers()[0])); List<X509Certificate> trustedchain = trustmanagerext.checkservertrusted(chain, "RSA", url.gethost()); // 公 開 鍵 ピンニングを 用 いて 検 証 する boolean isvalidchain = false; for (X509Certificate cert : trustedchain) { PublicKey key = cert.getpublickey(); MessageDigest md = MessageDigest.getInstance("SHA-256"); String keyhash = bytestohex(md.digest(key.getencoded())); // ピンニングしておいた 公 開 鍵 のハッシュ 値 と 比 較 する if(pins.contains(keyhash)) isvalidchain = true; if (isvalidchain) { // 処 理 を 継 続 する else { // 処 理 を 継 続 しない ~ 省 略 ~ private String bytestohex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { String s = String.format("%02x", b); sb.append(s); return sb.tostring(); Google Play 開 発 者 サービスを 利 用 した OpenSSL の 脆 弱 性 対 策 Google Play 開 発 者 サービス(バージョン 5.0 以 降 )では Provider Installer という 仕 組 みが 提 供 されている これ は OpenSSL を 含 む 暗 号 関 連 技 術 の 実 装 である Security Provider の 脆 弱 性 対 策 に 利 用 できる 詳 しくは Google Play 開 発 者 サービスによる Security Provider の 脆 弱 性 対 策 を 参 照 のこと All rights reserved Japan Smartphone Security Association. HTTPS で 通 信 する 383

386 5.5. プライバシー 情 報 を 扱 う 近 年 プライバシー 情 報 を 守 るための 世 界 的 な 潮 流 として プライバシー バイ デザイン が 提 唱 されており この 概 念 に 基 づき 各 国 政 府 においてもプライバシー 保 護 のための 法 制 化 を 進 めているところである スマ-トフォン 内 の 利 用 者 情 報 を 活 用 するアプリは 利 用 者 が 個 人 情 報 やプライバシーの 観 点 から 安 全 安 心 にアプリ を 活 用 できるように 利 用 者 情 報 を 適 切 に 取 り 扱 うとともに 利 用 者 に 対 して 分 かりやすい 説 明 を 行 い 利 用 者 に 利 用 の 可 否 の 選 択 を 促 すことが 求 められる そのためには アプリがどのような 情 報 をどのように 扱 うか 等 を 示 したアプ リ 毎 のアプリケーション プライバシーポリシー( 以 下 アプリ プライバシーポリシー)を 作 成 提 示 するとともに 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 の 取 得 利 用 については 事 前 にユーザーの 同 意 を 得 る 必 要 がある なお アプ リケーション プライバシーポリシーは 従 来 から 存 在 する 個 人 情 報 保 護 方 針 や 利 用 規 約 等 とは 異 なり 別 途 作 成 を 要 するものであることに 留 意 すること プライバシーポリシーの 作 成 や 運 用 に 関 して 詳 しくは 総 務 省 が 提 唱 する スマートフォン プライバシー イニシアティ ブ 及 び スマートフォン プライバシー イニシアティブⅡ ( 以 下 総 務 省 SPI と 省 略 )を 参 照 のこと また 本 記 事 で 扱 う 用 語 については 本 文 内 の 解 説 および 用 語 解 説 を 参 照 すること サンプルコード アプリ プライバシーポリシーの 作 成 には 一 般 に 公 開 されている アプリケーション プライバシーポリシー 作 成 支 援 ツール 24 を 利 用 することもできる このツールの 出 力 は HTML 形 式 および XML 形 式 となっており 概 要 版 アプリケー ション プライバシーポリシーと 詳 細 版 アプリケーション プライバシーポリシーのそれぞれのファイルが 作 成 される 作 成 された XML ファイルには 検 査 用 のタグがつくなど 総 務 省 SPI に 準 拠 した 形 となっている 以 下 のサンプルコードで は 上 記 ツールを 使 って 作 成 した HTML ファイルを 利 用 してアプリ プライバシーポリシーを 提 示 する 例 を 示 す All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

387 図 概 要 版 アプリケーション プライバシーポリシーの 例 具 体 的 には 次 の 判 定 フローに 従 うことで 利 用 するサンプルコードを 判 断 できる はじめ Yes 取 得 するユーザー 情 報 を 外 部 サーバーに 送 信 する No Yes 利 用 者 による 取 り 換 えが 困 難 な 情 報 を サーバーに 送 信 する No Yes 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を サーバーに 送 信 する No [ 包 括 同 意 個 別 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ [ 包 括 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ 開 発 [ 包 括 同 意 なし] アプリ プライバシーポリシーを 組 み 込 んだアプリ 開 発 アプリ プライバシーポリシーを 組 み 込 まないアプリ 図 プライバシー 情 報 を 扱 うサンプルコードを 選 択 するフローチャート ここで 包 括 同 意 とは アプリ 初 回 起 動 時 のアプリ プライバシーポリシーの 提 示 確 認 により アプリがサーバーに 送 信 する 利 用 者 情 報 について 包 括 的 に 同 意 を 得 ることである All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 385

388 また 個 別 同 意 とは 個 々の 利 用 者 情 報 について 送 信 の 直 前 に 個 別 の 同 意 を 得 ることである [ 包 括 同 意 個 別 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ ポイント:([ 包 括 同 意 個 別 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ) 1. 初 回 起 動 時 (アップデート 時 )に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る 2. ユーザーの 包 括 同 意 が 得 られていない 場 合 は 利 用 者 情 報 の 送 信 はしない 3. 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は 個 別 にユーザーの 同 意 を 得 る 4. ユーザーの 個 別 同 意 が 得 られていない 場 合 は 該 当 情 報 の 送 信 はしない 5. ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する 6. 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する 7. ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する 8. 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する 9. アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく MainActivity.java package org.jssec.android.privacypolicy; import java.io.ioexception; import org.json.jsonexception; import org.json.jsonobject; import org.jssec.android.privacypolicy.confirmfragment.dialoglistener; import com.google.android.gms.common.connectionresult; import com.google.android.gms.common.googleplayservicesclient; import com.google.android.gms.common.googleplayservicesutil; import com.google.android.gms.location.locationclient; import android.location.location; import android.os.asynctask; import android.os.bundle; import android.content.intent; import android.content.intentsender; import android.content.sharedpreferences; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.support.v4.app.fragmentactivity; import android.support.v4.app.fragmentmanager; import android.text.editable; import android.text.textwatcher; import android.view.menu; import android.view.menuitem; import android.view.view; import android.widget.textview; import android.widget.toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, Googl eplayservicesclient.onconnectionfailedlistener, DialogListener { private static final String BASE_URL = " private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; 386 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

389 private static final String ID_KEY = "id"; private static final String LOCATION_KEY = "location"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_COMPREHENSIVE_AGREED_KEY = "privacypolicycomprehensiveagreed"; private static final String PRIVACY_POLICY_DISCRETE_TYPE1_AGREED_KEY = "privacypolicydiscretetype1agreed"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private static final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; private String UserId = ""; private LocationClient mlocationclient = null; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int DIALOG_TYPE_PRE_CONFIRMATION = 2; private static final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchhandler = new TextWatcher() { public void beforetextchanged(charsequence s, int start, int count, int after) { public void ontextchanged(charsequence s, int start, int before, int count) { boolean buttonenable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); ; public void aftertextchanged(editable s) { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); // ユーザー 識 別 用 ID をサーバーから 取 得 する new GetDataAsyncTask().execute(); findviewbyid(r.id.buttonstart).setenabled(false); ((TextView) findviewbyid(r.id.edittextnickname)).addtextchangedlistener(watchhandler); int resultcode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); if (resultcode == ConnectionResult.SUCCESS) { mlocationclient = new LocationClient(this, this, this); protected void onstart() { super.onstart(); SharedPreferences pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); int privacypolicyagreed = pref.getint(privacy_policy_comprehensive_agreed_key, -1); All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 387

390 if (privacypolicyagreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // ポイント 1 初 回 起 動 時 (アップデート 時 )に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る // アップデート 時 については 新 しい 利 用 者 情 報 を 扱 うようになった 場 合 にのみ 再 度 包 括 同 意 を 得 る 必 要 があ る ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreePrivacyPol icy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setdialoglistener(this); FragmentManager fragmentmanager = getsupportfragmentmanager(); dialog.show(fragmentmanager, "dialog"); // Location 情 報 取 得 用 if (mlocationclient!= null) { mlocationclient.connect(); protected void onstop() { if (mlocationclient!= null) { mlocationclient.disconnect(); super.onstop(); public void onsendtoserver(view view) { // 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 について 既 に 同 意 を 得 ているか 確 認 する // 実 際 には 送 信 する 情 報 の 種 別 毎 に 同 意 を 得 る 必 要 があることに 注 意 すること SharedPreferences pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); int privacypolicyagreed = pref.getint(privacy_policy_discrete_type1_agreed_key, -1); if (privacypolicyagreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // ポイント 3 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は 個 別 にユーザーの 同 意 を 得 る ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofirmSendLocati on, DIALOG_TYPE_PRE_CONFIRMATION); dialog.setdialoglistener(this); FragmentManager fragmentmanager = getsupportfragmentmanager(); dialog.show(fragmentmanager, "dialog"); else { // 同 意 済 みのため 送 信 処 理 を 開 始 する onpositivebuttonclick(dialog_type_pre_confirmation); public void onpositivebuttonclick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // ポイント 1 初 回 起 動 時 (アップデート 時 )に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る SharedPreferences.Editor pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE).edit(); pref.putint(privacy_policy_comprehensive_agreed_key, getversioncode()); pref.apply(); else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // ポイント 3 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は 個 別 にユーザーの 同 意 を 得 る if (mlocationclient!= null && mlocationclient.isconnected()) { Location currentlocation = mlocationclient.getlastlocation(); if (currentlocation!= null) { String locationdata = "Latitude:" + currentlocation.getlatitude() + ", Longitude:" + currentl ocation.getlongitude(); String nickname = ((TextView) findviewbyid(r.id.edittextnickname)).gettext().tostring(); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + "\n - nickname : " + nick 388 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

391 name + "\n - location : " + locationdata, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, locationdata, nickname); // 同 意 を 得 た 旨 状 態 を 保 存 する // 実 際 には 送 信 する 情 報 の 種 別 毎 に 同 意 を 得 る 必 要 があることに 注 意 すること SharedPreferences.Editor pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE).edit(); pref.putint(privacy_policy_discrete_type1_agreed_key, getversioncode()); pref.apply(); public void onnegativebuttonclick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // ポイント 2 ユーザーの 包 括 同 意 が 得 られていない 場 合 は 利 用 者 情 報 の 送 信 はしない // サンプルアプリではアプリケーションを 終 了 する finish(); else if (type == DIALOG_TYPE_PRE_CONFIRMATION) { // ポイント 4 ユーザーの 個 別 同 意 が 得 られていない 場 合 は 該 当 情 報 の 送 信 はしない // ユーザー 同 意 が 得 られなかったので 何 もしない private int getversioncode() { int versioncode = -1; PackageManager packagemanager = this.getpackagemanager(); try { PackageInfo packageinfo = packagemanager.getpackageinfo(this.getpackagename(), PackageManager.GET_ACT IVITIES); versioncode = packageinfo.versioncode; catch (NameNotFoundException e) { // 例 外 処 理 は 割 愛 return versioncode; public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.action_show_pp: // ポイント 5 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する Intent intent = new Intent(); intent.setclass(this, WebViewAssetsActivity.class); startactivity(intent); return true; case R.id.action_del_id: // ポイント 6 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // ポイント 7 ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 389

392 (); Android アプリのセキュア 設 計 セキュアコーディングガイド // 利 用 者 情 報 の 送 信 を 停 止 した 場 合 包 括 同 意 に 関 する 同 意 は 破 棄 されたものとする SharedPreferences.Editor pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE).edit pref.putint(privacy_policy_comprehensive_agreed_key, 0); pref.apply(); _SHORT).show(); // 本 サンプルでは 利 用 者 情 報 を 送 信 しない 場 合 ユーザーに 提 供 する 機 能 が 無 くなるため // この 段 階 でアプリを 終 了 する この 処 理 はアプリ 毎 の 都 合 に 合 わせて 変 更 すること String message = getstring(r.string.stopsenduserdata); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + message, Toast.LENGTH finish(); return true; return false; public void onconnected(bundle connectionhint) { if (mlocationclient!= null && mlocationclient.isconnected()) { Location currentlocation = mlocationclient.getlastlocation(); if (currentlocation!= null) { String locationdata = "Latitude \t: " + currentlocation.getlatitude() + "\n\tlongitude \t: " + cu rrentlocation.getlongitude(); String text = "\n" + getstring(r.string.your_location_title) + "\n\t" + locationdata; TextView apptext = (TextView) findviewbyid(r.id.apptext); apptext.settext(text); public void onconnectionfailed(connectionresult result) { if (result.hasresolution()) { try { result.startresolutionforresult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); catch (IntentSender.SendIntentException e) { e.printstacktrace(); public void ondisconnected() { mlocationclient = null; private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extmessage = ""; protected String doinbackground(string... params) { // ポイント 8 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する // 本 サンプルではサーバー 側 で 生 成 した ID を 利 用 する SharedPreferences sp = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); UserId = sp.getstring(id_key, null); 390 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

393 if (UserId == null) { // SharedPreferences 内 にトークンが 存 在 しなため サーバーから ID を 取 り 寄 せる try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); // 取 り 寄 せた ID を SharedPreferences に 保 存 する sp.edit().putstring(id_key, UserId).commit(); return UserId; protected void onpostexecute(final String data) { String status = (data!= null)? "success" : "error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { private String extmessage = ""; protected Boolean doinbackground(string... params) { String url = params[0]; String id = params[1]; String location = params.length > 2? params[2] : null; String nickname = params.length > 3? params[3] : null; Boolean result = false; try { JSONObject jsondata = new JSONObject(); jsondata.put(id_key, id); if (location!= null) jsondata.put(location_key, location); if (nickname!= null) jsondata.put(nick_name_key, nickname); NetworkUtil.sendJSON(url, "", jsondata.tostring()); result = true; catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); catch (JSONException e) { extmessage = e.tostring(); return result; protected void onpostexecute(boolean result) { String status = result? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 391

394 Android アプリのセキュア 設 計 セキュアコーディングガイド ConfirmFragment.java package org.jssec.android.privacypolicy; import android.app.activity; import android.app.alertdialog; import android.app.dialog; import android.content.context; import android.content.dialoginterface; import android.content.intent; import android.os.bundle; import android.support.v4.app.dialogfragment; import android.view.layoutinflater; import android.view.view; import android.view.view.onclicklistener; import android.widget.textview; public class ConfirmFragment extends DialogFragment { private DialogListener mlistener = null; public static interface DialogListener { public void onpositivebuttonclick(int type); public void onnegativebuttonclick(int type); public static ConfirmFragment newinstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putint("title", title); args.putint("sentence", sentence); args.putint("type", type); fragment.setarguments(args); return fragment; public Dialog oncreatedialog(bundle args) { // ポイント 1 初 回 起 動 時 に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る // ポイント 3 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は 個 別 にユーザーの 同 意 を 得 る final int title = getarguments().getint("title"); final int sentence = getarguments().getint("sentence"); final int type = getarguments().getint("type"); LayoutInflater inflater = (LayoutInflater) getactivity().getsystemservice(context.layout_inflater_service ); View content = inflater.inflate(r.layout.fragment_comfirm, null); TextView linkpp = (TextView) content.findviewbyid(r.id.tx_link_pp); linkpp.setonclicklistener(new OnClickListener() { public void onclick(view v) { // ポイント 5 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する Intent intent = new Intent(); intent.setclass(getactivity(), WebViewAssetsActivity.class); startactivity(intent); 392 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

395 ); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.seticon(r.drawable.ic_launcher); builder.settitle(title); builder.setmessage(sentence); builder.setview(content); builder.setpositivebutton(r.string.buttonok, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int whichbutton) { if (mlistener!= null) { mlistener.onpositivebuttonclick(type); ); builder.setnegativebutton(r.string.buttonng, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int whichbutton) { if (mlistener!= null) { mlistener.onnegativebuttonclick(type); ); Dialog dialog = builder.create(); dialog.setcanceledontouchoutside(false); return dialog; public void onattach(activity activity) { super.onattach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); mlistener = (DialogListener) activity; public void setdialoglistener(dialoglistener listener) { mlistener = listener; WebViewAssetsActivity.java package org.jssec.android.privacypolicy; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class WebViewAssetsActivity extends Activity { // ポイント 9 アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく private static final String ABST_PP_URL = "file:///android_asset/privacypolicy/app-policy-abst-privacypolicy- 1.0.html"; All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 393

396 public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_webview); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings websettings = webview.getsettings(); websettings.setallowfileaccess(false); webview.loadurl(abst_pp_url); [ 包 括 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ ポイント:[ 包 括 同 意 あり] アプリ プライバシーポリシーを 組 み 込 んだアプリ 1. 初 回 起 動 時 (アップデート 時 )に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る 2. ユーザーの 包 括 同 意 が 得 られていない 場 合 は 利 用 者 情 報 の 送 信 はしない 3. ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する 4. 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する 5. ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する 6. 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する 7. アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく MainActivity.java package org.jssec.android.privacypolicynopreconfirm; import java.io.ioexception; import org.json.jsonexception; import org.json.jsonobject; import org.jssec.android.privacypolicynopreconfirm.mainactivity; import org.jssec.android.privacypolicynopreconfirm.r; import org.jssec.android.privacypolicynopreconfirm.confirmfragment.dialoglistener; import android.os.asynctask; import android.os.bundle; import android.content.intent; import android.content.sharedpreferences; import android.content.pm.packageinfo; import android.content.pm.packagemanager; import android.content.pm.packagemanager.namenotfoundexception; import android.support.v4.app.fragmentactivity; import android.support.v4.app.fragmentmanager; import android.telephony.telephonymanager; import android.text.editable; import android.text.textwatcher; import android.view.menu; import android.view.menuitem; import android.view.view; import android.widget.textview; import android.widget.toast; 394 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

397 public class MainActivity extends FragmentActivity implements DialogListener { private final String BASE_URL = " private final String GET_ID_URI = BASE_URL + "/get_id.php"; private final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private final String DEL_ID_URI = BASE_URL + "/del_id.php"; private final String ID_KEY = "id"; private final String NICK_NAME_KEY = "nickname"; private final String IMEI_KEY = "imei"; private final String PRIVACY_POLICY_AGREED_KEY = "privacypolicyagreed"; private final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private final int DIALOG_TYPE_COMPREHENSIVE_AGREEMENT = 1; private final int VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW = 1; private TextWatcher watchhandler = new TextWatcher() { public void beforetextchanged(charsequence s, int start, int count, int after) { public void ontextchanged(charsequence s, int start, int before, int count) { boolean buttonenable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); ; public void aftertextchanged(editable s) { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); // ユーザー 識 別 用 ID をサーバーから 取 得 する new GetDataAsyncTask().execute(); findviewbyid(r.id.buttonstart).setenabled(false); ((TextView) findviewbyid(r.id.edittextnickname)).addtextchangedlistener(watchhandler); protected void onstart() { super.onstart(); SharedPreferences pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); int privacypolicyagreed = pref.getint(privacy_policy_agreed_key, -1); if (privacypolicyagreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // ポイント 1 初 回 起 動 時 (アップデート 時 )に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る // アップデート 時 については 新 しい 利 用 者 情 報 を 扱 うようになった 場 合 にのみ 再 度 包 括 同 意 を 得 る 必 要 があ All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 395

398 る ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.privacyPolicy, R.string.agreePrivacyPol icy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setdialoglistener(this); FragmentManager fragmentmanager = getsupportfragmentmanager(); dialog.show(fragmentmanager, "dialog"); public void onsendtoserver(view view) { String nickname = ((TextView) findviewbyid(r.id.edittextnickname)).gettext().tostring(); TelephonyManager tm = (TelephonyManager) getsystemservice(telephony_service); String imei = tm.getdeviceid(); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + "\n - nickname : " + nickname + ", im ei = " + imei, Toast.LENGTH_SHORT).show(); new SendDataAsyncTack().execute(SEND_DATA_URI, UserId, nickname, imei); public void onpositivebuttonclick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // ポイント 1 初 回 起 動 時 に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る SharedPreferences.Editor pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE).edit(); pref.putint(privacy_policy_agreed_key, getversioncode()); pref.apply(); public void onnegativebuttonclick(int type) { if (type == DIALOG_TYPE_COMPREHENSIVE_AGREEMENT) { // ポイント 2 ユーザーの 包 括 同 意 が 得 られていない 場 合 は 利 用 者 情 報 の 送 信 はしない // サンプルアプリではアプリケーションを 終 了 する finish(); private int getversioncode() { int versioncode = -1; PackageManager packagemanager = this.getpackagemanager(); try { PackageInfo packageinfo = packagemanager.getpackageinfo(this.getpackagename(), PackageManager.GET_ACT IVITIES); versioncode = packageinfo.versioncode; catch (NameNotFoundException e) { // 例 外 処 理 は 割 愛 return versioncode; public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.action_show_pp: // ポイント 3 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する 396 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

399 Intent intent = new Intent(); intent.setclass(this, WebViewAssetsActivity.class); startactivity(intent); return true; case R.id.action_del_id: // ポイント 4 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; case R.id.action_donot_send_id: // ポイント 5 ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する RT).show(); // 利 用 者 情 報 の 送 信 を 停 止 した 場 合 包 括 同 意 に 関 する 同 意 は 破 棄 されたものとする SharedPreferences.Editor pref = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE).edit(); pref.putint(privacy_policy_agreed_key, 0); pref.apply(); // 本 サンプルでは 利 用 者 情 報 を 送 信 しない 場 合 ユーザーに 提 供 する 機 能 が 無 くなるため // この 段 階 でアプリを 終 了 する この 処 理 はアプリ 毎 の 都 合 に 合 わせて 変 更 すること String message = getstring(r.string.stopsenduserdata); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + message, Toast.LENGTH_SHO finish(); return true; return false; private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extmessage = ""; protected String doinbackground(string... params) { // ポイント 6 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する // 本 サンプルではサーバー 側 で 生 成 した ID を 利 用 する SharedPreferences sp = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); UserId = sp.getstring(id_key, null); if (UserId == null) { // SharedPreferences 内 にトークンが 存 在 しなため サーバーから ID を 取 り 寄 せる try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); // 取 り 寄 せた ID を SharedPreferences に 保 存 する sp.edit().putstring(id_key, UserId).commit(); return UserId; protected void onpostexecute(final String data) { String status = (data!= null)? "success" : "error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); private class SendDataAsyncTack extends AsyncTask<String, Void, Boolean> { All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 397

400 private String extmessage = ""; protected Boolean doinbackground(string... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2? params[2] : null; String imei = params.length > 3? params[3] : null; Boolean result = false; try { JSONObject jsondata = new JSONObject(); jsondata.put(id_key, id); if (nickname!= null) jsondata.put(nick_name_key, nickname); if (imei!= null) jsondata.put(imei_key, imei); NetworkUtil.sendJSON(url, "", jsondata.tostring()); result = true; catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); catch (JSONException e) { extmessage = e.tostring(); return result; protected void onpostexecute(boolean result) { String status = result? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); ConfirmFragment.java package org.jssec.android.privacypolicynopreconfirm; import android.app.activity; import android.app.alertdialog; import android.app.dialog; import android.content.context; import android.content.dialoginterface; import android.content.intent; import android.os.bundle; import android.support.v4.app.dialogfragment; import android.view.layoutinflater; import android.view.view; import android.view.view.onclicklistener; import android.widget.textview; public class ConfirmFragment extends DialogFragment { 398 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

401 private DialogListener mlistener = null; public static interface DialogListener { public void onpositivebuttonclick(int type); public void onnegativebuttonclick(int type); public static ConfirmFragment newinstance(int title, int sentence, int type) { ConfirmFragment fragment = new ConfirmFragment(); Bundle args = new Bundle(); args.putint("title", title); args.putint("sentence", sentence); args.putint("type", type); fragment.setarguments(args); return fragment; public Dialog oncreatedialog(bundle args) { // ポイント 1 初 回 起 動 時 に アプリが 扱 う 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る final int title = getarguments().getint("title"); final int sentence = getarguments().getint("sentence"); final int type = getarguments().getint("type"); ); LayoutInflater inflater = (LayoutInflater) getactivity().getsystemservice(context.layout_inflater_service View content = inflater.inflate(r.layout.fragment_comfirm, null); TextView linkpp = (TextView) content.findviewbyid(r.id.tx_link_pp); linkpp.setonclicklistener(new OnClickListener() { public void onclick(view v) { // ポイント 3 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する Intent intent = new Intent(); intent.setclass(getactivity(), WebViewAssetsActivity.class); startactivity(intent); ); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.seticon(r.drawable.ic_launcher); builder.settitle(title); builder.setmessage(sentence); builder.setview(content); builder.setpositivebutton(r.string.buttonok, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int whichbutton) { if (mlistener!= null) { mlistener.onpositivebuttonclick(type); ); builder.setnegativebutton(r.string.buttonng, new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int whichbutton) { if (mlistener!= null) { mlistener.onnegativebuttonclick(type); ); All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 399

402 Dialog dialog = builder.create(); dialog.setcanceledontouchoutside(false); return dialog; public void onattach(activity activity) { super.onattach(activity); if (!(activity instanceof DialogListener)) { throw new ClassCastException(activity.toString() + " must implement DialogListener."); mlistener = (DialogListener) activity; public void setdialoglistener(dialoglistener listener) { mlistener = listener; WebViewAssetsActivity.java package org.jssec.android.privacypolicynopreconfirm; import org.jssec.android.privacypolicynopreconfirm.r; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class WebViewAssetsActivity extends Activity { // ポイント 7 アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく private final String ABST_PP_URL = "file:///android_asset/privacypolicy/app-policy-abst-privacypolicy-1.0.htm l"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_webview); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings websettings = webview.getsettings(); websettings.setallowfileaccess(false); webview.loadurl(abst_pp_url); 400 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

403 [ 包 括 同 意 なし] アプリ プライバシーポリシーを 組 み 込 んだアプリ ポイント:[ 包 括 同 意 なし] アプリ プライバシーポリシーを 組 み 込 んだアプリ 1. ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する 2. 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する 3. ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する 4. 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する 5. アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく MainActivity.java package org.jssec.android.privacypolicynocomprehensive; import java.io.ioexception; import org.json.jsonexception; import org.json.jsonobject; import android.os.asynctask; import android.os.bundle; import android.content.intent; import android.content.sharedpreferences; import android.support.v4.app.fragmentactivity; import android.text.editable; import android.text.textwatcher; import android.view.menu; import android.view.menuitem; import android.view.view; import android.widget.textview; import android.widget.toast; public class MainActivity extends FragmentActivity { private static final String BASE_URL = " private static final String GET_ID_URI = BASE_URL + "/get_id.php"; private static final String SEND_DATA_URI = BASE_URL + "/send_data.php"; private static final String DEL_ID_URI = BASE_URL + "/del_id.php"; private static final String ID_KEY = "id"; private static final String NICK_NAME_KEY = "nickname"; private static final String PRIVACY_POLICY_PREF_NAME = "privacypolicy_preference"; private String UserId = ""; private TextWatcher watchhandler = new TextWatcher() { public void beforetextchanged(charsequence s, int start, int count, int after) { public void ontextchanged(charsequence s, int start, int before, int count) { boolean buttonenable = (s.length() > 0); MainActivity.this.findViewById(R.id.buttonStart).setEnabled(buttonEnable); All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 401

404 ; Android アプリのセキュア 設 計 セキュアコーディングガイド public void aftertextchanged(editable s) { protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); // ユーザー 識 別 用 ID をサーバーから 取 得 する new GetDataAsyncTask().execute(); findviewbyid(r.id.buttonstart).setenabled(false); ((TextView) findviewbyid(r.id.edittextnickname)).addtextchangedlistener(watchhandler); public void onsendtoserver(view view) { String nickname = ((TextView) findviewbyid(r.id.edittextnickname)).gettext().tostring(); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + "\n - nickname : " + nickname, Toast. LENGTH_SHORT).show(); new senddataasynctack().execute(send_data_uri, UserId, nickname); public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.action_show_pp: // ポイント 1 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する Intent intent = new Intent(); intent.setclass(this, WebViewAssetsActivity.class); startactivity(intent); return true; case R.id.action_del_id: // ポイント 2 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する new senddataasynctack().execute(del_id_uri, UserId); return true; case R.id.action_donot_send_id: // ポイント 3 ユーザー 操 作 により 利 用 者 情 報 の 送 信 を 停 止 する 手 段 を 用 意 する RT).show(); // 本 サンプルでは 利 用 者 情 報 を 送 信 しない 場 合 ユーザーに 提 供 する 機 能 が 無 くなるため // この 段 階 でアプリを 終 了 する この 処 理 はアプリ 毎 の 都 合 に 合 わせて 変 更 すること String message = getstring(r.string.stopsenduserdata); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + message, Toast.LENGTH_SHO finish(); return true; return false; private class GetDataAsyncTask extends AsyncTask<String, Void, String> { private String extmessage = ""; 402 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

405 protected String doinbackground(string... params) { // ポイント 4 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する // 本 サンプルではサーバー 側 で 生 成 した ID を 利 用 する SharedPreferences sp = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); UserId = sp.getstring(id_key, null); if (UserId == null) { // SharedPreferences 内 にトークンが 存 在 しなため サーバーから ID を 取 り 寄 せる try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); // 取 り 寄 せた ID を SharedPreferences に 保 存 する sp.edit().putstring(id_key, UserId).commit(); return UserId; protected void onpostexecute(final String data) { String status = (data!= null)? "success" : "error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); private class senddataasynctack extends AsyncTask<String, Void, Boolean> { private String extmessage = ""; protected Boolean doinbackground(string... params) { String url = params[0]; String id = params[1]; String nickname = params.length > 2? params[2] : null; Boolean result = false; try { JSONObject jsondata = new JSONObject(); jsondata.put(id_key, id); if (nickname!= null) jsondata.put(nick_name_key, nickname); NetworkUtil.sendJSON(url, "", jsondata.tostring()); result = true; catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); catch (JSONException e) { extmessage = e.tostring(); return result; protected void onpostexecute(boolean result) { All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 403

406 String status = result? "Success" : "Error"; Toast.makeText(MainActivity.this, this.getclass().getsimplename() + " - " + status + " : " + extmessa ge, Toast.LENGTH_SHORT).show(); WebViewAssetsActivity.java package org.jssec.android.privacypolicynocomprehensive; import org.jssec.android.privacypolicynocomprehensive.r; import android.app.activity; import android.os.bundle; import android.webkit.websettings; import android.webkit.webview; public class WebViewAssetsActivity extends Activity { // ポイント 5 アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく private static final String ABST_PP_URL = "file:///android_asset/privacypolicy/app-policy-abst-privacypolicy- 1.0.html"; public void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_webview); WebView webview = (WebView) findviewbyid(r.id.webview); WebSettings websettings = webview.getsettings(); websettings.setallowfileaccess(false); webview.loadurl(abst_pp_url); アプリ プライバシーポリシーを 組 み 込 まないアプリ ポイント:(アプリ プライバシーポリシーを 組 み 込 まないアプリ) 1. 取 得 した 情 報 を 端 末 内 部 でのみ 利 用 する 場 合 アプリ プライバシーポリシーを 表 示 しなくても 良 い 2. マーケットプレイス 等 のアプリ 説 明 欄 に 取 得 した 情 報 を 外 部 送 信 しない 旨 を 記 載 する MainActivity.java package org.jssec.android.privacypolicynoinfosent; import com.google.android.gms.common.connectionresult; import com.google.android.gms.common.googleplayservicesclient; import com.google.android.gms.location.locationclient; import android.location.location; import android.net.uri; import android.os.bundle; import android.content.intent; import android.content.intentsender; 404 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

407 import android.support.v4.app.fragmentactivity; import android.view.menu; import android.view.view; import android.widget.textview; import android.widget.toast; public class MainActivity extends FragmentActivity implements GooglePlayServicesClient.ConnectionCallbacks, Googl eplayservicesclient.onconnectionfailedlistener { private LocationClient mlocationclient = null; private final int CONNECTION_FAILURE_RESOLUTION_REQUEST = 257; protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mlocationclient = new LocationClient(this, this, this); protected void onstart() { super.onstart(); // Location 情 報 取 得 用 if (mlocationclient!= null) { mlocationclient.connect(); protected void onstop() { if (mlocationclient!= null) { mlocationclient.disconnect(); super.onstop(); public boolean oncreateoptionsmenu(menu menu) { getmenuinflater().inflate(r.menu.main, menu); return true; public void onstartmap(view view) { // ポイント 1 取 得 した 情 報 を 端 末 内 部 でのみ 利 用 する 場 合 アプリ プライバシーポリシーを 表 示 しなくても 良 い if (mlocationclient!= null && mlocationclient.isconnected()) { Location currentlocation = mlocationclient.getlastlocation(); if (currentlocation!= null) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("geo:" + currentlocation.getlatitude() + "," + currentlocation.getlongitude())); startactivity(intent); public void onconnected(bundle connectionhint) { if (mlocationclient!= null && mlocationclient.isconnected()) { All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 405

408 Location currentlocation = mlocationclient.getlastlocation(); if (currentlocation!= null) { String locationdata = "Latitude \t: " + currentlocation.getlatitude() + "\n\tlongitude \t: " + cu rrentlocation.getlongitude(); String text = "\n" + getstring(r.string.your_location_title) + "\n\t" + locationdata; w(); Toast.makeText(MainActivity.this, this.getclass().getsimplename() + text, Toast.LENGTH_SHORT).sho TextView apptext = (TextView) findviewbyid(r.id.apptext); apptext.settext(text); public void onconnectionfailed(connectionresult result) { if (result.hasresolution()) { try { result.startresolutionforresult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST); catch (IntentSender.SendIntentException e) { e.printstacktrace(); public void ondisconnected() { mlocationclient = null; Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show(); 406 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

409 ポイント 2 マーケットプレイス 等 のアプリ 説 明 欄 に 取 得 した 情 報 を 外 部 送 信 しない 旨 を 記 載 する 図 マーケットプレイス 上 での 説 明 例 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 407

410 ルールブック Android アプリのセキュア 設 計 セキュアコーディングガイド プライバシー 情 報 を 扱 う 際 には 以 下 のルールを 守 ること 1. 送 信 する 利 用 者 情 報 は 必 要 最 低 限 に 留 める ( 必 須 ) 2. 初 回 起 動 時 (アップデート 時 )に ユーザーによる 取 り 換 えが 困 難 または 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る ( 必 須 ) 3. 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は ユーザーに 個 別 同 意 を 得 る ( 必 須 ) 4. ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する ( 必 須 ) 5. アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく ( 推 奨 ) 6. 送 信 した 利 用 者 情 報 をユーザー 操 作 により 削 除 および 送 信 停 止 する 手 段 を 用 意 する ( 推 奨 ) 7. 端 末 固 有 ID と UUID/cookie を 使 い 分 ける ( 推 奨 ) 8. 利 用 者 情 報 を 端 末 内 のみで 利 用 する 場 合 外 部 送 信 しない 旨 をユーザーに 通 知 する ( 推 奨 ) 送 信 する 利 用 者 情 報 は 必 要 最 低 限 に 留 める ( 必 須 ) アプリマニュアルなどの 説 明 を 元 にユーザーが 想 起 できるアプリの 動 作 および 目 的 以 外 で 利 用 者 情 報 にアクセス しないよう 設 計 すること また アプリの 動 作 および 目 的 に 必 要 な 利 用 者 情 報 であっても アプリ 内 部 でのみ 必 要 な 情 報 と 外 部 サーバー 等 に 送 信 すべき 情 報 を 精 査 し 必 要 最 低 限 の 情 報 のみを 送 信 すること 例 えば アラームアプリで 指 定 した 時 間 にアラームが 鳴 る 機 能 しか 提 供 していないにもかかわらず 位 置 情 報 をサ ーバーに 送 信 している 場 合 ユーザーは 位 置 情 報 とアプリの 機 能 を 関 連 付 けることは 難 しい 一 方 で このアラーム アプリが ユーザーがいる 場 所 に 応 じて 設 定 されたアラームを 鳴 らす 機 能 を 持 つ 場 合 は アプリが 位 置 情 報 を 利 用 す る 理 由 を 説 明 することができる このように ユーザーが 機 能 と 利 用 者 情 報 との 関 連 を 理 解 できる 説 明 が 可 能 であれ ば 利 用 者 情 報 の 利 用 に 妥 当 性 があるとみなせる 初 回 起 動 時 (アップデート 時 )に ユーザーによる 取 り 換 えが 困 難 または 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る ( 必 須 ) ユーザーによる 取 り 換 えが 困 難 な 利 用 者 情 報 や 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 外 部 サーバーに 送 信 す る 場 合 ユーザーがアプリを 使 用 する 前 に アプリがどのような 情 報 をどのような 目 的 でサーバーに 送 信 するか 第 三 者 提 供 はあるかなどについて 事 前 にユーザーの 同 意 (オプトイン)を 得 ることが 求 められる 具 体 的 には アプリの 初 回 起 動 時 にアプリ プライバシーポリシーを 提 示 して ユーザーの 確 認 と 同 意 を 得 るようにするべきである また ア プリケーションのアップデートにより 新 たな 利 用 者 情 報 を 外 部 サーバーに 送 信 するようになった 時 にも 再 度 ユーザ ーの 確 認 と 同 意 を 得 るようにする 必 要 がある 同 意 が 得 られない 場 合 は アプリを 終 了 するなど 情 報 の 送 信 が 必 要 な 機 能 ( 処 理 )を 無 効 にすること これにより ユーザーが 利 用 者 情 報 の 利 用 状 況 を 理 解 した 上 でアプリを 使 用 していることが 保 証 され ユーザーに 安 心 感 を 提 供 するとともにアプリへの 信 頼 感 を 得 ることが 期 待 できる MainActivity.java protected void onstart() { 408 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

411 super.onstart(); ~ 省 略 ~ if (privacypolicyagreed <= VERSION_TO_SHOW_COMPREHENSIVE_AGREEMENT_ANEW) { // ポイント 初 回 起 動 時 (アップデート 時 )に ユーザーによる 取 り 換 えが 困 難 または 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 の 送 信 について 包 括 同 意 を 得 る // アップデート 時 については 新 しい 利 用 者 情 報 を 扱 うようになった 場 合 にのみ 再 度 包 括 同 意 を 得 る 必 要 がある ConfirmFragment dialog = ConfirmFragment.newInstance( R.string.privacyPolicy, R.string.agreePrivacyPolicy, DIALOG_TYPE_COMPREHENSIVE_AGREEMENT); dialog.setdialoglistener(this); FragmentManager fragmentmanager = getsupportfragmentmanager(); dialog.show(fragmentmanager, "dialog"); 図 包 括 同 意 の 例 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は ユーザーに 個 別 同 意 を 得 る ( 必 須 ) 慎 重 な 取 扱 いが 求 められる 利 用 者 情 報 を 外 部 サーバーに 送 信 する 場 合 は 包 括 同 意 に 加 えて 利 用 者 情 報 (または 利 用 者 情 報 の 送 信 を 伴 う 機 能 ) 毎 に 事 前 にユーザーの 同 意 (オプトイン)を 得 ることが 必 要 である ユーザーの 同 意 が 得 られなかった 場 合 は 外 部 サーバーへの 情 報 通 信 は 実 施 してはならない これにより ユーザーは 包 括 同 意 で 確 認 した 利 用 者 情 報 の 送 信 に 関 して より 具 体 的 なアプリ 機 能 ( 提 供 サービス)と の 関 連 を 知 ることができ アプリ 提 供 者 はユーザーのより 正 確 な 判 断 による 同 意 を 得 ることが 期 待 できる MainActivity.java public void onsendtoserver(view view) { All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 409

412 // ポイント 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 を 送 信 する 場 合 は 個 別 にユーザーの 同 意 を 得 る ConfirmFragment dialog = ConfirmFragment.newInstance(R.string.sendLocation, R.string.cofirmSendLo cation, DIALOG_TYPE_PRE_CONFIRMATION); dialog.setdialoglistener(this); FragmentManager fragmentmanager = getsupportfragmentmanager(); dialog.show(fragmentmanager, "dialog"); 図 個 別 同 意 の 例 ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する ( 必 須 ) 一 般 に ユーザーが 該 当 アプリをインストールする 前 にアプリ プライバシーポリシーを 確 認 する 手 段 として Android アプリのマーケットプレイス 上 にアプリ プライバシーポリシーのリンクを 表 示 する 機 能 がある この 機 能 への 対 応 に 加 え アプリを 端 末 にインストールした 後 でも ユーザーがアプリ プライバシーポリシーを 参 照 できる 手 段 を 用 意 するこ と 特 に 利 用 者 情 報 を 外 部 サーバー 等 に 送 信 する 場 合 の 個 別 同 意 確 認 時 にはアプリ プライバシーポリシーを 容 易 に 確 認 できる 手 段 を 用 意 し ユーザーが 正 しく 判 断 できるようにすることが 望 ましい MainActivity.java public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.action_show_pp: // ポイント ユーザーがアプリ プライバシーポリシーを 確 認 できる 手 段 を 用 意 する Intent intent = new Intent(); intent.setclass(this, WebViewAssetsActivity.class); startactivity(intent); return true; 410 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

413 図 プライバシーポリシー 表 示 用 メニュー アプリ プライバシーポリシー 概 要 版 を assets フォルダ 内 に 配 置 しておく ( 推 奨 ) アプリ プライバシーポリシーの 概 要 版 は assets フォルダ 内 に 配 置 しておき 必 要 に 応 じて 閲 覧 に 利 用 することが 望 ましい アプリ プライバシーポリシーが assets フォルダにあれば いつでも 容 易 にアクセス 可 能 であると 同 時 に 悪 意 のある 第 三 者 により 偽 造 や 改 造 されたアプリ プライバシーポリシーを 参 照 させられるリスクが 回 避 可 能 なためで ある また assets フォルダに 配 置 するファイルは 利 用 者 情 報 に 関 する 第 三 者 検 証 が 可 能 であることが 望 ましい 本 節 で 紹 介 した アプリケーション プライバシーポリシー 作 成 支 援 ツール を 利 用 する 場 合 検 査 用 タグのある XML ファイルも assets フォルダに 配 置 することで 第 三 者 検 証 が 可 能 になる 送 信 した 利 用 者 情 報 をユーザー 操 作 により 削 除 および 送 信 停 止 する 手 段 を 用 意 する ( 推 奨 ) 利 用 者 の 求 めに 応 じ 外 部 サーバー 上 に 送 信 された 利 用 者 情 報 を 削 除 する 機 能 を 提 供 することが 望 ましい 同 様 に アプリ 自 身 で 端 末 内 に 利 用 者 情 報 (もしくはそのコピー)を 保 存 した 場 合 も その 情 報 を 削 除 する 機 能 をユーザーに 提 供 することが 望 ましい また 利 用 者 の 求 めに 応 じ 利 用 者 情 報 の 送 信 を 停 止 する 機 能 を 提 供 することが 望 ましい 本 ルール( 推 奨 )の 内 利 用 者 情 報 の 削 除 については EU で 提 唱 されている 忘 れられる 権 利 により 定 められたも のであるが 今 後 は 利 用 者 データ 保 護 に 関 する 個 人 の 権 利 の 強 化 も 提 案 されているため 特 に 理 由 がない 限 り 本 ガイドでは 利 用 者 情 報 の 削 除 機 能 を 用 意 することを 推 奨 する また 利 用 者 情 報 の 送 信 停 止 については 主 にブラウ ザによる 対 応 が 進 んでいる Do Not Track( 追 跡 拒 否 ) の 観 点 により 定 められたものであり 削 除 同 様 に 送 信 停 止 機 能 を 用 意 することを 推 奨 する All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 411

414 MainActivity.java public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { ~ 省 略 ~ case R.id.action_del_id: // ポイント 送 信 した 情 報 をユーザー 操 作 により 削 除 する 手 段 を 用 意 する new SendDataAsyncTack().execute(DEL_ID_URI, UserId); return true; 端 末 固 有 ID と UUID/cookie を 使 い 分 ける ( 推 奨 ) IMEI などの 端 末 固 有 ID は 利 用 者 情 報 と 紐 付 けて 送 信 すべきでない 端 末 固 有 ID と 利 用 者 情 報 が 紐 付 いた 形 で 一 度 でも 公 開 や 漏 えいしてしまうと 後 から 端 末 固 有 ID の 変 更 が 不 可 能 なため ID と 利 用 者 情 報 の 紐 づけを 切 るこ とができない( 難 しい)ことが その 理 由 である この 場 合 端 末 固 有 ID に 変 わって UUID/cookie などの 乱 数 をベー スにした 都 度 作 成 する 変 更 可 能 な ID を 使 って 利 用 者 情 報 との 紐 づけおよび 送 信 をすると 良 い これにより 上 記 で 説 明 した 忘 れられる 権 利 を 考 慮 した 実 装 とすることができる MainActivity.java protected String doinbackground(string... params) { // ポイント 利 用 者 情 報 の 紐 づけには UUID/cookie を 利 用 する // 本 サンプルではサーバー 側 で 生 成 した ID を 利 用 する SharedPreferences sp = getsharedpreferences(privacy_policy_pref_name, MODE_PRIVATE); UserId = sp.getstring(id_key, null); if (UserId == null) { // SharedPreferences 内 にトークンが 存 在 しなため サーバーから ID を 取 り 寄 せる try { UserId = NetworkUtil.getCookie(GET_ID_URI, "", "id"); catch (IOException e) { // 証 明 書 エラーなどの 例 外 をキャッチする extmessage = e.tostring(); // 取 り 寄 せた ID を SharedPreferences に 保 存 する sp.edit().putstring(id_key, UserId).commit(); return UserId; 利 用 者 情 報 を 端 末 内 のみで 利 用 する 場 合 外 部 送 信 しない 旨 をユーザーに 通 知 する ( 推 奨 ) 利 用 者 の 端 末 内 部 で 一 時 的 に 利 用 者 情 報 にアクセスするのみの 場 合 であっても 利 用 者 の 理 解 を 助 け 透 明 性 を 高 めるために その 旨 を 伝 えることが 望 ましい 具 体 的 には アクセスした 利 用 者 情 報 は ある 決 まった 目 的 のために 端 末 内 部 で 一 時 的 に 使 用 するのみであり 蓄 積 や 外 部 送 信 をしない 旨 をを 利 用 者 に 通 知 すると 良 い 通 知 方 法 として は マーケットプレイス 上 でのアプリ 説 明 欄 に 記 載 するなどの 方 法 が 考 えられる なお 端 末 内 での 一 時 利 用 のみの 場 合 は アプリ プライバシーポリシーへの 記 載 は 必 須 ではない 412 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

415 図 マーケットプレイス 上 での 説 明 例 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う 413

416 アドバンスト Android アプリのセキュア 設 計 セキュアコーディングガイド プライバシーポリシーを 取 りまく 状 況 スマートフォンにおける 利 用 者 情 報 を 取 得 し 外 部 に 送 信 する 場 合 には 利 用 者 に 対 してアプリがどのような 情 報 をど のように 扱 うか 等 を 示 したアプリ プライバシーポリシーを 作 成 提 示 する 必 要 がある アプリ プライバシーポリシーに 記 載 すべき 内 容 は 総 務 省 SPI にて 定 義 されている アプリ プライバシーポリシーは 個 々のアプリが 扱 うすべての 利 用 者 情 報 と その 用 途 や 情 報 の 保 存 先 送 信 先 等 を 明 らかにすることに 主 眼 が 置 かれている それとは 別 に 事 業 者 が 個 々のアプリから 収 集 したすべての 利 用 者 情 報 を どのように 保 管 管 理 廃 棄 をするかを 示 す 事 業 者 プライバシーポリシーも 必 要 となる 従 来 個 人 情 報 保 護 法 をもと に 作 成 されているプライバシーポリシーとはこの 事 業 者 プライバシーポリシーが 相 当 する プライバシーポリシーの 作 成 提 示 方 法 やそれぞれのプラバシーポリシーの 役 割 分 担 などは JSSEC スマホ アプリ のプライバシーポリシー 作 成 開 示 についての 考 察 ( icy.pdf) に 詳 細 説 明 が 記 載 されているので 参 照 のこと 用 語 解 説 以 下 に 本 ガイドで 使 用 している 用 語 について JSSEC スマホ アプリのプライバシーポリシー 作 成 開 示 について の 考 察 ( に 記 載 されている 用 語 解 説 を 引 用 しておく 表 用 語 事 業 者 プライバシーポリシー 解 説 事 業 者 における 個 人 情 報 保 護 方 針 を 記 したプライバシーポリシー 個 人 情 報 保 護 法 に 基 づき 作 成 アプリ プライバシーポリシー アプリケーション 向 けプライバシーポリシー 総 務 省 SPI の 指 針 に 基 づき 作 成 概 要 版 と 詳 細 版 での 解 りやすい 説 明 が 望 まれる 概 要 版 アプリ プライバシーポリシー アプリが 取 得 する 利 用 者 情 報 取 得 目 的 第 三 者 提 供 の 有 無 など を 簡 潔 に 記 述 した 文 書 詳 細 版 アプリ プライバシーポリシー 利 用 者 による 取 り 換 えが 容 易 な 利 用 者 情 報 利 用 者 による 取 り 換 えが 困 難 な 利 用 者 情 報 慎 重 な 取 り 扱 いが 求 められる 利 用 者 情 報 総 務 省 SPI の 定 める8 項 目 に 準 拠 した 詳 細 な 内 容 を 記 述 した 文 書 cookie UUID など IMEI IMSI ICCID MAC アドレス OS が 生 成 する ID など 位 置 情 報 アドレス 帳 電 話 番 号 メールアドレスなど 414 All rights reserved Japan Smartphone Security Association. プライバシー 情 報 を 扱 う

417 5.6. 暗 号 技 術 を 利 用 する セキュリティの 世 界 では 脅 威 を 分 析 し 対 策 するための 観 点 として 機 密 性 (Confidentiality) 完 全 性 (Integrity) 可 用 性 (Availavility)という 用 語 が 使 われる それぞれ 秘 密 のデータを 第 三 者 に 見 られないようにすること 参 照 する データが 改 ざんされないように(または 改 ざんされたことを 検 知 )すること サービスやデータが 必 要 な 時 にいつでも 利 用 できることを 意 味 しており セキュリティを 確 保 する 際 に 考 慮 すべき 大 切 な 要 素 である 中 でも 機 密 性 や 完 全 性 のために 暗 号 技 術 が 用 いられることが 多 く Android においてもアプリが 機 密 性 や 完 全 性 を 実 現 するための 様 々な 暗 号 機 能 が 用 意 されている ここでは Android アプリにおける 暗 号 化 復 号 ( 機 密 性 の 実 現 ) メッセージ 認 証 コード/デジタル 署 名 ( 完 全 性 の 実 現 )の 安 全 な 実 装 方 法 をサンプルコードで 示 す サンプルコード 暗 号 化 復 号 する( 機 密 性 を 確 保 する) 改 ざんを 検 知 する( 完 全 性 を 確 認 する) というユースケースの 具 体 的 な 用 途 や 条 件 に 対 して 様 々な 暗 号 方 式 が 開 発 されている ここでは 暗 号 技 術 をどのような 用 途 で 利 用 するかという 観 点 から 暗 号 方 式 を 大 きく 3 つに 分 類 してサンプルコードを 用 意 している それぞれの 暗 号 技 術 の 特 徴 から 利 用 すべ き 暗 号 方 式 鍵 の 種 類 を 判 断 することができる より 細 やかな 判 断 が 必 要 な 場 合 には 暗 号 方 式 の 選 択 も 参 照 すること また 暗 号 技 術 を 利 用 する 実 装 をする 際 には 乱 数 生 成 における 脆 弱 性 と 対 策 もあらかじめ 参 照 するこ と 第 三 者 の 盗 聴 からデータを 守 る 図 盗 聴 からデータを 守 るサンプルコードを 選 択 するフローチャート All rights reserved Japan Smartphone Security Association. 暗 号 技 術 を 利 用 する 415

418 第 三 者 によるデータの 改 ざんを 検 知 する 図 データの 改 ざんを 検 知 するサンプルコードを 選 択 するフローチャート パスワード 鍵 を 利 用 して 暗 号 化 復 号 する ユーザーの 情 報 資 産 の 秘 匿 性 を 守 る 目 的 にはパスワードベース 鍵 暗 号 を 使 うことができる ポイント: 1. 明 示 的 に 暗 号 モードとパディングを 設 定 する 2. 脆 弱 でない( 基 準 を 満 たす) 暗 号 技 術 (アルゴリズム モード パディング 等 )を 使 用 する 3. パスワードから 鍵 を 生 成 する 場 合 は Salt を 使 用 する 4. パスワードから 鍵 を 生 成 する 場 合 は 適 正 なハッシュの 繰 り 返 し 回 数 を 指 定 する 5. 十 分 安 全 な 長 さを 持 つ 鍵 を 利 用 する AesCryptoPBEKey.java package org.jssec.android.cryptsymmetricpasswordbasedkey; import java.security.invalidalgorithmparameterexception; import java.security.invalidkeyexception; import java.security.nosuchalgorithmexception; import java.security.securerandom; import java.security.spec.invalidkeyspecexception; import java.util.arrays; import javax.crypto.badpaddingexception; import javax.crypto.cipher; import javax.crypto.illegalblocksizeexception; import javax.crypto.nosuchpaddingexception; import javax.crypto.secretkey; import javax.crypto.secretkeyfactory; import javax.crypto.spec.ivparameterspec; import javax.crypto.spec.pbekeyspec; public final class AesCryptoPBEKey { 416 All rights reserved Japan Smartphone Security Association. 暗 号 技 術 を 利 用 する

答申第585号

答申第585号 別 紙 諮 問 第 722 号 答 申 1 審 査 会 の 結 論 平 成 23 年 月 日 区 営 業 所 で 起 きた 物 損 事 故 に 関 する 全 ての 内 容 の 文 書 の 開 示 請 求 に 対 し 終 業 点 呼 記 録 簿 ほか7 件 を 対 象 公 文 書 として 特 定 し 一 部 開 示 と した 決 定 は 妥 当 である 2 審 査 請 求 の 内 容 (1) 審 査

More information

2 役 員 の 報 酬 等 の 支 給 状 況 平 成 27 年 度 年 間 報 酬 等 の 総 額 就 任 退 任 の 状 況 役 名 報 酬 ( 給 与 ) 賞 与 その 他 ( 内 容 ) 就 任 退 任 2,142 ( 地 域 手 当 ) 17,205 11,580 3,311 4 月 1

2 役 員 の 報 酬 等 の 支 給 状 況 平 成 27 年 度 年 間 報 酬 等 の 総 額 就 任 退 任 の 状 況 役 名 報 酬 ( 給 与 ) 賞 与 その 他 ( 内 容 ) 就 任 退 任 2,142 ( 地 域 手 当 ) 17,205 11,580 3,311 4 月 1 独 立 行 政 法 人 統 計 センター( 法 人 番 号 7011105002089)の 役 職 員 の 報 酬 給 与 等 について Ⅰ 役 員 報 酬 等 について 1 役 員 報 酬 についての 基 本 方 針 に 関 する 事 項 1 役 員 報 酬 の 支 給 水 準 の 設 定 についての 考 え 方 独 立 行 政 法 人 通 則 法 第 52 条 第 3 項 の 規 定 に 基 づき

More information

Microsoft Word - 不正アクセス行為の禁止等に関する法律等に基づく公安

Microsoft Word - 不正アクセス行為の禁止等に関する法律等に基づく公安 不 正 アクセス 行 為 の 禁 止 等 に 関 する 法 律 等 に 基 づく 公 安 委 員 会 による 援 助 等 の 措 置 に 関 する 訓 令 平 成 12 年 7 月 1 日 警 察 本 部 訓 令 第 25 号 改 正 平 成 14 年 11 月 22 日 本 部 訓 令 第 29 号 平 成 16 年 3 月 25 日 本 部 訓 令 第 6 号 平 成 24 年 5 月 1 日

More information

社会保険加入促進計画に盛込むべき内容

社会保険加入促進計画に盛込むべき内容 一 般 社 団 法 人 日 本 造 園 建 設 業 協 会 社 会 保 険 等 加 入 促 進 計 画 平 成 24 年 10 月 一 般 社 団 法 人 日 本 造 園 建 設 業 協 会 1 計 画 策 定 の 趣 旨 目 的 この 計 画 は 一 般 社 団 法 人 日 本 造 園 建 設 業 協 会 ( 以 下 日 造 協 という ) 及 び 日 造 協 の 正 会 員 ( 以 下 会 員

More information

一般競争入札について

一般競争入札について ( 一 般 競 争 入 札 ) 総 合 評 価 落 札 方 式 ガイドライン 平 成 21 年 4 月 ( 独 ) 工 業 所 有 権 情 報 研 修 館 1.はじめに 現 在 公 共 調 達 の 透 明 性 公 正 性 をより 一 層 めることが 喫 緊 の 課 題 とな っており 独 立 行 政 法 人 も 含 めた 政 府 全 体 で 随 意 契 約 の 見 直 しに 取 り 組 んで おります

More information

1

1 精 華 町 個 人 情 報 保 護 条 例 改 正 に 向 けての 考 え 方 ( 案 ) 平 成 27 年 4 月 精 華 町 0 1 目 次 1 個 人 情 報 保 護 に 関 する 法 体 系 と 番 号 法 における 特 定 個 人 情 報 の 保 護 措 置... 1 2 番 号 法 と 精 華 町 個 人 情 報 保 護 条 例 における 個 人 情 報 の 定 義 上 の 差 異...

More information

1 林 地 台 帳 整 備 マニュアル( 案 )について 林 地 台 帳 整 備 マニュアル( 案 )の 構 成 構 成 記 載 内 容 第 1 章 はじめに 本 マニュアルの 目 的 記 載 内 容 について 説 明 しています 第 2 章 第 3 章 第 4 章 第 5 章 第 6 章 林 地

1 林 地 台 帳 整 備 マニュアル( 案 )について 林 地 台 帳 整 備 マニュアル( 案 )の 構 成 構 成 記 載 内 容 第 1 章 はじめに 本 マニュアルの 目 的 記 載 内 容 について 説 明 しています 第 2 章 第 3 章 第 4 章 第 5 章 第 6 章 林 地 ( 資 料 3) 林 地 台 帳 及 び 地 図 整 備 マニュアル( 案 ) 概 要 本 資 料 は 現 時 点 での 検 討 状 況 を 基 に 作 成 したものであり 今 後 事 務 レベルの 検 討 会 等 を 経 て 成 案 を 得 ることとしてい ます 平 成 28 年 7 月 林 野 庁 計 画 課 1 林 地 台 帳 整 備 マニュアル( 案 )について 林 地 台 帳 整 備 マニュアル(

More information

参加表明書・企画提案書様式

参加表明書・企画提案書様式 秋 田 市 道 路 除 排 雪 車 両 運 行 管 理 システム( 仮 称 ) 導 入 業 務 委 託 公 募 型 プロポーザル 参 加 表 明 書 企 画 提 案 書 様 式 平 成 25 年 7 月 秋 田 市 建 設 部 道 路 維 持 課 ( 様 式 1) 参 加 表 明 書 業 務 の 名 称 秋 田 市 除 排 雪 車 両 運 行 管 理 システム( 仮 称 ) 導 入 業 務 委 託

More information

平成25年度 独立行政法人日本学生支援機構の役職員の報酬・給与等について

平成25年度 独立行政法人日本学生支援機構の役職員の報酬・給与等について 平 成 25 年 度 独 立 行 政 法 日 本 学 生 支 援 機 構 の 役 職 員 の 報 酬 給 与 等 について Ⅰ 役 員 報 酬 等 について 1 役 員 報 酬 についての 基 本 方 針 に 関 する 事 項 1 平 成 25 年 度 における 役 員 報 酬 についての 業 績 反 映 のさせ 方 日 本 学 生 支 援 機 構 は 奨 学 金 貸 与 事 業 留 学 生 支 援

More information

(3) 調 査 の 進 め 方 2 月 28 日 2 月 28 日 ~6 月 30 日 平 成 25 年 9 月 サウンディング 型 市 場 調 査 について 公 表 松 戸 市 から 基 本 的 な 土 地 情 報 サウンディングの 実 施 活 用 意 向 アイデアのある 民 間 事 業 者 と

(3) 調 査 の 進 め 方 2 月 28 日 2 月 28 日 ~6 月 30 日 平 成 25 年 9 月 サウンディング 型 市 場 調 査 について 公 表 松 戸 市 から 基 本 的 な 土 地 情 報 サウンディングの 実 施 活 用 意 向 アイデアのある 民 間 事 業 者 と 公 民 連 携 によるサウンディング 型 市 場 調 査 の 実 施 要 領 1 調 査 の 名 称 公 民 連 携 によるサウンディング 型 市 場 調 査 ( ) 2 調 査 の 対 象 松 戸 市 東 松 戸 二 丁 目 5 番 地 1 および 14 番 地 4 他 6 筆 ( 以 下 ( 旧 ) 紙 敷 土 地 区 画 整 理 66 65 街 区 と 言 う)の 土 地 約 13,876 m2

More information

為 が 行 われるおそれがある 場 合 に 都 道 府 県 公 安 委 員 会 がその 指 定 暴 力 団 等 を 特 定 抗 争 指 定 暴 力 団 等 として 指 定 し その 所 属 する 指 定 暴 力 団 員 が 警 戒 区 域 内 において 暴 力 団 の 事 務 所 を 新 たに 設

為 が 行 われるおそれがある 場 合 に 都 道 府 県 公 安 委 員 会 がその 指 定 暴 力 団 等 を 特 定 抗 争 指 定 暴 力 団 等 として 指 定 し その 所 属 する 指 定 暴 力 団 員 が 警 戒 区 域 内 において 暴 力 団 の 事 務 所 を 新 たに 設 暴 力 団 員 による 不 当 な 行 為 の 防 止 等 に 関 する 法 律 の 一 部 を 改 正 する 法 律 暴 力 団 員 による 不 当 な 行 為 の 防 止 等 に 関 する 法 律 例 規 整 備 * 暴 力 団 員 による 不 当 な 行 為 の 防 止 等 に 関 する 法 律 の 一 部 を 改 正 する 法 律 例 規 整 備 公 布 年 月 日 番 号 平 成 24 年

More information

Microsoft PowerPoint - 報告書(概要).ppt

Microsoft PowerPoint - 報告書(概要).ppt 市 町 村 における 地 方 公 務 員 制 度 改 革 に 係 る 論 点 と 意 見 について ( 概 要 ) 神 奈 川 県 市 町 村 における 地 方 公 務 員 制 度 改 革 に 係 る 検 討 会 議 について 1 テーマ 地 方 公 務 員 制 度 改 革 ( 総 務 省 地 方 公 務 員 の 労 使 関 係 制 度 に 係 る 基 本 的 な 考 え 方 )の 課 題 の 整

More information

< F2D8AC493C CC81698EF3928D8ED2816A2E6A7464>

< F2D8AC493C CC81698EF3928D8ED2816A2E6A7464> 5. 滋 賀 県 建 設 工 事 監 督 要 領 5-1 滋 賀 県 工 事 監 督 要 領 ( 趣 旨 ) 第 1 条 この 要 領 は 滋 賀 県 建 設 工 事 執 行 規 則 ( 昭 和 58 年 4 月 20 日 滋 賀 県 規 則 第 30 号 以 下 執 行 規 則 という )に 定 めるもののほか 県 が 施 行 する 請 負 工 事 の 監 督 について 必 要 な 事 項 を 定

More information

<4D6963726F736F667420576F7264202D203193FA8AD45F95CA8E86325F89898F4B315F94F093EF8AA98D90939994AD97DF914F82CC8FEE95F182CC8EFB8F57814589C28E8B89BB2E646F63>

<4D6963726F736F667420576F7264202D203193FA8AD45F95CA8E86325F89898F4B315F94F093EF8AA98D90939994AD97DF914F82CC8FEE95F182CC8EFB8F57814589C28E8B89BB2E646F63> 1.ログイン 方 法 1-1: 県 域 統 合 型 GIS 総 合 ポータルから 研 修 用 のユーザID 及 びパスワードを 入 力 後 ログインする 1-2:ログイン 後 マップ 編 集 を 選 択 します 1-3:マップ 一 覧 から 編 集 したいマップを 選 ぶ 今 回 の 場 合 1. 避 難 勧 告 等 発 令 までの 情 報 収 集 可 視 化 ( 班 ) を 選 択 する 1 2.

More information

「給与・年金の方」からの確定申告書作成編

「給与・年金の方」からの確定申告書作成編 所 得 が 給 与 のみ 公 的 年 金 のみ 給 与 と 公 的 年 金 のみ の 方 で 入 力 方 法 選 択 画 面 で 給 与 年 金 の 方 を 選 択 された 場 合 の 確 定 申 告 書 作 成 の 操 作 手 順 を 説 明 します ~ この 操 作 の 手 引 きをご 利 用 になる 前 に ~ この 操 作 の 手 引 きでは 確 定 申 告 書 の 作 成 方 法 をご 説

More information

の 購 入 費 又 は 賃 借 料 (2) 専 用 ポール 等 機 器 の 設 置 工 事 費 (3) ケーブル 設 置 工 事 費 (4) 防 犯 カメラの 設 置 を 示 す 看 板 等 の 設 置 費 (5) その 他 設 置 に 必 要 な 経 費 ( 補 助 金 の 額 ) 第 6 条 補

の 購 入 費 又 は 賃 借 料 (2) 専 用 ポール 等 機 器 の 設 置 工 事 費 (3) ケーブル 設 置 工 事 費 (4) 防 犯 カメラの 設 置 を 示 す 看 板 等 の 設 置 費 (5) その 他 設 置 に 必 要 な 経 費 ( 補 助 金 の 額 ) 第 6 条 補 美 作 市 防 犯 カメラ 設 置 支 援 事 業 補 助 金 交 付 要 綱 ( 趣 旨 ) 第 1 条 この 告 示 は 地 域 の 防 犯 活 動 を 推 進 し 安 全 安 心 のまちづくりの 実 現 を 図 るため 犯 罪 等 の 防 止 を 目 的 に 防 犯 カメラの 設 置 を 行 う 住 民 団 体 に 対 し 予 算 の 範 囲 内 において その 設 置 に 要 する 経 費

More information

別 紙 第 号 高 知 県 立 学 校 授 業 料 等 徴 収 条 例 の 一 部 を 改 正 する 条 例 議 案 高 知 県 立 学 校 授 業 料 等 徴 収 条 例 の 一 部 を 改 正 する 条 例 を 次 のように 定 める 平 成 26 年 2 月 日 提 出 高 知 県 知 事 尾

別 紙 第 号 高 知 県 立 学 校 授 業 料 等 徴 収 条 例 の 一 部 を 改 正 する 条 例 議 案 高 知 県 立 学 校 授 業 料 等 徴 収 条 例 の 一 部 を 改 正 する 条 例 を 次 のように 定 める 平 成 26 年 2 月 日 提 出 高 知 県 知 事 尾 付 議 第 3 号 高 知 県 立 学 校 授 業 料 等 徴 収 条 例 の 一 部 を 改 正 する 条 例 議 案 に 係 る 意 見 聴 取 に 関 する 議 案 平 成 26 年 2 月 高 知 県 議 会 定 例 会 提 出 予 定 の 条 例 議 案 に 係 る 地 方 教 育 行 政 の 組 織 及 び 運 営 に 関 する 法 律 ( 昭 和 31 年 法 律 第 162 号 )

More information

全設健発第     号

全設健発第     号 全 設 健 発 第 114 号 平 成 28 年 2 月 23 日 事 業 主 殿 全 国 設 計 事 務 所 健 康 保 険 組 合 理 事 長 石 井 純 公 印 省 略 健 康 保 険 法 の 改 正 の ご 案 内 等 に つ い て 時 下 益 々ご 清 栄 のこととお 慶 び 申 し 上 げます 当 健 康 保 険 組 合 の 運 営 につきましては 日 頃 よりご 協 力 いただき 厚

More information

(別紙3)保険会社向けの総合的な監督指針の一部を改正する(案)

(別紙3)保険会社向けの総合的な監督指針の一部を改正する(案) 監 督 指 針 Ⅱ 保 険 監 督 上 の 評 価 項 目 Ⅱ-2-7 商 品 開 発 に 係 る 内 部 管 理 態 勢 Ⅱ-2-7-2 主 な 着 眼 点 (1)~(4) (5) 関 連 部 門 との 連 携 1~3 4 関 連 部 門 は 販 売 量 拡 大 や 収 益 追 及 を 重 視 する 例 えば 営 業 推 進 部 門 や 収 益 部 門 から 不 当 な 影 響 を 受 けることなく

More information

Microsoft Word - 101 第1章 定款.doc

Microsoft Word - 101 第1章 定款.doc 第 1 章 定 款 規 約 山 梨 県 土 地 改 良 事 業 団 体 連 合 会 定 款 昭 和 33 年 8 月 1 日 制 定 昭 和 33 年 10 月 9 日 認 可 第 1 章 総 則 ( 目 的 ) 第 1 条 この 会 は 土 地 改 良 事 業 を 行 う 者 ( 国 県 及 び 土 地 改 良 法 第 95 条 第 1 項 の 規 定 により 土 地 改 良 事 業 を 行 う

More information

その 他 事 業 推 進 体 制 平 成 20 年 3 月 26 日 に 石 垣 島 国 営 土 地 改 良 事 業 推 進 協 議 会 を 設 立 し 事 業 を 推 進 ( 構 成 : 石 垣 市 石 垣 市 議 会 石 垣 島 土 地 改 良 区 石 垣 市 農 業 委 員 会 沖 縄 県 農

その 他 事 業 推 進 体 制 平 成 20 年 3 月 26 日 に 石 垣 島 国 営 土 地 改 良 事 業 推 進 協 議 会 を 設 立 し 事 業 を 推 進 ( 構 成 : 石 垣 市 石 垣 市 議 会 石 垣 島 土 地 改 良 区 石 垣 市 農 業 委 員 会 沖 縄 県 農 国 営 かんがい 排 水 事 業 石 垣 島 地 区 事 業 の 概 要 本 事 業 は 沖 縄 本 島 から 南 西 約 400kmにある 石 垣 島 に 位 置 する 石 垣 市 の4,338haの 農 業 地 帯 において 農 業 用 水 の 安 定 供 給 を 図 るため 農 業 水 利 施 設 の 改 修 整 備 を 行 うものである 事 業 の 目 的 必 要 性 本 地 区 は さとうきびを

More information

2 出 願 資 格 審 査 前 記 1の 出 願 資 格 (5) 又 は(6) により 出 願 を 希 望 する 者 には, 出 願 に 先 立 ち 出 願 資 格 審 査 を 行 いますので, 次 の 書 類 を 以 下 の 期 間 に 岡 山 大 学 大 学 院 自 然 科 学 研 究 科 等

2 出 願 資 格 審 査 前 記 1の 出 願 資 格 (5) 又 は(6) により 出 願 を 希 望 する 者 には, 出 願 に 先 立 ち 出 願 資 格 審 査 を 行 いますので, 次 の 書 類 を 以 下 の 期 間 に 岡 山 大 学 大 学 院 自 然 科 学 研 究 科 等 Ⅱ 入 学 者 選 抜 試 験 学 生 募 集 要 項 ( 自 然 科 学 研 究 科 環 境 学 研 究 科 共 通 ) ( 入 学 時 期 : 平 成 18 年 10 月 又 は 平 成 19 年 4 月 ) 1 出 願 資 格 次 の 各 号 のいずれかに 該 当 する 者 です (1) 修 士 の 学 位 若 しくは 専 門 職 学 位 を 有 する 者 又 は 平 成 19 年 3 月 (

More information

01.活性化計画(上大久保)

01.活性化計画(上大久保) 別 記 様 式 第 1 号 ( 第 四 関 係 ) か み お お く ぼ 上 大 久 保 ち く 地 区 か っ せ い か 活 性 化 け い か く 計 画 栃 木 県 鹿 沼 市 平 成 26 年 2 月 1 活 性 化 計 画 の 目 標 及 び 計 画 期 間 計 画 の 名 称 上 大 久 保 地 区 活 性 化 計 画 都 道 府 県 名 栃 木 県 市 町 村 名 鹿 沼 市 地

More information

Microsoft PowerPoint - 【那須野】セキュリティ問題について

Microsoft PowerPoint - 【那須野】セキュリティ問題について 平 成 26 年 度 森 林 情 報 高 度 利 活 用 開 発 事 業 報 告 会 1 森 林 クラウドシステム 標 準 化 事 業 ~ 情 報 セキュリティ 検 討 ワーキンググループ 活 動 報 告 ~ 2015 年 3 月 20 日 森 林 クラウドシステム 標 準 化 事 業 情 報 セキュリティ 検 討 WG 1. 目 的 2 森 林 の 有 する 多 面 的 機 能 を 将 来 にわたって

More information

通 知 カード と 個 人 番 号 カード の 違 い 2 通 知 カード ( 紙 )/H27.10 個 人 番 号 カード (ICカード)/H28.1 様 式 (おもて) (うら) 作 成 交 付 主 な 記 載 事 項 全 国 ( 外 国 人 含 む)に 郵 送 で 配 布 希 望 者 に 交

通 知 カード と 個 人 番 号 カード の 違 い 2 通 知 カード ( 紙 )/H27.10 個 人 番 号 カード (ICカード)/H28.1 様 式 (おもて) (うら) 作 成 交 付 主 な 記 載 事 項 全 国 ( 外 国 人 含 む)に 郵 送 で 配 布 希 望 者 に 交 1 マイナンバー 制 度 の 導 について( 案 ) 平 成 27 年 7 22 日 部 会 議 資 料 総 務 部 ( 政 管 理 課 情 報 政 策 課 庶 務 課 ) 市 活 部 ( 市 窓 課 籍 住 記 録 課 ) 通 知 カード と 個 人 番 号 カード の 違 い 2 通 知 カード ( 紙 )/H27.10 個 人 番 号 カード (ICカード)/H28.1 様 式 (おもて) (うら)

More information

マンションの管理委託契約に係る標準管理委託契約書について

マンションの管理委託契約に係る標準管理委託契約書について マンションの 管 理 委 託 契 約 に 係 る 標 準 管 理 委 託 契 約 書 について マンションの 管 理 委 託 契 約 に 係 る 標 準 管 理 委 託 契 約 書 について 平 成 15 年 4 月 9 日 < 問 い 合 わせ 先 > 総 合 政 策 局 不 動 産 業 課 ( 内 線 25117 25116) TEL:03-5253-8111 1. 趣 旨 マンションの 管 理

More information

文化政策情報システムの運用等

文化政策情報システムの運用等 名 開 始 終 了 ( 予 定 ) 年 度 番 号 0406 平 成 25 年 行 政 レビューシート ( 文 部 科 学 省 ) 文 化 政 策 情 報 システム 運 用 等 担 当 部 局 庁 文 化 庁 作 成 責 任 者 平 成 8 年 度 なし 担 当 課 室 長 官 官 房 政 策 課 政 策 課 長 清 水 明 会 計 区 分 一 般 会 計 政 策 施 策 名 根 拠 法 令 ( 具

More information

<4D6963726F736F667420576F7264202D20313431323235817988C482C682EA817A89BA90BF8E7793B1834B8343836883898343839381698A4F8D91906C8DDE8A889770816A>

<4D6963726F736F667420576F7264202D20313431323235817988C482C682EA817A89BA90BF8E7793B1834B8343836883898343839381698A4F8D91906C8DDE8A889770816A> 外 国 人 建 設 就 労 者 受 入 事 業 に 関 する 下 請 指 導 ガイドライン 第 1 趣 旨 復 興 事 業 の 更 なる 加 速 を 図 りつつ 2020 年 オリンピック パラリンピック 東 京 大 会 の 関 連 施 設 整 備 等 による 一 時 的 な 建 設 需 要 の 増 大 に 対 応 するため 2020 年 度 までの 緊 急 かつ 時 限 的 な 措 置 として 国

More information

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション 株 式 会 社 化 に 伴 う から 特 定 の 員 への 株 式 譲 渡 に 係 る 課 税 関 係 と 手 続 きについて 平 成 20 年 2 月 商 工 中 金 当 資 料 は 貴 において 本 件 取 引 に 関 する 検 討 をされるに 際 して ご 参 考 のための 情 報 提 供 のみを 目 的 として 国 税 庁 の 確 認 を 受 けた 内 容 に 基 づき 商 工 中 金 が

More information

<4D6963726F736F667420576F7264202D20925093C689D789B582B581698AAE90AC92CA926D816A2E646F63>

<4D6963726F736F667420576F7264202D20925093C689D789B582B581698AAE90AC92CA926D816A2E646F63> 消 防 危 第 245 号 平 成 1 7 年 1 0 月 2 6 日 各 都 道 府 県 消 防 防 災 主 管 部 長 東 京 消 防 庁 各 指 定 都 市 消 防 長 殿 消 防 庁 危 険 物 保 安 室 長 給 油 取 扱 所 等 における 単 独 荷 卸 しに 係 る 運 用 について 危 険 物 取 扱 者 の 立 会 いなしに 移 動 タンク 貯 蔵 所 に 乗 務 する 危 険

More information

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション 平 成 24 25 年 度 補 正 予 算 創 業 補 助 金 事 業 完 了 後 の 各 種 報 告 書 作 成 に 関 する 手 順 書 事 業 化 等 状 況 報 告 書 事 業 化 状 況 報 告 書 補 助 ツール の 記 入 ポイント 取 得 財 産 等 処 分 承 認 申 請 書 登 録 変 更 届 など 当 資 料 は 福 岡 事 務 局 採 択 者 の 方 向 けに 作 成 しております

More information

< 目 次 > 8. 雇 用 保 険 高 年 齢 雇 用 継 続 給 付 27 ( 育 児 休 業 給 付 介 護 休 業 給 付 ) 8.1 高 年 齢 雇 用 継 続 給 付 画 面 のマイナンバー 設 定 27 8.2 高 年 齢 雇 用 継 続 給 付 の 電 子 申 請 28 8.3 高

< 目 次 > 8. 雇 用 保 険 高 年 齢 雇 用 継 続 給 付 27 ( 育 児 休 業 給 付 介 護 休 業 給 付 ) 8.1 高 年 齢 雇 用 継 続 給 付 画 面 のマイナンバー 設 定 27 8.2 高 年 齢 雇 用 継 続 給 付 の 電 子 申 請 28 8.3 高 雇 用 保 険 マイナンバー 改 定 向 け 操 作 説 明 書 < 目 次 > 1.マイナンバー 等 の 法 令 改 定 対 応 について 3 1.1 法 令 改 定 の 変 更 点 3 1.2 その 他 主 要 な 変 更 点 4 2.マイナンバー 管 理 システムとの 連 携 設 定 5 2.1 マイナ de 社 労 夢 の 運 用 設 定 5 2.2 マイナ de 社 労 夢 CL の 運

More information

1 育 児 休 業 代 替 任 期 付 職 員 ( 一 般 事 務 職 )とは 育 児 休 業 代 替 任 期 付 職 員 とは 一 般 の 職 員 が 育 児 休 業 を 取 得 した 際 に 代 替 職 員 とし て 勤 務 する 職 員 です 一 般 事 務 職 については 候 補 者 として

1 育 児 休 業 代 替 任 期 付 職 員 ( 一 般 事 務 職 )とは 育 児 休 業 代 替 任 期 付 職 員 とは 一 般 の 職 員 が 育 児 休 業 を 取 得 した 際 に 代 替 職 員 とし て 勤 務 する 職 員 です 一 般 事 務 職 については 候 補 者 として 川 崎 市 育 児 休 業 代 替 任 期 付 職 員 一 般 事 務 職 の 候 補 者 登 録 案 内 川 崎 市 総 務 企 画 局 人 事 部 人 事 課 概 要 登 録 選 考 ( 教 養 考 査 及 び 作 文 考 査 )を 実 施 し ます 登 録 選 考 実 施 日 平 成 2 8 年 7 月 31 日 ( 日 ) 受 付 期 間 平 成 28 年 6 月 1 日 ( 水 ) ~ 平

More information

私立大学等研究設備整備費等補助金(私立大学等

私立大学等研究設備整備費等補助金(私立大学等 私 立 大 学 等 研 究 設 備 整 備 費 等 補 助 金 ( 私 立 大 学 等 研 究 設 備 等 整 備 費 ) 交 付 要 綱 目 次 第 1 章 通 則 ( 第 1 条 - 第 4 条 ) 第 2 章 私 立 大 学 等 ( 第 5 条 - 第 15 条 ) 第 3 章 専 修 学 校 ( 第 16 条 - 第 25 条 ) 第 4 章 補 助 金 の 返 還 ( 第 26 条 ) 第

More information

スライド 1

スライド 1 ファイル 共 有 かんたんガイド NTTレゾナント( 株 ) ビジネスgoo 事 務 局 1 ファイル 共 有 かんたんガイドについて まず 初 めに アップロード ダウンロード ファイル フォルダの 公 開 / 共 有 の 操 作 方 法 をご 紹 介 します お 客 様 のアカウントでログインをしていただき 実 際 の 画 面 でも 操 作 をして いただけますと 幸 いです ファイル 共 有

More information

要 な 指 示 をさせることができる ( 検 査 ) 第 8 条 甲 は 乙 の 業 務 にかかる 契 約 履 行 状 況 について 作 業 完 了 後 10 日 以 内 に 検 査 を 行 うものとする ( 発 生 した 著 作 権 等 の 帰 属 ) 第 9 条 業 務 によって 甲 が 乙 に

要 な 指 示 をさせることができる ( 検 査 ) 第 8 条 甲 は 乙 の 業 務 にかかる 契 約 履 行 状 況 について 作 業 完 了 後 10 日 以 内 に 検 査 を 行 うものとする ( 発 生 した 著 作 権 等 の 帰 属 ) 第 9 条 業 務 によって 甲 が 乙 に 契 約 書 ( 案 ) 独 立 行 政 法 人 医 薬 品 医 療 機 器 総 合 機 構 契 約 担 当 役 石 井 信 芳 ( 以 下 甲 という) と ( 以 下 乙 という)の 間 に 医 療 情 報 データベースに 関 するデータマッピング 業 務 ( 千 葉 大 学 医 学 部 附 属 病 院 )( 以 下 業 務 という)について 下 記 条 項 により 請 負 契 約 を 締 結 する

More information

福 山 市 では, 福 山 市 民 の 安 全 に 関 する 条 例 ( 平 成 10 年 条 例 第 12 号 )に 基 づき, 安 全 で 住 みよい 地 域 社 会 の 形 成 を 推 進 しています また, 各 地 域 では, 防 犯 を 始 め 様 々な 安 心 安 全 活 動 に 熱 心

福 山 市 では, 福 山 市 民 の 安 全 に 関 する 条 例 ( 平 成 10 年 条 例 第 12 号 )に 基 づき, 安 全 で 住 みよい 地 域 社 会 の 形 成 を 推 進 しています また, 各 地 域 では, 防 犯 を 始 め 様 々な 安 心 安 全 活 動 に 熱 心 福 山 市 防 犯 カメラの 設 置 及 び 利 用 に 関 するガイドライン 2014 年 ( 平 成 26 年 )4 月 市 民 局 市 民 部 生 活 安 全 推 進 課 福 山 市 では, 福 山 市 民 の 安 全 に 関 する 条 例 ( 平 成 10 年 条 例 第 12 号 )に 基 づき, 安 全 で 住 みよい 地 域 社 会 の 形 成 を 推 進 しています また, 各 地

More information

<4D F736F F D208ED089EF95DB8CAF89C193FC8FF38BB CC8EC091D492B28DB88C8B89CA82C982C282A282C42E646F63>

<4D F736F F D208ED089EF95DB8CAF89C193FC8FF38BB CC8EC091D492B28DB88C8B89CA82C982C282A282C42E646F63> 社 会 保 険 加 入 状 況 等 の 実 態 調 査 結 果 平 成 27 年 6 月 18 日 一 般 社 団 法 人 日 本 電 設 工 業 協 会 社 会 保 険 加 入 状 況 等 の 実 態 調 査 結 果 について 1. 調 査 の 目 的 社 会 保 険 加 入 促 進 計 画 の 計 画 期 間 (H24 年 度 ~H28 年 度 までの5 年 間 )の 中 間 時 点 として 1

More information

一 般 社 団 法 人 全 国 銀 行 協 会 御 中 依 頼 人 氏 名 平 成 年 月 日 印 登 録 支 援 専 門 家 委 嘱 ( 初 回 委 嘱 )の 依 頼 について(GL5 項 (2)) 私 は 自 然 災 害 による 被 災 者 の 債 務 整 理 に 関 するガイドライン 第 5

一 般 社 団 法 人 全 国 銀 行 協 会 御 中 依 頼 人 氏 名 平 成 年 月 日 印 登 録 支 援 専 門 家 委 嘱 ( 初 回 委 嘱 )の 依 頼 について(GL5 項 (2)) 私 は 自 然 災 害 による 被 災 者 の 債 務 整 理 に 関 するガイドライン 第 5 自 然 災 害 による 被 災 者 の 債 務 整 理 に 関 するガイドライン の 利 用 を 考 えら れている 皆 様 へ 大 分 県 弁 護 士 会 平 成 28 年 4 月 14 日 に 発 生 し,その 後 も 断 続 的 に 発 生 している 熊 本, 大 分 の 地 震 により 被 災 された 方 に 対 して, 心 よりお 見 舞 い 申 し 上 げます 大 分 県 においては,

More information

Microsoft Word - ★HP版平成27年度検査の結果

Microsoft Word - ★HP版平成27年度検査の結果 平 成 7 年 度 検 査 結 果 について () 検 査 体 制 等 農 政 部 農 地 整 備 課 の 検 査 員 名 以 上 により 土 地 改 良 区 等 あたり 日 間 から 日 間 実 施 しました 農 業 振 興 事 務 所 の 土 地 改 良 区 指 導 担 当 職 員 及 び 関 係 市 町 職 員 が 立 会 いました () 検 査 件 数 定 期 検 査 8( 土 地 改 良

More information

観光ガイド育成業務委託プロポーザル実施要領

観光ガイド育成業務委託プロポーザル実施要領 佐 賀 市 グルメパンフレット 編 集 デザイン 業 務 委 託 企 画 コンペティション 実 施 要 領 平 成 26 年 9 月 一 般 社 団 法 人 佐 賀 市 観 光 協 会 1. 趣 旨 一 般 社 団 法 人 佐 賀 市 観 光 協 会 が 推 進 するグルメパンフレット うまか 店 の 更 なる 利 用 促 進 のため 最 新 の 情 報 を 反 映 した 魅 力 あるパンフレットを

More information

<4D6963726F736F667420576F7264202D208DE3905F8D8291AC8B5A8CA48A948EAE89EF8ED0208BC696B18BA492CA8E64976C8F91816995BD90AC3237944E378C8E89FC92F994C5816A>

<4D6963726F736F667420576F7264202D208DE3905F8D8291AC8B5A8CA48A948EAE89EF8ED0208BC696B18BA492CA8E64976C8F91816995BD90AC3237944E378C8E89FC92F994C5816A> 第 1 編 共 通 業 務 共 通 仕 様 書 平 成 27 年 7 月 第 1 章 一 般 1.1 目 的 業 務 共 通 仕 様 書 ( 以 下 技 研 仕 様 書 という )は 阪 神 高 速 技 研 株 式 会 社 ( 以 下 会 社 という )が 発 注 する 調 査 検 討 資 料 作 成 設 計 補 助 測 量 作 業 その 他 こ れらに 類 する 業 務 に 係 る 業 務 請 負

More information

スライド 1

スライド 1 Android 版 目 視 録 運 用 操 作 マニュアル 作 成 2012/03/22 更 新 2014/09/26 目 視 録 とは 携 帯 またはパソコンで 施 工 写 真 を 登 録 確 認 できるシステムです ご 利 用 の 為 にはIDとパスワードが 必 要 です TEG ログインID ( ) パスワード ( ) https://teg.mokusiroku.com/

More information

は 共 有 名 義 )で 所 有 権 保 存 登 記 又 は 所 有 権 移 転 登 記 を された も の で あ る こと (3) 居 室 便 所 台 所 及 び 風 呂 を 備 え 居 住 の ために 使 用 す る 部 分 の 延 べ 床 面 積 が 5 0 平 方 メ ー ト ル 以 上

は 共 有 名 義 )で 所 有 権 保 存 登 記 又 は 所 有 権 移 転 登 記 を された も の で あ る こと (3) 居 室 便 所 台 所 及 び 風 呂 を 備 え 居 住 の ために 使 用 す る 部 分 の 延 べ 床 面 積 が 5 0 平 方 メ ー ト ル 以 上 蕨 市 三 世 代 ふれあい 家 族 住 宅 取 得 補 助 金 交 付 要 綱 ( 目 的 ) 第 1 条 この 要 綱 は 子 育 て 中 の 子 世 帯 及 びその 親 世 帯 の 同 居 又 は 近 居 ( 以 下 同 居 等 と い う ) を 促 進 す る た め 住 宅 の 取 得 に 係 る 費 用 の 一 部 を 補 助 す る こ と に よ り 三 世 代 の 市 内 定 住

More information

川崎市木造住宅耐震診断助成金交付要綱

川崎市木造住宅耐震診断助成金交付要綱 千 葉 市 耐 震 改 修 費 補 助 事 業 要 綱 第 1 章 総 則 ( 趣 旨 ) 第 1 条 この 要 綱 は 地 震 による 住 宅 の 倒 壊 等 の 被 害 から 市 民 の 生 命 身 体 及 び 財 産 を 保 護 するため 住 宅 の 耐 震 改 修 の 実 施 について 必 要 な 事 項 を 定 め 耐 震 改 修 に 要 する 費 用 の 一 部 を 補 助 することにより

More information

預 金 を 確 保 しつつ 資 金 調 達 手 段 も 確 保 する 収 益 性 を 示 す 指 標 として 営 業 利 益 率 を 採 用 し 営 業 利 益 率 の 目 安 となる 数 値 を 公 表 する 株 主 の 皆 様 への 還 元 については 持 続 的 な 成 長 による 配 当 可

預 金 を 確 保 しつつ 資 金 調 達 手 段 も 確 保 する 収 益 性 を 示 す 指 標 として 営 業 利 益 率 を 採 用 し 営 業 利 益 率 の 目 安 となる 数 値 を 公 表 する 株 主 の 皆 様 への 還 元 については 持 続 的 な 成 長 による 配 当 可 ミスミグループ コーポレートガバナンス 基 本 方 針 本 基 本 方 針 は ミスミグループ( 以 下 当 社 グループ という)のコーポレートガバナン スに 関 する 基 本 的 な 考 え 方 を 定 めるものである 1. コーポレートガバナンスの 原 則 (1) 当 社 グループのコーポレートガバナンスは 当 社 グループの 持 続 的 な 成 長 と 中 長 期 的 な 企 業 価 値 の

More information

する 婦 人 相 談 所 その 他 適 切 な 施 設 による 支 援 の 明 記 禁 止 命 令 等 をすることが できる 公 安 委 員 会 等 の 拡 大 等 の 措 置 が 講 じられたものである 第 2 改 正 法 の 概 要 1 電 子 メールを 送 信 する 行 為 の 規 制 ( 法

する 婦 人 相 談 所 その 他 適 切 な 施 設 による 支 援 の 明 記 禁 止 命 令 等 をすることが できる 公 安 委 員 会 等 の 拡 大 等 の 措 置 が 講 じられたものである 第 2 改 正 法 の 概 要 1 電 子 メールを 送 信 する 行 為 の 規 制 ( 法 1 0 年 保 存 平 成 35 年 12 月 31 日 満 了 FNo.-20120102 崎 安 (ス) 第 6 6 号 平 成 25 年 7 月 12 日 各 所 属 長 殿 長 崎 県 警 察 本 部 長 ストーカー 行 為 等 の 規 制 等 に 関 する 法 律 の 一 部 を 改 正 する 法 律 の 施 行 につ いて( 通 達 ) ストーカー 行 為 等 の 規 制 等 に 関 する

More information

m07 北見工業大学 様式①

m07 北見工業大学 様式① 国 立 大 学 法 人 北 見 工 業 大 学 ( 法 人 番 号 6460305000387)の 役 職 員 の 報 酬 給 与 等 について Ⅰ 役 員 報 酬 等 について 1 役 員 報 酬 についての 基 本 方 針 に 関 する 事 項 1 役 員 報 酬 の 支 給 水 準 の 設 定 についての 考 え 方 当 該 法 人 の 主 要 事 業 は 教 育 研 究 事 業 である 役

More information

学校教育法等の一部を改正する法律の施行に伴う文部科学省関係省令の整備に関する省令等について(通知)

学校教育法等の一部を改正する法律の施行に伴う文部科学省関係省令の整備に関する省令等について(通知) 27 文 科 初 第 1593 号 平 成 28 年 3 月 22 日 各 都 道 府 県 知 事 各 都 道 府 県 教 育 委 員 会 各 指 定 都 市 教 育 委 員 会 殿 附 属 学 校 を 置 く 各 国 立 大 学 法 人 学 長 構 造 改 革 特 別 区 域 法 第 12 条 第 1 項 の 認 定 を 受 けた 地 方 公 共 団 体 の 長 文 部 科 学 省 初 等 中 等

More information

●電力自由化推進法案

●電力自由化推進法案 第 一 八 五 回 参 第 二 号 電 力 自 由 化 推 進 法 案 目 次 第 一 章 総 則 ( 第 一 条 - 第 三 条 ) 第 二 章 電 力 自 由 化 の 基 本 方 針 ( 第 四 条 - 第 九 条 ) 第 三 章 電 力 自 由 化 推 進 本 部 ( 第 十 条 - 第 十 九 条 ) 附 則 第 一 章 総 則 ( 目 的 ) 第 一 条 この 法 律 は 平 成 二 十

More information

( 補 助 金 等 交 付 決 定 通 知 に 加 える 条 件 ) 第 7 条 市 長 は 交 付 規 則 第 11 条 に 規 定 するところにより 補 助 金 の 交 付 決 定 に 際 し 次 に 掲 げる 条 件 を 付 するものとする (1) 事 業 完 了 後 に 消 費 税 及 び

( 補 助 金 等 交 付 決 定 通 知 に 加 える 条 件 ) 第 7 条 市 長 は 交 付 規 則 第 11 条 に 規 定 するところにより 補 助 金 の 交 付 決 定 に 際 し 次 に 掲 げる 条 件 を 付 するものとする (1) 事 業 完 了 後 に 消 費 税 及 び 戸 田 市 学 童 保 育 室 運 営 等 事 業 費 補 助 事 業 実 施 要 綱 ( 目 的 ) 第 1 条 この 要 綱 は 市 内 で 放 課 後 児 童 健 全 育 成 事 業 ( 児 童 福 祉 法 ( 昭 和 22 年 法 律 第 164 号 ) 第 6 条 の 3 第 2 項 に 規 定 する 放 課 後 児 童 健 全 育 成 事 業 をい う 以 下 同 じ )を 実 施 するものに

More information

(Microsoft Word - \221\346\202P\202U\201@\214i\212\317.doc)

(Microsoft Word - \221\346\202P\202U\201@\214i\212\317.doc) (1) 1 ア 調 査 すべき の 手 法 情 報 できる 主 要 な 眺 望 地 点 及 び 主 要 で 身 近 な 視 点 の 状 況 な 実 視 施 点 地 ( 区 点 不 域 のうち 特 周 定 辺 の 多 主 数 の 要 な なものをいう 人 々 眺 望 又 地 は 点 周 ( 辺 の 不 以 住 特 下 民 定 が 同 多 じ ) 数 の する 人 及 々が 場 び 所 対 利 で 象

More information

目 次 利 用 に 際 しての 注 意 事 項... ユーザー 登 録... ログイン... 課 題 申 請... 5 装 置 予 約... 6 ライセンス 取 得 方 法... 7 利 用 料 金 の 確 認 ( 準 備 中 )... 5 8 外 部 発 表 登 録 の 方 法... 5 < 附

目 次 利 用 に 際 しての 注 意 事 項... ユーザー 登 録... ログイン... 課 題 申 請... 5 装 置 予 約... 6 ライセンス 取 得 方 法... 7 利 用 料 金 の 確 認 ( 準 備 中 )... 5 8 外 部 発 表 登 録 の 方 法... 5 < 附 NIMS 蓄 電 池 基 盤 プラットフォーム 装 置 管 理 システム ユーザーマニュアル https://www.battery-pf.jp/ 第 版 作 成 日 更 新 平 成 6 年 0 月 0 日 平 成 6 年 0 月 0 日 目 次 利 用 に 際 しての 注 意 事 項... ユーザー 登 録... ログイン... 課 題 申 請... 5 装 置 予 約... 6 ライセンス 取

More information

平成17年度高知県県産材利用推進事業費補助金交付要綱

平成17年度高知県県産材利用推進事業費補助金交付要綱 高 知 県 副 業 型 林 家 育 成 支 援 事 業 募 集 要 領 第 1 趣 旨 この 要 領 は 高 知 県 副 業 型 林 家 育 成 支 援 事 業 費 補 助 金 交 付 要 綱 に 基 づき 当 該 補 助 金 の 交 付 の 対 象 となる 事 業 者 を 公 募 して 選 定 する 手 続 等 当 該 事 業 の 円 滑 な 実 施 を 図 るために 必 要 な 事 項 を 定

More information

募集要項

募集要項 大 阪 府 住 宅 供 給 公 社 コピー 用 紙 購 入 における 単 価 契 約 の 入 札 参 加 者 募 集 要 領 1 趣 旨 大 阪 府 住 宅 供 給 公 社 ( 以 下 公 社 という )において コピー 用 紙 の 納 入 を 行 う 業 者 を 募 集 する 2 入 札 に 付 する 事 項 (1) 調 達 件 名 大 阪 府 住 宅 供 給 公 社 コピー 用 紙 購 入 における

More information

Taro13-公示.jtd

Taro13-公示.jtd 参 加 者 の 有 無 を 確 認 する 公 募 手 続 に 係 る 参 加 意 思 確 認 書 の 提 出 を 求 める 公 示 平 成 19 年 4 月 12 日 九 州 地 方 整 備 局 熊 本 河 川 国 道 事 務 所 長 七 條 牧 生 次 のとおり 参 加 意 思 確 認 書 の 提 出 を 招 請 する 1. 業 務 概 要 (1) 業 務 名 用 地 補 償 総 合 技 術 (

More information

いう )は 警 告 をしたときは 速 やかに その 内 容 及 び 日 時 を 当 該 警 告 を 求 める 旨 の 申 出 をした 者 に 通 知 しなければならないこととされ また 警 告 をし なかったときは 速 やかに その 旨 及 び 理 由 を 当 該 警 告 を 求 める 旨 の 申

いう )は 警 告 をしたときは 速 やかに その 内 容 及 び 日 時 を 当 該 警 告 を 求 める 旨 の 申 出 をした 者 に 通 知 しなければならないこととされ また 警 告 をし なかったときは 速 やかに その 旨 及 び 理 由 を 当 該 警 告 を 求 める 旨 の 申 ストーカー 行 為 等 の 規 制 等 に 関 する 法 律 の 一 部 を 改 正 する 法 律 の 施 行 について ( 平 成 25 年 7 月 16 日 付 け 通 達 香 生 企 第 311 号 ) ストーカー 行 為 等 の 規 制 等 に 関 する 法 律 の 一 部 を 改 正 する 法 律 ( 平 成 25 年 法 律 第 73 号 以 下 改 正 法 という( 別 添 官 報 参

More information

中根・金田台地区 平成23年度補償説明業務

中根・金田台地区 平成23年度補償説明業務 簡 易 公 募 型 競 争 入 札 方 式 に 準 じた 手 続 による 手 続 開 始 掲 示 次 とおり 指 名 競 争 入 札 参 加 者 選 定 手 続 を 開 始 します 平 成 23 年 6 月 1 日 中 根 金 田 台 開 発 事 務 所 長 関 根 宣 由 1 務 概 要 (1) 務 名 中 根 金 田 台 地 区 平 成 23 年 度 補 償 説 明 務 (2) 務 内 容 研

More information

(6) 事 務 局 職 場 積 立 NISAの 運 営 に 係 る 以 下 の 事 務 等 を 担 当 する 事 業 主 等 の 組 織 ( 当 該 事 務 を 代 行 する 組 織 を 含 む )をいう イ 利 用 者 からの 諸 届 出 受 付 事 務 ロ 利 用 者 への 諸 連 絡 事 務

(6) 事 務 局 職 場 積 立 NISAの 運 営 に 係 る 以 下 の 事 務 等 を 担 当 する 事 業 主 等 の 組 織 ( 当 該 事 務 を 代 行 する 組 織 を 含 む )をいう イ 利 用 者 からの 諸 届 出 受 付 事 務 ロ 利 用 者 への 諸 連 絡 事 務 職 場 積 立 NISAに 関 するガイドライン 第 1 章 総 則 1. 制 定 の 趣 旨 NISA 推 進 連 絡 協 議 会 は NISA 推 進 連 絡 協 議 会 に 参 加 する 業 界 団 体 等 に 属 する 金 融 商 品 取 引 業 者 及 び 金 融 機 関 等 ( 以 下 NISA 取 扱 業 者 という )が 取 り 扱 う 職 場 積 立 NISAについて 適 正 かつ

More information

Taro-データ公安委員会相互協力事

Taro-データ公安委員会相互協力事 公 安 委 員 会 相 互 協 力 事 務 処 理 要 綱 の 制 定 について( 例 規 ) 最 終 改 正 平 成 26.2.7 例 規 組 二 第 5 号 京 都 府 警 察 本 部 長 から 各 部 長 各 所 属 長 あて 暴 力 団 員 による 不 当 な 行 為 の 防 止 等 に 関 する 法 律 の 事 務 取 扱 いに 関 する 訓 令 ( 平 成 4 年 京 都 府 警 察 本

More information

<5461726F2D8179835A8362836794C5817A313230333039817988C495B6817A>

<5461726F2D8179835A8362836794C5817A313230333039817988C495B6817A> - 1 - 省 百 七 旅 客 部 改 省 令 平 成 省 令 伴 並 平 成 省 令 並 ま づ 並 令 づ く 領 平 成 月 大 臣 前 田 武 志 づ く 領 語 お 使 語 監 督 針 平 成 省 千 百 お 使 語 - 2 - 務 名 簿 款 寄 附 為 登 記 証 明 組 織 図 保 制 証 機 器 機 器 設 設 備 記 載 決 算 報 足 経 的 礎 証 績 分 証 程 七 イ 概

More information

(2)大学・学部・研究科等の理念・目的が、大学構成員(教職員および学生)に周知され、社会に公表されているか

(2)大学・学部・研究科等の理念・目的が、大学構成員(教職員および学生)に周知され、社会に公表されているか 平 成 23 年 度 自 己 報 告 書 1 理 念 目 的 (1) 大 学 学 部 研 究 科 等 の 理 念 目 的 は 適 切 に 設 定 されているか 平 成 19 年 6 月 に の 目 標 として 大 学 の 発 展 に 貢 献 する 力 のある 組 織 とい う 共 通 の 目 標 を 掲 げ この 目 標 を 常 に 念 頭 に 置 きながら 日 々の 業 務 に 当 たっている さらに

More information

Microsoft Word - 全国エリアマネジメントネットワーク規約.docx

Microsoft Word - 全国エリアマネジメントネットワーク規約.docx 全 国 エリアマネジメントネットワーク 規 約 第 1 章 総 則 ( 名 称 ) 第 1 条 この 会 は 全 国 エリアマネジメントネットワーク( 以 下 本 会 という )と 称 する ( 目 的 ) 第 2 条 本 会 は 全 国 のエリアマネジメント 組 織 による 連 携 協 議 の 場 を 提 供 し エリアマネジメン トに 係 る 政 策 提 案 情 報 共 有 及 び 普 及 啓

More information

2 役 員 の 報 酬 等 の 支 給 状 況 役 名 法 人 の 長 理 事 理 事 ( 非 常 勤 ) 平 成 25 年 度 年 間 報 酬 等 の 総 額 就 任 退 任 の 状 況 報 酬 ( 給 与 ) 賞 与 その 他 ( 内 容 ) 就 任 退 任 16,936 10,654 4,36

2 役 員 の 報 酬 等 の 支 給 状 況 役 名 法 人 の 長 理 事 理 事 ( 非 常 勤 ) 平 成 25 年 度 年 間 報 酬 等 の 総 額 就 任 退 任 の 状 況 報 酬 ( 給 与 ) 賞 与 その 他 ( 内 容 ) 就 任 退 任 16,936 10,654 4,36 独 立 行 政 法 人 駐 留 軍 等 労 働 者 労 務 管 理 機 構 の 役 職 員 の 報 酬 給 与 等 について Ⅰ 役 員 報 酬 等 について 1 役 員 報 酬 についての 基 本 方 針 に 関 する 事 項 1 平 成 25 年 度 における 役 員 報 酬 についての 業 績 反 映 のさせ 方 検 証 結 果 理 事 長 は 今 中 期 計 画 に 掲 げた 新 たな 要

More information

Microsoft Word - 佐野市生活排水処理構想(案).doc

Microsoft Word - 佐野市生活排水処理構想(案).doc 佐 野 市 生 活 排 水 処 理 構 想 ( 案 ) 平 成 27 年 12 月 佐 野 市 目 次 1. 生 活 排 水 処 理 構 想 について 1.1 生 活 排 水 処 理 構 想 とは P.1 1.2 生 活 排 水 処 理 施 設 の 種 類 P.1 2. 佐 野 市 の 現 状 と 課 題 2.1 整 備 状 況 P.2 2.2 主 な 汚 水 処 理 施 設 P.2 2.3 生 活

More information

Microsoft Word - 【事務連絡】居所情報の登録申請が間に合わなかった場合の取扱いの周知について.docx

Microsoft Word - 【事務連絡】居所情報の登録申請が間に合わなかった場合の取扱いの周知について.docx 事 務 連 絡 平 成 27 年 11 月 5 日 各 都 道 府 県 障 害 福 祉 主 管 部 ( 局 ) 長 殿 厚 生 労 働 省 社 会 援 護 局 障 害 保 健 福 祉 部 企 画 課 長 期 入 所 者 等 がマイナンバー 通 知 カードを 入 所 等 先 で 受 け 取 るに 当 たっての 居 所 情 報 の 登 録 申 請 が 間 に 合 わなかった 場 合 の 取 扱 いについて(

More information

スライド 1

スライド 1 公 的 年 金 制 度 の 健 全 性 及 び 信 頼 性 の 確 保 のための 厚 生 年 金 保 険 法 等 の 一 部 を 改 正 する 法 律 について 厚 生 労 働 省 年 金 局 公 的 年 金 制 度 の 健 全 性 及 び 信 頼 性 の 確 保 のための 厚 生 年 金 保 険 法 等 の 一 部 を 改 正 する 法 律 ( 平 成 25 年 法 律 第 63 号 )の 概 要

More information

第1章 総則

第1章 総則 第 8 節 市 街 化 調 整 区 域 内 の 建 築 許 可 の 手 続 き 8-1 法 第 43 条 に 基 づく 建 築 許 可 の 手 続 き 8-1-1 建 築 許 可 等 の 手 続 きフロー 市 街 化 調 整 区 域 における 建 築 許 可 に 関 する 標 準 的 な 手 続 きについては 次 のフローのと おりとなります 建 築 主 地 目 が 農 地 の 場 合 建 築 許

More information

(5) 給 与 制 度 の 総 合 的 見 直 しの 実 施 状 況 について 概 要 の 給 与 制 度 の 総 合 的 見 直 しにおいては 俸 給 表 の 水 準 の 平 均 2の 引 き 下 げ 及 び 地 域 手 当 の 支 給 割 合 の 見 直 し 等 に 取 り 組 むとされている

(5) 給 与 制 度 の 総 合 的 見 直 しの 実 施 状 況 について 概 要 の 給 与 制 度 の 総 合 的 見 直 しにおいては 俸 給 表 の 水 準 の 平 均 2の 引 き 下 げ 及 び 地 域 手 当 の 支 給 割 合 の 見 直 し 等 に 取 り 組 むとされている 清 瀬 市 の 給 与 定 員 管 理 等 について 1 総 括 (1) 件 費 の 状 況 ( 普 通 会 計 決 算 ) 住 民 基 本 台 帳 口 歳 出 額 実 質 収 支 件 費 件 費 率 ( 参 考 ) (25 年 度 末 ) 25 年 度 千 74,247 27,195,534 A 768,602 千 4,616,550 B 千 17.0 B/A 昨 年 度 の 件 費 率 17.3

More information

Microsoft Word - 新提案書作成・審査要領、提案書作成様式(別添3,4)

Microsoft Word - 新提案書作成・審査要領、提案書作成様式(別添3,4) ( 別 添 3) 平 成 27 年 度 いわき 市 南 部 清 掃 センター 指 定 廃 棄 物 セメント 固 型 化 施 設 解 体 撤 去 等 調 査 委 託 業 務 に 係 る 提 案 書 作 成 審 査 要 領 環 境 省 本 書 は 平 成 27 年 度 いわき 市 南 部 清 掃 センター 指 定 廃 棄 物 セメント 固 型 化 処 理 施 設 解 体 撤 去 等 調 査 委 託 業

More information

大阪府電子調達システムの開発業務 (第一期)に係る仕様書案に対する意見招請のお知らせ

大阪府電子調達システムの開発業務 (第一期)に係る仕様書案に対する意見招請のお知らせ 地 方 独 立 行 政 法 人 大 阪 府 立 病 院 機 構 公 告 第 83 号 平 成 28 年 度 における 地 方 独 立 行 政 法 人 大 阪 府 立 病 院 機 構 職 員 に 対 するストレスチェック 制 度 実 施 等 に 関 する 業 務 の 委 託 に 係 る 単 価 契 約 ( 単 価 の 設 定 を 契 約 の 主 目 的 とし 一 定 の 期 間 内 において 供 給

More information

ができます 4. 対 象 取 引 の 範 囲 第 1 項 のポイント 付 与 の 具 体 的 な 条 件 対 象 取 引 自 体 の 条 件 は 各 加 盟 店 が 定 めます 5.ポイントサービスの 利 用 終 了 その 他 いかなる 理 由 によっても 付 与 されたポイントを 換 金 すること

ができます 4. 対 象 取 引 の 範 囲 第 1 項 のポイント 付 与 の 具 体 的 な 条 件 対 象 取 引 自 体 の 条 件 は 各 加 盟 店 が 定 めます 5.ポイントサービスの 利 用 終 了 その 他 いかなる 理 由 によっても 付 与 されたポイントを 換 金 すること 大 好 きポイント コンサドーレ 札 幌 サービス 利 用 規 約 第 1 条 ( 目 的 ) 1. 本 規 約 は フェリカポケットマーケティング 株 式 会 社 ( 以 下 当 社 )が 発 行 する 大 好 きコンサドーレ 札 幌 WAON カ ード 及 びポイントサービスの 利 用 条 件 について 定 めます 2. 利 用 者 が 大 好 きコンサドーレ 札 幌 WAON カードの 利 用

More information

<4D6963726F736F667420576F7264202D203032208E598BC68A8897CD82CC8DC490B68B7982D18E598BC68A8893AE82CC8A76905682C98AD682B782E993C195CA915B9275964082C98AEE82C382AD936F985E96C68B9690C582CC93C197E1915B927582CC898492B75F8E96914F955D89BF8F915F2E646F6

<4D6963726F736F667420576F7264202D203032208E598BC68A8897CD82CC8DC490B68B7982D18E598BC68A8893AE82CC8A76905682C98AD682B782E993C195CA915B9275964082C98AEE82C382AD936F985E96C68B9690C582CC93C197E1915B927582CC898492B75F8E96914F955D89BF8F915F2E646F6 様 式 租 税 特 別 措 置 等 に 係 る 政 策 の 事 前 評 価 書 1 政 策 評 価 の 対 象 とした 産 業 活 力 の 再 生 及 び 産 業 活 動 の 革 新 に 関 する 特 別 措 置 法 に 基 づく 登 録 免 租 税 特 別 措 置 等 の 名 称 許 税 の 特 例 措 置 の 延 長 ( 国 税 32)( 登 録 免 許 税 : 外 ) 2 要 望 の 内 容

More information

様式(補助金)

様式(補助金) 別 添 1 提 案 書 の 様 式 1. 提 案 書 は 次 頁 以 下 の 記 載 例 に 従 って 記 入 して 下 さい 2. 用 紙 は A4 版 を 利 用 し 左 とじにして 下 さい 3. 提 案 書 は 9 部 ( 正 1 部 副 ( 正 のコピー)8 部 )を 提 出 して 下 さい 4. 提 案 書 は それぞれA4フラットファイルに 綴 じた 上 で 提 出 してください 5.

More information

Microsoft Word - FBE3A91F.doc

Microsoft Word - FBE3A91F.doc 広 島 大 学 ウェブマネジメントシステム 共 通 事 項 (Ver:2009.04.16) 本 システムに 関 する 問 い 合 わせ 本 システムに 関 する 問 い 合 わせは 下 記 までご 連 絡 ください 社 会 連 携 情 報 政 策 室 広 報 グループ 内 線 :5017 5017 6131 E-mail mail:koho@office.hiroshima koho@office.hiroshima-u.ac.jp

More information

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション インターネット 出 願 手 引 き (システムWAKABA 継 続 入 学 申 請 手 引 き) 目 次 1.システムWAKABAトップ 画 面 2 2. 学 生 種 選 択 画 面 3 3. 出 願 申 請 画 面 (1) 全 科 履 修 生 の 場 合 4 (2) 全 科 履 修 生 以 外 の 場 合 6 4.オンライン 授 業 8 5. 科 目 登 録 申 請 画 面 (1) 授 業 種 別

More information

平成27年度大学改革推進等補助金(大学改革推進事業)交付申請書等作成・提出要領

平成27年度大学改革推進等補助金(大学改革推進事業)交付申請書等作成・提出要領 平 成 7 年 度 大 学 改 革 推 進 等 補 助 金 ( 大 学 改 革 推 進 事 業 ) 交 付 申 請 書 等 作 成 提 出 要 領 交 付 申 請 等 に 当 たっては 大 学 改 革 推 進 等 補 助 金 ( 大 学 改 革 推 進 事 業 ) 取 扱 要 領 ( 以 下 取 扱 要 領 という ) も 参 照 の 上 以 下 の 関 係 書 類 を 作 成 し 各 大 学 短

More information

変 更 履 歴 日 付 Document ver. 変 更 箇 所 変 更 内 容 2015/3/27 1.0.0 新 規 追 加 2015/9/24 誤 字 修 正 2016/2/19 1.01 動 作 環 境 最 新 のものへ 変 更 全 体 オペレーター の 表 記 を 削 除 2016/5/

変 更 履 歴 日 付 Document ver. 変 更 箇 所 変 更 内 容 2015/3/27 1.0.0 新 規 追 加 2015/9/24 誤 字 修 正 2016/2/19 1.01 動 作 環 境 最 新 のものへ 変 更 全 体 オペレーター の 表 記 を 削 除 2016/5/ KDDI Smart Mobile Safety Manager Mac ユーザーマニュアル 最 終 更 新 日 2016 年 5 月 26 日 Document ver.1.0.2 1 変 更 履 歴 日 付 Document ver. 変 更 箇 所 変 更 内 容 2015/3/27 1.0.0 新 規 追 加 2015/9/24 誤 字 修 正 2016/2/19 1.01 動 作 環 境

More information

001-00 セルフメディケーション推進のための一般用医薬品等に関する所得控除制度の創設(個別要望事項:HP掲載用)

001-00 セルフメディケーション推進のための一般用医薬品等に関する所得控除制度の創設(個別要望事項:HP掲載用) 平 成 28 年 度 地 方 税 制 改 正 ( 税 負 担 軽 減 措 置 等 ) 要 望 事 項 ( 新 設 拡 充 延 長 その 他 ) No 1 府 省 庁 名 厚 生 労 働 省 対 象 税 目 個 人 住 民 税 法 人 住 民 税 事 業 税 不 動 産 取 得 税 固 定 資 産 税 事 業 所 税 その 他 ( ) 要 望 項 目 名 要 望 内 容 ( 概 要 ) セルフメディケーション

More information

電子申告控除無料作成案内-2.xdw

電子申告控除無料作成案内-2.xdw 溝 口 信 之 " 電 子 申 告 " 税 理 士 事 務 電 子 証 明 書 税 額 控 除 の 申 告 を 無 料 で 提 供 します! 1,000 円 コストで 5,000 円 の 還 付 申 告 本 人 の 電 子 署 名 及 び 電 子 証 明 書 を 付 して 得 税 の 確 定 申 告 をe-Taxで 行 うと 最 高 5,000 円 の 得 税 の 税 額 控 除 を 受 けることができるようになりました

More information

平 成 27 年 11 月 ~ 平 成 28 年 4 月 に 公 開 の 対 象 となった 専 門 協 議 等 における 各 専 門 委 員 等 の 寄 附 金 契 約 金 等 の 受 取 状 況 審 査 ( 別 紙 ) 専 門 協 議 等 の 件 数 専 門 委 員 数 500 万 円 超 の 受

平 成 27 年 11 月 ~ 平 成 28 年 4 月 に 公 開 の 対 象 となった 専 門 協 議 等 における 各 専 門 委 員 等 の 寄 附 金 契 約 金 等 の 受 取 状 況 審 査 ( 別 紙 ) 専 門 協 議 等 の 件 数 専 門 委 員 数 500 万 円 超 の 受 資 料 5-1 平 成 28 年 6 月 16 日 専 門 協 議 等 の 実 施 に 関 する 各 専 門 委 員 における 寄 附 金 契 約 金 等 の 受 取 状 況 承 認 審 査 及 び 安 全 対 策 に 係 る 専 門 協 議 等 を 依 頼 した 専 門 委 員 の 寄 附 金 契 約 金 等 の 受 取 状 況 については 医 薬 品 医 療 機 器 総 合 機 構 における 専

More information

Microsoft PowerPoint - 総合型DB資料_県版基金説明用.pptx

Microsoft PowerPoint - 総合型DB資料_県版基金説明用.pptx 全 国 建 設 企 業 年 金 基 金 ( 仮 称 )について 平 成 29 年 春 の 設 立 に 向 けて 準 備 を 進 めています 平 成 26 年 10 月 全 国 建 設 厚 生 年 金 基 金 ( 注 ) 現 時 点 における 制 度 の 大 枠 であり 詳 細 については 今 後 検 討 を 行 います 1. 全 国 建 設 企 業 年 金 基 金 ( 仮 称 )について 全 国 建

More information

独立行政法人国立病院機構呉医療センター医療機器安全管理規程

独立行政法人国立病院機構呉医療センター医療機器安全管理規程 独 立 行 政 法 人 国 立 病 院 機 構 呉 医 療 センタ- 医 療 機 器 安 全 管 理 規 程 目 次 第 1 章 総 則 ( 第 1 条 ~ 第 4 条 ) 第 2 章 組 織 及 び 職 務 ( 第 5 条 ~ 第 10 条 ) 第 3 章 研 修 ( 第 11 条 ~ 第 12 条 ) 第 4 章 保 守 点 検 及 び 修 理 ( 第 13 条 ~ 第 16 条 ) 第 5 章

More information

はじめに この 平 成 28 年 度 地 域 型 住 宅 グリーン 化 事 業 適 用 申 請 書 記 入 の 手 引 き は 申 請 者 のみなさん が 申 請 書 の 作 成 と 評 価 業 務 をスムーズに 行 うため 申 請 書 の 書 き 方 を 手 引 きするものです 申 請 書 を 本

はじめに この 平 成 28 年 度 地 域 型 住 宅 グリーン 化 事 業 適 用 申 請 書 記 入 の 手 引 き は 申 請 者 のみなさん が 申 請 書 の 作 成 と 評 価 業 務 をスムーズに 行 うため 申 請 書 の 書 き 方 を 手 引 きするものです 申 請 書 を 本 平 成 28 年 度 地 域 型 住 宅 グリーン 化 事 業 適 用 申 請 書 記 入 の 手 引 き ( 様 式 1 様 式 3 4) 平 成 28 年 地 域 型 住 宅 グリーン 化 事 業 評 価 事 務 局 1 はじめに この 平 成 28 年 度 地 域 型 住 宅 グリーン 化 事 業 適 用 申 請 書 記 入 の 手 引 き は 申 請 者 のみなさん が 申 請 書 の 作

More information

<5461726F2D8CF68D908A549776816989BA97AC89CD90EC8FF38BB592B28DB8>

<5461726F2D8CF68D908A549776816989BA97AC89CD90EC8FF38BB592B28DB8> 入 札 公 告 をご 覧 いただく 前 に ( 公 告 概 要 のお 知 らせ) この 度 公 告 する 滝 沢 ダム 下 流 河 川 状 況 調 査 の 主 な 内 容 は 以 下 のとおりです ( 入 札 公 告 本 文 は このお 知 らせの 後 段 に 掲 載 しております ) 一. 業 務 内 容 等 について 1 業 務 名 滝 沢 ダム 下 流 河 川 状 況 調 査 2 履 行 期

More information

Taro-2220(修正).jtd

Taro-2220(修正).jtd 株 式 会 社 ( 募 集 株 式 の 発 行 ) 株 式 会 社 変 更 登 記 申 請 書 1. 商 号 商 事 株 式 会 社 1. 本 店 県 市 町 丁 目 番 号 1. 登 記 の 事 由 募 集 株 式 発 行 ( 情 報 番 号 2220 全 25 頁 ) 1. 登 記 すべき 事 項 変 更 ( 注 ) 変 更 の 年 月 日 は, 払 込 期 日 又 は 払 込 期 間 の 末

More information

一宮市町内会に対する防犯カメラ設置補助金交付要綱

一宮市町内会に対する防犯カメラ設置補助金交付要綱 瀬 戸 市 防 犯 カメラ 設 置 費 補 助 金 交 付 要 綱 ( 目 的 ) 第 1 条 この 要 綱 は 地 域 防 犯 のために 必 要 な 箇 所 に 防 犯 カメラを 設 置 する 連 区 自 治 会 及 び 瀬 戸 防 犯 協 会 連 合 会 ( 以 下 連 区 自 治 会 等 という )に 対 し その 設 置 費 用 を 補 助 することにより 安 全 安 心 なまちづくりを 推

More information

4 承 認 コミュニティ 組 織 は 市 長 若 しくはその 委 任 を 受 けた 者 又 は 監 査 委 員 の 監 査 に 応 じなければ ならない ( 状 況 報 告 ) 第 7 条 承 認 コミュニティ 組 織 は 市 長 が 必 要 と 認 めるときは 交 付 金 事 業 の 遂 行 の

4 承 認 コミュニティ 組 織 は 市 長 若 しくはその 委 任 を 受 けた 者 又 は 監 査 委 員 の 監 査 に 応 じなければ ならない ( 状 況 報 告 ) 第 7 条 承 認 コミュニティ 組 織 は 市 長 が 必 要 と 認 めるときは 交 付 金 事 業 の 遂 行 の 地 域 づくり 一 括 交 付 金 の 交 付 に 関 する 要 綱 ( 趣 旨 ) 第 1 条 この 要 綱 は 川 西 市 地 域 分 権 の 推 進 に 関 する 条 例 ( 平 成 26 年 川 西 市 条 例 第 10 号 以 下 条 例 という ) 第 14 条 の 規 定 に 基 づく 地 域 づくり 一 括 交 付 金 ( 以 下 交 付 金 という )の 交 付 に 関 し 必 要

More information

1 本 店 の 申 請 において 代 理 人 を 立 てない 場 合 電 子 証 明 書 の 利 用 者 は 代 表 者 で 取 得 してください 6 電 子 証 明 書 の 利 用 者 は 誰 にすればよいのですか? 2 本 店 の 申 請 で 代 理 人 を 立 てる または 支 店 の 申 請

1 本 店 の 申 請 において 代 理 人 を 立 てない 場 合 電 子 証 明 書 の 利 用 者 は 代 表 者 で 取 得 してください 6 電 子 証 明 書 の 利 用 者 は 誰 にすればよいのですか? 2 本 店 の 申 請 で 代 理 人 を 立 てる または 支 店 の 申 請 2012 年 10 月 1 日 更 新 東 京 電 子 自 治 体 共 同 運 営 電 子 証 明 書 の 取 得 電 子 調 達 サービス 電 子 証 明 書 に 関 する 1 行 政 書 士 などに 資 格 審 査 申 請 を 委 託 する 場 合 は 電 子 証 明 書 を 貸 し 出 せばよいのですか? 電 子 証 明 書 は 実 印 と 同 じ 扱 いとなります 貸 し 出 しによって 電

More information

2 1.ヒアリング 対 象 (1) 対 象 範 囲 分 類 年 金 医 療 保 険 雇 用 保 険 税 備 考 厚 生 年 金 の 資 格 喪 失 国 民 年 金 の 加 入 老 齢 給 付 裁 定 請 求 など 健 康 保 険 の 資 格 喪 失 国 民 健 康 保 険 の 加 入 健 康 保 険

2 1.ヒアリング 対 象 (1) 対 象 範 囲 分 類 年 金 医 療 保 険 雇 用 保 険 税 備 考 厚 生 年 金 の 資 格 喪 失 国 民 年 金 の 加 入 老 齢 給 付 裁 定 請 求 など 健 康 保 険 の 資 格 喪 失 国 民 健 康 保 険 の 加 入 健 康 保 険 1 参 考 資 料 6 退 職 関 連 手 続 の 現 行 業 務 分 析 1. ヒアリング 対 象 2. ワンストップ 化 に 向 けて 検 討 すべき 課 題 ( 参 考 )ヒアリング 結 果 分 析 2 1.ヒアリング 対 象 (1) 対 象 範 囲 分 類 年 金 医 療 保 険 雇 用 保 険 税 備 考 厚 生 年 金 の 資 格 喪 失 国 民 年 金 の 加 入 老 齢 給 付 裁

More information

総合評価点算定基準(簡易型建築・電気・管工事)

総合評価点算定基準(簡易型建築・電気・管工事) 別 記 3 総 合 評 価 点 算 定 基 準 ( 簡 易 型 建 築 電 気 管 工 事 ) 1 総 合 評 価 点 の 算 定 方 法 総 合 評 価 点 は 以 下 すべてを 満 たす 者 について 次 の 算 式 により 算 定 する 1 入 札 書 が 無 効 でない 者 2 予 定 価 格 の 制 限 の 範 囲 内 の 者 ( 失 格 となった 者 を 除 く ) 3 施 工 計 画

More information

この 章 では 電 子 入 札 システムをご 利 用 いただくための 事 前 準 備 について 説 明 します 事 前 準 備 と して ID 初 期 パスワードの 確 認 初 期 パスワード 初 期 見 積 用 暗 証 番 号 の 変 更 IC カード 登 録 またはICカード 更 新 を 行 っ

この 章 では 電 子 入 札 システムをご 利 用 いただくための 事 前 準 備 について 説 明 します 事 前 準 備 と して ID 初 期 パスワードの 確 認 初 期 パスワード 初 期 見 積 用 暗 証 番 号 の 変 更 IC カード 登 録 またはICカード 更 新 を 行 っ 目 次... 1 1.1 ID 初 期 パスワードの 確 認... 3 1.2 初 期 パスワード 初 期 見 積 用 暗 証 番 号 の 変 更... 6 1.3 ICカード 登 録... 10 1.4 ICカード 更 新... 18 1.5 Internet Explorer の 設 定... 25 目 次 をクリックすると 当 該 ページへ 遷 移 します この 章 では 電 子 入 札 システムをご

More information

( 別 紙 ) 以 下 法 とあるのは 改 正 法 第 5 条 の 規 定 による 改 正 後 の 健 康 保 険 法 を 指 す ( 施 行 期 日 は 平 成 28 年 4 月 1 日 ) 1. 標 準 報 酬 月 額 の 等 級 区 分 の 追 加 について 問 1 法 改 正 により 追 加

( 別 紙 ) 以 下 法 とあるのは 改 正 法 第 5 条 の 規 定 による 改 正 後 の 健 康 保 険 法 を 指 す ( 施 行 期 日 は 平 成 28 年 4 月 1 日 ) 1. 標 準 報 酬 月 額 の 等 級 区 分 の 追 加 について 問 1 法 改 正 により 追 加 別 添 事 務 連 絡 平 成 27 年 12 月 18 日 日 本 年 金 機 構 厚 生 年 金 保 険 部 長 殿 厚 生 労 働 省 年 金 局 事 業 管 理 課 長 持 続 可 能 な 医 療 保 険 制 度 を 構 築 するための 国 民 健 康 保 険 法 等 の 一 部 を 改 正 する 法 律 による 健 康 保 険 法 及 び 船 員 保 険 法 改 正 内 容 の 一 部 に

More information

改 訂 来 歴 改 訂 番 号 発 行 日 改 訂 内 容 承 認 照 査 作 成 0 03-2-20 新 規 発 行 ( 認 証 ロゴマークは 別 途 ) 熊 野 03-2-20 熊 野 03-2-20 関 谷 03-2-20 改 訂 番 号 1~13 の 改 訂 内 容 は 旧 版 PCG-00

改 訂 来 歴 改 訂 番 号 発 行 日 改 訂 内 容 承 認 照 査 作 成 0 03-2-20 新 規 発 行 ( 認 証 ロゴマークは 別 途 ) 熊 野 03-2-20 熊 野 03-2-20 関 谷 03-2-20 改 訂 番 号 1~13 の 改 訂 内 容 は 旧 版 PCG-00 製 品 認 証 に 係 わる 手 引 き 非 管 理 版 一 般 財 団 法 人 発 電 設 備 技 術 検 査 協 会 認 証 セ ン タ ー JAPEIC-MS&PCC A 1/8 改 訂 来 歴 改 訂 番 号 発 行 日 改 訂 内 容 承 認 照 査 作 成 0 03-2-20 新 規 発 行 ( 認 証 ロゴマークは 別 途 ) 熊 野 03-2-20 熊 野 03-2-20 関 谷 03-2-20

More information

1 書 誌 作 成 機 能 (NACSIS-CAT)の 軽 量 化 合 理 化 電 子 情 報 資 源 への 適 切 な 対 応 のための 資 源 ( 人 的 資 源,システム 資 源, 経 費 を 含 む) の 確 保 のために, 書 誌 作 成 と 書 誌 管 理 作 業 の 軽 量 化 を 図

1 書 誌 作 成 機 能 (NACSIS-CAT)の 軽 量 化 合 理 化 電 子 情 報 資 源 への 適 切 な 対 応 のための 資 源 ( 人 的 資 源,システム 資 源, 経 費 を 含 む) の 確 保 のために, 書 誌 作 成 と 書 誌 管 理 作 業 の 軽 量 化 を 図 平 成 2 8 年 3 月 25 日 NACSIS-CAT 検 討 作 業 部 会 NACSIS-CAT/ILL の 軽 量 化 合 理 化 について( 基 本 方 針 )( 案 ) これからの 学 術 情 報 システム 構 築 検 討 委 員 会 ( 以 下, これから 委 員 会 ) は これか らの 学 術 情 報 システムの 在 り 方 について ( 平 成 27 年 5 月 29 日 )

More information

新ひだか町住宅新築リフォーム等緊急支援補助金交付要綱

新ひだか町住宅新築リフォーム等緊急支援補助金交付要綱 新 ひだか 町 住 宅 新 築 リフォーム 耐 震 等 支 援 補 助 金 交 付 要 綱 平 成 26 年 6 月 27 日 要 綱 第 15 号 ( 目 的 ) 第 1 条 この 要 綱 は 住 宅 の 新 築 工 事 増 改 築 工 事 リフォーム 工 事 又 は 耐 震 補 強 工 事 ( 以 下 新 築 リフォーム 等 工 事 という ) を 行 う 者 に 対 し その 工 事 費 の

More information

スライド 1

スライド 1 CSVファイルによる 利 用 者 情 報 設 定 ガイド NTTレゾナント( 株 ) ビジネスgoo 事 務 局 1 当 ガイドについて ビジネスgooへ 利 用 者 の 情 報 を 登 録 または 更 新 を 行 う 際 数 名 程 度 の 設 定 であれば 設 定 画 面 への 手 入 力 で 問 題 はないと 思 いますが 数 十 人 単 位 となりますと 手 入 力 では 大 変 です ビジネスgooではCSVファイルを

More information

スマートフォン プライバシー イニシアティブ を 踏 まえた 対 応 10 平 成 24 年 8 月 に 諸 問 題 研 究 会 報 告 書 として 提 言 された スマートフォン プライバシー イニシアティブ が 発 表 され スマートフォンの 利 用 者 情 報 の 取 扱 いの 在 り 方 と

スマートフォン プライバシー イニシアティブ を 踏 まえた 対 応 10 平 成 24 年 8 月 に 諸 問 題 研 究 会 報 告 書 として 提 言 された スマートフォン プライバシー イニシアティブ が 発 表 され スマートフォンの 利 用 者 情 報 の 取 扱 いの 在 り 方 と 指 針 の 実 効 性 向 上 のための 取 組 9 スマートフォン 利 用 者 情 報 取 扱 指 針 については 関 係 事 業 者 等 が 直 接 参 照 して 適 切 な 対 応 を 行 うほか 以 下 のような 実 効 性 向 上 のための 取 組 が 考 えられる - 事 業 者 業 界 団 体 自 身 による 取 組 状 況 のフォローアップと 公 表 - 本 指 針 を 踏 まえた

More information

Taro-1-14A記載例.jtd

Taro-1-14A記載例.jtd 募 集 株 式 の 発 行 ( 非 公 開 会 社 のうち 非 取 締 役 会 設 置 会 社 ) 受 付 番 号 票 貼 付 欄 株 式 会 社 変 更 登 記 申 請 書 1. 会 社 法 人 等 番 号 0000-00 - 000000 分 かる 場 合 に 記 載 してください 1. 商 号 1. 本 店 1. 登 記 の 事 由 商 事 株 式 会 社 県 市 町 丁 目 番 号 募 集

More information

16 日本学生支援機構

16 日本学生支援機構 様 式 1 公 表 されるべき 事 項 独 立 行 政 法 人 日 本 学 生 支 援 機 構 ( 法 人 番 号 7020005004962)の 役 職 員 の 報 酬 給 与 等 について Ⅰ 役 員 報 酬 等 について 1 役 員 報 酬 についての 基 本 方 針 に 関 する 事 項 1 役 員 報 酬 の 支 給 水 準 の 設 定 についての 考 え 方 日 本 学 生 支 援 機

More information