param( [switch]$Force, [string]$OpenSslPath = "", [string]$ServerIp = "127.0.0.1", [string]$CaCertificatePath = "", [string]$CaPrivateKeyPath = "" ) $ErrorActionPreference = "Stop" $version = "2.5.9" $packageHash = "FEE9A664C0052DEEE66D40CED2E19BFE20BC423DE5AE5234CFC0CEBC22CA3F5E" $dllHash = "C17C65813FE007EB486A5270D244F3659261355795B27ABB99BD19FF77539263" $projectRoot = Split-Path -Parent $PSScriptRoot $downloads = Join-Path $projectRoot "third_party\downloads" $packages = Join-Path $projectRoot "third_party\packages" $installRoot = Join-Path $projectRoot "third_party\install\msquic" $packageFile = Join-Path $downloads "msquic-openssl-$version.nupkg" $archiveFile = "$packageFile.zip" $packageRoot = Join-Path $packages "msquic-openssl-$version" $packageNative = Join-Path $packageRoot "build\native" $url = "https://api.nuget.org/v3-flatcontainer/microsoft.native.quic.msquic.openssl/$version/microsoft.native.quic.msquic.openssl.$version.nupkg" New-Item -ItemType Directory -Force $downloads, $packages, $installRoot | Out-Null $downloadRequired = $Force -or -not (Test-Path $packageFile) if (-not $downloadRequired) { $actualHash = (Get-FileHash $packageFile -Algorithm SHA256).Hash $downloadRequired = $actualHash -ne $packageHash } if ($downloadRequired) { Write-Host "Downloading MsQuic $version (OpenSSL)..." Invoke-WebRequest -Uri $url -OutFile $packageFile } if ((Get-FileHash $packageFile -Algorithm SHA256).Hash -ne $packageHash) { throw "MsQuic NuGet SHA256 verification failed: $packageFile" } if ($Force -or -not (Test-Path (Join-Path $packageNative "include\msquic.h"))) { if (Test-Path $packageRoot) { Remove-Item -LiteralPath $packageRoot -Recurse -Force } Copy-Item -LiteralPath $packageFile -Destination $archiveFile -Force Expand-Archive -LiteralPath $archiveFile -DestinationPath $packageRoot -Force } $binDir = Join-Path $installRoot "bin" $includeDir = Join-Path $installRoot "include" $licenseDir = Join-Path $installRoot "licenses" $certDir = Join-Path $installRoot "certs" New-Item -ItemType Directory -Force $binDir, $includeDir, $licenseDir, $certDir | Out-Null Copy-Item -LiteralPath (Join-Path $packageNative "bin\x64\msquic.dll") -Destination $binDir -Force Copy-Item -LiteralPath (Join-Path $packageNative "include\msquic.h") -Destination $includeDir -Force Copy-Item -LiteralPath (Join-Path $packageNative "include\msquic_winuser.h") -Destination $includeDir -Force Copy-Item -LiteralPath (Join-Path $packageRoot "LICENSE") -Destination $licenseDir -Force $installedDll = Join-Path $binDir "msquic.dll" if ((Get-FileHash $installedDll -Algorithm SHA256).Hash -ne $dllHash) { throw "Installed msquic.dll SHA256 verification failed: $installedDll" } if ([string]::IsNullOrWhiteSpace($OpenSslPath)) { $openSslCandidates = @( "C:\Program Files\Git\mingw64\bin\openssl.exe", "C:\Program Files\Git\usr\bin\openssl.exe", "C:\Strawberry\c\bin\openssl.exe" ) $OpenSslPath = $openSslCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1 } if (-not $OpenSslPath -or -not (Test-Path $OpenSslPath)) { throw "OpenSSL was not found. Pass -OpenSslPath to generate the QUIC test certificate." } $parsedServerIp = $null if (-not [System.Net.IPAddress]::TryParse($ServerIp, [ref]$parsedServerIp)) { throw "ServerIp must be a numeric IPv4 or IPv6 address: $ServerIp" } $externalCaRequested = -not [string]::IsNullOrWhiteSpace($CaCertificatePath) -or -not [string]::IsNullOrWhiteSpace($CaPrivateKeyPath) if ($externalCaRequested -and ([string]::IsNullOrWhiteSpace($CaCertificatePath) -or [string]::IsNullOrWhiteSpace($CaPrivateKeyPath))) { throw "CaCertificatePath and CaPrivateKeyPath must be specified together." } $exportedCaCertificate = Join-Path $certDir "quic-test-ca.crt" if ($externalCaRequested) { if (-not (Test-Path -LiteralPath $CaCertificatePath)) { throw "CA certificate was not found: $CaCertificatePath" } if (-not (Test-Path -LiteralPath $CaPrivateKeyPath)) { throw "CA private key was not found: $CaPrivateKeyPath" } $signingCaCertificate = (Resolve-Path -LiteralPath $CaCertificatePath).Path $signingCaPrivateKey = (Resolve-Path -LiteralPath $CaPrivateKeyPath).Path Copy-Item -LiteralPath $signingCaCertificate -Destination $exportedCaCertificate -Force } else { $signingCaCertificate = $exportedCaCertificate $signingCaPrivateKey = Join-Path $certDir "quic-test-ca.key.pem" if ($Force -or -not (Test-Path -LiteralPath $signingCaCertificate) -or -not (Test-Path -LiteralPath $signingCaPrivateKey)) { & $OpenSslPath req -x509 -newkey rsa:3072 -sha256 -nodes -days 3650 ` -subj "/CN=CMVR QUIC Test CA" ` -addext "basicConstraints=critical,CA:TRUE" ` -addext "keyUsage=critical,keyCertSign,cRLSign" ` -keyout $signingCaPrivateKey -out $signingCaCertificate if ($LASTEXITCODE -ne 0) { throw "OpenSSL failed to create the QUIC test CA." } } } $certificate = Join-Path $certDir "quic-test-server.pfx" $temporaryKey = Join-Path $certDir "quic-test-server.key.pem" $temporaryRequest = Join-Path $certDir "quic-test-server.csr.pem" $serverCertificate = Join-Path $certDir "quic-test-server.crt" $extensionFile = Join-Path $certDir "quic-test-server.ext" $serialFile = Join-Path $certDir "quic-test-ca.srl" @( "authorityKeyIdentifier=keyid,issuer" "basicConstraints=critical,CA:FALSE" "keyUsage=critical,digitalSignature,keyEncipherment" "extendedKeyUsage=serverAuth" "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:$ServerIp" ) | Set-Content -LiteralPath $extensionFile -Encoding Ascii & $OpenSslPath req -new -newkey rsa:2048 -sha256 -nodes ` -subj "/CN=$ServerIp" -keyout $temporaryKey -out $temporaryRequest if ($LASTEXITCODE -ne 0) { throw "OpenSSL failed to create the QUIC server certificate request." } $signArguments = @( "x509", "-req", "-in", $temporaryRequest, "-CA", $signingCaCertificate, "-CAkey", $signingCaPrivateKey, "-CAserial", $serialFile, "-days", "825", "-sha256", "-extfile", $extensionFile, "-out", $serverCertificate ) if (-not (Test-Path -LiteralPath $serialFile)) { $signArguments += "-CAcreateserial" } & $OpenSslPath @signArguments if ($LASTEXITCODE -ne 0) { throw "OpenSSL failed to sign the QUIC server certificate." } & $OpenSslPath pkcs12 -export -out $certificate -inkey $temporaryKey ` -in $serverCertificate -certfile $exportedCaCertificate ` -name "cmvr-quic-test-server" -password "pass:cmvr-test" if ($LASTEXITCODE -ne 0) { throw "OpenSSL failed to export the QUIC PKCS#12 certificate." } & $OpenSslPath verify -CAfile $exportedCaCertificate $serverCertificate if ($LASTEXITCODE -ne 0) { throw "The generated QUIC server certificate does not verify against the selected CA." } & $OpenSslPath x509 -in $serverCertificate -noout -checkip $ServerIp if ($LASTEXITCODE -ne 0) { throw "The generated QUIC server certificate does not contain IP SAN $ServerIp." } Remove-Item -LiteralPath $temporaryKey, $temporaryRequest, $extensionFile -Force Write-Host "MsQuic $version installed: $installedDll" Write-Host "QUIC server certificate: $certificate (password: cmvr-test)" Write-Host "QUIC client CA certificate: $exportedCaCertificate" Write-Host "Certificate IP SAN: $ServerIp"