Azure Confidential Ledger の Python SDK を macOS で使うと、“CCF Node” certificate is not permitted for this usage という例外で API 呼び出しが失敗することがあります。本稿はこの症状を体系的に整理し、最短で直す設定、仕組みから理解する恒久対策、そして検証・再発防止の実装テンプレートまで一気通貫で解説します。Ubuntu/Windows で起きにくい理由も併せて説明します。
症状
macOS 上で Python SDK(azure.confidentialledger)を用い、create_ledger_entry() や get_ledger_entry() を呼ぶと、次の例外で失敗します。
azure.core.exceptions.ServiceRequestError: ('“CCF Node” certificate is not permitted for this usage',)
同じコードでも Ubuntu や Windows では再現しないケースが多く、「macOS 固有の検証厳格化によるエラー」です。pip install certifi だけでは解決しません。
結論(最短の直し方)
- 最も簡単かつ推奨:SDK へ台帳証明書ファイルを渡さない(
ledger_certificate_pathを指定しない)。SDK に証明書選択と検証を任せます。 - 既存実装を保持したい:正しい CA チェーン(ルート/中間 CA)だけを
ledger_certificate_pathに渡します。ノード(CCF Node)証明書を「信頼ルート」として渡さないでください。 - 併せて行う一般対策:macOS の Python 証明書バンドル整備(Install Certificates.command、
certifi更新、必要ならREQUESTS_CA_BUNDLE設定)。ただしこれは根本対策ではありません。
なぜ macOS だけで失敗するのか ― 仕組みから理解する
Azure Confidential Ledger(ACL)は、CCF(Confidential Consortium Framework)上に構築され、ノード証明書(台帳ノードが持つ TLS サーバ証明書)と、台帳 ID サービスが発行する CA(ルート/中間)が存在します。ここで重要なのは 証明書の役割(EKU:拡張キー使用目的) です。
- ノード証明書(“CCF Node”):TLS サーバ証明書(エンドエンティティ)。
「サーバ認証(1.3.6.1.5.5.7.3.1)」等は持ちますが、信頼根(CA)として使う用途(keyCertSign)や適切なbasicConstraints CA:TRUEは通常ありません。 - ルート/中間 CA:他の証明書を署名するための CA 証明書。
basicConstraintsがCA:TRUEで、keyCertSignなど CA 用の用途 を持ちます。
macOS は Keychain/SecureTransport による検証が厳密で、CA 用途でない証明書を信頼根として扱う と、今回のように「この用途に許可されていない(not permitted for this usage)」と判断して拒否します。つまり、ノード証明書を “CA バンドル” として渡してしまう実装 があると macOS で落ちます。
Ubuntu/Windows ではなぜ通るのか
多くの環境で、Linux/Windows の Python は certifi(Mozilla CA バンドル)をデフォルト利用します。ACL の SDK は、台帳 ID サービスから適切な CA 情報を取得・照合する経路を持っており、ledger_certificate_path を渡さなければ、ノード証明書を誤って信頼根にしない ため問題になりません。逆に言えば、macOS でも 証明書ファイルを渡さない か、正しい CA チェーンだけを渡す なら安定します。
対処一覧(要点早見表)
| 対処 | 詳細 | 備考 |
|---|---|---|
A. ledger_certificate_path を指定しない | SDK に証明書ファイルを渡さず、ConfidentialLedgerClient(endpoint=..., credential=...) のみで初期化する。 | SDK が台帳 ID サービスからサービス用 CA を自動取得・検証。 ノード証明書を誤って trust root にする問題を回避。 |
| B. 正しい CA チェーンを渡す | get_ledger_identity() の結果をそのまま保存しない。台帳 ID サービスが発行するルート/中間 CA(CA:TRUE のみ)を PEM で保存し、 ledger_certificate_path に指定する。 | 既存の「ファイルで証明書を渡す」実装を保持したい場合。 ノード(CCF Node)証明書を CA として渡すのは NG。 |
| C. macOS 側の一般的 SSL 検証対策 | Python 同梱の Install Certificates.command を実行、pip install -U certifi、必要に応じて REQUESTS_CA_BUNDLE を設定。 | 「certificate verify failed」系の汎用対策。 今回の根本原因(ノード証明書の誤用)そのものは解消しない。 |
やってはいけないこと
- ノード(“CCF Node”)証明書を CA バンドルとして渡す:macOS で高確率に落ちます。
- 本番で
verify=False:検証を無効化するのは一時的デバッグ用途に限定。セキュリティと監査要件を満たしません。 - OS キーチェーンにノード証明書を恒久的に追加:攻撃面を広げ、運用監査の観点でも推奨できません。
実装:最短で直す安全版(A 案)
最推奨は SDK に証明書解決を任せる ことです。証明書ファイルを一切渡さず初期化します。
from azure.identity import DefaultAzureCredential
from azure.confidentialledger import ConfidentialLedgerClient
ledger_url = "https://<your-ledger-name>.<region>.confidentialledger.azure.com"
credential = DefaultAzureCredential()
# ポイント:ledger_certificate_path を渡さない
ledger = ConfidentialLedgerClient(endpoint=ledger_url, credential=credential)
# 動作確認(権限は事前に割り当てておく)
entry = {"contents": "hello from macOS"}
create_resp = ledger.create_ledger_entry(entry=entry)
# get_ledger_entry / list_ledger_entries など通常操作が例外なく通るはず
この方法では、SDK が台帳 ID サービスと突き合わせて「正しい CA チェーン」を自動で扱うため、ノード証明書を誤用する余地がありません。
実装:既存の証明書ファイル渡しを維持する(B 案)
運用要件等で「証明書ファイルをアプリに同梱し、ledger_certificate_path で明示する」方針を維持する場合、必ず CA 用途の証明書(ルート/中間のみ)を PEM で用意します。ノード証明書は含めません。
1) どのファイルを作ればよいか
- ledger-ca.pem:台帳 ID サービスが提示する CA チェーン(ルート + 中間)。
内容は-----BEGIN CERTIFICATE-----… の複数連結で構いません。 - 含めてはいけないもの:ノード(“CCF Node”)証明書(エンドエンティティ)。
2) PEM の健全性チェック(OpenSSL)
手元の PEM が CA:TRUE のみで構成されているかを確認します。
# 連結 PEM を 1 本ずつ確認
openssl x509 -in ledger-ca.pem -noout -text | grep -E "CA:TRUE|Key Usage|Extended Key Usage|Subject|Issuer"
basicConstraints: CA:TRUE と keyCertSign が見えるはずです。Extended Key Usage にサーバ認証だけがある証明書はノード証明書の可能性が高いので、CA バンドルへ入れないでください。
3) SDK への適用例
from azure.identity import DefaultAzureCredential
from azure.confidentialledger import ConfidentialLedgerClient
ledger_url = "https://..confidentialledger.azure.com"
credential = DefaultAzureCredential()
# ledger_certificate_path に CA チェーンのみを指定
ledger = ConfidentialLedgerClient(
endpoint=ledger_url,
credential=credential,
ledger_certificate_path="ledger-ca.pem"
)
# 以降の API は macOS でもエラーにならない
resp = ledger.list_ledger_entries()
macOS の一般的 SSL 検証エラー対策(C 案)
今回の主因は「証明書の種類の誤り」ですが、macOS 特有の証明書ストアや Python バンドルの整備不足で別の検証エラーが混ざることがあります。合わせて以下を実施しておくと安心です。
- Install Certificates.command を実行
Python.org 配布版等では標準キーチェーンからcertifiバンドルを生成・配置するスクリプトが同梱されています。 certifiを最新化python -m pip install -U certifi- 必要なら
REQUESTS_CA_BUNDLEを指定
アプリで使う CA バンドルを固定化したい場合に利用(CI/コンテナ等)。
繰り返しになりますが、“CCF Node” エラーの決定的解決は A または Bです。
原因の深掘り:エラー文言が示すもの
“CCF Node” certificate is not permitted for this usage は、渡された証明書が「その用途に許可されていない」 ことを意味します。用途(Usage)は概ね以下のどちらかの文脈です。
- 信頼根(CA)用途:
basicConstraints CA:TRUE、keyCertSignを満たすこと。 - サーバ(エンドエンティティ)用途:サーバ認証(1.3.6.1.5.5.7.3.1)を満たすこと。
ノード証明書は後者(サーバ用途)向けのため、前者(CA としてアンカー)に使おうとすると macOS が拒否します。Linux/Windows でも OpenSSL の検証方針やアダプタ差(urllib3 など)で見え方は変わりますが、正しい証明書を渡すという設計に倒すのが最も安全で持続的です。
再現と検証の手順
再現例(NG パターン)
# 誤った例:ノード証明書を ledger_certificate_path に渡してしまう
ledger = ConfidentialLedgerClient(
endpoint=ledger_url,
credential=credential,
ledger_certificate_path="node-cert.pem" # <-- エンドエンティティ(NG)
)
# macOS で ServiceRequestError(“CCF Node” ...)になりやすい
修正後の動作確認
- A 案なら、
ledger_certificate_pathを削除して初期化する。 - B 案なら、
ledger-ca.pemを指定して初期化する。 - 次のような最小コードで CRUD を確認する。
entry = {"contents": "check"}
created = ledger.create_ledger_entry(entry=entry)
# 取得・列挙(例)
items = list(ledger.list_ledger_entries())
assert len(items) >= 1
よくある質問(FAQ)
Q. pip install certifi を入れたのに直りません。
A. 今回は「どの証明書を、どういう用途で使うか」の問題です。certifi は OS 既定の CA バンドルを補うものですが、ノード証明書を CA に誤用 してしまえば macOS は拒否します。A か B の対処を行ってください。
Q. verify=False なら通ります。問題ありますか?
A. 本番では非推奨です。中間者攻撃などのリスクを招き、監査要件にも抵触します。正しい CA チェーンを用意するか、SDK に任せる設計へ改めましょう。
Q. Apple Silicon(M1/M2/M3)特有の問題ですか?
A. いいえ。アーキテクチャではなく、macOS の TLS/Keychain による EKU/KeyUsage の厳格検証 によるものです。
Q. CI(macOS ランナー)や社内 Mac 全体で安定させるには?
A. 次の運用を推奨します。
- A 案の採用:アプリ側で証明書を同梱せず、SDK に委任。
- 証明書を同梱する場合:CA のみを PEM 連結で管理。ノード証明書を含めないポリシーを CI でチェック。
- 静的解析:
openssl x509 -noout -textの結果にCA:TRUEが無いファイルを CA バンドルとしてコミットさせない。
チェックリスト(導入・運用時の安全確認)
- SDK 初期化時に
ledger_certificate_pathを渡していない(A 案)。 - 渡す場合は CA チェーンのみで、
basicConstraints: CA:TRUEを満たしている(B 案)。 - ノード(“CCF Node”)証明書を CA バンドルや OS キーチェーンへ登録していない。
- macOS で Install Certificates.command を一度実行し、
certifiは最新。 - CI で PEM を検査する自動化(下記スニペット参照)を組み込んだ。
CI 向け:PEM を機械判定する補助スクリプト
CA バンドルにノード証明書が混入しないよう、簡易チェックを追加します。
import subprocess, sys, re, pathlib
def is_ca_cert(pem_path: pathlib.Path) -> bool:
out = subprocess.check_output(
["openssl", "x509", "-in", str(pem_path), "-noout", "-text"],
text=True
)
bc = re.search(r"Basic Constraints:\s*CA:TRUE", out, re.IGNORECASE)
ku = re.search(r"Key Usage:.*keyCertSign", out, re.IGNORECASE | re.DOTALL)
return bool(bc and ku)
def main(pem_file: str) -> int:
text = pathlib.Path(pem_file).read_text()
# 連結 PEM を 1 ファイルずつ分解
blocks = [b + "-----END CERTIFICATE-----\n" for b in text.split("-----END CERTIFICATE-----") if "-----BEGIN CERTIFICATE-----" in b]
tmpdir = pathlib.Path(".pem_check")
tmpdir.mkdir(exist_ok=True)
ok = True
for i, block in enumerate(blocks):
p = tmpdir / f"part{i}.pem"
p.write_text(block)
if not is_ca_cert(p):
print(f"[NG] {p} は CA:TRUE / keyCertSign を満たしません。", file=sys.stderr)
ok = False
return 0 if ok else 1
if **name** == "**main**":
sys.exit(main(sys.argv[1]))
CI で python check_ca.py ledger-ca.pem のように実行し、非 CA 証明書の混入をブロックできます。
実装テンプレート(アプリ本体)
アプリ側で「証明書の責務」を曖昧にしないための最小・安全なテンプレートです。
from dataclasses import dataclass
from azure.identity import DefaultAzureCredential
from azure.confidentialledger import ConfidentialLedgerClient
@dataclass
class LedgerConfig:
endpoint: str
ca_bundle_path: str | None = None # A 案:None、B 案:CA のみを指定
def create_client(cfg: LedgerConfig) -> ConfidentialLedgerClient:
cred = DefaultAzureCredential()
if cfg.ca_bundle_path:
return ConfidentialLedgerClient(endpoint=cfg.endpoint, credential=cred, ledger_certificate_path=cfg.ca_bundle_path)
else:
return ConfidentialLedgerClient(endpoint=cfg.endpoint, credential=cred)
def put_and_get_sample(client: ConfidentialLedgerClient) -> None:
data = {"contents": "sample"}
client.create_ledger_entry(entry=data)
for e in client.list_ledger_entries():
# 実環境では適切な条件でフィルタ
print(e)
if **name** == "**main**":
# A 案:証明書は SDK に任せる
cfg = LedgerConfig(endpoint="https://..confidentialledger.azure.com")
ledger = create_client(cfg)
put_and_get_sample(ledger)
トラブルに強いチーム運用のヒント
- 責務分離:アプリは「CA バンドルを前提」にせず、原則 SDK に委譲。やむを得ずバンドルするならファイル名・配置・更新手順を標準化。
- 監査可能性:CA バンドルはリポジトリ管理し、変更差分をレビューフック(上の OpenSSL チェック)で強制。
- マルチ OS 実行:macOS/Linux/Windows のマトリクスでサニティテストを行い、1 OS 固有の緩さに依存しない。
まとめ
- エラーの本質は ノード(“CCF Node”)証明書の誤用。CA ではない証明書を CA として渡すと macOS が用途不一致で拒否します。
- 最短の回避は A 案:
ledger_certificate_pathを指定せず SDK に任せる。 - 既存方針を維持するなら B 案:CA:TRUE のみを連結した PEM を指定。ノード証明書は含めない。
- C 案の一般対策は補助的。根治は A/B。
以上のいずれかを適用すれば、macOS でも Azure Confidential Ledger の読み書きを安全に安定運用できます。
参考:証明書の見分け方チートシート(手元検証)
| 確認観点 | CA 証明書(OK) | ノード証明書(NG:CA として使わない) |
|---|---|---|
| basicConstraints | CA:TRUE | CA:FALSE(または記述なし) |
| Key Usage | keyCertSign(+ cRLSign 等) | Digital Signature、Key Encipherment 等 |
| Extended Key Usage | (未指定 or CA 用途) | TLS Web Server Authentication(1.3.6.1.5.5.7.3.1)等 |
# 例:詳細表示
openssl x509 -in X.pem -noout -text
補遺:既存のノード証明書しか持っていない場合の応急処置
運用上、今すぐ CA バンドルを用意できないが macOS で検証したい、という場面のメモです。
- まずは A 案(証明書を渡さない)を試す。
- どうしても手元のファイルだけで試す必要がある場合でも、決してノード証明書を CA として渡さないこと。検証目的でも
verify=Falseは最小限に。 - 最短で CA を入手できるよう、台帳 ID サービスからのチェーン取得フローを優先して整備する。
あくまで応急であり、本番では A または B に切り替えてください。

コメント