diff --git a/.circleci/config.yml b/.circleci/config.yml index 9ec8e2b..0ff3105 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -114,8 +114,8 @@ jobs: # print out the certificate openssl pkcs12 -info -in cert.pfx -passin pass:${KEYCHAIN_PASSWORD} -passout pass:${KEYCHAIN_PASSWORD} -nokeys - run: - name: Install Node 18.20.5 - command: nvm install 18.20.5 && nvm use 18.20.5 + name: Install Node 20.19.5 + command: nvm install 20.19.5 && nvm use 20.19.5 - run: name: Install Yarn command: npm install -g yarn @@ -137,9 +137,18 @@ jobs: - run: name: Package Application command: CERTIFICATE_PASSWORD=$KEYCHAIN_PASSWORD yarn package + - run: + name: Print project directory contents + command: ls -la + - run: + name: Print /out directory contents + command: ls -la out - run: name: Create distribution files - command: yarn only-make + command: DEBUG=* yarn only-make + - run: + name: Print /out/make directory contents + command: ls -la out/make - store_artifacts: name: Store Artifacts path: out/make/ @@ -302,7 +311,269 @@ jobs: ) echo "UPLOAD_ASSET_RESPONSE" echo "$UPLOAD_ASSET_RESPONSE" | jq . + windows-test-certificate-and-signing: + executor: + name: win/default + shell: bash.exe + + steps: + - checkout + + - run: + name: Create Self-Signed Certificate + command: | + echo "Creating self-signed certificate..." + + echo "Generating private key..." + openssl genrsa -out test-key.pem 2048 + + echo "✓ Private key created" + + echo "Creating self-signed certificate using the private key..." + MSYS_NO_PATHCONV=1 openssl req -new -key test-key.pem -x509 -days 365 -out test-cert.pem -subj "/C=US/O=Loadmill LTD/CN=Loadmill Test Certificate" + + echo "Verifying certificate creation..." + openssl x509 -in test-cert.pem -text -noout + + echo "✓ Certificate and key created" + + # Create PFX from cert and key + openssl pkcs12 -export -out cert.pfx \ + -inkey test-key.pem \ + -in test-cert.pem \ + -passout pass:TestPassword123 + + echo "✓ PFX file created" + + # Cleanup temp files + rm test-key.pem test-cert.pem + + echo "✓ Certificate exported to cert.pfx" + + echo "Verifying the PFX file was created" + if [ -f cert.pfx ]; then + echo "✓ cert.pfx file created successfully" + ls -lh cert.pfx + else + echo "✗ ERROR: cert.pfx was not created!" + exit 1 + fi + echo "File creation complete." + - run: + name: Verify Certificate + command: | + echo "Verifying certificate with openssl..." + openssl pkcs12 -info -in cert.pfx \ + -passin pass:TestPassword123 \ + -passout pass:TestPassword123 \ + -nokeys + + echo "Dumping certificate info..." + + echo "" + echo "=== Certificate Details ===" + openssl pkcs12 -info -in cert.pfx \ + -passin pass:TestPassword123 \ + -passout pass:TestPassword123 \ + -nokeys | grep -E "subject=|issuer=" + + echo "Finished verifying certificate." + - run: + name: Create Dummy EXE to Sign + command: | + echo "Creating a dummy executable..." + + # Create a simple C# program + cat > test.cs \<< 'EOF' + using System; + class Program { + static void Main() { + Console.WriteLine("Hello from signed executable!"); + } + } + EOF + + # Compile it (csc.exe is in .NET Framework) + CSC_PATH="/c/Windows/Microsoft.NET/Framework64/v4.0.30319/csc.exe" + + if [ -f "$CSC_PATH" ]; then + echo "Compiling test.cs..." + "$CSC_PATH" /out:test.exe test.cs + + if [ -f test.exe ]; then + echo "✓ test.exe created successfully" + ls -lh test.exe + else + echo "✗ ERROR: Failed to create test.exe" + exit 1 + fi + else + echo "✗ ERROR: C# compiler not found at $CSC_PATH" + echo "Creating a dummy PE file instead..." + echo -ne "MZ\x90\x00" > test.exe + echo "✓ Dummy test.exe created" + fi + + - run: + name: Sign the EXE with signtool + command: | + echo "Attempting to sign test.exe..." + + # Find signtool.exe (usually in Windows SDK) + SIGNTOOL_PATHS=( + "/c/Program Files (x86)/Windows Kits/10/bin/x64/signtool.exe" + "/c/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe" + "/c/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x64/signtool.exe" + ) + + SIGNTOOL="" + for path in "${SIGNTOOL_PATHS[@]}"; do + if [ -f "$path" ]; then + SIGNTOOL="$path" + echo "✓ Found signtool at: $path" + break + fi + done + + if [ -z "$SIGNTOOL" ]; then + echo "✗ ERROR: signtool.exe not found!" + echo "Searching for signtool..." + FOUND=$(find "/c/Program Files (x86)/Windows Kits/" -name "signtool.exe" 2>/dev/null | head -1) + if [ -n "$FOUND" ]; then + SIGNTOOL="$FOUND" + echo "✓ Found signtool at: $SIGNTOOL" + else + echo "signtool.exe not found anywhere!" + exit 1 + fi + fi + + # Sign the file + echo "" + echo "Signing test.exe with certificate..." + + CERT_PATH=$(pwd)/cert.pfx + CERT_PATH_WIN=$(cygpath -w "$CERT_PATH") + SIGNTOOL_WIN=$(cygpath -w "$SIGNTOOL") + + echo "CERT_PATH (Unix): $CERT_PATH" + echo "CERT_PATH (Windows): $CERT_PATH_WIN" + echo "SIGNTOOL (Windows): $SIGNTOOL_WIN" + + # Use cmd.exe to run signtool to avoid Bash argument parsing issues + cmd.exe /c "\"$SIGNTOOL_WIN\" sign /f \"$CERT_PATH_WIN\" /p TestPassword123 /fd SHA256 /v /debug test.exe" + EXIT_CODE=$? + echo "" + echo "Signtool exit code: $EXIT_CODE" + + if [ $EXIT_CODE -eq 0 ]; then + echo "✓✓✓ SUCCESS! File signed successfully! ✓✓✓" + else + echo "✗✗✗ FAILED! Signing failed with exit code $EXIT_CODE ✗✗✗" + exit 1 + fi + + - run: + name: Verify Signature + command: | + echo "Verifying signature on test.exe..." + + # Find signtool + SIGNTOOL_PATHS=( + "/c/Program Files (x86)/Windows Kits/10/bin/x64/signtool.exe" + "/c/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe" + "/c/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x64/signtool.exe" + ) + + SIGNTOOL="" + for path in "${SIGNTOOL_PATHS[@]}"; do + if [ -f "$path" ]; then + SIGNTOOL="$path" + break + fi + done + + if [ -n "$SIGNTOOL" ]; then + "$SIGNTOOL" verify /pa /v test.exe || true + echo "" + echo "Note: Verification will fail because it's self-signed (not trusted)" + echo "But the signature itself should be present!" + fi + + # Check if signature exists using PowerShell + echo "" + echo "Checking signature with Get-AuthenticodeSignature..." + powershell.exe -Command " + \$sig = Get-AuthenticodeSignature test.exe + Write-Host \"Status: \$(\$sig.Status)\" + Write-Host \"StatusMessage: \$(\$sig.StatusMessage)\" + Write-Host \"SignerCertificate: \$(\$sig.SignerCertificate.Subject)\" + + if (\$sig.SignerCertificate) { + Write-Host \"\`n✓✓✓ SIGNATURE EXISTS! ✓✓✓\" + } else { + Write-Host \"\`n✗✗✗ NO SIGNATURE FOUND ✗✗✗\" + } + " + + - run: + name: Summary + command: | + echo "" + echo "=========================================" + echo " TEST SUMMARY" + echo "=========================================" + echo "✓ Certificate created: cert.pfx" + echo "✓ Password: TestPassword123" + echo "✓ Test file signed: test.exe" + echo "" + echo "Next steps:" + echo "1. Use the same bash/OpenSSL commands in your main build" + echo "2. Replace 'TestPassword123' with your actual password" + echo "3. Apply to electron-forge config" + echo "=========================================" + - run: + name: Install Node 20.19.5 + command: | + nvm install 20.19.5 + nvm use 20.19.5 + - run: + name: Install Yarn + command: npm install -g yarn + - restore_cache: + keys: + - yarn-packages-windows-build-{{ .Branch }}-{{ checksum "yarn.lock" }} + - yarn-packages-windows-build-{{ .Branch }}- + - yarn-packages-windows-build- + - run: + name: Install dependencies + command: yarn --frozen-lockfile --cache-folder ~/.cache/yarn + - save_cache: + paths: + - ~/.cache/yarn + key: yarn-packages-windows-build-{{ .Branch }}-{{ checksum "yarn.lock" }} + - run: + name: Run Tests + command: yarn test + - run: + name: Package Application + command: yarn package + - run: + name: Print project directory contents + command: ls -la + - run: + name: Print /out directory contents + command: ls -la out + - run: + name: Create distribution files + command: DEBUG=* yarn only-make + - run: + name: Print /out/make directory contents + command: ls -la out/make + - store_artifacts: + name: Store Artifacts + path: out/make/ workflows: version: 2 build: @@ -312,6 +583,7 @@ workflows: branches: ignore: - /agent-version-.*/ + - windows-certificate pre-steps: - run: name: Use Node 20 @@ -331,5 +603,6 @@ workflows: branches: ignore: - /agent-version-.*/ - requires: - - build_macos + - windows-certificate + + - windows-test-certificate-and-signing \ No newline at end of file diff --git a/forge.config.js b/forge.config.js index 7534b8d..0af0bcf 100644 --- a/forge.config.js +++ b/forge.config.js @@ -8,11 +8,15 @@ module.exports = { makers: [ { config: { - certificateFile: './cert.pfx', - certificatePassword: process.env.CERTIFICATE_PASSWORD, + // certificateFile: './cert.pfx', + // certificatePassword: process.env.CERTIFICATE_PASSWORD, iconUrl: 'https://loadmill.com/favicon.ico', setupExe: `${productName}-${name}-${version}-Setup.exe`, setupIcon: './images/loadmill-icon-256-256.ico', + // windowsSign: { + // // tell signtool where to find the certificate and password + signWithParams: '/as /f ./cert.pfx /p TestPassword123 /fd SHA256 /v /debug', + // }, }, name: '@electron-forge/maker-squirrel', }, @@ -38,49 +42,49 @@ module.exports = { }, ], packagerConfig: { - afterCopyExtraResources: [ - (buildPath, electronVersion, platform, arch, callback) => { - const path = require('path'); - const fs = require('fs'); - const glob = require('glob'); - console.log('Running afterCopyExtraResources hook...'); - console.log({ arch, buildPath, electronVersion, platform }); + // afterCopyExtraResources: [ + // (buildPath, electronVersion, platform, arch, callback) => { + // const path = require('path'); + // const fs = require('fs'); + // const glob = require('glob'); + // console.log('Running afterCopyExtraResources hook...'); + // console.log({ arch, buildPath, electronVersion, platform }); - const chromiumDir = path.join( - buildPath, - 'Loadmill.app', - 'Contents', - 'Resources', - 'standalone_playwright', - 'node_modules', - 'playwright-core', - '.local-browsers', - ); + // const chromiumDir = path.join( + // buildPath, + // 'Loadmill.app', + // 'Contents', + // 'Resources', + // 'standalone_playwright', + // 'node_modules', + // 'playwright-core', + // '.local-browsers', + // ); - console.log({ chromiumDir }); + // console.log({ chromiumDir }); - try { - const matches = glob.sync( - path.join(chromiumDir, '**/gpu_shader_cache.bin'), - { nodir: true }, - ); + // try { + // const matches = glob.sync( + // path.join(chromiumDir, '**/gpu_shader_cache.bin'), + // { nodir: true }, + // ); - matches.forEach(file => { - console.log(`Changing permissions for: ${file}`); - fs.chmodSync(file, 0o644); - }); + // matches.forEach(file => { + // console.log(`Changing permissions for: ${file}`); + // fs.chmodSync(file, 0o644); + // }); - console.log('Permissions changed successfully.'); - } catch (err) { - console.warn('Permission fix failed (may be fine if files don’t exist):', err.message); - } + // console.log('Permissions changed successfully.'); + // } catch (err) { + // console.warn('Permission fix failed (may be fine if files don’t exist):', err.message); + // } - callback(); - }, - ], - extraResource: [ - 'standalone_playwright', - ], + // callback(); + // }, + // ], + // extraResource: [ + // 'standalone_playwright', + // ], icon: isWindowsOS ? './images/loadmill-icon-256-256' : './images/MyIcon', osxNotarize: { appleId: process.env.APPLE_ID, diff --git a/scripts/prepare-standalone-playwright.ts b/scripts/prepare-standalone-playwright.ts index ab9ad80..bbe02d6 100644 --- a/scripts/prepare-standalone-playwright.ts +++ b/scripts/prepare-standalone-playwright.ts @@ -7,6 +7,10 @@ const TARGET_DIR = 'standalone_playwright'; const PLAYWRIGHT_VERSION = '1.50.0'; const main = async (): Promise => { + if (TARGET_DIR) { // Set to true to skip preparation + logInfo('⚠️ Skipping standalone Playwright preparation (disabled by flag)'); + return; + } try { logInfo('🎭 Preparing standalone Playwright...'); diff --git a/scripts/windows-self-signed-certificate.ps1 b/scripts/windows-self-signed-certificate.ps1 new file mode 100644 index 0000000..49d0103 --- /dev/null +++ b/scripts/windows-self-signed-certificate.ps1 @@ -0,0 +1,250 @@ +# ========================================================== +# Loadmill Signing Script (Self-signed, Test Only) +# ========================================================== + +# -------------------------- +# CONFIGURATION +# -------------------------- +$env:LOADMILL_DIR = "C:\Users\Gilad\AppData\Local\desktop_app\app-3.8.2" +# Path to signtool.exe from Windows SDK +$signtool = "C:\Program Files (x86)\Windows +Kits\10\bin\10.0.19041.0\x64\signtool.exe" + +Write-Host "=== Loadmill folder path ===" -ForegroundColor Yellow +Write-Host $env:LOADMILL_DIR +Write-Host "`n=== Signtool path ===" -ForegroundColor Yellow +Write-Host $signtool + +# -------------------------- +# STEP 1: Create self-signed certificate +# -------------------------- +Write-Host "`n=== STEP 1: Creating temporary self-signed certificate ===" +-ForegroundColor Cyan +try { + $cert = New-SelfSignedCertificate ` + -Type CodeSigningCert ` + -Subject "CN=Loadmill Test Cert" ` + -CertStoreLocation "Cert:\localMachine\My" ` + -KeyExportPolicy Exportable ` + -KeyLength 2048 ` + -HashAlgorithm SHA256 + + Write-Host "Certificate created successfully." -ForegroundColor Green + Write-Host "Thumbprint: $($cert.Thumbprint)" + + # Store the thumbprint for later use + $thumbprint = $cert.Thumbprint +} catch { + Write-Host "ERROR: Failed to create self-signed certificate." +-ForegroundColor Red + Write-Host $_ + exit +} + +# -------------------------- +# STEP 1.1: Add cert to trust store (silently) +# -------------------------- +Write-Host "`n=== STEP 1.1: Adding certificate to Trusted Root (silent) +===" -ForegroundColor Cyan + +# Check if running as Administrator +$isAdmin = ([Security.Principal.WindowsPrincipal] +[Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + +if (-not $isAdmin) { + Write-Host "WARNING: Not running as Administrator. Certificate +installation may prompt for confirmation." -ForegroundColor Yellow + Write-Host "For silent installation, run PowerShell as Administrator." +-ForegroundColor Yellow +} + +try { + # Get the certificate by thumbprint + Write-Host "Looking for temporary certificate with thumbprint: +$thumbprint" + $cert = Get-ChildItem "Cert:\localMachine\My\$thumbprint" -ErrorAction +Stop + + Write-Host "Certificate found: $($cert.Subject)" + + # Add to Trusted Root store (localMachine scope, no prompt) + Write-Host "Adding certificate to Trusted Root store..." + $rootStore = New-Object +System.Security.Cryptography.X509Certificates.X509Store("Root", +"localMachine") + $rootStore.Open("ReadWrite") + $rootStore.Add($cert) + $rootStore.Close() + Write-Host "Added to Root store successfully." -ForegroundColor Green + + # Add to Trusted Publishers store + Write-Host "Adding certificate to Trusted Publishers store..." + $pubStore = New-Object +System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher", +"localMachine") + $pubStore.Open("ReadWrite") + $pubStore.Add($cert) + $pubStore.Close() + Write-Host "Added to TrustedPublisher store successfully." +-ForegroundColor Green + + Write-Host "Certificate installation completed without prompts." +-ForegroundColor Green +} catch { + Write-Host "ERROR: Failed to add certificate to trust stores." +-ForegroundColor Red + Write-Host $_.Exception.Message +} + +# -------------------------- +# STEP 2: Sign all .exe and .dll files +# -------------------------- +Write-Host "`n=== STEP 2: Signing all .exe and .dll files ===" +-ForegroundColor Cyan +$filesToSign = Get-ChildItem $env:LOADMILL_DIR -Recurse -File -Include +*.exe, *.dll + +Write-Host "Found $($filesToSign.Count) files to sign." + +foreach ($file in $filesToSign) { + Write-Host "Signing file: $($file.FullName)" + try { + $signArgs = @( + "sign", + "/fd", "SHA256", + "/sm", + "/s", "My", + "/sha1", $cert.Thumbprint, + $file.FullName + ) + & $signtool $signArgs + if ($LASTEXITCODE -eq 0) { + Write-Host "Signed successfully: $($file.Name)" +-ForegroundColor Green + } else { + Write-Host "WARNING: signtool exited with code $LASTEXITCODE +for $($file.Name)" -ForegroundColor Yellow + } + } catch { + Write-Host "ERROR signing file: $($file.FullName)" -ForegroundColor +Red + Write-Host $_ + } +} + +# -------------------------- +# STEP 3: Verify all files using signtool +# -------------------------- +Write-Host "`n=== STEP 3: Verifying with signtool ===" -ForegroundColor Cyan +$signtoolFailures = @() + +foreach ($file in $filesToSign) { + Write-Host "Verifying file: $($file.FullName)" -ForegroundColor Yellow + try { + & $signtool verify /pa $file.FullName + if ($LASTEXITCODE -ne 0) { + Write-Host "signtool verification FAILED for $($file.Name)" +-ForegroundColor Red + $signtoolFailures += $file.FullName + } else { + Write-Host "Verified successfully: $($file.Name)" +-ForegroundColor Green + } + } catch { + Write-Host "ERROR verifying file: $($file.FullName)" +-ForegroundColor Red + Write-Host $_ + $signtoolFailures += $file.FullName + } +} + +# -------------------------- +# STEP 4: Verify all files using Get-AuthenticodeSignature +# -------------------------- +Write-Host "`n=== STEP 4: Verifying with Get-AuthenticodeSignature ===" +-ForegroundColor Cyan +$verificationResults = @() + +foreach ($file in $filesToSign) { + Write-Host "Checking signature status for: $($file.FullName)" +-ForegroundColor Yellow + try { + $sig = Get-AuthenticodeSignature $file.FullName + $verificationResults += [PSCustomObject]@{ + File = $file.FullName + Status = $sig.Status + } + } catch { + Write-Host "ERROR reading signature: $($file.FullName)" +-ForegroundColor Red + $verificationResults += [PSCustomObject]@{ + File = $file.FullName + Status = "Error" + } + } +} + +# -------------------------- +# STEP 5: Print summary of any failures +# -------------------------- +Write-Host "`n=== Signature Verification Summary ===" -ForegroundColor Cyan + +# Files failed signtool +if ($signtoolFailures.Count -gt 0) { + Write-Host "`nFiles failed signtool verification:" -ForegroundColor Red + $signtoolFailures | ForEach-Object { Write-Host $_ -ForegroundColor Red +} +} else { + Write-Host "All files passed signtool verification." -ForegroundColor +Green +} + +# Files failed PS verification +$psFailures = $verificationResults | Where-Object { $_.Status -ne 'Valid' } +if ($psFailures.Count -gt 0) { + Write-Host "`nFiles failed Get-AuthenticodeSignature verification:" +-ForegroundColor Red + $psFailures | Format-Table -AutoSize +} else { + Write-Host "All files passed Get-AuthenticodeSignature verification." +-ForegroundColor Green +} + +# -------------------------- +# STEP 6: Cleanup self-signed certificate +# -------------------------- +Write-Host "`n=== STEP 6: Cleaning up temporary certificate ===" +-ForegroundColor Cyan +try { + # Remove from Personal store + Remove-Item "Cert:\localMachine\My\$($cert.Thumbprint)" -ErrorAction +SilentlyContinue + + # Remove from Root store + $rootStore = New-Object +System.Security.Cryptography.X509Certificates.X509Store("Root", +"localMachine") + $rootStore.Open("ReadWrite") + $certs = $rootStore.Certificates | Where-Object { $_.Thumbprint -eq +$cert.Thumbprint } + foreach ($c in $certs) { $rootStore.Remove($c) } + $rootStore.Close() + + # Remove from TrustedPublisher store + $pubStore = New-Object +System.Security.Cryptography.X509Certificates.X509Store("TrustedPublisher", +"localMachine") + $pubStore.Open("ReadWrite") + $certs = $pubStore.Certificates | Where-Object { $_.Thumbprint -eq +$cert.Thumbprint } + foreach ($c in $certs) { $pubStore.Remove($c) } + $pubStore.Close() + + Write-Host "Temporary certificate removed from all stores +successfully." -ForegroundColor Green +} catch { + Write-Host "WARNING: Failed to remove certificate." -ForegroundColor Red + Write-Host $_ +} + +Write-Host "`n=== Script finished ===" -ForegroundColor Green \ No newline at end of file