diff --git a/docs/capi2-probe.md b/docs/capi2-probe.md index 7638b91d..b5d14535 100644 --- a/docs/capi2-probe.md +++ b/docs/capi2-probe.md @@ -30,5 +30,5 @@ This probe grants no ready-rule credit. It does not exercise TLS, remote connect - [CertGetCertificateChain flags and ownership](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetcertificatechain) - [CERT_CHAIN_PARA](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_para), [CERT_CHAIN_CONTEXT](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_chain_context), [CERT_SIMPLE_CHAIN](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_simple_chain) and [CERT_TRUST_STATUS](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/ns-wincrypt-cert_trust_status) - [Unnamed CngKey creation is ephemeral](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cngkey.create) -- [CertificateRequest.CreateSelfSigned](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.certificaterequest.createselfsigned) +- [CertificateRequest.Create with a signature generator](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.certificaterequest.create) - [GetSystemTimePreciseAsFileTime](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime) diff --git a/scripts/Capi2ProbeWorker.ps1 b/scripts/Capi2ProbeWorker.ps1 index a99ae510..f48bb370 100644 --- a/scripts/Capi2ProbeWorker.ps1 +++ b/scripts/Capi2ProbeWorker.ps1 @@ -13,7 +13,9 @@ try { $rsa=[Security.Cryptography.RSACng]::new($key) $request=[Security.Cryptography.X509Certificates.CertificateRequest]::new(('CN=WelaCapi2Probe_'+$Nonce),$rsa,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1) $now=[DateTimeOffset][Wela.WmiProbe.Native]::UtcNow() - $certificate=$request.CreateSelfSigned($now.AddMinutes(-5),$now.AddMinutes(5)) + $generator=[Security.Cryptography.X509Certificates.X509SignatureGenerator]::CreateForRSA($rsa,[Security.Cryptography.RSASignaturePadding]::Pkcs1) + $certificate=$request.Create($request.SubjectName,$generator,$now.AddMinutes(-5),$now.AddMinutes(5),[guid]::NewGuid().ToByteArray()) + if($certificate.HasPrivateKey){throw 'Only a public certificate is expected.'} $der=$certificate.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert) $started=[Wela.WmiProbe.Native]::UtcNow() $chain=[Wela.Capi2Probe.Native]::Build($der) diff --git a/tests/Capi2Probe.Tests.ps1 b/tests/Capi2Probe.Tests.ps1 index e59d4343..3fbc035d 100644 --- a/tests/Capi2Probe.Tests.ps1 +++ b/tests/Capi2Probe.Tests.ps1 @@ -37,5 +37,26 @@ $null=Assert-WelaWmiProbeInterval $operation ([DateTimeOffset]$now.AddSeconds(-2 $bad=Clone $operation;$bad.Clock='UtcNow';Reject {Assert-WelaWmiProbeInterval $bad ([DateTimeOffset]$now.AddSeconds(-2)) ([DateTimeOffset]$now.AddSeconds(2))} 'Require precise native clock.' Reject {Invoke-WelaCapi2Probe -Action Run} 'Run requires a new private output path.' Reject {Invoke-WelaCapi2Probe -Action Plan -OutputPath unused} 'Plan creates no files.' +# Lifecycle fixtures test failure receipts and no-operation planning independently of Windows telemetry. +$script:FixtureState=$state;$script:FixtureOperation=$operation;$script:FixtureXml=$xml;$script:FixtureMode='success';$script:FixtureStateReads=0;$script:FixtureActions=0 +function Get-WelaCapi2ProbeState {$script:FixtureStateReads++;$value=Clone $script:FixtureState;if($script:FixtureMode -eq 'drift' -and $script:FixtureStateReads -gt 1){$value.Host.UBR++};$value} +function Get-WelaCapi2ProbeWatermark {if($script:FixtureMode -eq 'denied'){throw 'Reader denied.'};if($script:FixtureMode -eq 'rollback' -and $script:FixtureActions){return [long]0};[long]11} +function Start-WelaCapi2ProbeBuild {param($State);$script:FixtureActions++;if($script:FixtureMode -eq 'worker'){throw 'Bounded worker failed.'};Clone $script:FixtureOperation} +function Read-WelaCapi2ProbeEvents {param($Operation);$items=@($script:FixtureXml);if($script:FixtureMode -eq 'none'){$items=@($script:FixtureXml.Replace('800B0109','0'))};if($script:FixtureMode -eq 'duplicate'){$items=@($script:FixtureXml,$script:FixtureXml)};[pscustomobject]@{Xml=$items;Capped=($script:FixtureMode -eq 'cap');Query='fixed-fixture';MaximumEvents=64}} +$planned=Invoke-WelaCapi2Probe +Assert ($planned.Status -eq 'PrerequisitesObserved' -and $script:FixtureActions -eq 0 -and -not $planned.OutputPath) 'Plan observes prerequisites without operation or files.' +$private=Join-Path ([IO.Path]::GetTempPath()) ('wela-capi2-fixture-'+[guid]::NewGuid().ToString('N'));$null=New-Item -ItemType Directory $private +try{ + foreach($mode in @('success','none','duplicate','cap','drift','rollback','worker','denied')){ + $script:FixtureMode=$mode;$script:FixtureStateReads=0;$script:FixtureActions=0 + $out=Join-Path $private $mode;$result=Invoke-WelaCapi2Probe -Action Run -OutputPath $out -TimeoutSeconds 1 + if($mode -eq 'success'){Assert ($result.Status -eq 'LocalChainEventObserved' -and $result.ExitCode -eq 0 -and (Test-Path "$out/event.xml")) 'Success retains one exact matched event.'} + else{Assert ($result.Status -eq 'Unverified' -and $result.ExitCode -eq 1 -and $result.Diagnostic) ('Failure remains explicit: '+$mode)} + Assert (Test-Path "$out/manifest.json") 'Every started bundle retains its manifest.' + Assert ($script:FixtureActions -le 1 -and $result.ChannelChanges -eq 0 -and $result.StoreChanges -eq 0 -and $result.TrustPolicyChanges -eq 0 -and $result.ReadyRuleCredit -eq 0) 'No operation retry or configuration/coverage credit.' + foreach($artifact in $result.Artifacts){Assert ($artifact.Sha256 -ceq (Get-FileHash -LiteralPath (Join-Path $out $artifact.Name)).Hash.ToLowerInvariant()) 'Lifecycle artifact hash matches.'} + } + Reject {Invoke-WelaCapi2Probe -Action Run -OutputPath (Join-Path $private 'success')} 'Existing evidence cannot be overwritten.' +}finally{Remove-Item -LiteralPath $private -Recurse -Force} Write-Host "PASS: $count portable CAPI2 assertions. No native event proof is claimed." $global:LASTEXITCODE=0