Skip to content

Commit

Permalink
Update New-SelfSignedCertificate (#21)
Browse files Browse the repository at this point in the history
* Update New-SelfSignedCertificate
fixed SupportsShouldProcess
updated SAN input
code clean

* update type to use full name

* fix issue where process fails when no SAN is provided
updated comments

* removed auto add of "www" to SAN

* reved module version to 0.2.3
  • Loading branch information
johnsarie27 authored Apr 15, 2024
1 parent 44438a6 commit 3f54471
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 96 deletions.
2 changes: 1 addition & 1 deletion PS.SSL.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
RootModule = 'PS.SSL.psm1'

# Version number of this module.
ModuleVersion = '0.2.2'
ModuleVersion = '0.2.3'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down
19 changes: 11 additions & 8 deletions Public/New-CertificateSigningRequest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ function New-CertificateSigningRequest {
.NOTES
Name: New-CertificateSigningRequest
Author: Justin Johns
Version: 0.2.0 | Last Edit: 2024-03-08
Version: 0.2.1 | Last Edit: 2024-04-14
- 0.2.1 - (2024-04-14) Fixed bug
- 0.2.0 - (2024-03-08) Fixed SupportsShouldProcess, updated SAN input, renamed function
- 0.1.1 - (2022-06-20) Added SupportsShouldProcess
- 0.1.0 - Initial versions
Expand All @@ -63,7 +64,7 @@ function New-CertificateSigningRequest {
[Parameter(Mandatory, ParameterSetName = '__input', HelpMessage = 'Common Name (CN)')]
[Alias('CN')]
[ValidatePattern('^[\w\.-]+\.(com|org|gov)$')]
[string] $CommonName,
[System.String] $CommonName,

[Parameter(ParameterSetName = '__input', HelpMessage = 'Country Name (C)')]
[Alias('C')]
Expand Down Expand Up @@ -117,16 +118,12 @@ function New-CertificateSigningRequest {
# ADD TEMPLATE TO LIST
$template.AddRange($CSR_Template)

# ADD WWW TO COMMON NAME AND ADD TO LIST
if ($CommonName -notmatch '^www') { $template.Add(('DNS.1 = www.{0}' -f $CommonName)) | Out-Null; $start = 2 }
else { $start = 1 }

# ADD SUBJECT ALTERNATIVE NAMES TO LIST
if ($PSBoundParameters.ContainsKey('SubjectAlternativeName')) {
# EVALUATE EACH SAN IN ARRAY
for ($i = $start; $i -lt ($SubjectAlternativeName.Count + $start); $i++) {
for ($i = 1; $i -lt ($SubjectAlternativeName.Count + 1); $i++) {
# ADD SAN TO END OF COLLECTION
$template.Add(('DNS.{0} = {1}' -f $i, $SubjectAlternativeName[$i - $start])) | Out-Null
$template.Add(('DNS.{0} = {1}' -f $i, $SubjectAlternativeName[$i - 1])) | Out-Null
}
}

Expand All @@ -139,6 +136,12 @@ function New-CertificateSigningRequest {
if ($PSBoundParameters.ContainsKey('OrganizationalUnit')) { $tokenList.Add('OU', $OrganizationalUnit) } else { $template.Remove('OU = #OU#') }
if ($PSBoundParameters.ContainsKey('Email')) { $tokenList.Add('E', $Email) } else { $template.Remove('emailAddress = "#E#"') }

# REMOVE SAN FROM TEMPLATE IF NOT PROVIDED
if (-Not $PSBoundParameters.ContainsKey('SubjectAlternativeName')) {
$template.Remove('[alt_names]')
$template.Remove('subjectAltName = @alt_names')
}

# REPLACE TOKENS IN TEMPLATE
foreach ($token in $tokenList.GetEnumerator()) {
$pattern = '#{0}#' -f $token.key
Expand Down
184 changes: 97 additions & 87 deletions Public/New-SelfSignedCertificate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,26 @@ function New-SelfSignedCertificate {
Organizational Unit Name (OU)
.PARAMETER Email
Email Address
.PARAMETER SAN1
Subject Alternative Name (SAN) 1
.PARAMETER SAN2
Subject Alternative Name (SAN) 2
.PARAMETER SAN3
Subject Alternative Name (SAN) 3
.PARAMETER SubjectAlternativeName
Subject Alternative Name (SAN)
.INPUTS
None.
.OUTPUTS
System.Object.
.EXAMPLE
PS C:\> New-SelfSignedCertificate
Explanation of what the example does
PS C:\> New-SelfSignedCertificate -CommonName myDomain.com
Generates a new self-signed certificate for myDomain.com
.NOTES
Name: New-SelfSignedCertificate
Author: Justin Johns
Version: 0.1.1 | Last Edit: 2022-04-13
- Renamed output template file
Version: 0.2.0 | Last Edit: 2024-04-14
- 0.2.0 - (2024-04-14) Fixed SupportsShouldProcess and updated SAN input
- 0.1.1 - (2022-04-13) Renamed output template file
- 0.1.0 - Initial versions
Comments: <Comment(s)>
General notes
#>
[CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = '__conf')]
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = '__conf')]
Param(
[Parameter(HelpMessage = 'Output directory for CSR and key file')]
[ValidateScript({ Test-Path -Path (Split-Path -Path $_) -PathType Container })]
Expand Down Expand Up @@ -93,100 +91,112 @@ function New-SelfSignedCertificate {
[ValidatePattern('^[\w\.@-]+$')]
[System.String] $Email,

[Parameter(ParameterSetName = '__input', HelpMessage = 'Subject Alternative Name (SAN) 1')]
[ValidatePattern('^[\w\.-]+\.(com|org|gov)$')]
[System.String] $SAN1,

[Parameter(ParameterSetName = '__input', HelpMessage = 'Subject Alternative Name (SAN) 2')]
[ValidatePattern('^[\w\.-]+\.(com|org|gov)$')]
[System.String] $SAN2,

[Parameter(ParameterSetName = '__input', HelpMessage = 'Subject Alternative Name (SAN) 3')]
[ValidatePattern('^[\w\.-]+\.(com|org|gov)$')]
[System.String] $SAN3
[Parameter(ParameterSetName = '__input', HelpMessage = 'Subject Alternative Name (SAN)')]
[Alias('SAN')]
[ValidatePattern('^[\w\.-]+\.(com|org|gov|info)$')]
[System.String[]] $SubjectAlternativeName
)
Begin {
Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"
Write-Verbose -Message ('Parameter Set: {0}' -f $PSCmdlet.ParameterSetName)

# SHOULD PROCESS
if ($PSCmdlet.ShouldProcess($OutputDirectory, "Create Files")) {
# CREATE OUTPUT DIRECTORY
if (-not (Test-Path -Path $OutputDirectory)) {
Write-Verbose -Message ('Creating new folder named: {0}' -f (Split-Path -Path $OutputDirectory -Leaf))
New-Item -Path $OutputDirectory -ItemType Directory | Out-Null
}

# CREATE OUTPUT DIRECTORY
if (-not (Test-Path -Path $OutputDirectory)) {
# CREATE TEMPLATE FILE
if ($PSCmdlet.ParameterSetName -eq '__input') {
# CREATE NEW LIST
$template = [System.Collections.ArrayList]::new()

Write-Verbose -Message ('Creating new folder named: {0}' -f (Split-Path -Path $OutputDirectory -Leaf))
New-Item -Path $OutputDirectory -ItemType Directory | Out-Null
}
# ADD TEMPLATE TO LIST
$template.AddRange($CSR_Template)

# CREATE TEMPLATE FILE
if ($PSCmdlet.ParameterSetName -eq '__input') {
# GET TEMPLATE
$template = [System.Collections.ArrayList]::new($CSR_Template)

# SET REPLACEMENT TOKENS
$tokenList = @{ CN = $CommonName }
if ($PSBoundParameters.ContainsKey('Country')) { $tokenList.Add('C', $Country) } else { $template.Remove('C = #C#') }
if ($PSBoundParameters.ContainsKey('State')) { $tokenList.Add('ST', $State) } else { $template.Remove('ST = #ST#') }
if ($PSBoundParameters.ContainsKey('Locality')) { $tokenList.Add('L', $Locality) } else { $template.Remove('L = #L#') }
if ($PSBoundParameters.ContainsKey('Organization')) { $tokenList.Add('O', $Organization) } else { $template.Remove('O = #O#') }
if ($PSBoundParameters.ContainsKey('OrganizationalUnit')) { $tokenList.Add('OU', $OrganizationalUnit) } else { $template.Remove('OU = #OU#') }
if ($PSBoundParameters.ContainsKey('Email')) { $tokenList.Add('E', $Email) } else { $template.Remove('emailAddress = "#E#"') }
if ($PSBoundParameters.ContainsKey('SAN1')) { $tokenList.Add('SAN1', $SAN1) } else { $template.Remove('DNS.2 = #SAN1#') }
if ($PSBoundParameters.ContainsKey('SAN2')) { $tokenList.Add('SAN2', $SAN2) } else { $template.Remove('DNS.3 = #SAN2#') }
if ($PSBoundParameters.ContainsKey('SAN3')) { $tokenList.Add('SAN3', $SAN3) } else { $template.Remove('DNS.4 = #SAN3#') }

# REPLACE TOKENS
foreach ( $token in $tokenList.GetEnumerator() ) {
$pattern = '#{0}#' -f $token.key
$template = $template -replace $pattern, $token.Value
# ADD SUBJECT ALTERNATIVE NAMES TO LIST
if ($PSBoundParameters.ContainsKey('SubjectAlternativeName')) {
# EVALUATE EACH SAN IN ARRAY
for ($i = 1; $i -lt ($SubjectAlternativeName.Count + 1); $i++) {
# ADD SAN TO END OF COLLECTION
$template.Add(('DNS.{0} = {1}' -f $i, $SubjectAlternativeName[$i - 1])) | Out-Null
}

# SHOW TEMPLATE
Write-Verbose -Message ($template -join "`n")

# SET TEMPLATE FILE PATH
$random = [System.IO.Path]::GetRandomFileName().Split('.')[0]
$configPath = Join-Path -Path $OutputDirectory -ChildPath ('template_{0}.conf' -f $random)

# CREATE TEMPLATE FILE
Set-Content -Path $configPath -Value $template -Confirm:$false
}
else {
$configPath = $ConfigFile

# SET REPLACEMENT TOKENS
$tokenList = @{ CN = $CommonName }
if ($PSBoundParameters.ContainsKey('Country')) { $tokenList.Add('C', $Country) } else { $template.Remove('C = #C#') }
if ($PSBoundParameters.ContainsKey('State')) { $tokenList.Add('ST', $State) } else { $template.Remove('ST = #ST#') }
if ($PSBoundParameters.ContainsKey('Locality')) { $tokenList.Add('L', $Locality) } else { $template.Remove('L = #L#') }
if ($PSBoundParameters.ContainsKey('Organization')) { $tokenList.Add('O', $Organization) } else { $template.Remove('O = #O#') }
if ($PSBoundParameters.ContainsKey('OrganizationalUnit')) { $tokenList.Add('OU', $OrganizationalUnit) } else { $template.Remove('OU = #OU#') }
if ($PSBoundParameters.ContainsKey('Email')) { $tokenList.Add('E', $Email) } else { $template.Remove('emailAddress = "#E#"') }

# REMOVE SAN FROM TEMPLATE IF NOT PROVIDED
if (-Not $PSBoundParameters.ContainsKey('SubjectAlternativeName')) {
$template.Remove('[alt_names]')
$template.Remove('subjectAltName = @alt_names')
}

# SET FILE NAME
$selectPattern = Get-Content -Path $configPath | Select-String -Pattern '^CN = (.+)$'
$fileName = $selectPattern.Matches.Groups[1].Value

# THE CHARACTER "*" IS NOT VALID IN A WINDOWS FILENAME. REPLACE "*" WITH "STAR"
if ($fileName -match '\*') { $fileName = $fileName.Replace('*', 'star') }
Write-Verbose -Message ('New file name: {0}' -f $fileName)

# SET OPENSSL PARAMETERS
# openssl req -new -newkey rsa:2048 -nodes -sha256 -out company_san.csr -keyout company_san.key -config req.conf
# USING THE "-legacy" PARAMETER WILL MAINTAIN COMPATABILITY WITH CERTAIN SERVERS THAT DO NOT YET SUPPORT
# THE LATEST CIPHERS OR PROTOCOLS
# EXAMPLE> openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
# -newkey rsa:4096 and -sha256 are in the default template
$sslParams = @{
FilePath = 'openssl'
ArgumentList = @(
'req -new -x509 -nodes -days {0}' -f $Days
'-config {0}' -f $configPath
'-keyout {0}' -f (Join-Path -Path $OutputDirectory -ChildPath ('{0}_PRIVATE.key' -f $fileName))
'-out {0}' -f (Join-Path -Path $OutputDirectory -ChildPath ('{0}.pem' -f $fileName))
)
Wait = $true
NoNewWindow = $true
PassThru = $true
# REPLACE TOKENS
foreach ($token in $tokenList.GetEnumerator()) {
$pattern = '#{0}#' -f $token.key
$template = $template -replace $pattern, $token.Value
}

# SHOW TEMPLATE
Write-Verbose -Message ("`n" + ($template -join "`n"))

# SET TEMPLATE FILE PATH
$random = [System.IO.Path]::GetRandomFileName().Split('.')[0]
$configPath = Join-Path -Path $OutputDirectory -ChildPath ('template_{0}.conf' -f $random)

# CREATE TEMPLATE FILE
Set-Content -Path $configPath -Value $template -Confirm:$false

# OUTPUT TEMPLATE PATH
Write-Verbose -Message ('Template file path: [{0}]' -f $configPath)
}
else {
$configPath = $ConfigFile
}

# SET FILE NAME
$selectPattern = Get-Content -Path $configPath | Select-String -Pattern '^CN = (.+)$'
$fileName = $selectPattern.Matches.Groups[1].Value

# THE CHARACTER "*" IS NOT VALID IN A WINDOWS FILENAME. REPLACE "*" WITH "STAR"
if ($fileName -match '\*') { $fileName = $fileName.Replace('*', 'star') }
Write-Verbose -Message ('New file name: {0}' -f $fileName)

# SET OPENSSL PARAMETERS
# openssl req -new -newkey rsa:2048 -nodes -sha256 -out company_san.csr -keyout company_san.key -config req.conf
# USING THE "-legacy" PARAMETER WILL MAINTAIN COMPATABILITY WITH CERTAIN SERVERS THAT DO NOT YET SUPPORT
# THE LATEST CIPHERS OR PROTOCOLS
# EXAMPLE> openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365
# -newkey rsa:4096 and -sha256 are in the default template
$sslParams = @{
FilePath = 'openssl'
ArgumentList = @(
'req -new -x509 -nodes -days {0}' -f $Days
'-config {0}' -f $configPath
'-keyout {0}' -f (Join-Path -Path $OutputDirectory -ChildPath ('{0}_PRIVATE.key' -f $fileName))
'-out {0}' -f (Join-Path -Path $OutputDirectory -ChildPath ('{0}.pem' -f $fileName))
)
Wait = $true
NoNewWindow = $true
PassThru = $true
}

# SHOULD PROCESS
if ($PSCmdlet.ShouldProcess($OutputDirectory, "Create Files")) {

# GENERATE CERTIFICATE FILES USING OPENSSL
$proc = Start-Process @sslParams

# CHECK FOR ERRORS
if ($proc.ExitCode -NE 0) {
# OUTPUT ERROR
Write-Error -Message ('openssl failed with exit code: {0}' -f $proc.ExitCode)
}
}
Expand Down

0 comments on commit 3f54471

Please sign in to comment.