Functions/New-RsaKeyPair.ps1
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function New-RsaKeyPair { <# .SYNOPSIS Generates a public/private RSA key pair. .DESCRIPTION The `New-RsaKeyPair` function uses the `certreq.exe` program to generate an RSA public/private key pair suitable for use in encrypting/decrypting CMS messages, credentials in DSC resources, etc. It uses the following `.inf` file as input (taken from the first example in the help for the `Protect-CmsMessage` cmdlet): [Version] Signature = "$Windows NT$" [Strings] szOID_ENHANCED_KEY_USAGE = "2.5.29.37" szOID_DOCUMENT_ENCRYPTION = "1.3.6.1.4.1.311.80.1" [NewRequest] Subject = $Subject MachineKeySet = false KeyLength = $Length KeySpec = AT_KEYEXCHANGE HashAlgorithm = $Algorithm Exportable = true RequestType = Cert KeyUsage = "CERT_KEY_ENCIPHERMENT_KEY_USAGE | CERT_DATA_ENCIPHERMENT_KEY_USAGE" ValidityPeriod = Days ValidityPeriodUnits = [Extensions] %szOID_ENHANCED_KEY_USAGE% = "{{text}}%szOID_DOCUMENT_ENCRYPTION%" You can control the subject (via the `-Subject` parameter), key length (via the `-Length` parameter), the hash algorithm (via the `-Algorithm` parameter), and the expiration date of the keys (via the `-ValidTo` parameter). The subject is always required and should begin with "CN=". The length, hash algorithm, and expiration date are optional, and default to `4096`, `sha512`, and `12/31/9999`, respectively. The `certreq.exe` command stores the private key in the current user's `My` certificate store. This function exports that private key to a file and removes it from the current user's `My` store. The private key is protected with the password provided via the `-Password` parameter. If you don't provide a password, you will be prompted for one. To not protect the private key with a password, pass `$null` as the value of the `-Password` parameter. The public key is saved as an X509Certificate. The private key is saved as a PFX file. Both can be loaded by .NET's `X509Certificate` class. Returns `System.IO.FileInfo` objects for the public and private key, in that order. Before Carbon 2.1, this function used the `makecert.exe` and `pvk2pfx.exe` programs, from the Windows SDK. These programs prompt multiple times for the private key password, so if you're using a version before 2.1, you can't run this function non-interactively. .OUTPUTS System.IO.FileInfo .LINK Get-Certificate .LINK Install-Certificate .EXAMPLE New-RsaKeyPair -Subject 'CN=MyName' -PublicKeyFile 'MyName.cer' -PrivateKeyFile 'MyName.pfx' -Password $secureString Demonstrates the minimal parameters needed to generate a key pair. The key will use a sha512 signing algorithm, have a length of 4096 bits, and expire on `12/31/9999`. The public key will be saved in the current directory as `MyName.cer`. The private key will be saved to the current directory as `MyName.pfx` and protected with password in `$secureString`. .EXAMPLE New-RsaKeyPair -Subject 'CN=MyName' -PublicKeyFile 'MyName.cer' -PrivateKeyFile 'MyName.pfx' -Password $null Demonstrates how to save the private key unprotected (i.e. without a password). You must set the password to `$null`. This functionality was introduced in Carbon 2.1. .EXAMPLE New-RsaKeyPair -Subject 'CN=MyName' -PublicKeyFile 'MyName.cer' -PrivateKeyFile 'MyName.pfx' -Algorithm 'sha1' -ValidTo (Get-Date -Year 2015 -Month 12 -Day 31) -Length 1024 -Password $secureString Demonstrates how to use all the parameters to create a truly customized key pair. The generated certificate will use the sha1 signing algorithm, becomes effective 1/1/2015, expires 12/31/2015, and is 1024 bits in length. #> [CmdletBinding()] [OutputType([IO.FileInfo])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams","")] param( [Parameter(Mandatory=$true,Position=0)] [ValidatePattern('^CN=')] [string] # The key's subject. Should be of the form `CN=Name,OU=Name,O=SuperMagicFunTime,ST=OR,C=US`. Only the `CN=Name` part is required. $Subject, [ValidateSet('md5','sha1','sha256','sha384','sha512')] [string] # The signature algorithm. Default is `sha512`. $Algorithm = 'sha512', [Parameter(DontShow=$true)] [DateTime] # The date/time the keys will become valid. Default is now. # # This parameter was made obsolete in Carbon 2.1. $ValidFrom = (Get-Date), [DateTime] # The date/time the keys should expire. Default is `DateTime::MaxValue`. $ValidTo = ([DateTime]::MaxValue), [int] # The length, in bits, of the generated key length. Default is `4096`. $Length = 4096, [Parameter(DontShow=$true)] [ValidateSet('commercial','individual')] [string] # The signing authority of the certificate. Must be `commercial` (for certificates used by commercial software publishers) or `individual`, for certificates used by individual software publishers. Default is `individual`. # # This parameter was made obsolete in Carbon 2.1. $Authority = 'individual', [Parameter(Mandatory=$true,Position=1)] [string] # The file where the public key should be stored. Saved as an X509 certificate. $PublicKeyFile, [Parameter(Mandatory=$true,Position=2)] [string] # The file where the private key should be stored. The private key will be saved as an X509 certificate in PFX format and will include the public key. $PrivateKeyFile, [securestring] # The password for the private key. If one is not provided, you will be prompted for one. Pass `$null` to not protect your private key with a password. # # This parameter was introduced in Carbon 2.1. $Password, [Switch] # Overwrites `PublicKeyFile` and/or `PrivateKeyFile`, if they exist. $Force ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( $PSBoundParameters.ContainsKey('ValidFrom') ) { Write-Warning -Message ('New-RsaKeyPair: The -ValidFrom parameter is obsolete and will be removed in a future version of Carbon. Please remove usages of this parameter.') } if( $PSBoundParameters.ContainsKey('Authority') ) { Write-Warning -Message ('New-RsaKeyPair: The -Authority parameter is obsolete and will be removed in a future version of Carbon. Please remove usages of this parameter.') } function Resolve-KeyPath { param( [Parameter(Mandatory=$true)] [string] $Path ) Set-StrictMode -Version 'Latest' $Path = Resolve-FullPath -Path $Path if( (Test-Path -Path $Path -PathType Leaf) ) { if( -not $Force ) { Write-Error ('File ''{0}'' exists. Use the -Force switch to overwrite.' -f $Path) return } } else { $root = Split-Path -Parent -Path $Path if( -not (Test-Path -Path $root -PathType Container) ) { New-Item -Path $root -ItemType 'Directory' -Force | Out-Null } } return $Path } $PublicKeyFile = Resolve-KeyPath -Path $PublicKeyFile if( -not $PublicKeyFile ) { return } $PrivateKeyFile = Resolve-KeyPath -Path $PrivateKeyFile if( -not $PrivateKeyFile ) { return } if( (Test-Path -Path $PrivateKeyFile -PathType Leaf) ) { if( -not $Force ) { Write-Error ('Private key file ''{0}'' exists. Use the -Force switch to overwrite.' -f $PrivateKeyFile) return } } $tempDir = '{0}-{1}' -f (Split-Path -Leaf -Path $PSCommandPath),([IO.Path]::GetRandomFileName()) $tempDir = Join-Path -Path $env:TEMP -ChildPath $tempDir New-Item -Path $tempDir -ItemType 'Directory' | Out-Null $tempInfFile = Join-Path -Path $tempDir -ChildPath 'temp.inf' try { $certReqPath = Get-Command -Name 'certreq.exe' | Select-Object -ExpandProperty 'Path' if( -not $certReqPath ) { return } # Taken from example 1 of the Protect-CmsMessage help topic. [int]$daysValid = [Math]::Floor(($ValidTo - $ValidFrom).TotalDays) [int]$MaxDaysValid = [Math]::Floor(([DateTime]::MaxValue - [DateTime]::UtcNow).TotalDays) Write-Debug -Message ('Days Valid: {0}' -f $daysValid) Write-Debug -Message ('Max Days Valid: {0}' -f $MaxDaysValid) if( $daysValid -gt $MaxDaysValid ) { Write-Debug -Message ('Adjusted Days Valid: {0}' -f $daysValid) $daysValid = $MaxDaysValid } (@' [Version] Signature = "$Windows NT$" [Strings] szOID_ENHANCED_KEY_USAGE = "2.5.29.37" szOID_DOCUMENT_ENCRYPTION = "1.3.6.1.4.1.311.80.1" [NewRequest] Subject = "{0}" MachineKeySet = false KeyLength = {1} KeySpec = AT_KEYEXCHANGE HashAlgorithm = {2} Exportable = true RequestType = Cert KeyUsage = "CERT_KEY_ENCIPHERMENT_KEY_USAGE | CERT_DATA_ENCIPHERMENT_KEY_USAGE" ValidityPeriod = Days ValidityPeriodUnits = {3} [Extensions] %szOID_ENHANCED_KEY_USAGE% = "{{text}}%szOID_DOCUMENT_ENCRYPTION%" '@ -f $Subject,$Length,$Algorithm,$daysValid) | Set-Content -Path $tempInfFile Get-Content -Raw -Path $tempInfFile | Write-Debug $output = & $certReqPath -q -new $tempInfFile $PublicKeyFile if( $LASTEXITCODE -or -not (Test-Path -Path $PublicKeyFile -PathType Leaf) ) { Write-Error ('Failed to create public/private key pair:{0}{1}' -f ([Environment]::NewLine),($output -join ([Environment]::NewLine))) return } else { $output | Write-Debug } $publicKey = Get-Certificate -Path $PublicKeyFile if( -not $publicKey ) { Write-Error ('Failed to load public key ''{0}'':{1}{2}' -f $PublicKeyFile,([Environment]::NewLine),($output -join ([Environment]::NewLine))) return } $privateCertPath = Join-Path -Path 'cert:\CurrentUser\My' -ChildPath $publicKey.Thumbprint if( -not (Test-Path -Path $privateCertPath -PathType Leaf) ) { Write-Error -Message ('Private key ''{0}'' not found. Did certreq.exe fail to install the private key there?' -f $privateCertPath) return } try { $privateCert = Get-Item -Path $privateCertPath if( -not $privateCert.HasPrivateKey ) { Write-Error -Message ('Certificate ''{0}'' doesn''t have a private key.' -f $privateCertPath) return } if( -not $PSBoundParameters.ContainsKey('Password') ) { $Password = Read-Host -Prompt 'Enter private key password' -AsSecureString } $privateCertBytes = $privateCert.Export( 'PFX', $Password ) [IO.File]::WriteAllBytes( $PrivateKeyFile, $privateCertBytes ) Get-Item $PublicKeyFile Get-Item $PrivateKeyFile } finally { Remove-Item -Path $privateCertPath } } finally { Remove-Item -Path $tempDir -Recurse } } |