Skip to content

Commit a2380bf

Browse files
benbpazure-sdk
authored andcommitted
Make x509 certificate script from azure-sdk-for-net common to repos
1 parent 8ce656c commit a2380bf

File tree

3 files changed

+364
-0
lines changed

3 files changed

+364
-0
lines changed

eng/common/TestResources/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Below is an example of how `$templateFileParameters` can be used to pass data fr
8989

9090
**Snippet from `test-resources-pre.ps1`**
9191
```powershell
92+
Import-Module -Name ./eng/common/scripts/X509Certificate2
9293
$cert = New-X509Certificate2 -SubjectName '[email protected], CN=Azure SDK, OU=Azure SDK, O=Microsoft, L=Frisco, S=TX, C=US' -ValidDays 3652
9394
# Create new entries in $templateFileParameters
9495
$templateFileParameters['ConfidentialLedgerPrincipalPEM'] = Format-X509Certificate2 -Certificate $cert
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Powershell module for generating self-signed x509 certificates
2+
3+
Usage:
4+
5+
```powershell
6+
Import-Module -Name ./eng/common/scripts/X509Certificate2 # assumes $PWD is repo root
7+
8+
$cert1 = New-X509Certificate2 -SubjectName '[email protected], CN=Azure SDK, OU=Azure SDK, O=Microsoft, L=Redmond, S=WA, C=US' -ValidDays 3652
9+
10+
$CaPublicKeyBase64 = $cert1 | Format-X509Certificate2 -Type CertificateBase64
11+
$CaPrivateKeyPem = $cert1 | Format-X509Certificate2 -Type Pkcs1
12+
$CaKeyPairPkcs12Base64 = $cert1 | Format-X509Certificate2 -Type Pkcs12Base64
13+
```
14+
15+
With V3 extensions
16+
17+
```powershell
18+
Import-Module -Name eng/scripts/X509Certificate2.psm1 # assumes $PWD is repo root
19+
20+
$cert2 = New-X509Certificate2 -SubjectName 'CN=Azure SDK' -SubjectAlternativeNames (New-X509Certificate2SubjectAlternativeNames -EmailAddress [email protected]) -KeyUsageFlags KeyEncipherment, NonRepudiation, DigitalSignature -CA -TLS -ValidDays 3652
21+
22+
$PemCertificateWithV3Extensions = ($cert2 | Format-X509Certificate2 -Type Certificate) + "`n" + ($cert2 | Format-X509Certificate2 -Type Pkcs8)
23+
$CertificateWithV3ExtensionsBase64 = $cert2 | Format-X509Certificate2 -Type CertificateBase64
24+
```
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
#Requires -Version 6.0
2+
3+
using namespace System.Security.Cryptography
4+
using namespace System.Security.Cryptography.X509Certificates
5+
using namespace System.Text
6+
7+
<#
8+
.Synopsis
9+
Generate an X509 v3 certificate.
10+
11+
.Description
12+
Generates an [X509Certificate2] from either a subject name, or individual X500 distinguished names.
13+
14+
.Parameter SubjectName
15+
The entire X500 subject name.
16+
17+
.Parameter Country
18+
The country e.g., "US".
19+
20+
.Parameter State
21+
The state or province e.g., "WA".
22+
23+
.Parameter City
24+
The city or locality e.g., "Redmond".
25+
26+
.Parameter Organization
27+
The organization e.g., "Microsoft".
28+
29+
.Parameter Department
30+
The department e.g., "Azure SDK".
31+
32+
.Parameter CommonName
33+
A common name e.g., "www.microsoft.com".
34+
35+
.Parameter SubjectAlternativeNames
36+
Additional subject names from New-X509Certificate2SubjectAlternativeNames.
37+
38+
.Parameter KeySize
39+
Size of the RSA key.
40+
41+
.Parameter KeyUsageFlags
42+
Additional key usage flags.
43+
44+
.Parameter CA
45+
Create self-signed certificate authority.
46+
47+
.Parameter TLS
48+
Create self-signed certificate suitable for TLS.
49+
50+
.Parameter NotBefore
51+
The start date when the certificate is valid. The default is the current time.
52+
53+
.Parameter ValidDays
54+
How many days from NotBefore until the certificate expires.
55+
56+
.Example
57+
New-X509Certificate2 -SubjectName '[email protected], CN=Azure SDK, OU=Azure SDK, O=Microsoft, L=Redmond, S=WA, C=US' -ValidDays 3652
58+
59+
Create a self-signed certificate valid for 10 years from now.
60+
61+
.Example
62+
New-X509Certificate2 -SubjectName 'CN=Azure SDK' -SubjectAlternativeNames (New-X509Certificate2SubjectAlternativeNames -EmailAddress [email protected]) -KeyUsageFlags KeyEncipherment, NonRepudiation, DigitalSignature -CA -TLS -ValidDays 3652
63+
64+
Create a self-signed certificate valid for 10 years from now with an alternative name, additional key usages including TLS connections, and that can sign other certificate requests.
65+
#>
66+
function New-X509Certificate2 {
67+
[CmdletBinding(DefaultParameterSetName='SubjectName')]
68+
[OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
69+
param (
70+
[Parameter(ParameterSetName='SubjectName', Mandatory=$true, Position=0)]
71+
[string] $SubjectName,
72+
73+
[Parameter(ParameterSetName='Builder', HelpMessage='Country Name (2 letter code)')]
74+
[Alias('C')]
75+
[string] $Country,
76+
77+
[Parameter(ParameterSetName='Builder', HelpMessage='State or Province Name (full name)')]
78+
[Alias('S', 'Province')]
79+
[string] $State,
80+
81+
[Parameter(ParameterSetName='Builder', HelpMessage='Locality Name (eg, city)')]
82+
[Alias('L', 'Locality')]
83+
[string] $City,
84+
85+
[Parameter(ParameterSetName='Builder', HelpMessage='Organization Name (eg, company)')]
86+
[Alias('O')]
87+
[string] $Organization,
88+
89+
[Parameter(ParameterSetName='Builder', HelpMessage='Organizational Unit Name (eg, section)')]
90+
[Alias('OU')]
91+
[string] $Department,
92+
93+
[Parameter(ParameterSetName='Builder', HelpMessage='Common Name (e.g. server FQDN or YOUR name)')]
94+
[Alias('CN')]
95+
[string] $CommonName,
96+
97+
[Parameter()]
98+
[ValidateNotNull()]
99+
[SubjectAlternativeNameBuilder] $SubjectAlternativeNames,
100+
101+
[Parameter()]
102+
[ValidateSet(1024, 2048, 4096)]
103+
[int] $KeySize = 2048,
104+
105+
[Parameter()]
106+
[X509KeyUsageFlags] $KeyUsageFlags,
107+
108+
[Parameter()]
109+
[switch] $CA,
110+
111+
[Parameter()]
112+
[switch] $TLS,
113+
114+
[Parameter()]
115+
[ValidateNotNullOrEmpty()]
116+
[DateTimeOffset] $NotBefore = [DateTimeOffset]::Now,
117+
118+
[Parameter()]
119+
[ValidateRange(1, [int]::MaxValue)]
120+
[int] $ValidDays = 365
121+
)
122+
123+
if ($PSCmdlet.ParameterSetName -eq 'Builder') {
124+
$sb = [StringBuilder]::new()
125+
if ($Country) { $null = $sb.Append("C=$Country,") }
126+
if ($State) { $null = $sb.Append("S=$State, ") }
127+
if ($City) { $null = $sb.Append("L=$City, ") }
128+
if ($Organization) { $null = $sb.Append("O=$Organization, ") }
129+
if ($Department) { $null = $sb.Append("OU=$Department, ") }
130+
if ($CommonName) { $null = $sb.Append("CN=$CommonName, ") }
131+
132+
$SubjectName = [X500DistinguishedName]::new($sb.ToString()).Format($false)
133+
}
134+
135+
$rsa = [RSA]::Create($KeySize)
136+
try {
137+
$req = [CertificateRequest]::new(
138+
[string] $SubjectName,
139+
$rsa,
140+
[HashAlgorithmName]::SHA256,
141+
[RSASignaturePadding]::Pkcs1
142+
)
143+
144+
$req.CertificateExtensions.Add(
145+
[X509BasicConstraintsExtension]::new(
146+
$CA,
147+
$false,
148+
0,
149+
$true
150+
)
151+
)
152+
153+
if ($SubjectAlternativeNames) {
154+
$req.CertificateExtensions.Add(
155+
$SubjectAlternativeNames.Build($false)
156+
)
157+
}
158+
159+
if ($KeyUsageFlags) {
160+
$req.CertificateExtensions.Add(
161+
[X509KeyUsageExtension]::new(
162+
$KeyUsageFlags,
163+
$true
164+
)
165+
)
166+
}
167+
168+
if ($TLS) {
169+
$oids = [OidCollection]::new()
170+
$null = $oids.Add([Oid]::new('1.3.6.1.5.5.7.3.1'))
171+
172+
$req.CertificateExtensions.Add(
173+
[X509EnhancedKeyUsageExtension]::new(
174+
$oids,
175+
$false
176+
)
177+
)
178+
}
179+
180+
$req.CreateSelfSigned($NotBefore, $NotBefore.AddDays($ValidDays))
181+
}
182+
finally {
183+
$rsa.Dispose()
184+
}
185+
}
186+
187+
<#
188+
.Synopsis
189+
Create subject alternative names for New-X509Certificate2.
190+
191+
.Description
192+
Subject alternative names include DNS names, email addresses, and IP addresses for which a certificate may also authenticate connections.
193+
194+
.Parameter DnsName
195+
One or more DNS names e.g., "www.microsoft.com".
196+
197+
.Parameter EmailAddress
198+
One or more email addresses e.g., "[email protected]".
199+
200+
.Parameter IpAddress
201+
One or more IP addresses.
202+
203+
.Parameter Uri
204+
Additional URIs not covered by other options.
205+
206+
.Parameter UserPrincipalName
207+
Additional user names not covered by other options.
208+
#>
209+
function New-X509Certificate2SubjectAlternativeNames {
210+
[CmdletBinding()]
211+
[OutputType([System.Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder])]
212+
param (
213+
[Parameter()]
214+
[ValidateNotNullOrEmpty()]
215+
[string[]] $DnsName,
216+
217+
[Parameter()]
218+
[ValidateNotNullOrEmpty()]
219+
[string[]] $EmailAddress,
220+
221+
[Parameter()]
222+
[ValidateScript({[System.Net.IPAddress]::TryParse($_, [ref] $null)})]
223+
[string[]] $IpAddress,
224+
225+
[Parameter()]
226+
[ValidateScript({[System.Uri]::TryParse($_, [ref] $null)})]
227+
[string[]] $Uri,
228+
229+
[Parameter()]
230+
[ValidateNotNullOrEmpty()]
231+
[string[]] $UserPrincipalName
232+
)
233+
234+
$subjectAlternativeNames = [SubjectAlternativeNameBuilder]::new()
235+
236+
foreach ($value in $DnsName) {
237+
$subjectAlternativeNames.AddDnsName($value)
238+
}
239+
240+
foreach ($value in $EmailAddress) {
241+
$subjectAlternativeNames.AddEmailAddress($value)
242+
}
243+
244+
foreach ($value in $IpAddress) {
245+
$subjectAlternativeNames.AddIpAddress($value)
246+
}
247+
248+
foreach ($value in $Uri) {
249+
$subjectAlternativeNames.AddUri($value)
250+
}
251+
252+
foreach ($value in $UserPrincipalName) {
253+
$subjectAlternativeNames.AddUserPrincipalName($value)
254+
}
255+
256+
$subjectAlternativeNames
257+
}
258+
259+
<#
260+
.Synopsis
261+
Exports a certificate to a file.
262+
263+
.Description
264+
Exports an X509Certificate2 to a file in one of the given formats.
265+
266+
.Parameter Path
267+
The path to the file to save.
268+
269+
.Parameter Type
270+
The type of encoding for the file to save.
271+
272+
.Parameter Certificate
273+
The certificate to save.
274+
#>
275+
function Export-X509Certificate2 {
276+
[CmdletBinding()]
277+
param (
278+
[Parameter(Mandatory=$true, Position=0)]
279+
[string] $Path,
280+
281+
[Parameter(Position=1)]
282+
[ValidateSet('Certificate', 'CertificateBase64', 'Pfx', 'Pkcs1', 'Pkcs12', 'Pkcs12Base64', 'Pkcs8', 'PrivateKey')]
283+
[string] $Type = 'Pfx',
284+
285+
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
286+
[X509Certificate2] $Certificate
287+
)
288+
289+
if ($Type -in 'Pfx', 'Pkcs12') {
290+
$Certificate.Export([X509ContentType]::Pfx) | Set-Content $Path -AsByteStream
291+
} else {
292+
Format-X509Certificate2 -Type $Type -Certificate $Certificate | Set-Content $Path -Encoding ASCII
293+
}
294+
}
295+
296+
<#
297+
.Synopsis
298+
Formats a certificate.
299+
300+
.Description
301+
Formats a certificate and prints it to the output buffer e.g., console. Useful for piping to clip.exe in Windows and pasting into code (additional formatting may be required).
302+
303+
.Parameter Type
304+
The type of encoding for the output.
305+
306+
.Parameter Certificate
307+
The certificate to format.
308+
#>
309+
function Format-X509Certificate2 {
310+
[CmdletBinding()]
311+
param (
312+
[Parameter(Position=0)]
313+
[ValidateSet('Certificate', 'CertificateBase64', 'Pkcs1', 'Pkcs12Base64', 'Pkcs8', 'PrivateKey')]
314+
[string] $Type = 'Certificate',
315+
316+
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
317+
[X509Certificate2] $Certificate
318+
)
319+
320+
function ConvertTo-Pem($tag, $data) {
321+
@"
322+
-----BEGIN $tag-----
323+
$([Convert]::ToBase64String($data, 'InsertLineBreaks'))
324+
-----END $tag-----
325+
"@
326+
}
327+
328+
if ($Type -eq 'Certificate') {
329+
ConvertTo-Pem 'CERTIFICATE' $Certificate.RawData
330+
} elseif ($Type -eq 'CertificateBase64') {
331+
[Convert]::ToBase64String($Certificate.RawData, 'InsertLineBreaks')
332+
} elseif ($Type -eq 'Pkcs1') {
333+
ConvertTo-Pem 'RSA PRIVATE KEY' $Certificate.PrivateKey.ExportRSAPrivateKey()
334+
} elseif ($Type -eq 'Pkcs12Base64') {
335+
[Convert]::ToBase64String($Certificate.Export([X509ContentType]::Pfx), 'InsertLineBreaks')
336+
} elseif ($Type -in 'Pkcs8', 'PrivateKey') {
337+
ConvertTo-Pem 'PRIVATE KEY' $Certificate.PrivateKey.ExportPkcs8PrivateKey()
338+
}
339+
}

0 commit comments

Comments
 (0)