mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-08 03:38:53 +02:00
release: prepare v0.9.7
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
param(
|
||||
[string]$NodeA = "http://127.0.0.1:8001",
|
||||
[string]$NodeB = "http://127.0.0.1:8002",
|
||||
[string]$AdminKey = "dm-test-node-local-admin-key-00000001"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$RuntimeRoot = Join-Path $Root ".runtime\dm-two-node"
|
||||
$ReportPath = Join-Path $RuntimeRoot "two-node-selftest.json"
|
||||
$Headers = @{ "X-Admin-Key" = $AdminKey }
|
||||
|
||||
function Invoke-Json(
|
||||
[string]$Method,
|
||||
[string]$Uri,
|
||||
[object]$Body = $null,
|
||||
[switch]$Admin
|
||||
) {
|
||||
$args = @{
|
||||
Method = $Method
|
||||
Uri = $Uri
|
||||
TimeoutSec = 30
|
||||
}
|
||||
if ($Admin) {
|
||||
$args.Headers = $Headers
|
||||
}
|
||||
if ($null -ne $Body) {
|
||||
$args.Body = ($Body | ConvertTo-Json -Depth 100)
|
||||
$args.ContentType = "application/json"
|
||||
}
|
||||
return Invoke-RestMethod @args
|
||||
}
|
||||
|
||||
function Assert-Ok([object]$Result, [string]$Step) {
|
||||
if (-not $Result -or -not [bool]$Result.ok) {
|
||||
$detail = if ($Result -and $Result.detail) { [string]$Result.detail } else { "no detail" }
|
||||
throw "$Step failed: $detail"
|
||||
}
|
||||
}
|
||||
|
||||
function Register-DmNode([string]$BaseUrl, [string]$Label) {
|
||||
$registered = Invoke-Json "Post" "$BaseUrl/api/wormhole/dm/register-key" -Admin
|
||||
Assert-Ok $registered "$Label key registration"
|
||||
if (-not [bool]$registered.prekeys_ok -or -not $registered.prekey_detail -or -not $registered.prekey_detail.bundle) {
|
||||
throw "$Label prekey registration failed"
|
||||
}
|
||||
return [pscustomobject]@{
|
||||
label = $Label
|
||||
base = $BaseUrl
|
||||
node_id = [string]$registered.node_id
|
||||
dh_pub_key = [string]$registered.dh_pub_key
|
||||
prekey_bundle = $registered.prekey_detail.bundle
|
||||
registered = $registered
|
||||
}
|
||||
}
|
||||
|
||||
function Try-Compose(
|
||||
[object]$Sender,
|
||||
[object]$Receiver,
|
||||
[string]$Plaintext
|
||||
) {
|
||||
$body = @{
|
||||
peer_id = $Receiver.node_id
|
||||
peer_dh_pub = $Receiver.dh_pub_key
|
||||
plaintext = $Plaintext
|
||||
local_alias = $Sender.label
|
||||
remote_alias = $Receiver.label
|
||||
}
|
||||
return Invoke-Json "Post" "$($Sender.base)/api/wormhole/dm/compose" $body
|
||||
}
|
||||
|
||||
function Decrypt-OnReceiver(
|
||||
[object]$Receiver,
|
||||
[object]$Sender,
|
||||
[object]$Envelope
|
||||
) {
|
||||
$body = @{
|
||||
peer_id = $Sender.node_id
|
||||
ciphertext = $Envelope.ciphertext
|
||||
nonce = $Envelope.nonce
|
||||
format = $Envelope.format
|
||||
local_alias = $Receiver.label
|
||||
remote_alias = $Sender.label
|
||||
session_welcome = $Envelope.session_welcome
|
||||
}
|
||||
return Invoke-Json "Post" "$($Receiver.base)/api/wormhole/dm/decrypt" $body -Admin
|
||||
}
|
||||
|
||||
function Search-PlaintextInNodeData([string]$Needle) {
|
||||
$hits = @()
|
||||
foreach ($nodeName in @("node-a", "node-b")) {
|
||||
$dataPath = Join-Path $RuntimeRoot "$nodeName\backend\data"
|
||||
if (-not (Test-Path $dataPath)) {
|
||||
continue
|
||||
}
|
||||
$matches = Get-ChildItem -Path $dataPath -Recurse -File -ErrorAction SilentlyContinue |
|
||||
Select-String -Pattern ([regex]::Escape($Needle)) -SimpleMatch -ErrorAction SilentlyContinue
|
||||
foreach ($match in @($matches)) {
|
||||
$hits += [pscustomobject]@{
|
||||
node = $nodeName
|
||||
path = $match.Path
|
||||
line = $match.LineNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
return @($hits)
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $RuntimeRoot | Out-Null
|
||||
|
||||
$healthA = Invoke-Json "Get" "$NodeA/api/health"
|
||||
$healthB = Invoke-Json "Get" "$NodeB/api/health"
|
||||
|
||||
$nodeAState = Register-DmNode $NodeA "node-a"
|
||||
$nodeBState = Register-DmNode $NodeB "node-b"
|
||||
|
||||
Invoke-Json "Post" "$NodeA/api/wormhole/dm/reset" @{ peer_id = $nodeBState.node_id } -Admin | Out-Null
|
||||
Invoke-Json "Post" "$NodeB/api/wormhole/dm/reset" @{ peer_id = $nodeAState.node_id } -Admin | Out-Null
|
||||
|
||||
$inviteA = Invoke-Json "Get" "$NodeA/api/wormhole/dm/invite"
|
||||
$inviteB = Invoke-Json "Get" "$NodeB/api/wormhole/dm/invite"
|
||||
$inviteImportBIntoA = Invoke-Json "Post" "$NodeA/api/wormhole/dm/invite/import" @{
|
||||
invite = $inviteB.invite
|
||||
alias = "node-b"
|
||||
} -Admin
|
||||
Assert-Ok $inviteImportBIntoA "node-a import node-b signed invite"
|
||||
$inviteImportAIntoB = Invoke-Json "Post" "$NodeB/api/wormhole/dm/invite/import" @{
|
||||
invite = $inviteA.invite
|
||||
alias = "node-a"
|
||||
} -Admin
|
||||
Assert-Ok $inviteImportAIntoB "node-b import node-a signed invite"
|
||||
|
||||
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
|
||||
$messageAB = "dm-two-node-a-to-b-$timestamp"
|
||||
$messageBA = "dm-two-node-b-to-a-$timestamp"
|
||||
|
||||
# Keep the actual round-trip below a clean first session so the receiver gets a
|
||||
# fresh welcome every time.
|
||||
Invoke-Json "Post" "$NodeA/api/wormhole/dm/reset" @{ peer_id = $nodeBState.node_id } -Admin | Out-Null
|
||||
Invoke-Json "Post" "$NodeB/api/wormhole/dm/reset" @{ peer_id = $nodeAState.node_id } -Admin | Out-Null
|
||||
|
||||
$composeAB = Try-Compose $nodeAState $nodeBState $messageAB
|
||||
Assert-Ok $composeAB "node-a compose to node-b"
|
||||
$decryptAB = Decrypt-OnReceiver $nodeBState $nodeAState $composeAB
|
||||
Assert-Ok $decryptAB "node-b decrypt from node-a"
|
||||
if ([string]$decryptAB.plaintext -ne $messageAB) {
|
||||
throw "node-b decrypted unexpected plaintext"
|
||||
}
|
||||
|
||||
$composeBA = Try-Compose $nodeBState $nodeAState $messageBA
|
||||
Assert-Ok $composeBA "node-b compose to node-a"
|
||||
$decryptBA = Decrypt-OnReceiver $nodeAState $nodeBState $composeBA
|
||||
Assert-Ok $decryptBA "node-a decrypt from node-b"
|
||||
if ([string]$decryptBA.plaintext -ne $messageBA) {
|
||||
throw "node-a decrypted unexpected plaintext"
|
||||
}
|
||||
|
||||
$plaintextHits = @()
|
||||
$plaintextHits += Search-PlaintextInNodeData $messageAB
|
||||
$plaintextHits += Search-PlaintextInNodeData $messageBA
|
||||
|
||||
$report = [pscustomobject]@{
|
||||
ok = $true
|
||||
checked_at = $timestamp
|
||||
nodes = @{
|
||||
node_a = @{
|
||||
url = $NodeA
|
||||
id = $nodeAState.node_id
|
||||
health_status = $healthA.status
|
||||
}
|
||||
node_b = @{
|
||||
url = $NodeB
|
||||
id = $nodeBState.node_id
|
||||
health_status = $healthB.status
|
||||
}
|
||||
}
|
||||
first_contact = @{
|
||||
node_a_to_node_b = @{
|
||||
local = "node-a"
|
||||
remote = "node-b"
|
||||
trust_level = [string]$inviteImportBIntoA.trust_level
|
||||
invite_attested = [bool]$inviteImportBIntoA.invite_attested
|
||||
}
|
||||
node_b_to_node_a = @{
|
||||
local = "node-b"
|
||||
remote = "node-a"
|
||||
trust_level = [string]$inviteImportAIntoB.trust_level
|
||||
invite_attested = [bool]$inviteImportAIntoB.invite_attested
|
||||
}
|
||||
invite_export_ok = ([bool]$inviteA.ok -and [bool]$inviteB.ok)
|
||||
invite_import_node_b_into_node_a = @{
|
||||
ok = [bool]$inviteImportBIntoA.ok
|
||||
detail = [string]$inviteImportBIntoA.detail
|
||||
}
|
||||
invite_import_node_a_into_node_b = @{
|
||||
ok = [bool]$inviteImportAIntoB.ok
|
||||
detail = [string]$inviteImportAIntoB.detail
|
||||
}
|
||||
}
|
||||
message_round_trip = @{
|
||||
node_a_to_node_b = @{
|
||||
compose_ok = [bool]$composeAB.ok
|
||||
decrypt_ok = [bool]$decryptAB.ok
|
||||
format = [string]$composeAB.format
|
||||
has_session_welcome = [bool]$composeAB.session_welcome
|
||||
ciphertext_contains_plaintext = ([string]$composeAB.ciphertext).Contains($messageAB)
|
||||
}
|
||||
node_b_to_node_a = @{
|
||||
compose_ok = [bool]$composeBA.ok
|
||||
decrypt_ok = [bool]$decryptBA.ok
|
||||
format = [string]$composeBA.format
|
||||
has_session_welcome = [bool]$composeBA.session_welcome
|
||||
ciphertext_contains_plaintext = ([string]$composeBA.ciphertext).Contains($messageBA)
|
||||
}
|
||||
}
|
||||
privacy_storage_check = @{
|
||||
plaintext_found_in_node_data = ($plaintextHits.Count -gt 0)
|
||||
hits = @($plaintextHits)
|
||||
}
|
||||
limits = @(
|
||||
"This proves two separate localhost backend processes can perform MLS DM compose/decrypt both ways.",
|
||||
"It proves signed invite import can resolve invite-scoped prekeys over the peer-authenticated local test lane.",
|
||||
"It does not prove RNS/Tor/relay delivery because this local runtime intentionally disables those transports."
|
||||
)
|
||||
}
|
||||
|
||||
$report | ConvertTo-Json -Depth 100 | Set-Content -Path $ReportPath -Encoding UTF8
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "DM two-node selftest passed."
|
||||
Write-Host "A -> B: $($composeAB.format), decrypted by node-b."
|
||||
Write-Host "B -> A: $($composeBA.format), decrypted by node-a."
|
||||
Write-Host "Plaintext in node data: $($report.privacy_storage_check.plaintext_found_in_node_data)"
|
||||
Write-Host "Invite import A<-B: $($report.first_contact.invite_import_node_b_into_node_a.ok) $($report.first_contact.invite_import_node_b_into_node_a.detail)"
|
||||
Write-Host "Invite import B<-A: $($report.first_contact.invite_import_node_a_into_node_b.ok) $($report.first_contact.invite_import_node_a_into_node_b.detail)"
|
||||
Write-Host "Report: $ReportPath"
|
||||
Reference in New Issue
Block a user