95 lines
5.1 KiB
PowerShell
95 lines
5.1 KiB
PowerShell
# Verae signing key handling for the installer (runs elevated, acts for the LOGGED-IN user).
|
|
# -Mode status : detect target user + existing key → INI [token]
|
|
# -Mode validate : check key in -TokenFile against Verae (free) → INI [validate]
|
|
# -Mode save : validate-format + write <profile>\.verae\xverae-token with user-only ACL → INI [save]
|
|
# The key is never printed; only a masked form (xVerae-...abcd) is logged.
|
|
param(
|
|
[ValidateSet("status","validate","save")][string]$Mode = "status",
|
|
[string]$TokenFile,
|
|
[Parameter(Mandatory=$true)][string]$IniOut
|
|
)
|
|
$ErrorActionPreference = "Continue"
|
|
. (Join-Path $PSScriptRoot "common.ps1")
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$ValidateUrl = "https://zap.verae.ai/v1/status" # documented as free; 401 = bad key
|
|
|
|
# Prefer the Go validator (verae-print.exe validate-key): PowerShell 5.1's
|
|
# Invoke-WebRequest was seen timing out on nimo (proxy auto-detect) while Go and
|
|
# curl answered in <1 s. The key is read from a file and never on a command line.
|
|
function Find-VeraeExe {
|
|
foreach ($c in @((Join-Path $PSScriptRoot "verae-print.exe"), (Join-Path (Split-Path -Parent $PSScriptRoot) "bin\verae-print.exe"))) {
|
|
if (Test-Path $c) { return $c }
|
|
}
|
|
return $null
|
|
}
|
|
function Test-VeraeKey([string]$tok, [string]$file) {
|
|
if ($tok -notmatch '^(xVerae-|key-)\S{8,}$') { return @{ result = "badformat"; detail = "must start with xVerae- (or legacy key-)" } }
|
|
$exe = Find-VeraeExe
|
|
if ($exe -and $file -and (Test-Path $file)) {
|
|
$ini = Join-Path $env:TEMP ("verae-val-" + [guid]::NewGuid().ToString("N") + ".ini")
|
|
try {
|
|
& $exe validate-key --token-file $file --out $ini 2>&1 | Out-Null
|
|
$kv = @{}
|
|
if (Test-Path $ini) { Get-Content $ini | ForEach-Object { if ($_ -match '^([^=\[]+)=(.*)$') { $kv[$Matches[1]] = $Matches[2] } } }
|
|
if ($kv.result) { return @{ result = $kv.result; detail = $kv.detail } }
|
|
} finally { Remove-Item $ini -ErrorAction SilentlyContinue }
|
|
}
|
|
try {
|
|
$r = Invoke-WebRequest -UseBasicParsing -TimeoutSec 10 -Uri $ValidateUrl -Headers @{ "x-api-key" = $tok }
|
|
return @{ result = "valid"; detail = "HTTP $($r.StatusCode)" }
|
|
} catch {
|
|
$code = $null
|
|
if ($_.Exception.Response) { $code = [int]$_.Exception.Response.StatusCode }
|
|
if ($code -eq 401 -or $code -eq 403) { return @{ result = "invalid"; detail = "HTTP $code from Verae" } }
|
|
if ($code) { return @{ result = "unknown"; detail = "HTTP $code from Verae" } }
|
|
return @{ result = "unreachable"; detail = $_.Exception.Message }
|
|
}
|
|
}
|
|
|
|
$u = Get-VeraeTargetUser
|
|
$tokPath = Join-Path $u.Profile ".verae\xverae-token"
|
|
Write-Host "user-token.ps1 mode=$Mode target=$($u.Account) sid=$($u.Sid) profile=$($u.Profile) via=$($u.Source) runningAs=$([Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
|
|
|
switch ($Mode) {
|
|
"status" {
|
|
$existing = $null
|
|
if (Test-Path $tokPath) { $existing = (Get-Content $tokPath -Raw -ErrorAction SilentlyContinue) }
|
|
if ($existing) { $existing = $existing.Trim() }
|
|
$v = @{ result = "none"; detail = "" }
|
|
if ($existing) { $v = Test-VeraeKey $existing $tokPath }
|
|
Write-Host "existing key: $([bool]$existing) $(Get-MaskedToken $existing) check=$($v.result) $($v.detail)"
|
|
Write-VeraeIni $IniOut "token" ([ordered]@{
|
|
account = $u.Account; sid = $u.Sid; profile = $u.Profile; source = $u.Source; path = $tokPath
|
|
present = $(if ($existing) { 1 } else { 0 }); masked = (Get-MaskedToken $existing)
|
|
check = $v.result; detail = $v.detail })
|
|
}
|
|
"validate" {
|
|
$tok = (Get-Content $TokenFile -Raw).Trim()
|
|
$v = Test-VeraeKey $tok $TokenFile
|
|
Write-Host "validate $(Get-MaskedToken $tok): $($v.result) $($v.detail)"
|
|
Write-VeraeIni $IniOut "validate" ([ordered]@{ result = $v.result; detail = $v.detail; masked = (Get-MaskedToken $tok) })
|
|
}
|
|
"save" {
|
|
$tok = (Get-Content $TokenFile -Raw).Trim()
|
|
$ok = $false; $err = ""
|
|
try {
|
|
if ($tok -notmatch '^(xVerae-|key-)\S{8,}$') { throw "bad token format" }
|
|
$dir = Split-Path $tokPath -Parent
|
|
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
# same format as internal/auth.SaveXVeraeToken: token + "\n", no BOM
|
|
[IO.File]::WriteAllText($tokPath, $tok + "`n", (New-Object Text.UTF8Encoding($false)))
|
|
# ACL: the logged-in user owns it; only user, SYSTEM, Administrators have access
|
|
icacls $dir /grant "*$($u.Sid):(OI)(CI)F" /C | Out-Host
|
|
icacls $dir /setowner "*$($u.Sid)" /C | Out-Host
|
|
icacls $tokPath /inheritance:r /grant:r "*$($u.Sid):F" "*S-1-5-18:F" "*S-1-5-32-544:F" /C | Out-Host
|
|
icacls $tokPath /setowner "*$($u.Sid)" /C | Out-Host
|
|
$back = (Get-Content $tokPath -Raw).Trim()
|
|
$ok = ($back -eq $tok)
|
|
if (-not $ok) { $err = "read-back mismatch" }
|
|
icacls $tokPath | Out-Host
|
|
} catch { $err = $_.Exception.Message }
|
|
Write-Host "save $(Get-MaskedToken $tok) -> $tokPath : $(if ($ok) {'OK'} else {"FAIL $err"})"
|
|
Write-VeraeIni $IniOut "save" ([ordered]@{ result = $(if ($ok) { "OK" } else { "FAIL" }); detail = $err; path = $tokPath; masked = (Get-MaskedToken $tok); account = $u.Account })
|
|
}
|
|
}
|
|
exit 0
|