Amazon Web Services ブログ

AWS Private CA と AWS KMS を使用したポスト量子 (ML-DSA) コード署名

本ブログは 2025 年 11 月 17 日に公開された AWS Blog “Post-quantum (ML-DSA) code signing with AWS Private CA and AWS KMS” を翻訳したものです。

2025 年 6 月の AWS Key Management Service (AWS KMS) における ML-DSA サポートの発表に続き、AWS Private Certificate Authority (AWS Private CA) でもポスト量子 ML-DSA 署名のサポートが導入されました。AWS Private CA を使用すると、独自のプライベート公開鍵基盤 (PKI) 階層を作成、管理できます。今回の対応により、コード署名、デバイス認証、AWS IAM Roles Anywhere を使用した AWS 外部のワークロード認証、IKEv2/IPsec や 相互 TLS (mTLS) などの通信トンネルといったユースケースにおいて、プライベート PKI を使用してお客様が管理する耐量子の信頼の基点を構築し利用できるようになります。

AWS のポスト量子暗号移行計画で述べているとおり、耐量子の信頼の基点を確立することは、長期間にわたってセキュリティを維持する必要があるシステムにとって極めて重要です。FIPS 204 で標準化された署名方式である ML-DSA は、大規模な展開に必要なパフォーマンス特性を維持しながら、量子コンピュータへの耐性を実現します。

以前、AWS Private CA と AWS KMS を使用したコード署名の方法を紹介しました。本記事では、AWS KMS が提供するポスト量子署名機能と AWS Private CA のポスト量子コード署名 PKI を組み合わせる方法を説明します。ポスト量子 PKI のルート証明書を事前にプロビジョニングしておくことで、署名済みコードの利用者は、暗号解読能力を持つ量子コンピュータ (CRQC) を利用した攻撃を受けても、ソフトウェアが改ざんされていないことを信頼できます。デモンストレーションとして、AWS SDK for Java を使用するサンプルプログラム diy-code-signing-kms-private-ca を使用します。このコードは、PKI インフラストラクチャの作成、コード署名証明書の生成、バイナリコードへの署名、およびその署名の検証を行います。本記事では機能をわかりやすく説明するためにステップごとに分解していますが、README ファイルに記載されているコマンドで Runner をそのまま実行して動作を確認することもできます。

本記事では、入力バイナリデータに対して生成された署名をカプセル化するために CMS (Cryptographic Message Syntax) 標準を使用します。CMS は、信頼を確立するために使用される署名、X.509 証明書、および証明書チェーンを格納します。この署名はデタッチド署名と呼ばれ、元のデータを含みません。デタッチド署名は、署名された元のファイルと組み合わせることで、OpenSSL などの標準ツールを使用してファイルの真正性を検証できます。

ポスト量子 PKI 階層の作成

本記事では、AWS Private CA を使用してコード署名 PKI を構築します。この PKI は、ルート CA が下位 CA に署名し、下位 CA がコード署名証明書に署名するという構成です。チェーン全体が耐量子の ML-DSA 証明書で構成されます。

CA 階層の作成

まず、ML-DSA を使用してポスト量子 CA 階層を作成します。この例では、ポスト量子署名アルゴリズムの ML-DSA-65 バリアントを使用します。Runner.java ファイルで示されているように、AWS Java SDK を使用して以下のように実行できます。

PrivateCA rootPrivateCA = PrivateCA.builder()
	.withCommonName(ROOT_COMMON_NAME)
	.withType(CertificateAuthorityType.ROOT)
	.withAlgorithmFamily(ML_DSA_65_ALGORITHM_FAMILY)
	.getOrCreate();

PrivateCA subordinatePrivateCA = PrivateCA.builder()
    .withIssuer(rootPrivateCA).withCommonName(SUBORDINATE_COMMON_NAME)
    .withType(CertificateAuthorityType.SUBORDINATE)
	.withAlgorithmFamily(ML_DSA_65_ALGORITHM_FAMILY)
    .getOrCreate();

コード署名の準備

コード署名には、非対称キーペアとコード署名証明書が必要です。非対称 ML-DSA キーペアは AWS KMS で生成し、コード署名証明書は AWS Private CA から発行します。

AWS KMS で ML-DSA キーペアを作成

まず、コード署名用の非対称キーペアを作成します。CA 階層の作成時と同様に、AWS Java SDK を使用してこの AWS KMS キー (キーペア) を作成できます。署名は、AWS KMS 内のキーペアのプライベートキーで行います。対応するパブリックキーは、下位 CA が署名するコード署名リーフ証明書に含まれます。これらの呼び出しは、Runner.java ファイルの main メソッド内で実行されます。

AsymmetricCMK codeSigningCMK = AsymmetricCMK
    .builder().withAlias(CMK_ALIAS)
	.withAlgorithmFamily(ML_DSA_65_ALGORITHM_FAMILY)
    .getOrCreate();

また、Security Blog の「AWS KMS と ML-DSA を使用してポスト量子署名を作成する方法」で紹介されているように、AWS マネジメントコンソールまたは AWS コマンドラインインターフェイス (AWS CLI) を使用して AWS KMS でキーペアを生成することもできます。

コード署名証明書の発行

AWS Private CA を使用した証明書署名リクエスト (CSR) の作成は、2 つのステップで行います。まず、ID (Subject) と先ほど作成した AWS KMS パブリックキーを含む CSR を作成します。Runner.java 内の以下のコードスニペットで、この処理を行います。

String codeSigningCSR = codeSigningCMK
	.generateCSR(END_ENTITY_COMMON_NAME);

CSR をディスク上の csr.pem に書き出した場合、OpenSSL 3.5 以降で以下のコマンドを使用して内容を確認できます。

openssl req -in csr.pem -inform pem -text -noout
Certificate Request:
	Data:
		Version: 1 (0x0)
		Subject: CN=CodeSigningCertificate
		Subject Public Key Info:
			Public Key Algorithm: ML-DSA-65
				ML-DSA-65 Public-Key:
				pub:
					<Public Key Data>   
		Attributes:
			Requested Extensions:
				X509v3 Basic Constraints:
					CA:FALSE
	Signature Algorithm: ML-DSA-65
	Signature Value:
		<Signature Data>

CSR に ML-DSA-65 パブリックキーが含まれていることがわかります。対応するプライベートキーがコード署名に使用されます。

次に、この CSR を使用して下位 CA からコード署名証明書を発行します。PrivateCA.java ファイルIssueCertificate リクエストの templateArn にコード署名テンプレートが使用されている点に注目してください。このテンプレートを使用することで、CSR で提示された値に関係なく、AWS Private CA が正しい Key Usage (KU) と Extended Key Usage (EKU) の拡張値を持つ証明書を発行できるようになります。

IssueCertificateRequest issueCertificateRequest = IssueCertificateRequest.builder()
	.idempotencyToken(UUID.randomUUID().toString())
	.certificateAuthorityArn(subordinatePrivateCA.arn())
	.csr(SdkBytes.fromUtf8String(csr))
	.signingAlgorithm(algorithmFamily.getPcaSigningAlgorithm())
	.templateArn("arn:aws:acm-pca:::template/CodeSigningCertificate/V1")
	.validity(validity)
	.build();

IssueCertificateResponse issueCertificateResponse = client
	.issueCertificate(issueCertificateRequest);

String certificateArn = issueCertificateResponse.certificateArn();

GetCertificateRequest getCertificateRequest = GetCertificateRequest.builder()
	.certificateAuthorityArn(ca.arn())
	.certificateArn(certificateArn)
	.build();

レスポンスには ML-DSA-65 コード署名証明書が含まれます。証明書を code-signing-cert.pem というファイル名で保存した後、OpenSSL 3.5 以降を使用して内容を確認できます。

openssl x509 -in code-signing-cert.pem -inform pem -text -noout
Certificate:
	Data:
		Version: 3 (0x2)
		Serial Number:
			1a:15:af:1e:64:8d:cd:29:b4:dc:66:2a:8b:1e:ee:b0
		Signature Algorithm: ML-DSA-65
		Issuer: CN=CodeSigningSubordinate-MLDSA65
		Validity
			Not Before: Sep 24 13:10:38 2025 GMT
			Not After : Sep 24 14:10:38 2026 GMT
		Subject: CN=CodeSigningCertificate
		Subject Public Key Info:
			Public Key Algorithm: ML-DSA-65
				ML-DSA-65 Public-Key:
				pub:
					<Public Key Data>
		X509v3 extensions:
			X509v3 Basic Constraints:
				CA:FALSE
			X509v3 Authority Key Identifier:
B7:EF:2E:C9:7A:A8:7E:B5:D6:2D:9A:3F:C7:A7:F8:9D:74:01:6A:EF
			X509v3 Subject Key Identifier:

7F:63:35:0C:56:F8:ED:F1:2A:DF:B5:2E:7C:F1:2C:D9:A0:0E:63:B6
			X509v3 Key Usage: critical
				Digital Signature
			X509v3 Extended Key Usage: critical
				Code Signing
	Signature Algorithm: ML-DSA-65
	Signature Value:
		<Signature Data>

証明書にコード署名キーペアの ML-DSA-65 パブリックキーと、下位 CA による ML-DSA-65 署名が含まれていることがわかります。また、KU と EKU の値も確認でき、AWS Private CA テンプレートによってコード署名用の証明書として適切に発行されていることを示しています。

コードへの署名

ここまでで、コード署名 PKI のセットアップが完了しました。AWS Private CA が発行したコード署名証明書と、AWS KMS に保管されている対応する ML-DSA キーペアの準備が整っています。

Java SDK を使用して、コードのバイナリファイルに対する CMS 署名を生成できます。内部的には、Runner.java に示すように、ML-DSA キーペアを指定して AWS KMS Sign API を呼び出すことで署名処理を行っています。以下は Java コードの一部です。最初のスニペットでは、証明書チェーンを構築し、コード署名用の AWS KMS キー、署名者の証明書、およびコードファイルのバイト配列表現である <DATA_TO_SIGN> と組み合わせて、CMS 構造のデタッチド署名を生成します。

	// Parse code-signing certificate from PEM
	X509CertificateHolder signerCert = CertificateUtils
		.fromPEM(codeSigningCertificate.certificate());

	Collection<X509CertificateHolder> chainCerts = CertificateUtils
		.toCertificateHolders(codeSigningCertificate.certificateChain());

	// Build certificate chain including code-signing cert and intermediate certs
	Collection<X509CertificateHolder> certChain = new ArrayList<> ();
	certChain.add(signerCert);

	// Parse certificate chain
	for (X509CertificateHolder chainCert : chainCerts) {
		if (!chainCert.equals(signerCert)) {
			certChain.add(chainCert);
		}
	}

	// Create detached CMS signature
	CMSCodeSigningObject cmsCodeSigningObject = CMSCodeSigningObject
		.createDetachedSignature(
			codeSigningCMK,
			ML_DSA_65_ALGORITHM_FAMILY,
			<DATA_TO_SIGN>,
			signerCert,
			certChain);

コード署名オブジェクトは signature-MLDSA65.p7s としてディスクに書き出されます。OpenSSL 3.5 以降で内容を確認できます。

openssl cms -cmsout -in signature-MLDSA65.p7s -inform DER -print
CMS_ContentInfo:
	contentType: pkcs7-signedData (1.2.840.113549.1.7.2)
	d.signedData:
		version: 1
		digestAlgorithms:
			algorithm: shake256 (2.16.840.1.101.3.4.2.12)
			parameter: <ABSENT>
		encapContentInfo:
			eContentType: pkcs7-data (1.2.840.113549.1.7.1)
			eContent: <ABSENT>
		certificates:
			d.certificate:
				cert_info:
					version: 2
					serialNumber: 0xD0B2937F5BABC80AD55C0A90E1DE7057
					signature:
						algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
						parameter: <ABSENT>
					issuer:			CN=CodeSigningSubordinate-MLDSA65
					validity:
						notBefore: Oct 28 15:05:27 2025 GMT
						notAfter: Oct 28 16:05:26 2026 GMT
					subject:		CN=CodeSigningCertificate
					key:		X509_PUBKEY:
						algor:
							algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
							parameter: <ABSENT>
						public_key:(0 unused bits)
							...
						issuerUID: <ABSENT>
						subjectUID: <ABSENT>
						extensions:
							object: X509v3 Basic Constraints (2.5.29.19)
							critical: FALSE
							value:
								0000 - 30 00 0.
                                    
							object: X509v3 Authority Key Identifier (2.5.29.35)
							critical: FALSE
							value:
								0000 - 30 16 80 14 b7 ef 2e c9-7a a8 7e b5 d60.......z.~..
								000d - 2d 9a 3f c7 a7 f8 9d 74-01 6a ef-.?....t.j.

                        	object: X509v3 Subject Key Identifier (2.5.29.14)
							critical: FALSE
							value:
								0000 - 04 14 7f 63 35 0c 56 f8-ed f1 2a df b5...c5.V...*..
								000d - 2e 7c f1 2c d9 a0 0e 63-b6.|.,...c.

                         	object: X509v3 Key Usage (2.5.29.15)
							critical: TRUE
							value:
								0000 - 03 02 07 80....
                                    
							object: X509v3 Extended Key Usage (2.5.29.37)
							critical: TRUE
							value:
								0000 - 30 0a 06 08 2b 06 01 05-05 07 03 030...+.......
					sig_alg:
						algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
						parameter: <ABSENT>
					signature:(0 unused bits)
						...
		d.certificate:
			cert_info:
			version: 2
			serialNumber: 29577999257397559174219641462943780786
			signature:
				algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
				parameter: <ABSENT>
				issuer:			CN=CodeSigningRoot-MLDSA65
				[...]
                
		d.certificate:
			cert_info:
			version: 2
			serialNumber: 0xB9419A2C5D2422B3A58A5B449546D74B
			signature:
				algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
				parameter: <ABSENT>
				issuer:			CN=CodeSigningRoot-MLDSA65
				[...]
	crls:
		<ABSENT>
	signerInfos:
		version: 1
		d.issuerAndSerialNumber:
			issuer:				CN=CodeSigningSubordinate-MLDSA65
			serialNumber: 0xD0B2937F5BABC80AD55C0A90E1DE7057
		digestAlgorithm:
			algorithm: shake256 (2.16.840.1.101.3.4.2.12)
			parameter: <ABSENT>
		signedAttrs:
			object: contentType (1.2.840.113549.1.9.3)
			set:
				OBJECT:pkcs7-data (1.2.840.113549.1.7.1)

			object: signingTime (1.2.840.113549.1.9.5)
			set:
				UTCTIME:Oct 28 16:05:27 2025 GMT

			object: id-aa-CMSAlgorithmProtection (1.2.840.113549.1.9.52)
			set:
				SEQUENCE:
	0:d=0hl=2 l=26 cons: SEQUENCE
	2:d=1hl=2 l=11 cons:SEQUENCE
	4:d=2hl=2 l=9 prim:OBJECT:shake256
	15:d=1hl=2 l=11 cons:cont [ 1 ]
	17:d=2hl=2 l=9 prim:OBJECT:ML-DSA-65

        	object: messageDigest (1.2.840.113549.1.9.4)
			set:
				OCTET STRING:
					...
		signatureAlgorithm:
			algorithm: ML-DSA-65 (2.16.840.1.101.3.4.3.18)
			parameter: <ABSENT>
		signature:
			[...]

CMS 署名オブジェクトには、コード署名証明書と下位 CA 証明書の両方が直接格納されています。ルート証明書は、お客様が管理するトラストストアに配置される想定です。これらの証明書に加えて、CMS オブジェクトの ASN.1 構造では、signerInfos 内の signedAttrs に入力データのダイジェストも含まれています。ダイジェストアルゴリズムは SHAKE256 で、OCTET STRING がバイナリダイジェストそのものを表します。CMS における ML-DSA の使用方法は RFC9882 で規定されています。

注意: この例では 1 つの ML-DSA 署名のみを使用していますが、ユースケースによっては従来型と耐量子型の 2 つの署名を含む場合もあります。このようなデュアル署名のアーティファクトを使用すると、従来型の署名のみをサポートするレガシーな検証者との後方互換性を維持できます。アップグレードされた検証者は、両方の署名を検証できます。

署名済みコードの検証

署名済みコードのアーティファクトをロードする前に、署名を検証する必要があります。検証には、コードの署名の検証と、信頼されたルート CA までの証明書チェーンの検証が含まれます。Runner.java ファイルの main メソッド内にある以下のコードスニペットで、証明書チェーンの検証とコードオブジェクト内の署名の検証を行います。

X509CertificateHolder rootCACertificate = CertificateUtils.fromPEM(rootCACertificatePEM); 
cmsCodeSigningObject.verifyDetachedSignature(<DATA_TO_SIGN>, rootCACertificate);

このコードは、コード署名証明書から ML-DSA パブリックキーを取得します。署名の検証には AWS へのアクセスや認証情報は不要です。トラストストアにルート CA 証明書をロードしているエンティティであれば、AWS KMS verify API にアクセスせずに署名を検証できます。

注意: Runner.java の実装では、ブラウザやデバイス・サーバーの OS 上のファイルシステムに組み込まれた証明書トラストストアは使用していません。本記事ではデモンストレーションの目的で、トラストストアを Java クラスオブジェクトのインスタンス内に配置しています。このコード署名の例を本番システムで使用する場合は、ホスト上のトラストストアを使用するよう実装を変更する必要があります。その際は、ルート CA 証明書を含む安全なトラストストアを構築して配布してください。

また、OpenSSL 3.5 以降を使用して、AWS Private CA から提供されるルート CA 証明書 root-ca-MLDSA65.pem をトラストアンカーとして、入力データファイルのデタッチド署名を検証することもできます。

openssl cms -verify -in signature-MLDSA65.p7s -content <input-data-file> \
            -CAfile root-ca-MLDSA65.pem -inform DER -purpose any \
            -binary -out /dev/null
CMS Verification successful

注意: 本記事ではコード署名に焦点を当てていますが、AWS Private CA はその他のプライベート PKI ユースケースでもポスト量子 ML-DSA 認証を実現できます。例えば、AWS 外部のアプリケーションが AWS IAM Roles Anywhere を使用して、証明書ベースの認証で一時的な AWS 認証情報を取得し、AWS リソースにアクセスできます。AWS IAM Roles Anywhere は現在、本記事で作成したような ML-DSA PKI をサポートしています。別のシナリオでは、mTLS クライアントや IKEv2/IPsec ピアが、AWS Private CA から発行された ML-DSA 証明書を使用して、ポスト量子 PKI ルート証明書を事前にプロビジョニングしたサーバーやピアに対して認証を行うことができます。

まとめ

今回の発表は、ポスト量子認証における重要なマイルストーンです。AWS Private CA に ML-DSA X.509 証明書が導入されたことで、プライベート PKI ユースケースに量子コンピュータへの耐性を取り入れることが可能になりました。対象となるユースケースには、mTLS や IKEv2/IPsec トンネルのクライアント認証、IAM Roles Anywhere、プライベート PKI 認証を使用するアプリケーションなどがあります。AWS Private CA の ML-DSA 証明書と AWS KMS による署名を組み合わせることで、ポスト量子コード署名や、CRQC が利用可能になった後も長期間にわたって動作するよう設計されたデバイスに向けた、ポスト量子の長期的な信頼の基点を確立できます。ポスト量子暗号全般と、ポスト量子暗号への移行に関する AWS の全体計画について、詳しくはそれぞれのページをご覧ください。


この記事に関するご質問がある場合は、AWS Security, Identity, & Compliance re:Post で新しいスレッドを作成するか、AWS サポートにお問い合わせください。AWS の PQC への取り組みの詳細については、PQC ページを参照してください。

 

Panos Kampanakis

Panos Kampanakis

Panos は AWS の Principal Security Engineer です。サイバーセキュリティ、応用暗号、セキュリティ自動化、脆弱性管理の分野で豊富な経験を持っています。サイバーセキュリティに関する論文を共著し、セキュリティ情報共有、暗号、公開鍵基盤のための共通の相互運用可能なプロトコルと言語を提供するさまざまなセキュリティ標準化団体に参加してきました。現在は、暗号技術的に安全なツール、プロトコル、標準を提供するため、エンジニアや業界標準パートナーと連携しています。

Jake Massimo

Jake Massimo

Jake は AWS Cryptography チームの Senior Applied Scientist です。国際会議、学術論文、標準化団体を通じて Amazon とグローバルな暗号コミュニティをつなぐ役割を担い、ポスト量子クラウドスケール暗号技術の導入に貢献しています。最近は、AWS とお客様が量子安全な暗号にシームレスに移行できるよう、コアライブラリやインフラストラクチャを含む AWS のポスト量子暗号機能のアーキテクチャ設計に注力しています。

著者

Kyle Schultheiss

Kyle は AWS Cryptography チームの Senior Software Engineer です。2018 年のサービス開始以来、AWS Private Certificate Authority サービスの開発に携わっています。以前は、Amazon Virtual Private Cloud、Amazon EC2、Amazon Route 53 などの AWS サービスの開発にも貢献しました。

本ブログは Security Solutions Architect の 中島 章博 が翻訳しました。