Operations Lab.

Archive for 12月 2014

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

WinRMとProxy

leave a comment »

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

企業などでは Proxy サーバが存在している環境も多いかと思います。PowerShell Remoting / WinRM は(構成によりますが)接続に HTTP/HTTPS を使用する為、Proxy 設定の影響をうけます。

Internet Explorer などのブラウザは、WPAD (Web Proxy Auto Discovery) を用いた自動構成と WinHTTP の静的 Proxy 設定が同時に有効になっていた場合に WPAD を優先してくれます。一方、PowerShell (WinRM) は(デフォルトでは) WPAD や構成スクリプト(pacファイル)を処理できないため、ブラウザは接続できるのに PowerShell Remoting では接続できない、という事が起こりえます。

特に、Proxy 設定されているノート PC を外部に持ち出した場合は要注意です。

Proxy 関連で問題がある場合、以下の様になります。(Proxy サーバに依存する部分がありますので、参考情報です。)

WinRM を Proxy できない Proxy サーバが設定されている場合

※Proxy サーバのサービスポートへは接続できるが、Proxy できない状態。

PS C:\> New-PSSession -Credential $cred $target
New-PSSession : [sv01.example.local] リモート サーバー sv01.example.local への接続に失敗し、次のエラー メッセージ
が返されました: WinRM クライアントは、リモート WS-Management サービスから HTTP 状態コード 504 を受け取りました。詳細に
ついては、about_Remote_Troubleshooting のヘルプ トピックを参照してください。
発生場所 行:1 文字:1
+ New-PSSession -Credential $cred $target
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
   gTransportException
    + FullyQualifiedErrorId : -2144108273,PSSessionOpenFailed

Proxy サーバから target ホストに接続できない場合

※例えば、Proxy サーバ上で target ホストの名前解決ができなかったり、ルーティングが存在しない状態。

PS C:\> New-PSSession -Credential $cred $target
New-PSSession : [sv01.example.local] リモート サーバー sv01.example.local への接続に失敗し、次のエラー メッセージ
が返されました: 指定したリモート ホストへの接続は拒否されました。 このリモート ホストで WS-Management サービスが実行さ
れていて、適切なポートと HTTP URL で要求をリッスンするようにリモート ホストが構成されていることを確認してください。詳細
については、about_Remote_Troubleshooting のヘルプ トピックを参照してください。
発生場所 行:1 文字:1
+ New-PSSession -Credential $cred $target
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
   gTransportException
    + FullyQualifiedErrorId : CannotConnectWinRMService,PSSessionOpenFailed

Proxy サーバに接続できない場合

※Proxy ホストには到達できるが、Proxy サービスを提供するポートへ接続できない状態。

PS C:\> New-PSSession -Credential $cred $target
New-PSSession : [sv01.example.local] リモート サーバー sv01.example.local への接続に失敗し、次のエラー メッセージ
が返されました: クライアントは、要求で指定された接続先に接続できません。 接続先のサービスが実行されていて、要求を受け付
けられる状態であることを確認してください。 接続先で実行されている WS-Management サービス (通常は IIS または WinRM) に関
するログとドキュメントを参照してください。 接続先が WinRM サービスの場合は、リモート ホスト上で次のコマンドを実行して、
WinRM サービスを分析および構成してください: "winrm quickconfig" 詳細については、about_Remote_Troubleshooting のヘルプ ト
ピックを参照してください。
発生場所 行:1 文字:1
+ New-PSSession -Credential $cred $target
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
   gTransportException
    + FullyQualifiedErrorId : CannotConnect,PSSessionOpenFailed

※Proxy ホストへ到達すらできない場合は、以下のようになります。

PS C:\> New-PSSession -Credential $cred $target
New-PSSession : [sv01.example.local] リモート サーバー sv01.example.local への接続に失敗し、次のエラー メッセージ
が返されました: WinRM クライアントは、サーバー名を解決できないため、要求を処理できません。詳細については、about_Remote_
Troubleshooting のヘルプ トピックを参照してください。
発生場所 行:1 文字:1
+ New-PSSession -Credential $cred $target
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
   gTransportException
    + FullyQualifiedErrorId : ComputerNotFound,PSSessionOpenFailed

解決方法

Proxy の問題であれば Proxy 設定を修正すればよいのですが、環境によっては管理者に Proxy 設定をロックされているかもしれないですので、PowerShell 上で(ある程度)指定する方法を。

New-PSSessionOption コマンドレットを使用すると、PSSession を生成する際にパラメータを調整できます。

New-PSSessionOption [-ApplicationArguments <PSPrimitiveDictionary>] [-CancelTimeout <Int32>] [-Culture <CultureInfo>] [-IdleTimeout <Int32>] [-IncludePortInSPN] [-MaximumReceivedDataSizePerCommand <Int32>] [-MaximumReceivedObjectSize <Int32>] [-MaximumRedirection <Int32>] [-NoCompression] [-NoEncryption] [-NoMachineProfile] [-OpenTimeout <Int32>] [-OperationTimeout <Int32>] [-OutputBufferingMode <OutputBufferingMode>] [-ProxyAccessType <ProxyAccessType>] [-ProxyAuthentication <AuthenticationMechanism>] [-ProxyCredential <PSCredential>] [-SkipCACheck] [-SkipCNCheck] [-SkipRevocationCheck] [-UICulture <CultureInfo>] [-UseUTF16] [<CommonParameters>]

ProxyAccessType パラメータを指定することで、New-PSSession / Enter-PSSession 時に、WinRM サービスへプロキシ設定を渡すことができます。

-ProxyAccessType <ProxyAccessType>

Determines which mechanism is used to resolve the host name. Valid values are IEConfig, WinHttpConfig, AutoDetect, NoProxyServer and None. The default value is None.

ProxyAccessType に指定可能な値は以下の通りです。

  • AutoDetectProxy 構成を自動検出します。
  • IEConfigInternet Explore の Proxy 設定を使用します。
  • NoneProxy 情報を(明示的には)指定しません。WSMan など、下位(ベースとなっている)サービスに Proxy の解決を委任します。WinHTTP の設定はベースサービス側でも参照されるため、結果として WinHTTP などの Proxy 設定が利用されれる場合があります。
  • NoProxyServerProxy サーバを使用しません。(使用しないことを明示的に設定します。)
  • WinHttpConfigWinHTTP の Proxy 設定を使用します。

注:具体的なサーバを指定することはできません。

詳細は、MSDN に記載があります。

Proxy サーバ設定を迂回したい場合は、ProxyAccessType に NoProxyServer を設定します。

PS C:\> $options = New-PSSessionOption -ProxyAccessType NoProxyServer
PS C:\> New-PSSession -Credential $cred -SessionOption $options $target

 Id Name            ComputerName    State         ConfigurationName     Availability
 -- ----            ------------    -----         -----------------     ------------
 16 Session16       sv01.example... Opened        Microsoft.PowerShell     Available

Written by kazu

2014/12/08 at 12:52

Azure の新しい Portal から仮想マシンを削除

leave a comment »

仮想マシンを消す時に、誤って他の仮想マシンを削除しないよう(?)、消そうとしている仮想マシン名をフルスペルで要求してくるんですね。なんて親切(笑)

azportal201412-01

リソースをまとめて削除できるのは分かりやすいかも。

Written by kazu

2014/12/02 at 23:05

カテゴリー: Microsoft Azure