import test from 'node:test'; import assert from 'node:assert/strict'; import {spawn} from 'node:child_process'; import {createRequire} from 'node:module'; const require=createRequire(import.meta.url),{createNativeManagementCredentialLoader}=require('../apps/network-broker/management-credential.cjs'); const binding={deviceId:'device',origin:'https://management.example',policyVersion:7,policyKeySha256:'a'.repeat(64),policyDigest:'b'.repeat(64)}; const emit="let bytes=Buffer.alloc(0);process.stdin.on('data',part=>{bytes=Buffer.concat([bytes,part]);});process.stdin.on('end',()=>{const input=JSON.parse(bytes.subarray(4).toString());const value={...input,token:require('node:crypto').randomBytes(32).toString('base64url')};RESPONSE;});"; const response="const b=Buffer.from(JSON.stringify(value)),h=Buffer.alloc(4);h.writeUInt32LE(b.length);process.stdout.write(Buffer.concat([h,b]));"; function loader(code,options={}){return createNativeManagementCredentialLoader({expectedBinding:binding,current:()=>true,deadlineMs:1000,...options,launch:(file,args,config)=>{assert.ok(file.endsWith('safetynet-service-host.exe'));assert.deepEqual(args,['--management-credential']);assert.deepEqual(Object.keys(config.env).sort(),['SAFETYNET_SERVICE_CLIENT_SID','SystemRoot','WINDIR']);assert.equal(config.shell,false);return spawn(process.execPath,['-e',code],{stdio:['pipe','pipe','ignore'],env:config.env});}});} test.before(()=>{process.env.SAFETYNET_SERVICE_CLIENT_SID='S-1-12-1-1-2-3-4';}); test.after(()=>{delete process.env.SAFETYNET_SERVICE_CLIENT_SID;}); test('fixed native-helper contract yields private credential only after exact bound reply and close',async()=>{const client=loader(emit.replace('RESPONSE',response+'setTimeout(()=>{},100);'));let finished=false;const work=client.load(binding);work.then(()=>{finished=true;});await new Promise(r=>setTimeout(r,50));assert.equal(finished,false);const value=await work;assert.equal(value.deviceId,binding.deviceId);assert.ok(Buffer.isBuffer(value.token));assert.equal(value.token.length,43);value.token.fill(0);client.close();}); test('wrong policy/device/origin or unsafe native reply cannot yield credential',async()=>{const client=loader(emit.replace('RESPONSE',"value.policyDigest='c'.repeat(64);"+response));await assert.rejects(client.load(binding),{message:'broker-management-refused'});for(const key of ['deviceId','origin','policyVersion','policyDigest','policyKeySha256'])await assert.rejects(client.load({...binding,[key]:key==='policyVersion'?8:'other'}),{message:'broker-management-refused'});client.close();}); test('nonzero or duplicate native reply is refused without retries',async()=>{for(const suffix of ['process.exitCode=5;','process.stdout.write(Buffer.from([2,0,0,0,123,125]));']){const client=loader(emit.replace('RESPONSE',response+suffix));await assert.rejects(client.load(binding),{message:'broker-management-refused'});client.close();}}); test('signal/revocation and native timeout wait actual owned helper termination',async()=>{const client=loader('setInterval(()=>{},1000);',{deadlineMs:40});await assert.rejects(client.load(binding),{message:'broker-management-refused'});client.close();let active=true;const revoked=loader(emit.replace('RESPONSE',response+'setTimeout(()=>{},150);'),{current:()=>active});const work=revoked.load(binding);active=false;await assert.rejects(work,{message:'broker-management-refused'});revoked.close();}); test('actual Windows LocalMachine DPAPI synthetic roundtrip keeps plaintext out of files', {skip:process.platform!=='win32'},async()=>{ const script=`$ErrorActionPreference='Stop' Add-Type -AssemblyName System.Security $stream=[Console]::OpenStandardInput() $header=New-Object byte[] 4 $offset=0;while($offset -lt 4){$n=$stream.Read($header,$offset,4-$offset);if($n -eq 0){throw 'invalid'};$offset+=$n} $length=[BitConverter]::ToUInt32($header,0);if($length -gt 4096){throw 'invalid'} $bytes=New-Object byte[] $length;$offset=0;while($offset -lt $length){$n=$stream.Read($bytes,$offset,$length-$offset);if($n -eq 0){throw 'invalid'};$offset+=$n} $request=[Text.Encoding]::UTF8.GetString($bytes)|ConvertFrom-Json $entropy=[Security.Cryptography.SHA256]::Create().ComputeHash($bytes) $random=New-Object byte[] 32;[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($random) $token=[Convert]::ToBase64String($random).TrimEnd('=').Replace('+','-').Replace('/','_') $plain=[Text.Encoding]::UTF8.GetBytes($token) $file=[IO.Path]::GetTempFileName() try{ $encrypted=[Security.Cryptography.ProtectedData]::Protect($plain,$entropy,[Security.Cryptography.DataProtectionScope]::LocalMachine) [IO.File]::WriteAllBytes($file,$encrypted) $decoded=[Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes($file),$entropy,[Security.Cryptography.DataProtectionScope]::LocalMachine) if([Convert]::ToBase64String($plain) -ne [Convert]::ToBase64String($decoded)){throw 'invalid'} $value=[ordered]@{deviceId=$request.deviceId;origin=$request.origin;policyVersion=$request.policyVersion;policyKeySha256=$request.policyKeySha256;policyDigest=$request.policyDigest;token=[Text.Encoding]::UTF8.GetString($decoded)} $out=[Text.Encoding]::UTF8.GetBytes(($value|ConvertTo-Json -Compress));$stdout=[Console]::OpenStandardOutput();$h=[BitConverter]::GetBytes([uint32]$out.Length);$stdout.Write($h,0,4);$stdout.Write($out,0,$out.Length);$stdout.Flush() }finally{[Array]::Clear($plain,0,$plain.Length);if($decoded){[Array]::Clear($decoded,0,$decoded.Length)};[Array]::Clear($random,0,$random.Length);[IO.File]::Delete($file)} `; const client=createNativeManagementCredentialLoader({expectedBinding:binding,current:()=>true,launch:()=>spawn('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(script,'utf16le').toString('base64')],{windowsHide:true,stdio:['pipe','pipe','ignore']})});const value=await client.load(binding);assert.equal(value.token.length,43);value.token.fill(0);client.close(); });