# MIT License # # Copyright (c) 2016 Raimund Andée # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. #region Internals #region .net Types $certStoreTypes = @' ... '@ $pkiInternalsTypes = @' ... /// /// 2.28 msPKI-Certificate-Name-Flag Attribute /// https://msdn.microsoft.com/en-us/library/cc226548.aspx /// ... '@ $gpoType = @' ... '@ #endregion .net Types $ApplicationPolicies = @{ # Remote Desktop 'Remote Desktop' = '1.3.6.1.4.1.311.54.1.2' # Windows Update 'Windows Update' = '1.3.6.1.4.1.311.76.6.1' # Windows Third Party Applicaiton Component 'Windows Third Party Application Component' = '1.3.6.1.4.1.311.10.3.25' # Windows TCB Component 'Windows TCB Component' = '1.3.6.1.4.1.311.10.3.23' # Windows Store 'Windows Store' = '1.3.6.1.4.1.311.76.3.1' #... } $ExtendedKeyUsages = @{ OldAuthorityKeyIdentifier = '.29.1' OldPrimaryKeyAttributes = '2.5.29.2' #... X509version3CertificateExtensionInhibitAny = '2.5.29.54' } #endregion Internals #region Get-LabCertificate function Get-LabCertificate { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding(DefaultParameterSetName = 'Find')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Find')] [string]$SearchString, [Parameter(Mandatory = $true, ParameterSetName = 'Find')] [System.Security.Cryptography.X509Certificates.X509FindType]$FindType, [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location, [System.Security.Cryptography.X509Certificates.StoreName]$Store, [string]$ServiceName, [Parameter(Mandatory = $true, ParameterSetName = 'All')] [switch]$All, [Parameter(ParameterSetName = 'All')] [switch]$IncludeServices, [string]$Password = 'AL', [Parameter(Mandatory)] [string[]]$ComputerName ) Write-LogFunctionEntry $variables = Get-Variable -Name PSBoundParameters $functions = Get-Command -Name Get-Certificate2, Sync-Parameter $x = $PSBoundParameters foreach ($computer in $ComputerName) { Invoke-LabCommand -ActivityName 'Adding Cert Store Types' -ComputerName $ComputerName -ScriptBlock { Add-Type -TypeDefinition $args[0] } -ArgumentList $certStoreTypes -NoDisplay Invoke-LabCommand -ActivityName 'Exporting certificates' -ComputerName $ComputerName -ScriptBlock { $variables['Password'] = $variables['Password'] | ConvertTo-SecureString -AsPlainText -Force Sync-Parameter -Command (Get-Command -Name Get-Certificate2) Get-Certificate2 @ALBoundParameters } -Variable $variables -Function $functions -PassThru } Write-LogFunctionExit } #endregion Get-LabCertificate #region Add-LabCertificate function Add-LabCertificate { # .ExternalHelp AutomatedLab.Help.xml [cmdletBinding(DefaultParameterSetName = 'ByteArray')] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'File')] [string]$Path, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByteArray')] [byte[]]$Cert, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [System.Security.Cryptography.X509Certificates.StoreName]$Store, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location, [Parameter(ValueFromPipelineByPropertyName = $true)] [string]$ServiceName, [Parameter(ValueFromPipelineByPropertyName = $true)] [ValidateSet('CER', 'PFX')] [string]$CertificateType = 'CER', [string]$Password = 'AL', [Parameter(Mandatory, ValueFromPipelineByPropertyName = $true)] [string[]]$ComputerName ) begin { Write-LogFunctionEntry } process { $variables = Get-Variable -Name PSBoundParameters $functions = Get-Command -Name Add-Certificate2, Sync-Parameter Invoke-LabCommand -ActivityName 'Adding Cert Store Types' -ComputerName $ComputerName -ScriptBlock { Add-Type -TypeDefinition $args[0] } -ArgumentList $certStoreTypes -NoDisplay Invoke-LabCommand -ActivityName 'Storing certificate bytes on target machine' -ComputerName $ComputerName -ScriptBlock { $tempFile = [System.IO.Path]::GetTempFileName() [System.IO.File]::WriteAllBytes($tempFile, $args[0]) Write-Verbose "Cert is written to '$tempFile'" } -ArgumentList (,$Cert) -Variable $variables Invoke-LabCommand -ActivityName 'Importing Cert file' -ComputerName $ComputerName -ScriptBlock { $variables['Password'] = $variables['Password'] | ConvertTo-SecureString -AsPlainText -Force Sync-Parameter -Command (Get-Command -Name Add-Certificate2) $ALBoundParameters.Add('Path', $tempFile) $ALBoundParameters.Remove('Cert') Add-Certificate2 @ALBoundParameters | Out-Null Remove-Item -Path $tempFile } -Variable $variables -Function $functions -PassThru } end { Write-LogFunctionExit } } #endregion Add-LabCertificate # ... #region Install-LabCAMachine function Install-LabCAMachine { # .ExternalHelp AutomatedLab.Help.xml [CmdletBinding()] param ( [Parameter(Mandatory)] [AutomatedLab.Machine]$Machine, [int]$PreDelaySeconds, [switch]$PassThru ) Write-LogFunctionEntry Write-Verbose -Message '****************************************************' Write-Verbose -Message "Starting installation of machine: $($machine.name)" Write-Verbose -Message '****************************************************' $role = $machine.Roles | Where-Object { $_.Name -eq ([AutomatedLab.Roles]::CaRoot) -or $_.Name -eq ([AutomatedLab.Roles]::CaSubordinate) } $param = [ordered]@{ } #region - Locate admin username and password for machine if ($machine.IsDomainJoined) { $domain = $lab.Domains | Where-Object { $_.Name -eq $machine.DomainName } $param.Add('UserName', ('{0}\{1}' -f $domain.Name, $domain.Administrator.UserName)) $param.Add('Password', $domain.Administrator.Password) $rootDc = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $machine.DomainName if ($rootDc) #if there is a root domain controller in the same domain as the machine { $rootDomain = (Get-Lab).Domains | Where-Object Name -eq $rootDc.DomainName $rootDomainNetBIOSName = ($rootDomain.Name -split '\.')[0] } else #else the machine is in a child domain and the parent domain need to be used for the query { $rootDomain = $lab.GetParentDomain($machine.DomainName) $rootDomainNetBIOSName = ($rootDomain.Name -split '\.')[0] $rootDc = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $rootDomain } $param.Add('ForestAdminUserName', ('{0}\{1}' -f $rootDomainNetBIOSName, $rootDomain.Administrator.UserName)) $param.Add('ForestAdminPassword', $rootDomain.Administrator.Password) Write-Debug -Message "Machine : $($machine.name)" Write-Debug -Message "Machine Domain : $($machine.DomainName)" Write-Debug -Message "Username for job : $($param.username)" Write-Debug -Message "Password for job : $($param.Password)" Write-Debug -Message "ForestAdmin Username : $($param.ForestAdminUserName)" Write-Debug -Message "ForestAdmin Password : $($param.ForestAdminPassword)" } else { $param.Add('UserName', ('{0}\{1}' -f $machine.Name, $machine.InstallationUser.UserName)) $param.Add('Password', $machine.InstallationUser.Password) } $param.Add('ComputerName', $Machine.Name) #endregion #region - Determine DNS name for machine. This is used when installing Enterprise CAs $caDNSName = $Machine.Name if ($Machine.DomainName) { $caDNSName += ('.' + $Machine.DomainName) } if ($Machine.DomainName) { $param.Add('DomainName', $Machine.DomainName) } else { $param.Add('DomainName', '') } if ($role.Name -eq 'CaSubordinate') { if (!($role.Properties.ContainsKey('ParentCA'))) { $param.Add('ParentCA', '') } else { $param.Add('ParentCA', $role.Properties.ParentCA) } if (!($role.Properties.ContainsKey('ParentCALogicalName'))) { $param.Add('ParentCALogicalName', '') } else { $param.Add('ParentCALogicalName', $role.Properties.ParentCALogicalName) } } #... if (!($role.Properties.ContainsKey('CPSURL'))) { $param.Add('CPSURL', 'http://' + $caDNSName + '/cps/cps.html') } else { $param.Add('CPSURL', $role.Properties.CPSURL) } if (!($role.Properties.ContainsKey('CPSText'))) { $param.Add('CPSText', 'Certification Practice Statement') } else { $param.Add('CPSText', $($role.Properties.CPSText)) } if (!($role.Properties.ContainsKey('InstallOCSP'))) { $param.Add('InstallOCSP', '') } else { $param.Add('InstallOCSP', ($role.Properties.InstallOCSP -like '*Y*')) } if (!($role.Properties.ContainsKey('OCSPHTTPURL01'))) { $param.Add('OCSPHTTPURL01', '') } else { $param.Add('OCSPHTTPURL01', $role.Properties.OCSPHTTPURL01) } if (!($role.Properties.ContainsKey('OCSPHTTPURL02'))) { $param.Add('OCSPHTTPURL02', '') } else { $param.Add('OCSPHTTPURL02', $role.Properties.OCSPHTTPURL02) } if (!($role.Properties.ContainsKey('DoNotLoadDefaultTemplates'))) { $param.Add('DoNotLoadDefaultTemplates', '') } else { $param.Add('DoNotLoadDefaultTemplates', $role.Properties.DoNotLoadDefaultTemplates -like '*Y*') } #region - Check if any unknown parameter name was passed $knownParameters = @() $knownParameters += 'ParentCA (only valid for Subordinate CA. Ignored for Root CAs)' $knownParameters += 'ParentCALogicalName (only valid for Subordinate CAs. Ignored for Root CAs)' $knownParameters += 'CACommonName' $knownParameters += 'CAType' #... $knownParameters += 'DoNotLoadDefaultTemplates' $knownParameters += 'PreDelaySeconds' $unkownParFound = $false foreach ($keySet in $role.Properties.GetEnumerator()) { if ($keySet.Key -cnotin $knownParameters) { Write-Warning -Message "Parameter name '$($keySet.Key)' is unknown/ignored)" $unkownParFound = $true } } if ($unkownParFound) { Write-Warning -Message 'Valid parameter names are:' Foreach ($name in ($knownParameters.GetEnumerator())) { Write-Warning -Message " $($name)" } Write-Warning -Message 'NOTE that all parameter names are CASE SENSITIVE!' } #endregion - Check if any unknown parameter names was passed #endregion - Parameters #region - Parameters debug Write-Debug -Message '---------------------------------------------------------------------------------------' Write-Debug -Message "Parameters for $($machine.name)" Write-Debug -Message '---------------------------------------------------------------------------------------' if ($machine.Roles.Properties.GetEnumerator().Count) { foreach ($r in $machine.Roles) { if (([AutomatedLab.Roles]$r.Name -band $roles) -ne 0) #if this is a CA role { foreach ($key in ($r.Properties.GetEnumerator() | Sort-Object -Property Key)) { Write-Debug -Message " $($key.Key.PadRight(27)) $($key.Value)" } } } } else { Write-Debug -message ' No parameters specified' } Write-Debug -Message '---------------------------------------------------------------------------------------' #endregion - Parameters debug #region ----- Input validation (raw values) ----- if ($role.Properties.ContainsKey('CACommonName') -and ($param.CACommonName.Length -gt 37)) { Write-Error -Message "CACommonName cannot be longer than 37 characters. Specified value is: '$($param.CACommonName)'"; return } if ($role.Properties.ContainsKey('CACommonName') -and ($param.CACommonName.Length -lt 1)) { Write-Error -Message "CACommonName cannot be blank. Specified value is: '$($param.CACommonName)'"; return } if ($role.Name -eq 'CaRoot') { if (-not ($param.CAType -in 'EnterpriseRootCA', 'StandAloneRootCA', '')) { Write-Error -Message "CAType needs to be 'EnterpriseRootCA' or 'StandAloneRootCA' when role is CaRoot. Specified value is: '$param.CAType'"; return } } if ($role.Name -eq 'CaSubordinate') { if (-not ($param.CAType -in 'EnterpriseSubordinateCA', 'StandAloneSubordinateCA', '')) { Write-Error -Message "CAType needs to be 'EnterpriseSubordinateCA' or 'StandAloneSubordinateCA' when role is CaSubordinate. Specified value is: '$param.CAType'"; return } } $availableCombinations = @() $availableCombinations += @{CryptoProviderName='Microsoft Base SMart Card Crypto Provider'; HashAlgorithmName='sha1','md2','md4','md5'; KeyLength='1024','2048','4096'} #... $combination = $availableCombinations | Where-Object {$_.CryptoProviderName -eq $param.CryptoProviderName} if (-not ($param.CryptoProviderName -in $combination.CryptoProviderName)) { Write-Error -Message "CryptoProviderName '$($param.CryptoProviderName)' is unknown. `nList of valid options for CryptoProviderName:`n $($availableCombinations.CryptoProviderName -join "`n ")"; return } elseif (-not ($param.HashAlgorithmName -in $combination.HashAlgorithmName)) { Write-Error -Message "HashAlgorithmName '$($param.HashAlgorithmName)' is not valid for CryptoProviderName '$($param.CryptoProviderName)'. The Crypto Provider selected supports the following Hash Algorithms:`n $($combination.HashAlgorithmName -join "`n ")"; return } elseif (-not ($param.KeyLength -in $combination.KeyLength)) { Write-Error -Message "Keylength '$($param.KeyLength)' is not valid for CryptoProviderName '$($param.CryptoProviderName)'. The Crypto Provider selected supports the following keylengths:`n $($combination.KeyLength -join "`n ")"; return } if ($role.Properties.ContainsKey('DatabaseDirectory') -and -not ($param.DatabaseDirectory -match '^[C-Z]:\\')) { Write-Error -Message 'DatabaseDirectory needs to be located on a local drive (drive letter C-Z)'; return } #... #if any validity parameter was defined, get these now and convert them all to hours (temporary variables) if ($param.ValidityPeriodUnits -ne '') { switch ($param.ValidityPeriod) { 'Years' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 365 * 24 } 'Months' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * (365/12) * 24 } 'Weeks' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 7 * 24 } 'Days' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 24 } 'Hours' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits } } } if ($param.CertsValidityPeriodUnits -ne '') { switch ($param.CertsValidityPeriod) { 'Years' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 365 * 24 } 'Months' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * (365/12) * 24 } 'Weeks' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 7 * 24 } 'Days' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 24 } 'Hours' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits } } } if ($param.CRLPeriodUnits -ne '') { switch ($param.CRLPeriod) { 'Years' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 365 * 24) } 'Months' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnit * (365/12) * 24) } 'Weeks' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 7 * 24) } 'Days' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 24) } 'Hours' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits) } } } if ($param.CRLDeltaPeriodUnits -ne '') { switch ($param.CRLDeltaPeriod) { 'Years' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 365 * 24) } 'Months' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * (365/12) * 24) } 'Weeks' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 7 * 24) } 'Days' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 24) } 'Hours' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits) } } } if ($param.CRLOverlapUnits -ne '') { switch ($param.CRLOverlapPeriod) { 'Years' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 365 * 24) } 'Months' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * (365/12) * 24) } 'Weeks' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 7 * 24) } 'Days' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 24) } 'Hours' { $CRLOverlapUnitsHours = [int]([int]${param}.CRLOverlapUnits) } } } #... } #endregion Install-LabCAMachine #... <#http://example.com.#> Write-Debug `$`{`} Write-Debug $false.$true.$param $(Write-Debug $true) :OuterLoop while ($true) { while ($true) { break OuterLoop } }