-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path27_CertChainValidation.ps1
More file actions
100 lines (86 loc) · 3.71 KB
/
27_CertChainValidation.ps1
File metadata and controls
100 lines (86 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<#
.SYNOPSIS
OOP Reference: Certificate Chain Validation
.DESCRIPTION
Topic: X.509 chain building, thumbprint pinning, revocation modes
Category: Advanced Crypto
Agent Task: Add a ValidateFromFile([string]$certPath) convenience method.
Add a static Development() factory that skips revocation + allows untrusted root.
Add Pester tests (use self-signed cert generated via New-SelfSignedCertificate
on Windows or openssl on Linux for test fixtures).
Done Conditions:
- Strict() mode catches expired certs
- Development() mode accepts self-signed without error
- Pester tests pass: Invoke-Pester -Output Detailed
Non-Scope:
- No OCSP stapling
- No CRL distribution point fetching in unit tests (use NoCheck revocation mode)
#>
class CertificateValidator {
[bool]$AllowUntrustedRoot = $false
[bool]$CheckRevocation = $true
[datetime]$ValidAt = [datetime]::UtcNow
[System.Collections.Generic.List[string]]$TrustedThumbprints
CertificateValidator() {
$this.TrustedThumbprints = [System.Collections.Generic.List[string]]::new()
}
[hashtable] Validate([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert) {
$result = @{
IsValid = $false
Errors = [System.Collections.Generic.List[string]]::new()
ChainInfo = $null
}
if ($this.ValidAt -lt $cert.NotBefore -or $this.ValidAt -gt $cert.NotAfter) {
$result.Errors.Add('Certificate expired or not yet valid')
}
$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
$chain.ChainPolicy.RevocationMode = $this.CheckRevocation ?
[System.Security.Cryptography.X509Certificates.X509RevocationMode]::Online :
[System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
if ($this.AllowUntrustedRoot) {
$chain.ChainPolicy.VerificationFlags =
[System.Security.Cryptography.X509Certificates.X509VerificationFlags]::AllowUnknownCertificateAuthority
}
$chainValid = $chain.Build($cert)
$result.ChainInfo = $chain.ChainElements | Select-Object `
@{N='Subject';E={$_.Certificate.Subject}},
@{N='Thumbprint';E={$_.Certificate.Thumbprint}},
@{N='Expires';E={$_.Certificate.NotAfter}}
if (-not $chainValid) {
foreach ($s in $chain.ChainStatus) {
$result.Errors.Add("Chain: $($s.StatusInformation.Trim())")
}
}
if ($this.TrustedThumbprints.Count -gt 0 -and
-not $this.TrustedThumbprints.Contains($cert.Thumbprint)) {
$result.Errors.Add('Certificate thumbprint not in pinned set')
}
$result.IsValid = ($result.Errors.Count -eq 0 -and $chainValid)
$chain.Dispose()
return $result
}
[hashtable] ValidateFromFile([string]$certPath) {
if ([string]::IsNullOrWhiteSpace($certPath)) {
throw [System.ArgumentException]::new('certPath cannot be null or empty', 'certPath')
}
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($certPath)
try {
return $this.Validate($cert)
}
finally {
$cert.Dispose()
}
}
static [CertificateValidator] Strict() {
$v = [CertificateValidator]::new()
$v.CheckRevocation = $true
$v.AllowUntrustedRoot = $false
return $v
}
static [CertificateValidator] Development() {
$v = [CertificateValidator]::new()
$v.CheckRevocation = $false
$v.AllowUntrustedRoot = $true
return $v
}
}