AWSTemplateFormatVersion: '2010-09-09' Resources: documentrootca: Type: AWS::SSM::Document Properties: Content: schemaVersion: '1.2' description: Provisions Offline RCA for ADCS parameters: domainfqdn: description: 'Fully Qualified Domain Name, example: lab.domain.com' type: String default: '' domainnetbios: description: 'ShortName / Netbios domain name, example: labx' type: String default: '' domaincnrca: description: RootCA Common Name type: String default: '' domaincnsubca: description: SubCA Common Name type: String default: '' rcavalidity: type: String description: Number of Years for RootCA default: '' subcavalidity: type: String description: Number of Years for Subordinate CA default: '' runtimeConfig: aws:runPowerShellScript: properties: id: '0.aws:runPowerShellScript' runCommand: - "### RootCA\r\n$adcsProperties = @{\r\n 'domain_fqdn' = '{{domainfqdn}}';\r\ \n 'domain_netbios' = '{{domainnetbios}}';\r\n 'rootca_common_name'\ \ = '{{domaincnrca}}';\r\n 'subca_common_name' = '{{domaincnsubca}}';\r\ \n 'rootca_validity' = '{{rcavalidity}}';\r\n 'rootca_crl_validity'\ \ = '{{rcavalidity}}';\r\n 'subca_validity' = '{{subcavalidity}}';\r\ \n}\r\n\r\n$adcsDirectories = @{\r\n 'adcsrootdir' = 'D:\\adcs_data';\r\ \n 'adcsdatadir' = 'certificate_exchange';\r\n 'adcsdbdir'\ \ = 'db';\r\n 'adcsiisdir' = 'iis';\r\n 'adcslogsdir' = 'logs';\r\ \n 'adcsloggingdir' = 'adcs_logs' ;\r\n 'currentlog' = 'ADCS_RCA_Deployment.log'\ \ ;\r\n 'default_iis_dir' = 'C:\\Windows\\system32\\CertSrv'\ \ ;\r\n}\r\n\r\n## Begin Transcripting the session to track for\ \ error messages\r\nStart-Transcript -Path $($adcsDirectories.'adcsrootdir'\ \ + '\\' + $adcsDirectories.'adcsloggingdir' + '\\' + $adcsDirectories.'currentlog')\ \ -append\r\nWrite-host $(get-date)\r\n\r\nWrite-host \"Formating\ \ EBS Data Volume\"\r\n## Find and Initialize D\r\n$data_disk =\ \ get-disk|?{$_.\"partitionstyle\" -like \"raw\"} \r\n$data_disk\ \ | Initialize-Disk -PartitionStyle GPT\r\n## Add Partition & Quick\ \ Format\r\n$data_part = $data_disk |New-Partition -DriveLetter\ \ D -UseMaximumSize\r\nformat-volume -driveletter D -newfilesystemlabel\ \ \"ADCS-Data\"\r\n\r\n## Establish ADCS_Data Folder Layout, and\ \ Make Directories\r\nmkdir \"$($adcsDirectories.'adcsrootdir')\"\ \r\nmkdir \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsdbdir')\"\ \r\nmkdir \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsiisdir')\"\ \r\nmkdir \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcslogsdir')\"\ \r\nmkdir \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsdatadir')\"\ \r\nmkdir \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsloggingdir')\"\ \r\n\r\n## Establish FQDN and DistringuishedNames for Root & Sub\ \ CAs\r\n$rootca_fqdn = $adcsProperties.'rootca_common_name' + '.'\ \ + $adcsProperties.'domain_fqdn'\r\n$rootca_distinguished_name\ \ = \"DC=\" + $($rootca_fqdn -replace \"\\.\" , \",DC=\")\r\n$subca_fqdn\ \ = $adcsProperties.'subca_common_name' + '.' + $adcsProperties.'domain_fqdn'\r\ \n$subca_distinguished_name = \"DC=\" + $($subca_fqdn -replace \"\ \\.\" , \",DC=\")\r\n\r\n## Establish File Naming Convention\r\n\ $adcsFiles = @{\r\n 'rootca_der' = \"$rootca_fqdn`-der.cer\"\ ; \r\n 'rootca_cer' = \"$rootca_fqdn`-p7b.p7b\"; \r\n 'rootca_b64'\ \ = \"$rootca_fqdn`-b64.cer\"; \r\n 'rootca_crl' = \"$rootca_fqdn.crl\"\ ; \r\n 'subca_req' = \"$subca_fqdn`_CA-REQ.req\";\r\n 'subca_cer'\ \ = \"$subca_fqdn`-b64.cer\"\r\n 'subca_rsp' = \"$subca_fqdn`-b64.rsp\"\ \r\n}\r\n\r\n## Establish File Path Convention\r\n$adcsFilePaths\ \ = @{\r\n 'rootca_der' = $adcsDirectories.'adcsrootdir' + '\\\ ' + $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'rootca_der'\ \ ;\r\n 'rootca_cer' = $adcsDirectories.'adcsrootdir' + '\\'\ \ + $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'rootca_cer'\ \ ;\r\n 'rootca_b64' = $adcsDirectories.'adcsrootdir' + '\\'\ \ + $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'rootca_b64'\ \ ;\r\n 'rootca_crl' = $adcsDirectories.'adcsrootdir' + '\\'\ \ + $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'rootca_crl'\ \ ;\r\n 'subca_req' = $adcsDirectories.'adcsrootdir' + '\\' +\ \ $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'subca_req'\ \ ;\r\n 'subca_cer' = $adcsDirectories.'adcsrootdir' + '\\' +\ \ $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'subca_cer'\ \ ;\r\n 'subca_rsp' = $adcsDirectories.'adcsrootdir' + '\\' +\ \ $adcsDirectories.'adcsdatadir' + '\\' + $adcsFiles.'subca_rsp'\ \ ;\r\n}\r\n## Establish SSM Parameter Store Layout\r\n$adcsSSM\ \ = @{\r\n 'root' = \"$subca_fqdn\" ;\r\n 'subdir' = 'adcs-subca'\ \ ;\r\n }\r\n\r\n### Install and Configure ADCS RCA\r\nadd-windowsfeature\ \ adcs-cert-authority, windows-server-backup -includemanagementtools\r\ \n\r\n Install-AdcsCertificationAuthority `\r\n -CAType StandaloneRootCa\ \ `\r\n -CACommonName \"$rootca_fqdn\" `\r\n -CADistinguishedNameSuffix\ \ \"$rootca_distinguished_name\" `\r\n -DatabaseDirectory \"\ $($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsdbdir')\"\ \ `\r\n -LogDirectory \"$($adcsDirectories.'adcsrootdir' + '\\\ ' + $adcsDirectories.'adcslogsdir')\" `\r\n -ValidityPeriod 'Years'\ \ `\r\n -ValidityPeriodUnits $($adcsProperties.'rootca_validity')\ \ `\r\n -CryptoProviderName 'RSA#Microsoft Software Key Storage\ \ Provider' `\r\n -KeyLength 4096 `\r\n -HashAlgorithmName\ \ SHA512 `\r\n -Force\r\n\r\n## Add and Configure ADCS with Web\ \ Enrollment\r\nAdd-WindowsFeature Adcs-Web-Enrollment -IncludeManagementTools\r\ \nInstall-AdcsWebEnrollment -Force\r\n\r\n## Copying All Files from\ \ Default IIS Deployment to RootCA IIS Dir\r\nStop-Service certsvc\r\ \ncopy-item \"$($adcsDirectories.'default_iis_dir')\\*\" \"$($adcsDirectories.'adcsrootdir'\ \ + '\\' + $adcsDirectories.'adcsiisdir')\" -recurse -force\r\n\r\ \n## Import the IIS PowerShell Module, then update the virtual IIS\ \ directories for ADCS\r\nImport-Module WebAdministration\r\nSet-ItemProperty\ \ 'IIS:\\Sites\\Default Web Site\\CertSrv' -Name physicalPath -Value\ \ \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsiisdir')\\\ en-US\"\r\nSet-ItemProperty 'IIS:\\Sites\\Default Web Site\\CertEnroll'\ \ -Name physicalPath -Value \"$($adcsDirectories.'adcsrootdir' +\ \ '\\' + $adcsDirectories.'adcsiisdir')\\CertEnroll\"\r\n\r\n##\ \ Get Certificate Thumbprint(Hash) for IIS SSL Binding\r\n$rootca_certificate_thumbprint\ \ = $(get-childitem -path cert:\\localmachine\\my | ?{$_.subject\ \ -like \"*$rootca_fqdn*\"}).thumbprint\r\n$guid = [guid]::NewGuid().ToString('B')\r\ \nnetsh http add sslcert hostnameport=\"${rootca_fqdn}:443\" certhash=$rootca_certificate_thumbprint\ \ certstorename=MY appid=\"$guid\"\r\nNew-WebBinding -name 'Default\ \ Web Site' -Protocol https -HostHeader $rootca_fqdn -Port 443\ \ -SslFlags 1\r\n\r\n## Disable IIS Directory Browsing\r\nC:\\Windows\\\ System32\\inetsrv\\appcmd.exe set config /section:directoryBrowse\ \ /enabled:false\r\n## Enable IIS Request Filtering\r\nC:\\Windows\\\ System32\\inetsrv\\appcmd.exe set config 'Default Web Site' /section:system.webServer/Security/requestFiltering\ \ -allowDoubleEscaping:True\r\n\r\niisreset\r\n\r\n### Remove Existing\ \ CDP and AIA\r\n$(Get-CAAuthorityInformationAccess) | Remove-CAAuthorityInformationAccess\ \ -force\r\n$(Get-CACRLDistributionPoint) | Remove-CACRLDistributionPoint\ \ -force\r\n\r\n### Add CDP HTTP\r\nAdd-CACRLDistributionPoint `\r\ \n -Uri \"http://$subca_fqdn/CertEnroll/.crl\"\ \ `\r\n -AddToCertificateCdp `\r\n -AddToFreshestCrl `\r\n\ \ -force\r\n\r\n### Add CDP LocalDisk\r\nAdd-CACRLDistributionPoint\ \ `\r\n -Uri \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsiisdir')\\\ CertEnroll\\.crl\" `\r\n\ \ -PublishToServer -PublishDeltaToServer `\r\n -force\r\n\r\ \n### Add AIA LocalDisk, Set to IIS Dir\r\ncertutil -setreg CA\\\ CACertPublicationURLs \"1:$($adcsDirectories.'adcsrootdir' + '\\\ ' + $adcsDirectories.'adcsiisdir')\\CertEnroll\\CertEnroll\\%1_%3%4.crt\"\ \r\n\r\n### Add AIA HTTP\r\nAdd-CAAuthorityInformationAccess `\r\ \n -Uri \"http://$subca_fqdn/CertEnroll/_.crt\"\ \ `\r\n -AddToCertificateAia `\r\n -force\r\n\r\n## With All\ \ New Settings in Place, Start RootCA CertificateAuthority Services\r\ \nStart-Service certsvc\r\n\r\n### SubCA Certificate Validity Interval\r\ \nCertutil -setreg CA\\ValidityPeriodUnits $($adcsProperties.'subca_validity')\r\ \nCertutil -setreg CA\\ValidityPeriod 'Years'\r\n\r\n### CRL Publication\ \ Intervals\r\ncertutil -setreg CA\\CRLPeriodUnits $($adcsProperties.'rootca_crl_validity')\r\ \ncertutil -setreg CA\\CRLPeriod 'Years'\r\n\r\ncertutil -setreg\ \ CA\\CRLDeltaPeriodUnits 0\r\ncertutil -setreg CA\\CRLDeltaPeriod\ \ 'days'\r\n\r\n## With All New Settings in Place, Restart RootCA\ \ CertificateAuthority Services\r\nStop-Service certsvc\r\nStart-Service\ \ certsvc\r\n\r\n### Publish CRL with CertUtil\r\ncertutil -crl\r\ \n\r\n### Publish Goodies for SubCA\r\n### Get RootCA CRL File from\ \ LocalDisk, and Copy to SubCA DataDirectory\r\ncopy-item \"$($adcsDirectories.'adcsrootdir'\ \ + '\\' + $adcsDirectories.'adcsiisdir')\\CertEnroll\\$($adcsFiles.'rootca_crl')\"\ \ \"$($adcsDirectories.'adcsrootdir' + '\\' + $adcsDirectories.'adcsdatadir')\"\ \r\n\r\n## Get RootCA Certificate, and Convert to DER, P7b, and\ \ Base64\r\n$rootca_certificate = $(get-childitem -path cert:\\\ localmachine\\my | ?{$_.subject -like \"*$rootca_fqdn*\"})\r\n\r\ \nExport-Certificate -Type p7b -Cert $rootca_certificate -FilePath\ \ $($adcsFilePaths.'rootca_cer')\r\nExport-Certificate -Cert $rootca_certificate\ \ -FilePath $($adcsFilePaths.'rootca_der')\r\nStart-Process -FilePath\ \ 'certutil.exe' -ArgumentList \"-encode $($adcsFilePaths.'rootca_der')\ \ $($adcsFilePaths.'rootca_b64')\" -WindowStyle Hidden\r\n\r\n##\ \ RootCA Get SubCA Request\r\nImport-Module AWSPowerShell\r\n\r\n\ $polling_retries = 0\r\ndo {\r\n ## Get SubCA Request File from\ \ Parameter Store, if not found, wait 60 seconds, for a max of 10\ \ minutes.\r\n Try {\r\n $adcsSSMSubCAFiles = @{\r\n \ \ 'subca_req' = $($((Get-SSMParameterValue -Name \"/$($adcsSSM.'root'\ \ + '/' + $adcsSSM.'subdir' + '/' + $adcsFiles.'subca_req')\" –WithDecryption\ \ $true).Parameters).value) ;\r\n }\r\n }\r\n Catch\ \ {}\r\n\r\n ## If the SubCA Request File is Empty, Exit and\ \ Wait for Next Polling Interval\r\n if (-not $($adcsSSMSubCAFiles.'subca_req').length\ \ -gt 0) {\r\n Start-Sleep -Seconds 60\r\n }else{\r\n\ \ write-host \"Found SubCA Request File after $polling_retries\ \ Minutes\"\r\n break;\r\n }\r\n$polling_retries++\r\n\ } while($polling_retries -lt 10)\r\n\r\n## If the SubCA Request\ \ File is Empty, Log and Exit Powershell\r\nif (-not $($adcsSSMSubCAFiles.'subca_req').length\ \ -gt 0) {\r\n Write-Host \"SSM Parameter Store does not contain\ \ the SubCA Request Parameter after 10 minutes\"\r\n Exit 404\r\ \n}\r\n\r\n## RCA Write SubCA Request File\r\n[IO.File]::WriteAllBytes($adcsFilePaths.'subca_req'\ \ , [Convert]::FromBase64String($adcsSSMSubCAFiles.'subca_req'))\r\ \n\r\n## Process SubCA Request File\r\n## Submit, Approve, Retrieve\ \ SubCA CER File\r\n$CAConfig = \"$env:computername\\$rootca_fqdn\"\ \r\n\r\n## This will auto submit and approve the SUBCA REQ file\r\ \ncertreq -config $CAConfig –submit $($adcsFilePaths.'subca_req')\ \ $($adcsFilePaths.'subca_cer')\r\n\r\n## Remove the response file,\ \ since it has no value\r\nremove-item $($adcsFilePaths.'subca_rsp')\ \ -force\r\n\r\n## Query the ADCS DB\r\n$adcs_db_query = certutil\ \ -view csv |convertfrom-csv\r\n\r\n## Query the DB for the SubCA\ \ Template, and SubCA Request\r\n$certutil_subca_request = $adcs_db_query\ \ | ?{($_.'Request Common Name' -match \"$subca_fqdn\") -and ($_.'Certificate\ \ Template' -match 'SubCA') -and ($_.'Request Disposition' -like\ \ '*Pending')}\r\n$latest_certutil_request_id = $($certutil_subca_request\ \ | Measure-Object -Property 'Request ID' -Maximum).Maximum\r\n\ \r\n## After Extracting the Certificate Request ID, Resubmit to\ \ ADCS for \"Auto Approval\"\r\ncertutil -resubmit $latest_certutil_request_id\r\ \n\r\n## Export the SubCA Certificate as B64.Cer \r\ncertreq -config\ \ $CAConfig -retrieve $latest_certutil_request_id $($adcsFilePaths.'subca_cer')\r\ \n\r\n####### Wait until these files Exist, RootCA\r\n$adcsRootCAFileContent\ \ = @{\r\n 'rootca_der' = $([Convert]::ToBase64String([IO.File]::ReadAllBytes($adcsFilePaths.'rootca_der')))\ \ ; \r\n 'rootca_cer' = $([Convert]::ToBase64String([IO.File]::ReadAllBytes($adcsFilePaths.'rootca_cer')))\ \ ; \r\n 'rootca_b64' = $([Convert]::ToBase64String([IO.File]::ReadAllBytes($adcsFilePaths.'rootca_b64')))\ \ ; \r\n 'rootca_crl' = $([Convert]::ToBase64String([IO.File]::ReadAllBytes($adcsFilePaths.'rootca_crl')))\ \ ; \r\n 'subca_cer' = $([Convert]::ToBase64String([IO.File]::ReadAllBytes($adcsFilePaths.'subca_cer')))\ \ ; \r\n}\r\n\r\n### RCA Write SSM Parameters\r\nImport-Module AWSPowerShell\r\ \nWrite-SSMParameter -Name \"/$($adcsSSM.'root' + '/' + $adcsSSM.'subdir'\ \ + '/' + $adcsFiles.'rootca_der')\" -Value $($adcsRootCAFileContent.'rootca_der')\ \ -Type \"SecureString\"\r\nWrite-SSMParameter -Name \"/$($adcsSSM.'root'\ \ + '/' + $adcsSSM.'subdir' + '/' + $adcsFiles.'rootca_cer')\" -Value\ \ $($adcsRootCAFileContent.'rootca_cer') -Type \"SecureString\"\r\ \nWrite-SSMParameter -Name \"/$($adcsSSM.'root' + '/' + $adcsSSM.'subdir'\ \ + '/' + $adcsFiles.'rootca_b64')\" -Value $($adcsRootCAFileContent.'rootca_b64')\ \ -Type \"SecureString\"\r\nWrite-SSMParameter -Name \"/$($adcsSSM.'root'\ \ + '/' + $adcsSSM.'subdir' + '/' + $adcsFiles.'rootca_crl')\" -Value\ \ $($adcsRootCAFileContent.'rootca_crl') -Type \"SecureString\"\r\ \nWrite-SSMParameter -Name \"/$($adcsSSM.'root' + '/' + $adcsSSM.'subdir'\ \ + '/' + $adcsFiles.'subca_cer')\" -Value $($adcsRootCAFileContent.'subca_cer')\ \ -Type \"SecureString\"\r\n\r\nStop-Transcript\r\n\r\n## Stop the\ \ RootCA since its supposed to be Offline\r\nShutdown -f -t 60 -s\r\ \n\r\nExit" timeoutSeconds: '3600' Outputs: docname: Description: ssm document for rootca Value: !Ref 'documentrootca'