Azure Confidential Ledgerの“CCF Node certificate is not permitted for this usage”エラーをmacOSのPython環境で解決する方法と原因の深掘り

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 証明書。
    basicConstraintsCA: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:TRUEkeyCertSign が見えるはずです。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 バンドルの整備不足で別の検証エラーが混ざることがあります。合わせて以下を実施しておくと安心です。

  1. Install Certificates.command を実行
    Python.org 配布版等では標準キーチェーンから certifi バンドルを生成・配置するスクリプトが同梱されています。
  2. certifi を最新化
    python -m pip install -U certifi
  3. 必要なら REQUESTS_CA_BUNDLE を指定
    アプリで使う CA バンドルを固定化したい場合に利用(CI/コンテナ等)。

繰り返しになりますが、“CCF Node” エラーの決定的解決は A または Bです。

原因の深掘り:エラー文言が示すもの

“CCF Node” certificate is not permitted for this usage は、渡された証明書が「その用途に許可されていない」 ことを意味します。用途(Usage)は概ね以下のどちらかの文脈です。

  • 信頼根(CA)用途basicConstraints CA:TRUEkeyCertSign を満たすこと。
  • サーバ(エンドエンティティ)用途:サーバ認証(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"  # &lt;-- エンドエンティティ(NG)
)
# macOS で ServiceRequestError(“CCF Node” ...)になりやすい

修正後の動作確認

  1. A 案なら、ledger_certificate_path を削除して初期化する。
  2. B 案なら、ledger-ca.pem を指定して初期化する。
  3. 次のような最小コードで CRUD を確認する。
entry = {"contents": "check"}
created = ledger.create_ledger_entry(entry=entry)
# 取得・列挙(例)
items = list(ledger.list_ledger_entries())
assert len(items) &gt;= 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 として使わない)
basicConstraintsCA:TRUECA:FALSE(または記述なし)
Key UsagekeyCertSign(+ cRLSign 等)Digital SignatureKey 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 で検証したい、という場面のメモです。

  1. まずは A 案(証明書を渡さない)を試す。
  2. どうしても手元のファイルだけで試す必要がある場合でも、決してノード証明書を CA として渡さないこと。検証目的でも verify=False は最小限に。
  3. 最短で CA を入手できるよう、台帳 ID サービスからのチェーン取得フローを優先して整備する。

あくまで応急であり、本番では A または B に切り替えてください。


コメント

コメントする

目次