セキュリティ方針 — 写丸君
最終更新: 2026年9月5日
この文書は、写丸君の署名鍵の扱いと、脆弱性への対応方法を定めたものです。 C2PA 適合プログラムの「Generator Product Security Requirements」(v0.1)の Assurance Level 1 の要件に 対応するように書いており、適合申請時のセキュリティ設計書(Security Architecture Document)の元にもなります。
1. 構成(Implementation Class: 公式の証明書で署名するときは Distributed、それ以外は Edge)
写丸君は、撮影・マニフェスト生成・ハッシュ計算を端末の中で行います。署名は 2 通りあります。
| 署名のしかた | どこで署名するか | C2PA の分類 | いつ |
| ① 端末の鍵で署名 | 端末の中(Android Keystore / Secure Enclave) | Edge | 既定。設定「公式の証明書で署名する」がオフのとき、または通信できないとき |
| ② 公式の証明書で署名 | 会社の署名サーバー(claim のハッシュ 32 バイトだけを送る) | Distributed | 設定をオンにして通信できるとき。Android 0.1.10 以降(Android 10 以降)。iPhone 版は②を使用していません(iPhone 版の適合が済むまで。設定をオンにしても①で署名します) |
C2PA 適合プログラムに申告しているのは②の形です。1 つのファイルに付く署名はどちらか 1 つで、重ねません。 iPhone 版も 1.1.0 から①②の両方の設定がありますが、②は使用していません(iPhone 版の適合が済むまで。設定をオンにしても①で署名します)。既定ではどちらの設定もオフで、通信しません(設定「会社の証明書で署名する」「会社のサーバーで署名する」をオンにしたときだけ通信します)。
| 部品 | 役割 |
| CameraX | 撮影。JPEG / MP4 を一時ファイルに書く |
| ImageConvert / FastPng | 必要なときだけ PNG に変換 |
| c2pa-android(c2pa-rs を含む) | マニフェストの組み立て、ハッシュ計算、署名の埋め込み、検証 |
| DeviceKey | 署名鍵と証明書の管理(Android Keystore。iPhone 版は Secure Enclave) |
| ServerSign | ②のとき、claim のハッシュを会社の署名サーバーに送って署名を受け取る。端末の鍵で「本物の写丸君である」証明を付ける |
| Tls | 会社のサーバーとの通信を TLS 1.3 だけに絞る(Android 10 以降) |
| LocationTracker | 位置情報(同意制) |
| MediaStorage | ギャラリーへの保存 |
2. 署名鍵の保護(要件 O.2 / 6.2.1 Level 1)
- 生成: 鍵は端末の Android Keystore の中で生成します(EC P-256)。StrongBox(セキュリティチップ)が 使える端末では StrongBox、無い端末では TEE(ハードウェア保護領域)に置かれます。 どちらの場合も鍵の平文はアプリのメモリに入らず、取り出すこともできません。 ハードウェア保護が無い古い端末ではソフトウェア実装の Keystore になり、その場合は設定画面と詳細画面に「ソフトウェア」と表示します
- 保管と暗号化: Keystore がハードウェア由来の鍵で暗号化して保管します。アプリ側で鍵を書き出すことはありません
- アクセス制御: 鍵を使えるのは写丸君のプロセスだけです(Android のアプリ分離)。署名以外の用途(PURPOSE_SIGN / VERIFY 以外)には使えないよう生成時に制限しています
- ローテーション: 鍵は「世代」で管理します。設定画面の「鍵を作り直す」でいつでも新しい鍵に切り替えられ、 古い鍵は削除されます。証明書の有効期間は 2 年で、残り 30 日を切ると起動時に自動で作り直します。 署名済みのファイルには証明書が埋め込まれているため、鍵を替えても過去のファイルの検証はできます
- 証明書: 設定「会社の証明書を使う」がオフのときは自己署名(
CN=写丸君, O=KARIROE, K.K., C=JP)。 オンにすると、株式会社カリロエの認証局が発行した端末証明書(CN=Shamaru Camera, O=KARIROE, K.K., C=JP、有効期間 1 年)に置き換わります。 発行の手順は第 5 章。鍵そのものは端末から出ません(公開鍵だけを送ります)。 受け取った証明書は、アプリに同梱したルート証明書で検証してから使います
- 鍵の危殆化時: 端末を紛失した場合など、利用者は「鍵を作り直す」で古い鍵を無効にできます (古い鍵で新たに署名することはできなくなります)
3. 脆弱性への対応(要件 O.3 / 6.3.1 Level 1)
3.1 依存ライブラリのスキャン(SCA / SBOM)
- リリースのたびに
tools/sca.sh(または tools/sca.ps1)を実行します
- Gradle の CycloneDX プラグインで依存ライブラリの一覧(SBOM、CycloneDX 形式)を生成
- Gradle の管理外である同梱の c2pa-android を SBOM に追加(
tools/sbom_add_bundled.py)
- OSV-Scanner で OSV / NIST NVD のデータベースと照合し、
app/build/reports/sca/ に結果を保存
- 実行結果(report.txt / report.json)はリリースの記録として保管します
3.2 修正期限
- 深刻度 CRITICAL / HIGH(CVSS v3 以上)の脆弱性は、検出から 90 日以内に修正版を配布します。 修正できないときは、その機能を無効化するなどの緩和策を同期限内に入れます
- MEDIUM 以下は次回の通常リリースで対応します
- 90 日を超えて CRITICAL / HIGH が残った版はリリースしません。
tools/sca.sh と CI は深刻度に関係なく 1 件でも検出があれば失敗するので、検出されたら報告書で深刻度を確認して上の期限で対応します
3.3 同梱ライブラリの更新
- c2pa-android は手で取得した AAR(
app/libs/c2pa-release.aar)です。中に c2pa-rs(Rust)が入っています
- 月に 1 回、および c2pa-android / c2pa-rs のセキュリティ勧告が出たときに https://github.com/contentauth/c2pa-android/releases を確認し、必要なら更新します
3.4 ビルドと配布
- ビルドは固定したツールチェーン(Gradle 8.13、AGP 8.11.2、Kotlin 2.2.20、Android SDK 36)で行います
- 配布物は Google Play のアプリ署名で保護されます。ソースコードは GitHub で公開します
4. 資産とアサーションの保護(要件 O.4)
- 撮影から署名までは 1 つのプロセス内で行い、一時ファイルはアプリ専用のキャッシュ領域(他アプリから読めない)に置きます
- 撮ったものは署名の前にアプリ専用の保留フォルダへ移し、保存が済んでから消します(途中でアプリが終了しても失われず、次回起動時に続きから処理します)
- 署名できなかったときは、撮ったものを失わないよう署名なしで保存し、その旨を画面に表示します。署名なしのファイルは一覧で「証明なし」と区別されます
- マニフェストの内容(出自、日時、機種、位置情報)は撮影時にアプリが決め、利用者が編集する手段はありません
5. 通信(要件 O.5 / O.6)
端末と外部の通信は、利用者が設定でオンにしたものだけです(README「外部通信について」)。 すべて既定はオフで、全部オフなら通信しません。
- 会社の認証局からの端末証明書の受け取り(Android 0.1.6 以降。1 年に 1 回)
- タイムスタンプ局への RFC 3161 要求(署名のたび)
- 会社の署名サーバーへの署名の依頼(Android 0.1.8 以降。署名のたび。②のとき)
TLS。 会社のサーバー(1 と 3)との通信は、Android 10 以降では TLS 1.3 だけに絞っています (Tls.kt。要件 O.5「部分システム間の通信は TLS 1.3 以上」)。サーバー側は Google 管理で TLS 1.2 も 受け付けますが、端末が 1.3 未満を拒むので、この端末が使う通信路は必ず 1.3 になります。 Android 9 には TLS 1.3 が無いため、②は Android 9 では使えません(①は使えます)。
- iPhone 版は、既定では通信しません。 時刻証明の機能が無く、遠隔マニフェストと OCSP の取得を切ったうえで、 c2pa に渡す HTTP の解決役を必ず失敗させています(
ios/Shamaru/Sources/Provenance.swift)。 1.1.0 から、設定でオンにしたときだけ会社の認証局と通信します(証明書の受け取りは Apple の App Attest で本物のアプリか確かめたうえで 1 年に 1 回、 サーバーでの署名は撮るたび)。会社のサーバーとの通信は TLS 1.3 以上だけです(tlsMinimumSupportedProtocolVersion)
会社の認証局(shamaru-ca)
署名そのものは端末の中で行います。署名鍵はどの経路でも端末の外に出ません。 サーバーがするのは「この公開鍵は本物の写丸君が動く改造されていない端末の中で作られた」ことを 確かめて、証明書を出すことだけです。
- 端末が送るもの: 公開鍵(CSR)と、Android の鍵の証明(Key Attestation)のチェーン
- サーバーが確かめること: チェーンが Google のルートに繋がる / 失効していない / 鍵が TEE か StrongBox の中で作られた / ブートローダーが閉じている / チャレンジが一致する / パッケージ名と署名鍵が写丸君のもの / 鍵が端末内で生成された / 証拠の鍵と CSR の鍵が同じ(他人の証拠の貼り付けを防ぐ)
- 中間 CA の秘密鍵は Cloud KMS の中にあり、取り出せません。ルートの秘密鍵は ネットワークから切り離して作り、暗号化した保管庫に置いています
- サーバーが残す記録は「通し番号・期限・種別・発行日時」だけです。 公開鍵・証拠・IP アドレスは残しません
- 端末は、受け取った証明書がアプリに同梱した会社のルートで検証できることを 確かめてから使います(サーバーになりすまされても、繋がらない証明書は受け取りません)
- 実装は非公開リポジトリ
shamaru-ca。ルートと中間の証明書は <https://callirrhoe.jp/app/shamaru-kun/ca/> で公開しています
会社の署名サーバー(shamaru-ca の /v1/shamaru/sign。②のとき)
②では、claim への署名を会社のサーバーの鍵で行います。鍵は Cloud KMS の中にあり、取り出せません (中間 CA の鍵とも別の鍵)。
- 端末が送るもの: 署名するデータのハッシュ値(32 バイト)、①でもらった端末証明書、使い捨ての乱数と時刻、 それらに端末の鍵で付けた署名
- サーバーが署名の前に確かめること: 端末証明書が自社の中間 CA から出ている(発行者を DER で突き合わせ、 署名を検証)/期限内/端末の署名が証明書の公開鍵で検証できる/時刻が ±5 分以内/乱数が未使用。 1 つでも外れたら署名しません(要件 O.2「署名の前に本物の端末だと確かめること」)
- 証明書は公開情報なので、それだけでは署名させません。 端末の鍵による所持の証明が要ります。 合言葉(API キー)は使っていないので、アプリの中に秘密はありません
- 回数は端末ごとに 1 分 60 回まで。数えるのに使う名前(公開鍵のハッシュ)は記録に残しません
- サーバーが残す記録は「署名した」という事実と、使った証明書の通し番号(全端末で共通)だけです。 ハッシュ値・端末証明書・IP アドレスは残しません
- 写真・動画・位置情報・利用者を特定できる情報は送りません。ハッシュの長さは 32 バイト固定なので、 写真を送る経路が構造的にありません
- 依存ライブラリは配置のたびに
npm audit(HIGH 以上で配置を止める)で確かめます
6. 脆弱性の報告
写丸君の脆弱性を見つけたときは、公開の場に書く前に info@callirrhoe.jp へ連絡してください。 受け取りから 7 日以内に返事をし、上記の期限で修正します。
7. 要件との対応表
| C2PA 要件 | 対応 |
| 6.1 O.1 自動登録時の認証 | 実装済み(Android 0.1.6 以降)。チャレンジ + Android Key Attestation で端末を認証してから発行します。アプリに CA の認証情報は一切埋め込んでいません。第 5 章 |
| 6.2.1 鍵の暗号化保管・最小権限・ローテーション | 第 2 章 |
| 6.3.1 SCA / SBOM と 90 日以内の修正 | 第 3 章 |
| 6.4 生成時の改ざん防止 | 第 4 章 |
| 6.5 / 6.6 通信・ホスティング環境 | 第 5 章。証明書の受け取りは TLS 1.3。署名サーバーは持たず、認証局は Google Cloud Run の上で動き、中間 CA の秘密鍵は Cloud KMS から取り出せません |