Files
jobtrackingapp/scripts/Probe-JobjaktSandbox.ps1
T

76 lines
4.8 KiB
PowerShell

#Requires -Version 7.2
param([ValidateSet('CreateKey','RemoveKey','Probe','TestOwnerKey')][string]$Action,[string]$Tag,[string]$Thumbprint,[string]$PlanPath)
$ErrorActionPreference = 'Stop'
if ($Action -eq 'CreateKey') {
$cert = New-SelfSignedCertificate -Subject "CN=Jobjakt-SYNTHETIC-$Tag" -CertStoreLocation Cert:\CurrentUser\My `
-KeyAlgorithm RSA -KeyLength 2048 -KeyExportPolicy NonExportable -KeyUsage DigitalSignature -NotAfter (Get-Date).AddHours(2)
try {
$rsa = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$null = $rsa.SignData([Text.Encoding]::UTF8.GetBytes('synthetic-positive-control'),[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)
@{thumbprint=$cert.Thumbprint;keyName=$rsa.Key.KeyName;tag=$Tag} | ConvertTo-Json -Compress
} catch {
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($cert.Thumbprint)" -DeleteKey
throw
} finally { if ($rsa) { $rsa.Dispose() } }
exit 0
}
if ($Action -eq 'TestOwnerKey') {
$cert=Get-Item -LiteralPath "Cert:\CurrentUser\My\$Thumbprint"
$rsa=[Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
try {
$null=$rsa.SignData([Text.Encoding]::UTF8.GetBytes('positive-control-no-approval'),[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)
@{keyName=$rsa.Key.KeyName;certificateSha256=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($cert.RawData)).ToLowerInvariant()} | ConvertTo-Json -Compress
} finally { $rsa.Dispose() }
exit 0
}
if ($Action -eq 'RemoveKey') {
$cert = Get-Item -LiteralPath "Cert:\CurrentUser\My\$Thumbprint"
if ($cert.Subject -cne "CN=Jobjakt-SYNTHETIC-$Tag") { throw 'Refusing to remove any non-canary certificate' }
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$Thumbprint" -DeleteKey
exit 0
}
$plan = Get-Content -LiteralPath $PlanPath -Raw | ConvertFrom-Json
$records = @()
foreach ($item in $plan.files) {
$readDenied=$false; $writeDenied=$false
try { $f=[IO.File]::Open($item.path,[IO.FileMode]::Open,[IO.FileAccess]::Read,[IO.FileShare]::ReadWrite); $f.Dispose() } catch { $readDenied=$true }
try { $f=[IO.File]::Open($item.path,[IO.FileMode]::Open,[IO.FileAccess]::Write,[IO.FileShare]::ReadWrite); $f.Dispose() } catch { $writeDenied=$true }
$records += @{category=$item.category;readDenied=$readDenied;writeDenied=$writeDenied}
}
function Can-SignCertificate([string]$CertificateThumbprint) {
if (-not $CertificateThumbprint) { return $false }
try {
$cert=Get-Item -LiteralPath "Cert:\CurrentUser\My\$CertificateThumbprint"
$rsa=[Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
try { $null=$rsa.SignData([Text.Encoding]::UTF8.GetBytes('sandbox-canary-only-no-approval'),[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1); return $true }
finally { if ($rsa) { $rsa.Dispose() } }
} catch { return $false }
}
$canSign = Can-SignCertificate $plan.key.thumbprint
try {
$key=[Security.Cryptography.CngKey]::Open($plan.key.keyName)
$rsa=[Security.Cryptography.RSACng]::new($key)
try { $null=$rsa.SignData([Text.Encoding]::UTF8.GetBytes('sandbox-canary-only-no-approval'),[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1); $canSign=$true }
finally { $rsa.Dispose(); $key.Dispose() }
} catch {}
$ownerCanSign = Can-SignCertificate $plan.ownerThumbprint
if ($plan.ownerKeyName) {
try {
$key=[Security.Cryptography.CngKey]::Open($plan.ownerKeyName)
$rsa=[Security.Cryptography.RSACng]::new($key)
try { $null=$rsa.SignData([Text.Encoding]::UTF8.GetBytes('sandbox-canary-only-no-approval'),[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1); $ownerCanSign=$true }
finally { $rsa.Dispose(); $key.Dispose() }
} catch {}
}
$networkDenied=$false
$client=[Net.Sockets.TcpClient]::new()
try { $connect=$client.ConnectAsync('127.0.0.1',[int]$plan.port); if (-not $connect.Wait(2000)) { $networkDenied=$true } else { $networkDenied=-not $client.Connected } }
catch { $networkDenied=$true }
finally { $client.Dispose() }
$positive=([IO.File]::ReadAllText($PlanPath).Length -gt 0)
$positivePath=Join-Path (Split-Path $PlanPath) 'positive.txt'
try { [IO.File]::WriteAllText($positivePath,'synthetic'); $positive=$positive -and [IO.File]::ReadAllText($positivePath).Equals('synthetic') }
catch { $positive=$false }
@{positiveControl=$positive;files=$records;networkDenied=$networkDenied;signingKeyDenied=(-not $canSign);
ownerKeyDenied=([bool]$plan.ownerThumbprint -and -not $ownerCanSign)} | ConvertTo-Json -Depth 8 -Compress