Skip to content

Instantly share code, notes, and snippets.

@joshcooper
Last active August 9, 2017 18:36
Show Gist options
  • Save joshcooper/9d6b4c7ebbd71347769d to your computer and use it in GitHub Desktop.
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
@Iristyle
Copy link

@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):

Function Download-File {

    # 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 Security.Cryptography.X509Certificates.X509Certificate2($cacert_file)

    Write-Host "Downloading" $url

    [Net.ServicePointManager]::ServerCertificateValidationCallback = {
        param(
            $sender, $certificate, $chain, $policyErrors
        )

        # match our cert thumbprints
        return $certifcate.GetCertHashString() == $cacert.GetCertHashString()
    }

    $webclient = New-Object Net.Webclient
    $webclient.DownloadFile($url, $file)
}

@joshcooper
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment