物理トークンからの脱却 AWSCloudHSM入門

はじめまして、Optimal Biz DS Windowsチームの今別府です。 DS Windowsチームといえば以下の記事で記憶に新しいかもしれませんが、こちらの移行の関連でWindows Agentのビルドに関わるその他の環境も進化しつつあります。

tech-blog.optim.co.jp

今回はWindows Agentのビルドに関わるものとして、コードサイニングに関する環境の構築を行ったので記載していこうと思います。

はじめに

コードサイニングとは、世の中に対して頒布するプログラムに対して、電子証明書を用いて署名する事によって、そのプログラムが確かにその発行者によって作成されたものであって、別の第三者が作成した偽物ではなく、発行後に改ざんされていないことを示すものです。

一般に企業が公開して頒布するプログラムにはコードサイニングがなされています。 これは悪意ある第三者が偽物を作って悪意あるプログラムを頒布することを抑止するため、またコードサイニング証明書を識別するウィルス対策ソフトに対してよい評価を得られやすくするために行われています。 Optimal BizのWindows Agentにおいても例外ではなく、コードサイニング証明書による署名が施されています。

Optimal Bizではコードサイニング証明書としてpfxファイルを用いてましたが、 Baseline Requirementsの要件変更(CA/ブラウザフォーラムにおけるコードサイニング基本要件の変更)及び、 Digicertの要件変更([重要]コードサイニング証明書における要件変更について(2022年11月))により、USBトークンへの移行を果たし、それによって署名を行ったWindows Agentをリリースしてきました。 しかし、後述このUSBトークンには運用上大変辛い部分が存在していたため、このたびAWS CloudHSMを用いたコードサイニング環境を構築しました。 今回はそのAWS CloudHSMを用いた環境構築について記載できればと思います。

USBトークンについて

pfxファイルから移行したDigicertのUSBトークン(イメージ)は以下のようなものでした。

DigicertのUSBトークンイメージ

画像の様なUSBの中に証明書がインポートされており、署名を行う際には署名を行うマシン上にツールをインストールしたうえで、USBを認識させる必要があります。 想像の通り、ハードウェアで存在しているので現物の1つを厳重に管理すれば、pfxファイルの様に複製もできませんし安全な証明書の保持手段だと思います。

しかし、物理ハードウェアであるがゆえに以下のような問題も運用していくうえで発生していました。

  • 1つしか存在しないため、他プロダクトとぶつかることがあったこと
    • 当時は社内で唯一のUSBトークンだったので社内の他プロダクトで署名を行う際には、Windows Agentのビルドの時期やビルドに問題が起こった場合を加味してバッティングしないようにする必要がある。
    • 予定の調整が大変
  • 証明書という厳重な管理を求められるものであるため社内での保管となり、使用するには出社の必要がある
    • 弊社では在宅日と出社日が存在するため、署名を行う際にはWindows Agentのビルドを出社の日になるように調整する必要がある。こちらもビルドに問題などが起きた場合、出社の切り替え等が発生する。
    • 予定の調整とメンバーの調整が大変
  • Windows Agentのビルドスクリプトの実装上、エラーが起きるとUSBトークンのロックがかかる恐れがあり二人以上の監視が必要
    • 過去、ビルドスクリプトの署名エラーでUSBトークンがロックされました。ロック自体はITM(ITマネジメント、弊社の情報システム部門)に解除してもらったものの、ITMの時間を奪ってしまうこと、ロック中は他プロダクトで使用できなくなることからビルド中は、異変を確認できるように2人体制でビルドでエラーが出ていないかチェックしながらのビルドを行うことになりました。
    • ビルドのためにメンバーの工数を数時間奪ってしまう。

上記の問題を解決するため、USBトークンを使わない方法で環境構築できないかと話が進んでいきました。

AWS CloudHSM

今回USBトークンの代わりとして白羽の矢が立ったのはAWSのCloudHSMといったサービスです。

CloudHSM自体は世の中にいろいろなサービスで提供されていますが、Windows Agentのビルド環境がAWSに構築されているため、AWS内のサービスで選定されました。

また、OPTiMでは証明書はDigicertに発行してもらうことになるのですが、Digicertで定められたセキュリティ要件を満たしていることも重要な点です。

Generate and use cryptographic keys on dedicated FIPS 140-2 Level 3 single-tenant HSM instances.

以上から、CloudHSMを用いた署名の環境を構築することになりました。

CloudHSMを使用した署名

構成

Windows Agentのビルド環境はEC2上に構築していたので既存のVPCなどの設定に合わせる形で、 CloudHSMの環境を構築しました。 セキュリティグループとしては既存のビルド環境への接続のためのものに加え、CloudHSMを使用するために必要なセキュリティグループを含めました。 また、CloudHSM証明書を置いていくためネットワークはPrivate Subnetを構成し、その中からEC2と接続するような形になっています。

CloudHSM構成

環境の構築

環境の構築についてですが、基本はAWSのDocumentに従えば構築は完了します。

しかし、Windows特有かもしれないですが、ドキュメントに罠が潜んでいるのでそれを踏まえて順番に書いていきます。

なお、EC2やVPCの設定やCloudHSMを使用するのに必要なIAMについての説明はここでは割愛します。

クラスターの作成

ドキュメントに従って、AWS ConsoleでCloudHSMを見つけて、クラスター作成を行います。 基本的にクラスターの作成を選択して、流れに沿って、内容を入れていけば問題ないと思います。

以下は例となります。今回はアベイラビリティーゾーンは1つのみで作成します。 注意書きに書いてあるように、冗長性を高めるためには2つのアベイラビリティーゾーンが推奨されています。 もし、冗長性を考えるならそれぞれの運用状況を踏まえて、選択を増やしていってください。

また、今回の構成では、CloudHSMはプライベートサブネット内に構成するので該当のアベイラビリティゾーンの選択肢からプライベートサブネットのものを選んで設定します。

クラスター設定

クラスターの作成ではバックアップの日数なども選択できます。 バックアップは無料で、CloudHSMとしての時間料金はかからないので、運用方法に合わせて選択を行うのが良いと思います。 tagなども、利用される環境に合わせて適宜設定を行います。

以上の設定を行ったら、クラスターの作成を実行してください。

セキュリティグループの追加

クラスターの作成を実行すると、裏で自動的にクラスターのIDが含まれるセキュリティグループが生成されます。 EC2のインスタンスとCloudHSMを接続するには、任意のEC2のインスタンスのセキュリティグループに、生成されたクラスターIDが含まれるセキュリティグループを関連付ける必要があるので設定の変更を行います。

セキュリティグループ設定

上記の設定を行うと、EC2でCloudHSMを認識できる状態になります。

HSMの作成

現在クラスターの作成は完了しましたが、HSMとしての機能を利用するためには、クラスター内にHSMの作成を行う必要があります。 HSMの作成にはまず、クラスタの初期化が必要です。 ドキュメントに従って、クラスタの初期化を行いましょう。

クラスターの初期化 - AWS CloudHSM / クラスター CSR の取得の手順に従ってCSRを取得したら、CSRに署名を行いましょう。

注意書きには実稼働の場合、信頼できるレベルのランダム性のあるソースを使用するように記載されていますが、 今回の説明にあたっては、開発・テスト向けの環境なのでドキュメント記載の通りでOpenSSLを用いた自己署名を行いました。

署名が完了したら、Console上の画面でアップロードが可能なのでクラスター証明書と発行した証明書のアップロードを行います。

証明書アップロード

このタイミングで発行した証明書(customerCA.crt)をアップロードしますが、この証明書はこの後の手順でEC2に配置することにも使用するので、見つけやすいようにしておくのが吉です。

アップロードするファイルを選択してアップロードと初期化を実行すると、クラスターの初期化とHSM作成が行われます。 米国西部 (オレゴン)us-west-2の場合は、HSMが作成されるまでは大体5分程度かかりました。

ビルドマシン(EC2)とHSMを接続しアクティベートする

ここまでで、CloudHSMの設定は完了しているので、続いてはCloudHSMをEC2に接続し、EC2での設定を行っていきます。

とりあえず、ドキュメントのクラスターのアクティブ化 - AWS CloudHSM通り、HSMの作成の手順で発行したcustomerCA.crtを以下のパスに配置しましょう

C:\ProgramData\Amazon\CloudHSM\customerCA.crt

次にEC2にHSM CLIのクライアントをインストールします。 しかし、Windowsで署名を行う際に限ってここはドキュメント通りにしてはいけません(2024年09月時点)。 どういうことかというと、AWSの手順(クラウドのインストールと設定HSM CLI - AWS CloudHSM)でWindows Server 2016/2019のコマンドでインストールされるSDKは最新のSDK5なのですが、 SDK5はCNG および KSP プロバイダーをまだサポートしていません

SDKバージョンの違い
https://docs.aws.amazon.com/ja_jp/cloudhsm/latest/userguide/choose-client-sdk.html

詳細は記載しませんがCNG について - Win32 apps | Microsoft Learnの様にWindowsで暗号機能を使用するために必要な物であるので、 Windows ServerでクライアントSDKを使用する場合はSDK3をインストールするようにしましょう。 なお、構築時試しにSDK5とSDK3を混在させたところ、共存は可能そうでしたが、基本的に環境が混ざるとろくなことが起こらないので、 ドキュメントとは反してSDK3でのアクティベートをここでは行います。

まず、次のページからSDK3をインストールしてきます。 構築当時のバージョンは3.4.4をインストールして使用しました。

次にHSMのIPをクライアントツールの設定に書き込みましょう以下のようなコマンドで、設定とクライアントツールの再起動を行います。 任意のIPアドレスの部分に入れるアドレスは、HSM作成の手順の後に画像の様にHSMが作成されていると思うのでENI IP アドレスのものを入れましょう。 今回は初期化で使いますが、HSMの運用上HSMの削除⇒作成を繰り返す場合は、 基本的にENI IPが変更され続けるのでこの操作が必要になります。覚えておきましょう。

ENI IPの場所

$ net.exe stop AWSCloudHSMClient
$ "C:\Program Files\Amazon\CloudHSM\configure.exe" -a 172.31.55.66 # 任意のIPアドレス
$ net.exe start AWSCloudHSMClient

その後は以下のようなコマンドを実行することでアクティベートできます。

途中で出てくるPRECOやCO、CU、AUはHSMで利用するユーザーの種別になります。 詳細は以下を確認ください。 CloudHSM 管理ユーティリティ (CMU) を使用する HSM ユーザーを管理する - AWS CloudHSM

# SDK3のCloudHSMのクライアントツールを起動します
PS C:\Program Files\Amazon\CloudHSM>  .\cloudhsm_mgmt_util.exe C:\ProgramData\Amazon\CloudHSM\data\cloudhsm_mgmt_util.cfg
Ignoring E2E enable flag in the configuration file

Connecting to the server(s), it may take time
depending on the server(s) load, please wait...

Connecting to server '172.31.55.66': hostname '172.31.55.66', port 2225...
Connected to server '172.31.55.66': hostname '172.31.55.66', port 2225.


# end to endの暗号化を有効にします
aws-cloudhsm>enable_e2e
E2E enabled on server 0(172.31.55.66)

# 必須ではないですが、現在のUserの一覧を確認します。PRECOとAUが存在していることが確認できます。
aws-cloudhsm>listUsers
Users on server 0(172.31.55.66):
Number of users found:2

    User Id             User Type       User Name                          MofnPubKey    LoginFailureCnt         2FA
         1              PRECO           admin                                    NO               0               NO
         2              AU              app_user                                 NO               0               NO

# HSMをアクティベートするための一時的なユーザを作成します。passwordはこの後変更するのでわかりやすいものにしても問題ないです。
aws-cloudhsm>loginHSM PRECO admin password
loginHSM success on server 0(172.31.55.66)

# こちらのコマンドで、PRECOのユーザーであるadminのパスワードを変更します。
aws-cloudhsm>changePswd PRECO admin {NewPassword}
*************************CAUTION********************************
This is a CRITICAL operation, should be done on all nodes in the
cluster. AWS does NOT synchronize these changes automatically with the
nodes on which this operation is not executed or failed, please
ensure this operation is executed on all nodes in the cluster.
****************************************************************

# 問題なければyを選択し続ける
Do you want to continue(y/n)?y
Changing password for admin(PRECO) on 1 nodes
changePswd success on server 0(172.31.55.66)

# 念のためもう一度ユーザーの確認をします。
aws-cloudhsm>listUsers
Users on server 0(172.31.55.66):
Number of users found:2

    User Id             User Type       User Name                          MofnPubKey    LoginFailureCnt         2FA
         1              CO              admin                                    NO               0               NO
         2              AU              app_user                                 NO               0               NO

上記コマンド実行後にCOが作成されるので、次はCOからCUを作成します。 基本的にはCloudHSM上のキーなどの操作はCUでしか行うこととなります。 次の様なコマンドで、CUを作成しましょう。

# SDK3のCloudHSMのクライアントツールを起動します
C:\Program Files\Amazon\CloudHSM>.\cloudhsm_mgmt_util.exe C:\ProgramData\Amazon\CloudHSM\data\cloudhsm_mgmt_util.cfg
Ignoring E2E enable flag in the configuration file

Connecting to the server(s), it may take time
depending on the server(s) load, please wait...

Connecting to server '172.31.58.6': hostname '172.31.58.6', port 2225...
Connected to server '172.31.58.6': hostname '172.31.58.6', port 2225.
E2E enabled on server 0(172.31.58.6)

# COのユーザでログインします。{CO_password}は前の手順でPRECOのパスワード変更時に入れたものと同じです。
aws-cloudhsm>loginHSM CO admin {CO_password}
loginHSM success on server 0(172.31.58.6)

# CUのユーザを作成します、{CU_user} {CU_password}には任意のユーザー名とパスワードを入れてください
aws-cloudhsm>createUser CU {CU_user} {CU_password}
*************************CAUTION********************************
This is a CRITICAL operation, should be done on all nodes in the
cluster. AWS does NOT synchronize these changes automatically with the
nodes on which this operation is not executed or failed, please
ensure this operation is executed on all nodes in the cluster.
****************************************************************

# 問題なければyを選択し続ける
Do you want to continue(y/n)?y
Creating User CU_user(CU) on 1 nodes
createUser success on server 0(172.31.58.6)

# すでに作成済みのCOと既存のAUユーザーのほかに、CUユーザーが作成されたか確認する
aws-cloudhsm>listUsers
Users on server 0(172.31.58.6):
Number of users found:3

    User Id             User Type       User Name                          MofnPubKey    LoginFailureCnt         2FA
         1              CO              admin                                    NO               0               NO
         2              AU              app_user                                 NO               0               NO
         3              CU              CU_user                                  NO               0               NO

ここまでで、HSMの接続に必要なユーザのアカウントは作成完了です。 あとはWindows の AWS CloudHSM 前提条件 - AWS CloudHSMに書いている様に、Windows上でログイン認証情報の登録が必要なので好きな方法で登録しましょう。

以上が完了するとHSMがEC2で認識されていると思います。 確認には以下のコマンドが使用できます。

C:\Program Files\Amazon\CloudHSM\tools>certutil -csplist

色々な出力がされますがProvider Name: Cavium Key Storage Providerがエラーなく表示されていれば問題はないです。 もし問題がある場合は、何らかの理由でHSMがEC2に認識されていない可能性が高いので手順を見直しましょう。

ちなみに、今回の環境構築中によくあった認識されない原因としては以下のようなものがありました。 地味に手順として忘れがちなものや、ヒューマンエラーが多かったので、皆さんも注意して手順を振り返ると解決がすぐにできるかもしれません。

  • 認識されない原因一覧
    • EC2セキュリティグループの設定忘れ
      • 初手から手順をさぼって忘れることが多い
    • configure.exeでEniIpを更新していない
      • 何回もHSMを生成して値が変わると、更新を忘れがち
    • Windows資格情報に登録している情報が異なる(CUのログイン失敗)
      • よくあるパスワードの記入ミス

証明書の発行・インポートと署名

以上でCloudHSMの環境構築が完了しました。 なお、ここまでようやくコードサイニング証明書を格納するHSMの環境が出来たのみなので、ここから先でCloudHSMを用いた証明書の発行と署名を行うところまで記載します。

CSRの作成と証明書発行

ドキュメントに記載の様に証明書を発行するためにCSRファイルを作成します。 CSRの発行のためにreqest.infを作成し、certreqコマンドを使ってCSRファイルを生成します。

request.infの内容は、証明書の要件や、使用者の運用によって変化するので適宜書き変えましょう。

以下に弊社での例を示します。

[Version]
Signature= $Windows NT$
[NewRequest]
Subject = "C=JP,CN=OPTiM Corporation,O=OPTiM Corporation,L=Minato-ku,S=Tokyo"
RequestType=PKCS10
HashAlgorithm = SHA256
KeyAlgorithm = RSA
KeyLength = 4096
ProviderName = Cavium Key Storage Provider
KeyUsage = "CERT_DIGITAL_SIGNATURE_KEY_USAGE"
MachineKeySet = True
Exportable = False

CSRファイルを生成したら、証明書を発行します。 OPTiMの証明書はDigicertによって発行されています。そのため、CSRファイルをDigicertに送付して発行してもらうことになりました。

証明書インポート

証明書が発行されたらそれをWindows Serverにインポートします。 コマンドなどいろいろな方法で取り込めますが、一例として以下の様に証明書を右クリックすることでインポートが可能です。

証明書をインポートした後、以下のコマンドで該当の証明書の情報が確認できればインポートは完了です。

PS C:\Users\Administrator\Desktop> Get-ChildItem -path cert:\LocalMachine\My

   PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My

Thumbprint                                Subject              EnhancedKeyUsageList
----------                                -------              --------------------
CA5D3A66FAAFC79D230F2816051B623471257456  CN=OPTiM Corporatio…

署名

では、最後に実際に署名に使用できるか確認します。 Windowsの場合はsigntoolコマンドで署名が可能です。 "証明書インポート"で出力したThumbprintと署名を行いたいファイル名を適宜指定することで署名が可能です。

$ signtool sign /sha1 {証明書のThumbprint} /sm /fd sha256 /td SHA256 /tr http://timestamp.digicert.com /ph {署名したいファイルパス}

以上の手順でファイルの署名が完了しました。おつかれさまです

おわりに

今回の対応によりコードサイニング署名を行う環境を、物理からクラウドの環境へ移行することが出来ました。 本記事では環境構築までの記載となりましたが、AWS CloudHSMを使うことで、コードサイニング署名のプロセスをCI/CDの一部に組み込むことが可能になりました。機会があればまたそのあたりについても記載できればと思います。

OPTiMではAWSのドキュメントをめげずに読解して開発に役立てようと思うエンジニアも募集しています。

www.optim.co.jp