-
-
Save joshcooper/9d6b4c7ebbd71347769d to your computer and use it in GitHub Desktop.
$verifyCallback = { | |
param( | |
$sender, $certificate, $chain, $policyErrors | |
) | |
$verified = $true | |
switch($policyErrors) { | |
None { | |
Write-Host "Verified Cert:" $certificate.SubjectName.Name | |
} | |
RemoteCertificateChainErrors { | |
foreach ($status in $chain.ChainStatus) { | |
switch ($status.Status) { | |
NoError { | |
Write-Host "Verified Cert Chain" | |
} | |
UntrustedRoot { | |
Write-Host "Verifying Cert Chain:" | |
foreach ($element in $chain.ChainElements) { | |
$cert = $element.Certificate | |
Write-Host " " $cert.SubjectName.Name "[" $cert.Thumbprint "]" | |
} | |
$idx = $chain.ChainElements.Count - 1 | |
$root = $chain.ChainElements[$idx].Certificate | |
if ($root.Equals($cacert)) { | |
Write-Host "Root is trusted:" $root.SubjectName.Name | |
} | |
else | |
{ | |
Write-Host "Root is not trusted:" $root.SubjectName.Name | |
$verified = $false | |
} | |
} | |
default { | |
Write-Host "Problem with cert chain [" $status.Status "] " $status.StatusInformation | |
$verified = $false | |
} | |
} | |
} | |
} | |
default { | |
Write-Host "Verification failed " $sslPolicyError | |
$verified = $false | |
} | |
} | |
return $verified | |
} | |
Function Download-File { | |
Param() | |
# using endpoint because it doesn't require client certs | |
$url = 'https://arcturus.corp.puppetlabs.net:8140/puppet-ca/v1/certificate/ca' | |
$file = '.\index.html' | |
$cacert_file = ".\ca.pem" | |
$cacert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($cacert_file) | |
Write-Host "Downloading" $url | |
[Net.ServicePointManager]::ServerCertificateValidationCallback = $verifyCallback | |
$webclient = New-Object system.net.webclient | |
$webclient.DownloadFile($url, $file) | |
} | |
Download-File |
Defaulting to false sounds good.
About the other part, I would expect $certificate
to be the server's certificate, which I wouldn't expect to ever match the cacert
. I think you need to always compare the root of the chain ($root = $chain.ChainElements[$idx].Certificate
) with the $cacert
.
Also there can be other errors associated with the chain, e.g. expired ca, intermediate, or server cert, revoked certs (not sure if powershell automatically checks those against a system-wide CRL), the root cert may not actually be trusted to be used as a CA, e.g. basicConstraints=CA:false
, etc. I'm not entirely sure which are reported as $policyErrors
, and which are reported when walking the $chain.ChainStatus
. So that's why I was handling both, and making sure we only verify the connection if there are no policyErrors
, or we get a RemoteCertificateChainErrors
policy error, and there is only a single UntrustedRoot
status and the root cert matches.
@joshcooper - I know this was quick'n'dirty, but in your example I think you want to rework the code to start with a secure default of
$verified = false
inside the callback.I've done the same general thing in the past, but I've taken a shorter approach given a whitelisted cert. I believe you should be able to simplify the approach to something like this (didn't verify locally since I don't have a running SG master handy):