Operations Lab.

PowerShell でコード署名用の証明書を作成する

with one comment

この Post は PowerShell Advent Calendar 2014 の 12/16 分です。

PowerShell 3.0 からは PKI モジュールに含まれる New-SelfSignedCertificate コマンドレットで自己証明書を作成できるようになりました。検証用証明書の作成に makecert.exe コマンドを利用(用意)しなくてもよくなったため大変便利ですが、New-SelfSignedCertificate で作成した証明書はコード署名には利用できません。

PS C:\> New-SelfSignedCertificate -DnsName test@operationslab.local -CertStoreLocation Cert:\CurrentUser\My

    ディレクトリ: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
A9E494C41DFD461E94EB3C11790AD11B50F8E5DA  CN=test@operationslab.local

# 自己証明書が作成、保管されます
PS C:\> Get-ChildItem Cert:\CurrentUser\My

    ディレクトリ: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
A9E494C41DFD461E94EB3C11790AD11B50F8E5DA  CN=test@operationslab.local

# コード署名に利用可能な証明書はありません
PS C:\> Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
PS C:\>

about_Signing のヘルプ項目には以下のような記述があり、あたかも New-SelfSignedCertificate で新規に作成した証明書を使用して PowerShell スクリプトに署名できそうですが、実際にはできません。これは、証明書の利用用途として「コード署名」が指定されていないためです。

CREATE A SELF-SIGNED CERTIFICATE

——————————–

To create a self-signed certificate in use the New-SelfSignedCertificate cmdlet in the PKI module. This module is introduced in Windows PowerShell 3.0 and is included in Windows 8 and Windows Server 2012. For more information, see the help topic for the New-SelfSignedCertificate cmdlet.

New-SelfSignedCertificate のヘルプを参照すると、以下のパラメータで証明書が構成される旨が記載されています。

  • Subject: Empty
  • Key: RSA 2048
  • EKUs: Client Authentication and Server Authentication
  • Key Usage: Digital Signature, Key Encipherment (a0)
  • Validity Period: One year

このうち、Subject については New-SelfSignedCertificate の DnsName オプションで上書きできますが、その他のパラメータは上書きできません。(CloneCert で複製を行った場合を除きます。)

コード署名を行うためには、EKU(Enhanced Key Usage)に「コード署名(Code Signing)」が含まれている必要があるため、New-SelfSignedCertificate は利用できません。

about_Signing では自己証明書を作成するもう一つの方法として makecert.exe が紹介されていますが、makecert.exe のためだけに Windows SDK をインストールするのは大変手間がかかります。(時間とディスク容量も。)外部コマンドを利用せず証明書を作成する方法はいくつかありますが、COM(Component Object Model)を利用する方法が一番簡単そうでしたので、今回は COM を使います。

※正直、COM は好きではないですが…。開発者ではないのと、レガシー感が払拭できないので…。

以下のコードを実行すると、CurrentUser\My 証明書ストアにコード署名用の証明書が作成されます。(サブジェクトやフレンドり名は適宜読み替えてください。)

# サブジェクト
$subject = "CN=admin@operationslab.local"

# フレンドリ名
$fn = "Operations Lab Code Signing"

# Enhanced Key Usage
# 日本語環境の場合は、日本語又はOIDで指定
# 複数指定する場合は、カンマ区切り(配列)
# $EKU = [Security.Cryptography.Oid[]]("サーバー認証", "クライアント認証")
$eku = [Security.Cryptography.Oid[]]"コード署名"

# Key Usage
# 日本語環境であっても、英語指定
# 複数指定する場合は、カンマ区切りの文字列
$ku = [Security.Cryptography.X509Certificates.X509KeyUsageFlags]"KeyEncipherment, DigitalSignature"

# 有効期限
# NotBefore
$start = [DateTime]::Now.AddDays(-1)
# NotAfter
$end = $start.AddYears(1)


# アルゴリズムと鍵長
$provider = "Microsoft Enhanced Cryptographic Provider v1.0"
$kalgo = [Security.Cryptography.Oid]"RSA"
$klength = 2048
$halgo = [Security.Cryptography.Oid]"SHA1"

# X509KeySpec
# 1: The key can be used to encrypt (including key exchange) or sign depending on the algorithm. For RSA algorithms, if this value is set, the key can be used for both signing and encryption.
# 2: The key can be used for signing.
$kspec = 1

# 保存先
# 現在のユーザーの「個人」証明書ストアに保存
$slocation = [Security.Cryptography.X509Certificates.StoreLocation]"CurrentUser"
$sname = [Security.Cryptography.X509Certificates.StoreName]"My"

$oidlist = New-Object -ComObject X509Enrollment.CObjectIDs
$eku | % {
    $o = New-Object -ComObject X509Enrollment.CObjectID
    $o.InitializeFromValue($_.Value)
    $oidlist.Add($o)
}

$x509eku = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage
$x509eku.InitializeEncode($oidlist)

$x509ku = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage
$x509ku.InitializeEncode([int]$ku)
$x509ku.Critical = $true

$algo = New-Object -ComObject X509Enrollment.CObjectId
$algo.InitializeFromValue($kalgo.Value)

$key = New-Object -ComObject X509Enrollment.CX509PrivateKey
$key.ProviderName = $provider
$key.Algorithm = $algo
$key.Length = $klength
$key.KeySpec = $kspec
$key.ExportPolicy = 0
$key.MachineContext = $false
$key.Create()

$dn = New-Object -ComObject X509Enrollment.CX500DistinguishedName
$dn.Encode($subject, 0)

$hash = New-Object -ComObject X509Enrollment.CObjectId
$hash.InitializeFromValue($halgo.Value)

$req = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate
$req.InitializeFromPrivateKey(1, $key, "")
$req.Subject = $dn
$req.Issuer = $dn
$req.NotBefore = $start
$req.NotAfter = $end
$req.SignatureInformation.HashAlgorithm = $hash
$req.X509Extensions.Add($x509eku)
$req.X509Extensions.Add($x509ku)
$req.Encode()

$enroll = New-Object -ComObject X509Enrollment.CX509enrollment
$enroll.InitializeFromRequest($req)
$enroll.CertificateFriendlyName = $fn
$enroll.InstallResponse(2, $enroll.CreateRequest(1), 1, "")

一番のハマり所は、EKU を [Security.Cryptography.Oid[]] にキャストする際、日本語環境では日本語の用途名を指定する必要があるところでしょうか。英語環境の場合は英語で指定する必要があります。分かりやすさのために EKU を文字列からキャストしていますが、ハマらないためには、OID を直接指定したほうが良いかもしれません。

なお、上記のコード実行後に確認すると、コード署名に利用可能な証明書が表示され、署名を行うことができます。

PS C:\> Get-ChildItem Cert:\CurrentUser\My

    ディレクトリ: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
A9E494C41DFD461E94EB3C11790AD11B50F8E5DA  CN=test@operationslab.local
3AB427ABF2A2C141156D6B5925D88180A120B394  CN=admin@operationslab.local

PS C:\> Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert

    ディレクトリ: Microsoft.PowerShell.Security\Certificate::CurrentUser\My

Thumbprint                                Subject
----------                                -------
3AB427ABF2A2C141156D6B5925D88180A120B394  CN=admin@operationslab.local

PS C:\> $cert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
PS C:\> Set-AuthenticodeSignature C:\test.ps1 $cert

    ディレクトリ: C:\

SignerCertificate                         Status        Path
-----------------                         ------        ----
3AB427ABF2A2C141156D6B5925D88180A120B394  UnknownError  test.ps1

因みに、Set-AuthenticodeSignature の結果、Status が UnknownError となるのは、自己証明書を使用しているためです。正規の認証局から発行された証明書を使用するか、自己証明書を「信頼されたルート証明機関」ストアへ(にも)配置する(つまりルート証明書として登録する)と、以下のように Status が Vaild になります。

PS C:\> Set-AuthenticodeSignature C:\test.ps1 $cert

    ディレクトリ: C:\

SignerCertificate                         Status  Path
-----------------                         ------  ----
3AB427ABF2A2C141156D6B5925D88180A120B394  Valid   test.ps1

広告

コメント / トラックバック1件

Subscribe to comments with RSS.

  1. […] <B>  PowerShell でコード署名用の証明書を作成する […]


コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。