Powershellスクリプトの署名と証明書の操作

Powershell、とっても便利じゃないですか。
便利、それは危険ということ。
セキュリティホールつかれてスクリプトが実行されたら大変ですよね!
利便性のためにすぐRemoteSignedにしがちですが、せめてAllSignedにしましょう。
ということで、方法を考えてみました。



方針を考える

署名用の使える証明書を取得するのも面倒なので、自己証明書にする。
スクリプト編集モードでは、Watcherで自動的に署名する。
スクリプト編集モードを解除したら、Watcherを止める。
ついでに、署名用の証明書も、出し入れする。
あと、自己証明書で署名したスクリプトを他のPCで実行する方法の確認。

スクリプト

とりあえず完成したもの

function Enable-ScriptEdit() {
	# 署名用自己証明書のインポート
	$x509store = New-Object System.Security.Cryptography.X509Certificates.X509Store('My', 'CurrentUser')
	$x509store.Open('ReadWrite')
	$x509store.Add((New-Object System.Security.Cryptography.X509Certificates.X509Certificate2([Convert]::FromBase64String(@"

		ここに、貴方の署名用自己証明書をエクスポート・Base64文字列化したものを書く
		エクスポート方法は「help about_signing」を見てね(.pfxファイルです)
		Base64文字列化は「[Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path))」

"@), (Read-Host 'Password' -AsSecureString))))
	$x509store.Close()

	# 署名Watcherの起動
	Start-Watcher {
		Param($sender, $ev)
		if($ev.ChangeType -ne 'Deleted') {
			$f = Get-Item $ev.FullPath
			$SignedFilePath = "$($f.BaseName).Signed$($f.Extension)"
			if((Test-Path $SignedFilePath) -and ($f.LastWriteTime -gt (Get-Item $SignedFilePath).LastWriteTime)) {
				Save-SignedScript -FileInfo $f
			}
		}
	} (Get-Location) '*.ps1'
}

function Disable-ScriptEdit() {
	#署名Watcherの停止
	Remove-Watcher (Get-Location) '*.ps1'

	#署名用自己証明書の削除
	$x509store = New-Object System.Security.Cryptography.X509Certificates.X509Store('My', 'CurrentUser')
	$x509store.Open('ReadWrite')
	$tar = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert
	if($tar -ne $null) { $x509store.Remove($tar) }
	$x509store.Close()
}

Save-SignedScript依存。
Enable-ScriptEdit/Disable-ScriptEditが現在のディレクトリーに依存してますが、私の手元では固定にしてます。
(未署名のスクリプト置き場と実行する場所を別にしているので。なので微妙に変えています)
自己証明書の削除も1件しかない想定のスクリプトですが、手元はWhere使って1件にしてたりします。
そんなこんなでAllSignedでもストレスフリー!

自己証明書署名のスクリプトを他のPCで

自己のルート証明書を入れないといけません。
.cerファイルを叩けばインポートの選択肢が出ますが、ウイザードに付き合わないといけなくなります。
以下のスクリプトなら、確認ダイアログが一枚出るだけ!

$x509store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root', 'CurrentUser')
$x509store.Open('ReadWrite')
$x509store.Add([System.Security.Cryptography.X509Certificates.X509Certificate2][Convert]::FromBase64String(@"

	ここに、貴方の自己ルート証明書をBase64文字列化したものを書く
	「help about_signing」通りやっているなら「root.cer」です。

"@))
$x509store.Close()

そうそう、引数一つのコンストラクターは、キャストで呼べるみたいよ!
X509Certificate2をNew-Objectで、byte渡したら、そんなたくさん引数のコンストラクターはないって怒られた。
@(,byte
)でもよかったけど、キャストでいいよ、とかどこかに書いてあったのでやってみたところ上手くいった!