Skip to content

Instantly share code, notes, and snippets.

@mhudasch
Last active February 9, 2022 12:27
Show Gist options
  • Save mhudasch/696ff53140dd7c5ce13687e2a7395beb to your computer and use it in GitHub Desktop.
Save mhudasch/696ff53140dd7c5ce13687e2a7395beb to your computer and use it in GitHub Desktop.
Exports trusted root CAs to programs that do not use the Windows Certificate Store
#Requires -Version 5
#Requires -PSEdition Desktop
function Get-RootCAFromUrl {
[CmdletBinding()]
param(
# The uri of the website with the server certificate
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[uri]
[Alias("WebSite")]
$Url
)
$requestCertificate = $null;
$currentSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol;
[Net.ServicePointManager]::SecurityProtocol = ($currentSecurityProtocol -bor
[Net.SecurityProtocolType]::Tls11 -bor
[Net.SecurityProtocolType]::Tls12)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [scriptblock] {
param(
$validationSender,
$validationX509Cert,
$validationX509Chain,
$validationSSLPolicyErrors
)
$ErrorActionPreference = "Stop";
$certificate2 = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($validationX509Cert);
if($null -eq ("System.IdentityModel.Selectors.X509CertificateValidator" -as [Type])) {
# certificate validator type is not known here
Write-Verbose -Message "X509CertificateValidator type is unknown. Assembly gets loaded.";
Add-Type -AssemblyName "System.IdentityModel" | Out-Null;
}
try {
[System.IdentityModel.Selectors.X509CertificateValidator]::PeerOrChainTrust.Validate($certificate2);
}
catch [System.IdentityModel.Tokens.SecurityTokenValidationException] {
Write-Verbose -Message "The server certificate of url: '$Url' ($($certificate2.Issuer)) was not trustworthy.";
Write-Debug -Message $Error[0].Exception.Message;
return $false;
}
$validationX509ChainRoot = @($validationX509Chain.ChainElements) | Select-Object -Last 1;
$rootCertificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($validationX509ChainRoot.Certificate)
Set-Variable -Name "requestCertificate" -Scope 1 -Value $rootCertificate;
$x509Issuer = "# Issuer: $($rootCertificate.Issuer)";
$x509Subject = "# Subject: $($rootCertificate.Subject)";
$x509Label = "# Label: `"$($rootCertificate.GetNameInfo("SimpleName", $false))`"";
$x509Serial = "# Serial: $($rootCertificate.SerialNumber)";
Write-Verbose -Message $(@("--Root Certificate--", $x509Issuer, $x509Subject, $x509Label, $x509Serial) -join "`n")
return $certificate2.Verify() -and $rootCertificate.Verify();
};
try {
$retryCount = 0;
$ex = $null;
while($retryCount -lt 5) {
try {
$request = [Net.HttpWebRequest]::Create($Url);
$request.GetResponse() | Out-Null;
break;
}
catch [System.Net.WebException] {
$ex = $_.Exception;
Write-Verbose -Message "Request failed. Reason: $($Error[0].Exception.Message)";
if($null -ne $requestCertificate) {
Write-Verbose -Message "We still could aquire the certificate we need!";
break;
}
$retryCount++;
if($retryCount -gt 5) {
break;
} else {
continue;
}
}
}
if (-not($requestCertificate)) {
throw "The given web site at url: '$Url' did not have a server certificate. Or the web request did not end in success state. ($($ex.Message))";
}
} finally {
# remove global cert validation callback
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null;
# revert back to security protocol defined earlier
[Net.ServicePointManager]::SecurityProtocol = $currentSecurityProtocol;
}
$x509 = $requestCertificate;
$bytes = $x509.Export("Cert");
$base64 = [Convert]::ToBase64String($bytes, "None");
$chunkSize = 64;
$base64WithLineBreaks = "";
# split the base64 string into chunks of 64 characters
# we do not use ToBase64String($bytes, "InsertLineBreaks") on purpose
# because it creates lines that are longer than 64 characters
for ($i = 0; $i -lt $base64.Length; $i += $chunkSize) {
$base64WithLineBreaks = $base64WithLineBreaks + $($base64.Substring($i, [Math]::Min($chunkSize, $base64.Length - $i)) + ([System.Environment]::NewLine));
}
$pem = "-----BEGIN CERTIFICATE-----$([System.Environment]::NewLine)$base64WithLineBreaks-----END CERTIFICATE-----";
$pem | Write-Output;
}
function Export-RootCA {
[CmdletBinding()]
param (
# The certificate to trust in base64 format (pem)
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[String]
[ValidateNotNullOrEmpty()]
[Alias("Base64", "Pem", "Certificate")]
$Base64Certificate,
# The target program that cannot access the Windows Certificate Store.
[Parameter(Mandatory = $true)]
[ValidateSet("Git", "Curl", "Rider", "WebStorm", "Java", "Node", "NPM", "AzureCli", "All")]
[String[]]
$Target
)
$principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent());
if (-not($principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))) {
throw "This command must be executed in elevated mode. Please start a command prompt as an administrator and try again." +
" The elevated mode is needed to access the several certificate stores.";
}
$javaBasedTooling = [scriptblock] {
param($jh, $cert)
if (-not(Test-Path -Path $jh)) {
Write-Error -Message "Could not locate a JVM installation under '$jh'.";
}
$certificateTempFolder = $([environment]::GetFolderPath('UserProfile'));
$jreBinFolder = Get-ChildItem -Path $jh -Filter "bin" -Directory -ErrorAction "Ignore";
if (-not(Test-Path -Path "$($jreBinFolder.FullName)\keytool.exe")) {
Write-Verbose -Message "Cannot find keytool in path '$($jreBinFolder.FullName)'.";
return;
}
$jreKeyStore = Get-ChildItem -Path $jh -Filter "lib/security/cacerts" -File -ErrorAction "Ignore" | Select-Object -ExpandProperty "FullName";
if (-not($jreKeyStore)) {
Write-Verbose -Message "Could not find certificate store for trusted ca certificates.";
return;
}
$base64Raw = (($cert -ireplace "-----BEGIN CERTIFICATE-----", "") -ireplace "-----END CERTIFICATE-----", "") -ireplace "`r?`n", ""
$x509 = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(, ([System.Convert]::FromBase64String($base64Raw)));
$certificatePath = [IO.Path]::Combine($certificateTempFolder, "temp.cer");
$certCN = [System.Text.RegularExpressions.RegEx]::Match($x509.Subject, "CN\=(.+?),").Groups[1].Value;
$certVersion = $x509.Version;
if (-not($certCN)) {
Write-Verbose -Message "The given certificate has not valid cn.";
return;
}
$certAlias = $certCN.Replace(" ", "").ToLowerInvariant() + $certVersion.ToString();
$x509Bytes = $x509.Export("Cert");
if (Test-Path -Path $certificatePath) {
Remove-Item -Path $certificatePath -Force -ErrorAction "Ignore" | Out-Null;
}
[io.File]::WriteAllBytes($certificatePath, $([byte[]]$x509Bytes));
try {
$keyToolQueryCommand = "`"$($jreBinFolder.FullName)\keytool.exe`" -list -rfc -keystore `"$jreKeyStore`" -storepass changeit -noprompt";
Write-Verbose -Message "> $keyToolQueryCommand";
$keyToolQueryResult = (& cmd /s /c " $keyToolQueryCommand " *>&1) | Out-string;
Write-Debug -Message $keyToolQueryResult;
if (-not($keyToolQueryResult) -or $keyToolQueryResult -imatch "keytool error\:") {
Write-Verbose -Message "keytool query failed.";
return;
}
if ($keyToolQueryResult -imatch "Alias name\: $certAlias") {
Write-Verbose -Message "The keystore already contains the certificate you are trying to import.";
return;
}
$keyToolImportCommand = "`"$($jreBinFolder.FullName)\keytool.exe`" -import -trustcacerts -file `"$certificatePath`" -keystore `"$jreKeyStore`" -storepass changeit -noprompt -alias `"$certAlias`" "
Write-Verbose -Message "> $keyToolImportCommand";
$keyToolImportResult = (& cmd /s /c " $keyToolImportCommand " *>&1) | Out-String;
Write-Verbose -Message $keyToolImportResult;
if (-not($keyToolImportResult) -or $keyToolImportResult -imatch "keytool error\:") {
Write-Verbose -Message "keytool import failed.";
return;
}
}
finally {
Remove-Item -Path $certificatePath -Force -ErrorAction "Ignore" | Out-Null;
}
}
$jetbrainsTooling = [scriptblock] {
param($exec, $cert)
Write-Host "Locating all $exec installations (this could take several minutes)...";
$toolExecutables = @(Get-ChildItem -Path "C:\" -File -Filter $exec -Recurse -Force -ErrorAction "Ignore");
Write-Host "Found $($toolExecutables.Count) installations of $exec";
if (0 -eq $toolExecutables.Count) {
return;
}
$toolExecutables | ForEach-Object { $_ | Write-Host }
foreach ($toolExecutable in $toolExecutables) {
Write-Verbose -Message "Try to locate the JVM used by $exec installation at $($toolExecutable.FullName)";
$toolBinFolder = Split-Path $toolExecutable.FullName -Parent;
$toolRootFolder = Split-Path $toolBinFolder -Parent;
$jreRootFolder = Get-ChildItem -Path $toolRootFolder -Filter "jbr" -Directory -ErrorAction "Ignore";
if (-not($jreRootFolder)) {
Write-Verbose -Message "Could not locate the JVM for the '$toolExecutable' installation.";
continue;
}
$javaBasedTooling.InvokeReturnAsIs($jreRootFolder.FullName, $cert);
}
}
$locateGitCAStore = [scriptblock] {
if (-not(Get-Command -Name git -ErrorAction "Ignore")) {
Write-Warning "Git is not installed on the computer or is not available as command." +
" If it is installed please add it to the PATH environment variable of the user.";
return $null;
}
$gitConfiguredRootCA = (git config --get http.sslcainfo);
$gitConfiguredSslBackend = (git config --get http.sslbackend);
if ("schannel" -ieq $gitConfiguredSslBackend) {
$gitExecutableLocation = Get-Command -Name git | Select-Object -ExpandProperty Definition;
if (Test-Path -Path $gitExecutableLocation) {
$gitInstallRoot = Split-Path -Path (Split-Path -Path $gitExecutableLocation -Parent) -Parent;
$minwCerts = [io.Path]::Combine($gitInstallRoot, "mingw64", "ssl", "certs", "ca-bundle.crt");
if (Test-Path -Path $minwCerts) {
git config --system http.sslcainfo "$minwCerts";
$gitConfiguredRootCA = (git config --get http.sslcainfo);
}
}
throw "Setting a ssl ca info location is not possible when the 'http.sslbackend' is configured for 'schannel'. " +
"Please change the value to 'openssl' to go on. The command to change the setting is 'git config http.sslbackend openssl'. " +
"To change the setting globally add the '--global' parameter.";
}
if (-not($gitConfiguredRootCA)) {
Write-Warning -Message "The current git ssl ca info location is not configured.";
return $null;
}
if (-not(Test-Path -Path $gitConfiguredRootCA)) {
Write-Warning -Message "The current git ssl ca info location configured but does not exist.";
Write-Warning -Message "Creating a new ssl ca info file under '$gitConfiguredRootCA'.";
New-Item -ItemType File -Path $gitConfiguredRootCA -ErrorAction "Ignore" | Out-Null;
if (-not(Test-Path -Path $gitConfiguredRootCA)) {
Write-Verbose -Message "Could not create a new ssl ca info file under '$gitConfiguredRootCA'.";
return $null;
}
}
return $gitConfiguredRootCA;
};
$locateCurlCAStore = [scriptblock] {
param($curlProxy)
if ($env:CURL_CA_BUNDLE -and (Test-Path -Path $env:CURL_CA_BUNDLE)) {
Write-Verbose -Message "A valid ca store for curl is saved under '$env:CURL_CA_BUNDLE' (from CURL_CA_BUNDLE environment variable).";
return $env:CURL_CA_BUNDLE;
}
if ($env:REQUESTS_CA_BUNDLE -and (Test-Path -Path $env:REQUESTS_CA_BUNDLE)) {
Write-Verbose -Message "A valid ca store for curl is saved under '$env:REQUESTS_CA_BUNDLE' (from REQUESTS_CA_BUNDLE environment variable).";
return $env:REQUESTS_CA_BUNDLE;
}
if (-not(Get-Command -Name curl -ErrorAction "Ignore")) {
Write-Warning -Message "Curl is not installed on the computer or is not available as command." +
" If it is installed please add it to the PATH environment variable of the user.";
Write-Warning -Message "No separate curl installation found so libcurl is likely in use.";
return $null;
}
$curlExecutable = Get-Command -Name "curl" | Select-Object -ExpandProperty "Definition";
$curlCAQueryCommand = " `"$curlExecutable`" --cacert nonexistent https://www.github.com";
Write-Verbose -Message "> $curlCAQueryCommand";
$curlResult = (& cmd.exe /s /c " `"$curlExecutable`" --cacert nonexistent https://www.github.com" *>&1) | Out-String
Write-Debug -Message $curlResult;
if ($curlResult -and $curlResult -imatch "Could not resolve host") {
if (-not($curlProxy)) {
Write-Verbose -Message "Curl could not reach github and no proxy was given.";
return $null;
}
# missing proxy
Write-Verbose -Message "Host could not be resolved. This is likely caused by a non-defined proxy.";
$curlCAQueryCommand = " `"$curlExecutable`" --cacert nonexistent https://www.github.com --proxy $curlProxy --verbose";
Write-Verbose -Message "> $curlCAQueryCommand";
$curlResult = (& cmd.exe /s /c " `"$curlExecutable`" --cacert nonexistent https://www.github.com --proxy $curlProxy --verbose" *>&1) | Out-String;
Write-Debug -Message $curlResult;
}
if ($curlResult -and $curlResult -imatch "CApath\:\s*none") {
Write-Verbose -Message "The current curl trusted root ca store location is not configured.";
return $null;
}
elseif ($curlResult -and $curlResult -imatch "(?i)CApath\:\s*(?<crtPath>.*?)") {
$crtFileMatch = [System.Text.RegularExpressions.Regex]::Match($curlResult, "(?i)CApath\:\s*(?<crtPath>.*?)");
if (-not($crtFileMatch.Success)) {
throw "Curl executable could not communicate the correct path of its CApath variable.";
}
return $crtFileMatch.Groups["crtPath"].Value;
}
return $null;
};
$locateNodeCAStore = [scriptblock] {
if (-not(Get-Command -Name node -ErrorAction "Ignore")) {
Write-Warning -Message "Node is not installed on the computer or is not available as command." +
" If it is installed please add it to the PATH environment variable of the user.";
return $null;
}
if ($env:NODE_EXTRA_CA_CERTS -and (Test-Path -Path $env:NODE_EXTRA_CA_CERTS)) {
return $env:NODE_EXTRA_CA_CERTS;
}
return $null;
}
$locateAzureCliCAStore = [scriptblock] {
if(-not(Get-Command -Name az -ErrorAction "Ignore")) {
Write-Warning -Message "The Azure CLI is not installed on the computer or is not available as command." +
" If it is installed pleasse add it to PATH environment variable of the user.";
return $null;
}
$azExecutable = Get-Command -Name az | Select-Object -ExpandProperty "Definition";
$certifiLibPemFilePath = [io.path]::Combine($([io.path]::GetDirectoryName($azExecutable)), "../Lib/site-packages/certifi/cacert.pem");
if(-not(Test-Path -Path $certifiLibPemFilePath)) {
throw "The cacert.pem file used in the Azure CLI cannot be found at the expected location.";
}
return $certifiLibPemFilePath;
}
$locateNpmCAStore = [scriptblock] {
if (-not(Get-Command -Name npm -ErrorAction "Ignore")) {
Write-Warning -Message "NPM is not installed on the computer or is not available as command." +
" If it is installed please add it to the PATH environment variable of the user.";
return $null;
}
$npmConfiguration = (npm config ls -l);
$configuredCAFileMatch = [System.Text.RegularExpressions.Regex]::Match($npmConfiguration, '(?i)cafile\s*\=\s*\"(?<caFile>.*?)\"');
if(-not($configuredCAFileMatch.Success)) {
return $null
}
return $configuredCAFileMatch.Groups["caFile"].Value;
}
$createNewCAStore = [scriptblock] {
$storePath = [io.path]::Combine("$([environment]::GetFolderPath('UserProfile'))", "ssl", "ca_certs.crt");
Write-Host "Creating new trusted root CA store at '$storePath'.";
New-Item -Path $([io.path]::Combine("$([environment]::GetFolderPath('UserProfile'))", "ssl")) -ItemType Directory -Force -ErrorAction Stop | Out-Null;
Set-Content -Path $([io.path]::Combine("$([environment]::GetFolderPath('UserProfile'))", "ssl", "ca_certs.crt")) -Value "" -Encoding utf8;
Write-Host "Done creating new trusted root CA store at '$storePath'.";
return $storePath;
}
$crtContainsPem = [scriptblock] {
param($crtFilePath, $pemCertificate)
$crtFileContent = Get-Content -Path $crtFilePath -Raw;
$pemMatch = [System.Text.RegularExpressions.Regex]::Match($pemCertificate, "-+BEGIN CERTIFICATE-+\r?\n((?:.+\r?\n)+?)-+END CERTIFICATE-+");
$certificateMatches = [System.Text.RegularExpressions.Regex]::Matches($crtFileContent, "-+BEGIN CERTIFICATE-+\n((?:.+\n)+?)-+END CERTIFICATE-+");
if (-not($pemMatch.Success) -or 0 -eq $certificateMatches.Count) {
return $false;
}
foreach ($certificateMatch in $certificateMatches) {
$cleanedCertificateMatch = $certificateMatch.Groups[1].Value -ireplace "`r?`n", "";
$cleanedPemMatch = $pemMatch.Groups[1].Value -ireplace "`r?`n", "";
if ($cleanedCertificateMatch -eq $cleanedPemMatch) {
return $true
}
}
return $false;
}
$beautifyCert = [scriptblock] {
param($base64Cert)
$base64Raw = (($base64Cert -ireplace "-----BEGIN CERTIFICATE-----", "") -ireplace "-----END CERTIFICATE-----", "") -ireplace "`r?`n", ""
$base64RawBytes = [System.Convert]::FromBase64String($base64Raw);
$x509 = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(, $base64RawBytes);
$x509Issuer = "# Issuer: $($x509.Issuer)";
$x509Subject = "# Subject: $($x509.Subject)";
$x509Label = "# Label: `"$($x509.GetNameInfo("SimpleName", $false))`"";
$x509Serial = "# Serial: $($x509.SerialNumber)";
$x509Md5 = "# MD5 Fingerprint: $( (@([System.Security.Cryptography.MD5]::Create().ComputeHash($base64RawBytes)) | ForEach-Object { $_.ToString('x2') }) -join ':' )";
$x509SHA1 = "# SHA1 Fingerprint: $( (@([System.Security.Cryptography.SHA1]::Create().ComputeHash($base64RawBytes)) | ForEach-Object { $_.ToString('x2') }) -join ':' )";
$x509SHA256 = "# SHA256 Fingerprint: $( (@([System.Security.Cryptography.SHA256]::Create().ComputeHash($base64RawBytes)) | ForEach-Object { $_.ToString('x2') }) -join ':' )";
(@($x509Issuer, $x509Subject, $x509Label, $x509Serial, $x509Md5, $x509SHA1, $x509SHA256) -join "`n") + "`n" + ($Base64Certificate -ireplace "`r?`n", "`n") | Write-Output;
}
if ($Target -icontains "all") {
$Target = @("Rider", "WebStorm", "Java", "Git", "Curl", "Node", "NPM");
}
foreach ($exportTarget in @($Target | Select-Object -Unique)) {
if ("Rider" -ieq $exportTarget) {
$jetbrainsTooling.InvokeReturnAsIs(@("rider64.exe", $Base64Certificate)) | Write-Output;
}
if ("WebStorm" -ieq $exportTarget) {
$jetbrainsTooling.InvokeReturnAsIs(@("webstorm64.exe", $Base64Certificate)) | Write-Output;
}
if ("Java" -ieq $exportTarget) {
$javaHome = $env:JAVA_HOME;
if (-not($javaHome)) {
# when there is no globally defined java runtime configured
# we take newest installed version
$probe = @(Get-ChildItem -Path ([io.Path]::Combine($env:ProgramFiles, "Java")) -Recurse -Filter "java.exe" -ErrorAction "Ignore");
if (-not($probe)) {
Write-Verbose -Message "Could not find a 64 bit installation of java.";
$probe = @(Get-ChildItem -Path ([io.Path]::Combine(${env:ProgramFiles(x86)}, "Java")) -Recurse -Filter "java.exe" -ErrorAction "Ignore");
if (-not($probe)) {
Write-Warning -Message "No installation of java found on this machine.";
return;
}
}
$newestVersion = $probe | Sort-Object -Property "DirectoryName" -Descending | Select-Object -First 1;
$javaHome = Split-Path -Path $(Split-Path -Path $newestVersion -Parent) -Parent;
}
$is32BitVersionHome = ($javaHome -match [System.Text.RegularExpressions.Regex]::Escape(${env:ProgramFiles(x86)}) -or
$javaHome -match [System.Text.RegularExpressions.Regex]::Escape("Progra~2"));
$is64BitVersionHome = ($javaHome -match [System.Text.RegularExpressions.Regex]::Escape($env:ProgramFiles) -or
$javaHome -match [System.Text.RegularExpressions.Regex]::Escape("Progra~1"));
if ($is32BitVersionHome) {
$32BitJavaHome = $javaHome;
$64BitJavaHome = [System.Text.RegularExpressions.Regex]::Replace($javaHome,
$([System.Text.RegularExpressions.Regex]::Escape(${env:ProgramFiles(x86)}) + "|" +
[System.Text.RegularExpressions.Regex]::Escape("Progra~2")),
$env:ProgramFiles);
}
if ($is64BitVersionHome) {
$32BitJavaHome = [System.Text.RegularExpressions.Regex]::Replace($javaHome,
$([System.Text.RegularExpressions.Regex]::Escape($env:ProgramFiles) + "|" +
[System.Text.RegularExpressions.Regex]::Escape("Progra~1")),
${env:ProgramFiles(x86)});
$64BitJavaHome = $javaHome;
}
if ($32BitJavaHome -and (Test-Path -Path $32BitJavaHome -ErrorAction "Ignore")) {
$javaBasedTooling.InvokeReturnAsIs($32BitJavaHome, $Base64Certificate);
}
if ($64BitJavaHome -and (Test-Path -Path $64BitJavaHome -ErrorAction "Ignore")) {
$javaBasedTooling.InvokeReturnAsIs($64BitJavaHome, $Base64Certificate);
}
}
if ("Git" -ieq $exportTarget) {
Write-Verbose -Message "Get the git ssl ca info location.";
$gitSslCrt = $locateGitCAStore.InvokeReturnAsIs();
if (-not($gitSslCrt)) {
Write-Warning -Message "The current git ssl ca info location is not known.";
# Creating one at '$home/ssl/ca_certs.crt'.";
$gitSslCrt = $createNewCAStore.InvokeReturnAsIs();
git config --global http.sslcainfo $gitSslCrt
}
Write-Host "The location of the git ssl ca info is '$gitSslCrt'.";
if ((-not($crtContainsPem.InvokeReturnAsIs(@($gitSslCrt, $Base64Certificate))))) {
$beautifyCert.InvokeReturnAsIs($Base64Certificate) | Add-Content -Path $gitSslCrt -Encoding utf8 -ErrorAction Stop;
Write-Host "Saved certificate in the crt file '$crtFile'.";
}
else {
Write-Host "The git ssl ca info store already contains the given certificate.";
}
}
if ("Curl" -ieq $exportTarget) {
$curlHttpProxy = if ($env:HTTPS_PROXY) { $env:HTTPS_PROXY } else { "http://localhost:9000" }
$crtFile = $locateCurlCAStore.InvokeReturnAsIs(@($curlHttpProxy));
if (-not($crtFile)) {
# fall back on the git configured trusted ca store file
$gitSslCrt = $locateGitCAStore.InvokeReturnAsIs();
if (-not($gitSslCrt)) {
# cannot fall back on git so we create a custom one
Write-Warning -Message "Cannot use git ca_certs so we create a new one and use it.";
$crtFile = $createNewCAStore.InvokeReturnAsIs();
}
else {
Write-Host "Using the ca_certs.crt of git as the one for curl.";
$crtFile = $gitSslCrt;
}
}
if (-not($crtContainsPem.InvokeReturnAsIs(@($crtFile, $Base64Certificate)))) {
$beautifyCert.InvokeReturnAsIs($Base64Certificate) | Add-Content -Path $crtFile -Encoding utf8 -ErrorAction Stop;
Write-Host "Saved certificate in the crt file '$crtFile'.";
}
else {
Write-Host "The curl trusted root ca store already contains the given certificate.";
}
if (-not($env:CURL_CA_BUNDLE) -or ($env:CURL_CA_BUNDLE -ne $crtFile)) {
[System.Environment]::SetEnvironmentVariable("CURL_CA_BUNDLE", ($crtFile -replace "\/", "\"), "User");
Write-Verbose -Message "Set the curl trusted root ca store '$crtFile' as new CURL_CA_BUNDLE in user environment variables.";
}
if (-not($env:REQUESTS_CA_BUNDLE) -or ($env:REQUESTS_CA_BUNDLE -ne $crtFile)) {
[System.Environment]::SetEnvironmentVariable("REQUESTS_CA_BUNDLE", ($crtFile -replace "\/", "\"), "User");
Write-Verbose -Message "Set the curl trusted root ca store '$crtFile' as new REQUESTS_CA_BUNDLE in user environment variables.";
}
}
if ("Node" -ieq $exportTarget) {
$crtFile = $locateNodeCAStore.InvokeReturnAsIs();
if (-not($crtFile)) {
# fall back on the git configured trusted ca store file
$gitSslCrt = $locateGitCAStore.InvokeReturnAsIs();
if (-not($gitSslCrt)) {
# cannot fall back on git so we create a custom one
Write-Warning -Message "Cannot use git ca_certs so we create a new one and use it.";
$crtFile = $createNewCAStore.InvokeReturnAsIs();
}
else {
Write-Host "Using the ca_certs.crt of git as the one for node.";
$crtFile = $gitSslCrt;
}
}
if (-not($crtContainsPem.InvokeReturnAsIs(@($crtFile, $Base64Certificate)))) {
$beautifyCert.InvokeReturnAsIs($Base64Certificate) | Add-Content -Path $crtFile -Encoding utf8 -ErrorAction Stop;
Write-Host "Saved certificate in the crt file '$crtFile'.";
}
else {
Write-Host "The node extra trusted root ca store already contains the given certificate.";
}
if (-not($env:NODE_EXTRA_CA_CERTS) -or ($env:NODE_EXTRA_CA_CERTS -ne $crtFile)) {
[System.Environment]::SetEnvironmentVariable("NODE_EXTRA_CA_CERTS", ($crtFile -replace "\/", "\"), "User");
Write-Verbose -Message "Set the node extra trusted root ca store '$crtFile' as new NODE_EXTRA_CA_CERTS in user environment variables.";
}
}
if("NPM" -ieq $exportTarget) {
$crtFile = $locateNpmCAStore.InvokeReturnAsIs();
$setInNpmRc = $null -eq $crtFile;
if (-not($crtFile)) {
# fall back on the git configured trusted ca store file
$gitSslCrt = $locateGitCAStore.InvokeReturnAsIs();
if (-not($gitSslCrt)) {
# cannot fall back on git so we create a custom one
Write-Warning -Message "Cannot use git ca_certs so we create a new one and use it.";
$crtFile = $createNewCAStore.InvokeReturnAsIs();
}
else {
Write-Host "Using the ca_certs.crt of git as the one for node.";
$crtFile = $gitSslCrt;
}
}
if (-not($crtContainsPem.InvokeReturnAsIs(@($crtFile, $Base64Certificate)))) {
$beautifyCert.InvokeReturnAsIs($Base64Certificate) | Add-Content -Path $crtFile -Encoding utf8 -ErrorAction Stop;
Write-Host "Saved certificate in the crt file '$crtFile'.";
}
else {
Write-Host "The npm root ca store already contains the given certificate.";
}
if($setInNpmRc -and (Get-Command -Name npm -ErrorAction "Ignore")) {
Write-Verbose -Message "Setting npm root ca store to '$crtFile'.";
$npmResult = (npm config set cafile '"$crtFile"');
@($npmResult) | ForEach-Object { Write-Verbose -Message $_; };
}
}
if("AzureCli" -ieq $exportTarget) {
$crtFile = $locateAzureCliCAStore.InvokeReturnAsIs();
if($null -eq $crtFile) {
Write-Warning -Message "No installation of Azure CLI found on this machine.";
return;
}
if (-not($crtContainsPem.InvokeReturnAsIs(@($crtFile, $Base64Certificate)))) {
$beautifyCert.InvokeReturnAsIs($Base64Certificate) | Add-Content -Path $crtFile -Encoding utf8 -ErrorAction Stop;
Write-Host "Saved certificate in the crt file '$crtFile'.";
} else {
Write-Host "The Azure CLI root ca store already contains the given certificate.";
}
}
}
}
@mhudasch
Copy link
Author

mhudasch commented Oct 5, 2021

First, call Get-RootCAFromUrl -Url "https://www.github.com" for example to get a base64 encoded trusted root certificate of a website. After that call Export-RootCA -Base64Certificate $cert -Target git to add the trusted root certificate to the git certificate store. You can also combine the calls like:

Get-RootCAFromUrl -Url "https://www.github.com" | Export-RootCA -Target git

Remember to run the command in admin mode though.
The currently supported targets are:

  • git (certificate file)
  • curl (gets linked with git cert file; otherwise gets its own)
  • npm (gets linked with git cert file; otherwise gets its own)
  • node (gets linked with git cert file; otherwise gets its own and sets environment variable)
  • java (keystore import of the cert)
  • rider / webstorm (keystore import of the cert)

Please give feedback to make it better :D - or if a tool is missing for you.

@hendrik-weist
Copy link

The documentation or error message should include the git command used to set the http.sslBackend to openssl. This is:
git config --global http.sslBackend openssl

@mhudasch
Copy link
Author

mhudasch commented Oct 11, 2021

The error message for git has been changed to accommodate the comment. It also takes into account that you might not want to change the setting globally. It can also be set for only a local repository by omitting the --global parameter.

@mhudasch
Copy link
Author

The [System.Net.ServicePointManager]::ServerCertificateValidationCallback is not available in PowerShell Core. see PowerShell/PowerShell#4899 ... So I had to limit the script to PowerShell 5.

@mhudasch
Copy link
Author

Added support for Azure CLI pem file insertion.

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