Preserve main GNSS and volume behavior while porting verified TLS, receive fixes and full-handshake optimizations. Add MQTT connection diagnostics and route MQTT through banban.api.talkingq.com:1883. Include migration documentation and regression tools. Firmware builds and package checks passed; user confirmed new-board connectivity after switching the MQTT host to the domain.
60 lines
3.9 KiB
PowerShell
60 lines
3.9 KiB
PowerShell
param([string]$OutputDir = "$PSScriptRoot/../artifacts/wss", [ValidateSet(443, 8080)][int]$ServerPort = 443)
|
|
$ErrorActionPreference = 'Stop'
|
|
$serverHost = 'banban.api.talkingq.com'
|
|
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
|
|
$OutputDir = (Resolve-Path -LiteralPath $OutputDir).Path
|
|
$tcp = New-Object System.Net.Sockets.TcpClient
|
|
$tls = $null
|
|
try {
|
|
$pending = $tcp.BeginConnect($serverHost, $serverPort, $null, $null)
|
|
if (-not $pending.AsyncWaitHandle.WaitOne(15000)) { throw 'TCP connect timeout' }
|
|
$tcp.EndConnect($pending)
|
|
$tcp.ReceiveTimeout = 15000
|
|
$tcp.SendTimeout = 15000
|
|
# Default SslStream validation checks the trusted chain and server hostname.
|
|
$tls = New-Object System.Net.Security.SslStream($tcp.GetStream(), $false)
|
|
$tls.ReadTimeout = 15000
|
|
$tls.WriteTimeout = 15000
|
|
$tls.AuthenticateAsClient($serverHost, $null, [System.Security.Authentication.SslProtocols]::Tls12, $false)
|
|
$leaf = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tls.RemoteCertificate)
|
|
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
|
|
$chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
|
|
if (-not $chain.Build($leaf)) { throw 'Trusted certificate chain could not be built' }
|
|
$certificates = @()
|
|
for ($i = 0; $i -lt $chain.ChainElements.Count; $i++) {
|
|
$cert = $chain.ChainElements[$i].Certificate
|
|
$pem = "-----BEGIN CERTIFICATE-----`n" + [Convert]::ToBase64String($cert.RawData, [Base64FormattingOptions]::InsertLineBreaks).Replace("`r`n", "`n") + "`n-----END CERTIFICATE-----`n"
|
|
[IO.File]::WriteAllText((Join-Path $OutputDir "chain-$i.pem"), $pem, [Text.Encoding]::ASCII)
|
|
$certificates += [ordered]@{subject=$cert.Subject; issuer=$cert.Issuer; not_before=$cert.NotBefore.ToUniversalTime().ToString('o'); not_after=$cert.NotAfter.ToUniversalTime().ToString('o'); thumbprint=$cert.Thumbprint}
|
|
if ($i -eq $chain.ChainElements.Count - 1) {
|
|
if ($cert.Subject -ne $cert.Issuer) { throw 'Chain did not terminate at a root CA' }
|
|
[IO.File]::WriteAllText((Join-Path $OutputDir 'root-ca.pem'), $pem, [Text.Encoding]::ASCII)
|
|
}
|
|
}
|
|
$nonce = New-Object byte[] 16
|
|
$rng = [Security.Cryptography.RandomNumberGenerator]::Create()
|
|
$rng.GetBytes($nonce)
|
|
$rng.Dispose()
|
|
$key = [Convert]::ToBase64String($nonce)
|
|
$request = "GET /ws HTTP/1.1`r`nHost: ${serverHost}:${serverPort}`r`nUpgrade: websocket`r`nConnection: Upgrade`r`nSec-WebSocket-Key: $key`r`nSec-WebSocket-Version: 13`r`n`r`n"
|
|
$bytes = [Text.Encoding]::ASCII.GetBytes($request)
|
|
$tls.Write($bytes, 0, $bytes.Length)
|
|
$response = ''
|
|
while (-not $response.EndsWith("`r`n`r`n") -and $response.Length -lt 16384) {
|
|
$next = $tls.ReadByte()
|
|
if ($next -lt 0) { break }
|
|
$response += [char]$next
|
|
}
|
|
$sha1 = [Security.Cryptography.SHA1]::Create()
|
|
$expected = [Convert]::ToBase64String($sha1.ComputeHash([Text.Encoding]::ASCII.GetBytes($key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')))
|
|
$sha1.Dispose()
|
|
$accepted = $response -match '^HTTP/1\.1 101 ' -and $response -match ('(?im)^Sec-WebSocket-Accept:\s*' + [regex]::Escape($expected) + '\s*$')
|
|
$report = [ordered]@{url="wss://${serverHost}:${serverPort}/ws"; tested_utc=[DateTime]::UtcNow.ToString('o'); tls_protocol=$tls.SslProtocol.ToString(); cipher=$tls.CipherAlgorithm.ToString(); cipher_bits=$tls.CipherStrength; certificate_validation='System trust and hostname validation passed'; certificates=$certificates; websocket_upgrade=$accepted; response_headers=$response; device_auth_sent=$false}
|
|
$report | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath (Join-Path $OutputDir 'server-probe.json')
|
|
$report | ConvertTo-Json -Depth 6
|
|
if (-not $accepted) { throw 'WebSocket upgrade did not return a valid HTTP 101 response' }
|
|
} finally {
|
|
if ($tls) { $tls.Dispose() }
|
|
$tcp.Close()
|
|
}
|