documentation-hub 5.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- package/vite.config.ts +152 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { app } from 'electron';
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
import { logger } from '../src/utils/logger';
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const log = logger.namespace('WindowsCertStore');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Windows Certificate Store Integration
|
|
14
|
+
* Provides methods to interact with Windows certificate store for automatic
|
|
15
|
+
* Zscaler and corporate certificate discovery
|
|
16
|
+
*/
|
|
17
|
+
export class WindowsCertStore {
|
|
18
|
+
private certificateCache: Map<string, string> = new Map();
|
|
19
|
+
private readonly tempCertDir: string;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.tempCertDir = path.join(app.getPath('userData'), 'certs');
|
|
23
|
+
this.ensureCertDirectory();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Ensure certificate directory exists
|
|
28
|
+
*/
|
|
29
|
+
private ensureCertDirectory(): void {
|
|
30
|
+
if (!fs.existsSync(this.tempCertDir)) {
|
|
31
|
+
fs.mkdirSync(this.tempCertDir, { recursive: true });
|
|
32
|
+
log.info('[ZscalerConfig] Created certificate directory:', this.tempCertDir);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Search for Zscaler certificate in Windows certificate store
|
|
38
|
+
*/
|
|
39
|
+
public async findZscalerCertificate(): Promise<string | null> {
|
|
40
|
+
if (process.platform !== 'win32') {
|
|
41
|
+
log.info('[ZscalerConfig] Not on Windows, skipping certificate store search');
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
log.info('[ZscalerConfig] Searching for Zscaler certificate in Windows store...');
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// PowerShell script to find and export Zscaler certificate
|
|
49
|
+
const psScript = `
|
|
50
|
+
$ErrorActionPreference = 'Stop'
|
|
51
|
+
|
|
52
|
+
# Search in multiple certificate stores
|
|
53
|
+
$stores = @('Root', 'CA', 'AuthRoot', 'Trust', 'My')
|
|
54
|
+
$zscalerCert = $null
|
|
55
|
+
|
|
56
|
+
foreach ($storeName in $stores) {
|
|
57
|
+
try {
|
|
58
|
+
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, 'LocalMachine')
|
|
59
|
+
$store.Open('ReadOnly')
|
|
60
|
+
|
|
61
|
+
# Look for certificates with Zscaler in the subject or issuer
|
|
62
|
+
$certs = $store.Certificates | Where-Object {
|
|
63
|
+
$_.Subject -like '*Zscaler*' -or
|
|
64
|
+
$_.Issuer -like '*Zscaler*' -or
|
|
65
|
+
$_.FriendlyName -like '*Zscaler*'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ($certs) {
|
|
69
|
+
$zscalerCert = $certs[0]
|
|
70
|
+
Write-Host "FOUND:$storeName"
|
|
71
|
+
Write-Host "SUBJECT:$($zscalerCert.Subject)"
|
|
72
|
+
Write-Host "ISSUER:$($zscalerCert.Issuer)"
|
|
73
|
+
Write-Host "THUMBPRINT:$($zscalerCert.Thumbprint)"
|
|
74
|
+
Write-Host "NOTAFTER:$($zscalerCert.NotAfter)"
|
|
75
|
+
|
|
76
|
+
# Export certificate to Base64
|
|
77
|
+
$base64 = [System.Convert]::ToBase64String($zscalerCert.RawData)
|
|
78
|
+
Write-Host "-----BEGIN CERTIFICATE-----"
|
|
79
|
+
# Output in 64-character lines
|
|
80
|
+
for ($i = 0; $i -lt $base64.Length; $i += 64) {
|
|
81
|
+
$length = [Math]::Min(64, $base64.Length - $i)
|
|
82
|
+
Write-Host $base64.Substring($i, $length)
|
|
83
|
+
}
|
|
84
|
+
Write-Host "-----END CERTIFICATE-----"
|
|
85
|
+
|
|
86
|
+
$store.Close()
|
|
87
|
+
exit 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
$store.Close()
|
|
91
|
+
} catch {
|
|
92
|
+
# Continue to next store
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Also check CurrentUser stores
|
|
97
|
+
foreach ($storeName in $stores) {
|
|
98
|
+
try {
|
|
99
|
+
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, 'CurrentUser')
|
|
100
|
+
$store.Open('ReadOnly')
|
|
101
|
+
|
|
102
|
+
$certs = $store.Certificates | Where-Object {
|
|
103
|
+
$_.Subject -like '*Zscaler*' -or
|
|
104
|
+
$_.Issuer -like '*Zscaler*' -or
|
|
105
|
+
$_.FriendlyName -like '*Zscaler*'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ($certs) {
|
|
109
|
+
$zscalerCert = $certs[0]
|
|
110
|
+
Write-Host "FOUND:$storeName (CurrentUser)"
|
|
111
|
+
Write-Host "SUBJECT:$($zscalerCert.Subject)"
|
|
112
|
+
Write-Host "ISSUER:$($zscalerCert.Issuer)"
|
|
113
|
+
Write-Host "THUMBPRINT:$($zscalerCert.Thumbprint)"
|
|
114
|
+
Write-Host "NOTAFTER:$($zscalerCert.NotAfter)"
|
|
115
|
+
|
|
116
|
+
# Export certificate to Base64
|
|
117
|
+
$base64 = [System.Convert]::ToBase64String($zscalerCert.RawData)
|
|
118
|
+
Write-Host "-----BEGIN CERTIFICATE-----"
|
|
119
|
+
for ($i = 0; $i -lt $base64.Length; $i += 64) {
|
|
120
|
+
$length = [Math]::Min(64, $base64.Length - $i)
|
|
121
|
+
Write-Host $base64.Substring($i, $length)
|
|
122
|
+
}
|
|
123
|
+
Write-Host "-----END CERTIFICATE-----"
|
|
124
|
+
|
|
125
|
+
$store.Close()
|
|
126
|
+
exit 0
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
$store.Close()
|
|
130
|
+
} catch {
|
|
131
|
+
# Continue
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Write-Host "NOTFOUND:No Zscaler certificate found in Windows stores"
|
|
136
|
+
exit 1
|
|
137
|
+
`.replace(/\n/g, ' ');
|
|
138
|
+
|
|
139
|
+
const { stdout } = await execAsync(
|
|
140
|
+
`powershell -NoProfile -NonInteractive -Command "${psScript}"`,
|
|
141
|
+
{ maxBuffer: 10 * 1024 * 1024 } // 10MB buffer for certificate data
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Parse the output
|
|
145
|
+
if (stdout.includes('-----BEGIN CERTIFICATE-----')) {
|
|
146
|
+
log.info('[ZscalerConfig] Found Zscaler certificate in Windows store');
|
|
147
|
+
|
|
148
|
+
// Extract certificate info
|
|
149
|
+
const lines = stdout.split('\n');
|
|
150
|
+
let certInfo: any = {};
|
|
151
|
+
let certPEM = '';
|
|
152
|
+
let inCert = false;
|
|
153
|
+
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
if (line.startsWith('FOUND:')) {
|
|
156
|
+
certInfo.store = line.substring(6).trim();
|
|
157
|
+
} else if (line.startsWith('SUBJECT:')) {
|
|
158
|
+
certInfo.subject = line.substring(8).trim();
|
|
159
|
+
} else if (line.startsWith('ISSUER:')) {
|
|
160
|
+
certInfo.issuer = line.substring(7).trim();
|
|
161
|
+
} else if (line.startsWith('THUMBPRINT:')) {
|
|
162
|
+
certInfo.thumbprint = line.substring(11).trim();
|
|
163
|
+
} else if (line.startsWith('NOTAFTER:')) {
|
|
164
|
+
certInfo.notAfter = line.substring(9).trim();
|
|
165
|
+
} else if (line.includes('-----BEGIN CERTIFICATE-----')) {
|
|
166
|
+
inCert = true;
|
|
167
|
+
certPEM += line + '\n';
|
|
168
|
+
} else if (line.includes('-----END CERTIFICATE-----')) {
|
|
169
|
+
certPEM += line + '\n';
|
|
170
|
+
inCert = false;
|
|
171
|
+
} else if (inCert) {
|
|
172
|
+
certPEM += line.trim() + '\n';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
log.info('[ZscalerConfig] Certificate info:', certInfo);
|
|
177
|
+
|
|
178
|
+
// Save certificate to file (PEM files are text files, use UTF-8)
|
|
179
|
+
const certPath = path.join(this.tempCertDir, 'zscaler-root.pem');
|
|
180
|
+
fs.writeFileSync(certPath, certPEM, 'utf-8');
|
|
181
|
+
log.info('[ZscalerConfig] Saved certificate to:', certPath);
|
|
182
|
+
|
|
183
|
+
// Cache the certificate
|
|
184
|
+
this.certificateCache.set('zscaler', certPath);
|
|
185
|
+
|
|
186
|
+
return certPath;
|
|
187
|
+
} else {
|
|
188
|
+
log.info('[ZscalerConfig] No Zscaler certificate found in Windows store');
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
log.error('[ZscalerConfig] Error searching for certificate:', error);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Export all trusted root certificates from Windows store
|
|
199
|
+
*/
|
|
200
|
+
public async exportTrustedRootCertificates(): Promise<string | null> {
|
|
201
|
+
if (process.platform !== 'win32') {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
log.info('[ZscalerConfig] Exporting trusted root certificates...');
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const psScript = `
|
|
209
|
+
$ErrorActionPreference = 'Stop'
|
|
210
|
+
|
|
211
|
+
# Get all certificates from Root store
|
|
212
|
+
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root', 'LocalMachine')
|
|
213
|
+
$store.Open('ReadOnly')
|
|
214
|
+
|
|
215
|
+
$certCount = 0
|
|
216
|
+
|
|
217
|
+
foreach ($cert in $store.Certificates) {
|
|
218
|
+
# Only export certificates that are currently valid
|
|
219
|
+
if ($cert.NotAfter -gt (Get-Date) -and $cert.NotBefore -lt (Get-Date)) {
|
|
220
|
+
$base64 = [System.Convert]::ToBase64String($cert.RawData)
|
|
221
|
+
Write-Host "-----BEGIN CERTIFICATE-----"
|
|
222
|
+
for ($i = 0; $i -lt $base64.Length; $i += 64) {
|
|
223
|
+
$length = [Math]::Min(64, $base64.Length - $i)
|
|
224
|
+
Write-Host $base64.Substring($i, $length)
|
|
225
|
+
}
|
|
226
|
+
Write-Host "-----END CERTIFICATE-----"
|
|
227
|
+
$certCount++
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
$store.Close()
|
|
232
|
+
Write-Host "EXPORTED:$certCount certificates"
|
|
233
|
+
`.replace(/\n/g, ' ');
|
|
234
|
+
|
|
235
|
+
const { stdout } = await execAsync(
|
|
236
|
+
`powershell -NoProfile -NonInteractive -Command "${psScript}"`,
|
|
237
|
+
{ maxBuffer: 50 * 1024 * 1024 } // 50MB buffer for all certificates
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (stdout.includes('-----BEGIN CERTIFICATE-----')) {
|
|
241
|
+
// Save bundle to file
|
|
242
|
+
const bundlePath = path.join(this.tempCertDir, 'windows-ca-bundle.pem');
|
|
243
|
+
|
|
244
|
+
// Extract just the certificates
|
|
245
|
+
const certLines = stdout.split('\n');
|
|
246
|
+
let bundle = '';
|
|
247
|
+
let inCert = false;
|
|
248
|
+
|
|
249
|
+
for (const line of certLines) {
|
|
250
|
+
if (line.includes('-----BEGIN CERTIFICATE-----')) {
|
|
251
|
+
inCert = true;
|
|
252
|
+
bundle += line + '\n';
|
|
253
|
+
} else if (line.includes('-----END CERTIFICATE-----')) {
|
|
254
|
+
bundle += line + '\n';
|
|
255
|
+
inCert = false;
|
|
256
|
+
} else if (inCert) {
|
|
257
|
+
bundle += line.trim() + '\n';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fs.writeFileSync(bundlePath, bundle);
|
|
262
|
+
log.info('[ZscalerConfig] Exported Windows CA bundle to:', bundlePath);
|
|
263
|
+
|
|
264
|
+
return bundlePath;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return null;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
log.error('[ZscalerConfig] Error exporting certificates:', error);
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Find certificate by thumbprint
|
|
276
|
+
*/
|
|
277
|
+
public async findCertificateByThumbprint(thumbprint: string): Promise<string | null> {
|
|
278
|
+
if (process.platform !== 'win32') {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Check cache first
|
|
283
|
+
if (this.certificateCache.has(thumbprint)) {
|
|
284
|
+
return this.certificateCache.get(thumbprint)!;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const psScript = `
|
|
289
|
+
$cert = Get-ChildItem -Path Cert:\\LocalMachine\\Root,Cert:\\LocalMachine\\CA,Cert:\\CurrentUser\\Root,Cert:\\CurrentUser\\CA -Recurse |
|
|
290
|
+
Where-Object { $_.Thumbprint -eq '${thumbprint}' } |
|
|
291
|
+
Select-Object -First 1
|
|
292
|
+
|
|
293
|
+
if ($cert) {
|
|
294
|
+
$base64 = [System.Convert]::ToBase64String($cert.RawData)
|
|
295
|
+
Write-Host "-----BEGIN CERTIFICATE-----"
|
|
296
|
+
for ($i = 0; $i -lt $base64.Length; $i += 64) {
|
|
297
|
+
$length = [Math]::Min(64, $base64.Length - $i)
|
|
298
|
+
Write-Host $base64.Substring($i, $length)
|
|
299
|
+
}
|
|
300
|
+
Write-Host "-----END CERTIFICATE-----"
|
|
301
|
+
}
|
|
302
|
+
`.replace(/\n/g, ' ');
|
|
303
|
+
|
|
304
|
+
const { stdout } = await execAsync(
|
|
305
|
+
`powershell -NoProfile -NonInteractive -Command "${psScript}"`
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (stdout.includes('-----BEGIN CERTIFICATE-----')) {
|
|
309
|
+
const certPath = path.join(this.tempCertDir, `cert-${thumbprint}.pem`);
|
|
310
|
+
fs.writeFileSync(certPath, stdout.trim());
|
|
311
|
+
this.certificateCache.set(thumbprint, certPath);
|
|
312
|
+
return certPath;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
log.error('[ZscalerConfig] Error finding certificate by thumbprint:', error);
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if running with elevated privileges (might be needed for some cert operations)
|
|
324
|
+
*/
|
|
325
|
+
public async checkElevation(): Promise<boolean> {
|
|
326
|
+
if (process.platform !== 'win32') {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
const { stdout } = await execAsync(
|
|
332
|
+
'powershell -Command "[Security.Principal.WindowsIdentity]::GetCurrent().Groups -contains \\"S-1-5-32-544\\""'
|
|
333
|
+
);
|
|
334
|
+
return stdout.trim().toLowerCase() === 'true';
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get certificate info from a PEM file
|
|
342
|
+
*/
|
|
343
|
+
public getCertificateInfo(pemPath: string): any {
|
|
344
|
+
try {
|
|
345
|
+
const pemContent = fs.readFileSync(pemPath, 'utf8');
|
|
346
|
+
const lines = pemContent.split('\n');
|
|
347
|
+
|
|
348
|
+
// Basic parsing - in production you'd use a proper X.509 parser
|
|
349
|
+
const info = {
|
|
350
|
+
path: pemPath,
|
|
351
|
+
exists: true,
|
|
352
|
+
size: fs.statSync(pemPath).size,
|
|
353
|
+
hash: crypto.createHash('sha256').update(pemContent).digest('hex')
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
return info;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create a combined certificate bundle
|
|
364
|
+
*/
|
|
365
|
+
public async createCombinedBundle(additionalCerts: string[] = []): Promise<string | null> {
|
|
366
|
+
try {
|
|
367
|
+
const bundlePath = path.join(this.tempCertDir, 'combined-ca-bundle.pem');
|
|
368
|
+
let combinedBundle = '';
|
|
369
|
+
|
|
370
|
+
// Add Windows trusted roots
|
|
371
|
+
const windowsBundle = await this.exportTrustedRootCertificates();
|
|
372
|
+
if (windowsBundle && fs.existsSync(windowsBundle)) {
|
|
373
|
+
combinedBundle += fs.readFileSync(windowsBundle, 'utf8') + '\n';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Add Zscaler certificate
|
|
377
|
+
const zscalerCert = await this.findZscalerCertificate();
|
|
378
|
+
if (zscalerCert && fs.existsSync(zscalerCert)) {
|
|
379
|
+
combinedBundle += fs.readFileSync(zscalerCert, 'utf8') + '\n';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Add any additional certificates
|
|
383
|
+
for (const certPath of additionalCerts) {
|
|
384
|
+
if (fs.existsSync(certPath)) {
|
|
385
|
+
combinedBundle += fs.readFileSync(certPath, 'utf8') + '\n';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (combinedBundle) {
|
|
390
|
+
fs.writeFileSync(bundlePath, combinedBundle);
|
|
391
|
+
log.info('[ZscalerConfig] Created combined certificate bundle:', bundlePath);
|
|
392
|
+
return bundlePath;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return null;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
log.error('[ZscalerConfig] Error creating combined bundle:', error);
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Clean up old certificate files
|
|
404
|
+
*/
|
|
405
|
+
public cleanupOldCertificates(daysOld: number = 7): void {
|
|
406
|
+
try {
|
|
407
|
+
const now = Date.now();
|
|
408
|
+
const files = fs.readdirSync(this.tempCertDir);
|
|
409
|
+
|
|
410
|
+
for (const file of files) {
|
|
411
|
+
const filePath = path.join(this.tempCertDir, file);
|
|
412
|
+
const stats = fs.statSync(filePath);
|
|
413
|
+
const age = (now - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
414
|
+
|
|
415
|
+
if (age > daysOld) {
|
|
416
|
+
fs.unlinkSync(filePath);
|
|
417
|
+
log.info(` Cleaned up old certificate: ${file}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
log.error('[ZscalerConfig] Error cleaning up certificates:', error);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Export singleton instance
|
|
427
|
+
export const windowsCertStore = new WindowsCertStore();
|