クライアント証明書を使った認証・認可方式

はじめに

利用ユーザに対して「マインバー操作」や「ユーザアカウント管理」などの重要な機能を提供する業務システムでは、そのような機能へのアクセスを制限する必要があり、その実現方式の一つとしてクライアント証明書を使う方法が考えられます。ここでは、典型的な業務システム(Webアプリ)を例題とした、クライアント証明書による認証・認可機能の実現方式について説明します。

  • パスワード認証や2要素認証などによるユーザ認証を行う一般的なWeb系の業務システムを想定しています。
  • サインインユーザアカウントの登録/編集やマイナンバー操作などの特権操作を行う機能は、通常の認証・認可より強いセキュリティ対策が求められます。その対策の一つとして、ここではクライアント証明書を使用する方式の導入を検討します。
  • 通常のユーザはパスワード認証や2要素認証などでサインインします。クライアント証明書は特に必要ありません。特権が必要なユーザには事前にクライアント証明書を発行しておきます。業務システムにアクセスする際にクライアント証明書を選択し、後は通常のユーザと同様にサインインします。
  • この業務システムは典型的なWeb系の業務システムのように、リバースプロキシ/ロードバランサ/WAF等のフロントサーバの後段に、Webアプリサーバ等のバックエンドサーバが配置されるようなシステム構成を想定しています。

実現方式

基本方針

  • クライアント証明書の発行や失効等の管理を行うために認証局が必要です。
    ここでは次のようなサードパーティーが提供する認証局(以後、ここでは認証局サービスと表記)を利用する想定です。opensslなどのツールを使用した独自の認証局(プライベート認証局)を利用することも可能です。

  • 前提とするシステム構成に基づいてフロントサーバとバックエンドサーバに機能を分けて実現します。
    1. クライアント証明書の検証(フロントサーバ)
      • ブラウザに対してクライアント証明書の提出を要求し、クライアント証明書の「証明書」としての妥当性を検証します。
      • 具体的には、クライアント証明書の発行元確認や改ざん検知のための署名検証、有効期限検証などを行います。また、事前に取得した証明書失効リスト(CRL: Certificate Revocation List)や、オンラインでの証明状態の確認(OCSP: Online Certificate Status Protocol)を使用して、クライアント証明書が失効していないことを確認します。
      • フロントサーバのSSL/TLS関連機能を使用して実現します。
        (クライアント証明書の検証は、SSL/TLS接続確立時のネゴシエーションの一部として実行されるようになっているので、その機能を有効化して実現する。)
    2. クライアント証明書の所有者検証(バックエンドサーバ)
      • 業務システム利用ユーザに発行されたクライアント証明書であることを検証します。
      • 具体的には、クライアント証明書に含まれるユーザ名・メールアドレスが、サインインユーザのものと一致するかを検証します。このような単純な検証が難しい場合、認証局に問い合わせて取得したシリアル番号がクライアント証明書のシリアル番号と一致するかで検証します。(証明書が同一かどうかはシリアル番号で検証できます。)
      • Webアプリの認証・認可機能を改修して実現します。
      • クライアント証明書に含まれるメールアドレスやシリアル番号などの詳細はこちらをご覧ください。
  • 特定のURLのみクライアント証明書を要求することも可能ですが、お薦めしません。

システム構成イメージ

  • リバースプロキシ/ロードバランサ/WAF等のフロントサーバの後段に、Webアプリサーバ等のバックエンドサーバが配置されるような、Web系アプリの典型的なシステム構成を想定しています。
  • フロントサーバでは、クライアント証明書の検証、ページ要求転送時のクライアント証明書の情報の付与を有効化します。
    また、証明書が失効していないことを確認するために、証明書失効リスト(CRL)やオンライン証明書状態確認(OCSP)を有効化します。
  • バックエンドサーバでは、クライアント証明書がサインインユーザに発行されたものであることを検証するユーザ認可機能を実装します。転送されたページ要求に含まれるクライアント証明書情報に含まれるユーザ名やメールアドレスが、サインインユーザのものと一致するかを検証します。クライアント証明書の情報だけでは所有者の検証が難しい場合、認証局から取得した証明書の情報(シリアル番号など)を使って検証することもできます。

認証・認可のフロー

  1. httpsのURIを開くとブラウザがSSL/TLS接続を開始します。ブラウザ側でのサーバ証明書の検証後、サーバ側でクライアント証明書の検証が行われます。ユーザがクライアント証明書を持っていない/選択しなかった場合、クライアント証明書の検証はスキップされます。(必須にすることも可能です。)
  2. クライアント証明書の検証では、中間証明書やルート証明書を使った署名の検証や有効期限の検証が行われます。また、オンラインでの証明書状態の確認(OCSP)や、事前に取得されている証明書失効リスト(CRL)を使って、証明書が失効していないかを検証します。
  3. 検証が成功した場合、またはクライアント証明書の提出をスキップした場合、HTTP通信が開始されます。バックエンドサーバに転送されるHTTP要求に、クライアント証明書の情報がHTTPヘッダとして付与されます。
  4. バックエンドサーバでのユーザ認証・認可時、HTTPヘッダに含まれるクライアント証明書の情報を使って、クライアント証明書の所有者検証を行います。クライアント証明書のユーザ名やメールアドレスが認証済ユーザのものと一致するかを検証します。
  5. クライアント証明書の情報だけでは所有者の検証を行えない場合、ユーザに発行したクライアント証明書の情報(シリアル番号)を認証局から取得し、提出されたクライアント証明書のシリアル番号と一致するかで検証することもできます。

概要設計

フロントサーバとバックエンドサーバの設計方針について説明します。

フロントサーバ

  • 「ブラウザに対するクライアント証明書の提出要求」を構成します。
    • 典型的なサーバでは主に「なし」「任意」「必須」の3つが用意されています。
      既定では「なし」なので、要件に応じて「任意」または「必須」を指定します。

      オプション説明
      なし(none)クライアント証明書の提出を不要とする。(既定)
      任意(optional)クライアント証明書の提出をユーザが選択可能とする。
      必須(required)クライアント証明書の提出を必須とする。
    • Apcheの場合は、クライアント証明書の提出要求はSSLVerifyClientディレクティブで指定します。
  • 証明書失効リスト(CRL)やオンライン証明書状態プロトコル(OCSP)を有効化し、提出されたクライアント証明書が失効していないことを検証します。
    • CRLでは、証明書失効リストを定期的に認証局からダウンロードする必要があるため、認証局での証明書失効リストの変更が反映されるまでに若干の時間差があります。要件として許容されない場合、OCSPを使用する必要があります。
    • なお、OCSPを使用する場合、ユーザによるページアクセス時、必要に応じてフロントサーバから認証局への問合せが実行されます。ファイヤーウォールの設定漏れ等でこの問合せが実行できないと、ページアクセスがタイムアウトする場合があるので注意が必要です。(厳密なファイヤーウォールが有効化されるステージング環境や本番運用環境で度々検出される不具合・考慮漏れです。)
    • 個人的な見解として、CRLを使った方が安全・安定だと考えます。OCSPの場合、認証局への問い合わせ頻度と認証局の応答性能が業務システムの応答性に影響を与えることや、(可能性は低いですが)認証局で障害が発生する場合の考慮も必要になり、設計の妥当性の説明が難しいため。
  • クライアント証明書の署名を検証するために、中間証明書やルート証明書を設定します。
    • 必要となる中間証明書やルート証明書は認証局サービスから提供されます。これらの証明書の入手方法や設定方法は、認証局サービスが提供するリファレンスやガイドをご覧ください。
    • Apacheの場合、SSLCACertificateFileディレクティブに中間証明書やルート証明書を指定します。このディレクティブでは単一ファイルしか指定できないので、必要となる中間証明書やルート証明書を単一ファイルにマージする必要があります。なお、使用するクライアント証明書を選択するブラウザの画面では、ここで指定した証明書が発行者になっているクライアント証明書のみが表示されるようです。
  • ページアクセス要求転送時、クライアント証明書の情報を付加する必要があります。
    • フロントサーバでは、その役割・機能として、クライアント(ブラウザ)からのページ要求(HTTP要求)をバックエンドに転送する必要があります。この際、バックエンドで必要となるクライアント証明書の情報を任意のHTTPヘッダに付与する設定を行います。
    • クライアント証明書を付与するHTTPヘッダ名は標準として決まっておらず、独自に決定する必要があります。実業務では、フロントサーバ担当(基盤チーム)とバックエンド担当者(アプリ基盤や業務チーム)で協議して決定することになると思います。経験的に、誰が決めるかでモメることが多いのですが、HTTP仕様やクライアント証明書を使った認可機能の設計を担当するアプリ基盤が決めることになると思います。
    • 付与するクライアント証明書の情報として、「証明書そのもの」、「証明書の特定フィールドの値」等を選択できます。複数のフィールド値を付与する場合、それぞれを付与するためのHTTPヘッダを用意する必要があります。全てのページ要求に付与されることになるので、「証明書そのもの」より必要最小限の「証明書の特定フィールドの値」を付与することをお薦めします。
      多くの場合、クライアント証明書に発行先となるユーザ情報を含むサブジェクト(Subject)やサブジェクト代替名(SAN: Subject Alternative Name)、証明書を識別するためのシリアル番号を付与することになると思います。
    • HTTPヘッダ名と値の例を次に示します。
      HTTPヘッダでは大小文字の区別はありません。「独自ヘッダは”X-“で始める」慣例は2012年に公開されたRFC6648で非推奨になっていますが、今でも広く使われており、その慣例に則ります。HTTPヘッダに付与できる値は、フロントサーバ(ソフトウェア)によって異なる可能性があります。

      付与する値の例ヘッダ名の例値の例
      クライアント証明書そのものX-CLIENTCERT—–BEGIN CERTIFICATE—–MIID3=…
      サブジェクトX-CLIENTCERT-SUBJECTCN=Taro Yamada, O=my company,
      emailAddress=yamada.taro@example.com
      SANのメールアドレスX-CLIENTCERT-SAN-EMAILyamada.taro@example.com
      SANのUser Principal NameX-CLIENTCERT-SAN-UPN38bb8b53@example.com
      シリアル番号X-CLIENTCERT-SERIAL4B8D965354B2DE894069F90A2D966725
    • Apacheの場合、クライアント証明書の情報は、例えばシリアル番号の場合は”SSL_CLIENT_M_SERIAL”、等のように環境変数に格納されます。RequestHeaderディレクティブを使って、任意のHTTPヘッダに対して環境変数の値を設定することで実現します。
  • その他
    • クライアント証明書の検証に失敗した場合のエラーページの表示方法はフロントサーバ(ソフトウェア)に依存します。フロントサーバ上へのHTMLの配置、エラーページURIへのリダイレクトなど、フロントサーバのエラーページ表示方法を把握した上で、エラーページの配置場所やエラーページの内容を決定する必要があります。

バックエンドサーバ

  • クライアント証明書の所有権の検証はユーザ認可処理の一部として実装する想定です。
    • 典型的な業務システムでは、ユーザ認証成功後に当該ユーザに関する詳細情報をセッションにロードします。ユーザ認可処理では、各機能へのアクセス可否の判定や、セッションのユーザ情報とフロントサーバから転送されたクライアント証明書の情報を使って所有者の検証を行い、特権機能へのアクセス可否を判定します。
    • クライアント証明書の情報は、フロントサーバから転送される全てのページ要求のHTTPヘッダに付与されます。任意のタイミングでHTTPヘッダからクライアント証明書の情報を取得できます。
  • セッションとクライアント証明書のユーザ情報の突合で所有者かどうかを検証します。
    • ユーザを一意に識別するメールアドレス、ユーザID、社員番号、氏名等(突合キー)が、クライアント証明書に含まれる値と一致するかを検証します。
    • ユーザに対してクライアント証明書を発行する場合、認証局に対して事前にユーザ情報を登録する必要があります。登録したユーザ情報は、クライアント証明書のサブジェクト(Subject)やサブジェクト代替名(SAN: Subject Alternative Name)フィールドに埋め込まれるので、そこから検証に必要となるユーザ取得を取得できます。クライアント証明書から取得可能なユーザ情報は、認証局サービスに確認してください。
    • クライアント証明書のフィールドに関しては、こちらで説明しています。
    • 突合キーとして、社員番号な内部ID等の業務システムの運用中に変更されない値を使用することをお薦めします。例えば、突合キーとしてメールアドレスを使う場合、業務システム利用中にメールアドレスが変更されると、クライアント証明書の変更(再発行)が必要になる可能性があり、設計・運用が複雑になるためです。
      (クライアント証明書に社員番号や内部IDを埋め込む場合、認証局へのユーザ登録情報にこれらの情報を含める必要があります。クライアント証明書に期待する値を埋め込めるかどうかは認証局サービスに依存します。)
    • セッションのユーザ情報とクライアント証明書の情報で所有者を検証できない場合、認証局サービスがAPIを提供していれば、認証局から必要な情報を取得することも可能です。例えば、セッションのメールアドレスをキーとして認証局から「当該ユーザに発行されたクライアント証明書情報(シリアル番号など)」を取得し、クライアント証明書のシリアル番号が一致するかを検証します。
    • 認証局に問い合わせするのではなく、業務システムのDBに保存したシリアル番号を使用する方式も考えられますが、認証局での証明書の再発行や失効設定時にデータの不整合が発生するのでお薦めできません。
    • HTTPの仕様上、HTTPヘッダ名に大小文字の区別はありません。実行環境でクライアント証明書の情報が取得できない不具合を回避するために、大小文字を無視したHTTPヘッダ名から値を取得するようにします。また、シリアル番号等のように取得した値によっては大小文字の区別がない場合があるので、突合キーの照合時に注意が必要です。