@harperfast/harper 5.3.0-beta.3 → 5.3.0-beta.4
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/bin/cliOperations.ts +25 -3
- package/bin/run.ts +18 -0
- package/components/Application.ts +52 -78
- package/components/DESIGN.md +76 -3
- package/components/Scope.ts +10 -4
- package/components/anthropic/index.ts +10 -1
- package/components/bedrock/index.ts +10 -1
- package/components/componentLoader.ts +23 -14
- package/components/mcp/tools/operations.ts +3 -1
- package/components/ollama/index.ts +10 -1
- package/components/openai/index.ts +265 -18
- package/components/operations.js +40 -1
- package/config/configUtils.ts +3 -19
- package/dataLayer/DESIGN.md +29 -5
- package/dataLayer/harperBridge/ResourceBridge.ts +39 -3
- package/dataLayer/restoreMarker.ts +250 -14
- package/dataLayer/schema.ts +2 -7
- package/dataLayer/schemaDescribe.ts +10 -0
- package/dist/bin/cliOperations.js +28 -3
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/run.js +19 -0
- package/dist/bin/run.js.map +1 -1
- package/dist/components/Application.d.ts +8 -2
- package/dist/components/Application.js +43 -69
- package/dist/components/Application.js.map +1 -1
- package/dist/components/Scope.d.ts +1 -1
- package/dist/components/Scope.js +9 -4
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +0 -10
- package/dist/components/anthropic/index.js +9 -1
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +0 -12
- package/dist/components/bedrock/index.js +9 -1
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.js +23 -14
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/mcp/tools/operations.js +1 -1
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/ollama/index.d.ts +0 -13
- package/dist/components/ollama/index.js +9 -1
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +11 -15
- package/dist/components/openai/index.js +224 -12
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +9 -0
- package/dist/components/operations.js +32 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/config/configUtils.d.ts +1 -2
- package/dist/config/configUtils.js +7 -22
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +29 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +25 -3
- package/dist/dataLayer/restoreMarker.js +213 -13
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/dataLayer/schema.js +1 -4
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +10 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +28 -0
- package/dist/resources/DatabaseTransaction.d.ts +21 -1
- package/dist/resources/DatabaseTransaction.js +147 -8
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +1 -1
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/Table.d.ts +31 -3
- package/dist/resources/Table.js +397 -79
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.js +16 -10
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +1 -0
- package/dist/resources/blob.js +14 -5
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.js +3 -3
- package/dist/resources/branchDatabase.js.map +1 -1
- package/dist/resources/databaseDropPreparation.d.ts +20 -0
- package/dist/resources/databaseDropPreparation.js +106 -0
- package/dist/resources/databaseDropPreparation.js.map +1 -0
- package/dist/resources/databases.d.ts +26 -21
- package/dist/resources/databases.js +1040 -204
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/derivedIndexRegistry.d.ts +23 -0
- package/dist/resources/derivedIndexRegistry.js +92 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -1
- package/dist/resources/derivedIndexRuntime.d.ts +10 -0
- package/dist/resources/derivedIndexRuntime.js +136 -25
- package/dist/resources/derivedIndexRuntime.js.map +1 -1
- package/dist/resources/derivedIndexes.d.ts +39 -0
- package/dist/resources/derivedIndexes.js +733 -0
- package/dist/resources/derivedIndexes.js.map +1 -0
- package/dist/resources/fullTextSchema.d.ts +8 -0
- package/dist/resources/fullTextSchema.js +42 -0
- package/dist/resources/fullTextSchema.js.map +1 -1
- package/dist/resources/fullTextSchemaLifecycle.d.ts +0 -5
- package/dist/resources/fullTextSchemaLifecycle.js +0 -9
- package/dist/resources/fullTextSchemaLifecycle.js.map +1 -1
- package/dist/resources/graphql.js +101 -9
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +0 -10
- package/dist/resources/indexes/hnswDerivedIndex.js +0 -230
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -1
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +3 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +57 -8
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -1
- package/dist/resources/models/Models.d.ts +26 -3
- package/dist/resources/models/Models.js +256 -13
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/TestBackend.d.ts +3 -1
- package/dist/resources/models/TestBackend.js +35 -1
- package/dist/resources/models/TestBackend.js.map +1 -1
- package/dist/resources/models/analyticsTable.d.ts +2 -2
- package/dist/resources/models/analyticsTable.js +4 -2
- package/dist/resources/models/analyticsTable.js.map +1 -1
- package/dist/resources/models/backendHelpers.d.ts +24 -0
- package/dist/resources/models/backendHelpers.js +18 -1
- package/dist/resources/models/backendHelpers.js.map +1 -1
- package/dist/resources/models/backendRegistry.d.ts +10 -21
- package/dist/resources/models/backendRegistry.js +74 -18
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +7 -0
- package/dist/resources/models/bootstrap.js +36 -4
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/models/decideHook.d.ts +33 -0
- package/dist/resources/models/decideHook.js +105 -0
- package/dist/resources/models/decideHook.js.map +1 -0
- package/dist/resources/models/decision.d.ts +64 -0
- package/dist/resources/models/decision.js +507 -0
- package/dist/resources/models/decision.js.map +1 -0
- package/dist/resources/models/decisionStore.d.ts +134 -0
- package/dist/resources/models/decisionStore.js +350 -0
- package/dist/resources/models/decisionStore.js.map +1 -0
- package/dist/resources/models/embedHook.d.ts +66 -8
- package/dist/resources/models/embedHook.js +228 -61
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/generativeDecision.d.ts +58 -0
- package/dist/resources/models/generativeDecision.js +315 -0
- package/dist/resources/models/generativeDecision.js.map +1 -0
- package/dist/resources/models/types.d.ts +232 -4
- package/dist/security/authn/oidc/tokenExchange.js +3 -33
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -1
- package/dist/security/authn/oidc/tokenUseTable.d.ts +9 -0
- package/dist/security/authn/oidc/tokenUseTable.js +28 -0
- package/dist/security/authn/oidc/tokenUseTable.js.map +1 -0
- package/dist/security/certificateVerification/certificateVerificationSource.d.ts +0 -1
- package/dist/security/certificateVerification/certificateVerificationSource.js +3 -3
- package/dist/security/certificateVerification/certificateVerificationSource.js.map +1 -1
- package/dist/security/certificateVerification/crlVerification.js +77 -165
- package/dist/security/certificateVerification/crlVerification.js.map +1 -1
- package/dist/security/certificateVerification/types.d.ts +0 -3
- package/dist/security/certificateVerification/verificationTables.d.ts +19 -0
- package/dist/security/certificateVerification/verificationTables.js +125 -0
- package/dist/security/certificateVerification/verificationTables.js.map +1 -0
- package/dist/security/certificateVerification/verificationUtils.d.ts +1 -1
- package/dist/security/certificateVerification/verificationUtils.js +6 -31
- package/dist/security/certificateVerification/verificationUtils.js.map +1 -1
- package/dist/security/jsLoader.d.ts +5 -0
- package/dist/security/jsLoader.js +8 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/Server.d.ts +1 -0
- package/dist/server/Server.js.map +1 -1
- package/dist/server/http.js +2 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +21 -2
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobOwnership.d.ts +25 -0
- package/dist/server/jobs/jobOwnership.js +127 -0
- package/dist/server/jobs/jobOwnership.js.map +1 -0
- package/dist/server/jobs/jobProcess.js +9 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +18 -5
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/loadRootComponents.js +10 -0
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +11 -1
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverHandlers.js +12 -2
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/threads/itc.d.ts +4 -1
- package/dist/server/threads/itc.js +29 -6
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +12 -4
- package/dist/server/threads/manageThreads.js +241 -119
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/threadServer.d.ts +1 -0
- package/dist/server/threads/threadServer.js +15 -1
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/server/threads/windowsProcessTree.d.ts +91 -0
- package/dist/server/threads/windowsProcessTree.js +361 -0
- package/dist/server/threads/windowsProcessTree.js.map +1 -0
- package/dist/server/throttle.d.ts +1 -1
- package/dist/server/throttle.js +8 -1
- package/dist/server/throttle.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.js +53 -46
- package/dist/upgrade/directives/5-3-0.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +10 -1
- package/dist/utility/errors/hdbError.js +55 -11
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/fsync.d.ts +2 -0
- package/dist/utility/fsync.js +20 -0
- package/dist/utility/fsync.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +3 -0
- package/dist/utility/hdbTerms.js +3 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/signalling.d.ts +7 -1
- package/dist/utility/signalling.js +27 -3
- package/dist/utility/signalling.js.map +1 -1
- package/dist/utility/when.d.ts +1 -0
- package/dist/utility/when.js +21 -0
- package/dist/utility/when.js.map +1 -1
- package/dist/validation/configValidator.js +30 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +13 -0
- package/json/systemSchema.json +28 -0
- package/npm-shrinkwrap.json +151 -55
- package/package.json +14 -13
- package/resources/DESIGN.md +80 -100
- package/resources/DatabaseTransaction.ts +157 -10
- package/resources/PrimaryRocksDatabase.ts +2 -2
- package/resources/Table.ts +494 -101
- package/resources/auditStore.ts +18 -10
- package/resources/blob.ts +13 -6
- package/resources/branchDatabase.ts +3 -3
- package/resources/databaseDropPreparation.ts +120 -0
- package/resources/databases.ts +1155 -205
- package/resources/derivedIndexRegistry.ts +122 -0
- package/resources/derivedIndexRuntime.ts +157 -30
- package/resources/derivedIndexes.ts +856 -0
- package/resources/fullTextSchema.ts +55 -0
- package/resources/fullTextSchemaLifecycle.ts +0 -15
- package/resources/graphql.ts +107 -12
- package/resources/indexes/DESIGN.md +7 -0
- package/resources/indexes/hnswDerivedIndex.ts +0 -260
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +72 -9
- package/resources/models/DESIGN.md +39 -0
- package/resources/models/Models.ts +361 -18
- package/resources/models/TestBackend.ts +56 -1
- package/resources/models/analyticsTable.ts +6 -4
- package/resources/models/backendHelpers.ts +17 -0
- package/resources/models/backendRegistry.ts +76 -21
- package/resources/models/bootstrap.ts +57 -7
- package/resources/models/decideHook.ts +135 -0
- package/resources/models/decision.ts +505 -0
- package/resources/models/decisionStore.ts +419 -0
- package/resources/models/embedHook.ts +272 -55
- package/resources/models/generativeDecision.ts +459 -0
- package/resources/models/types.ts +231 -4
- package/security/DESIGN.md +22 -2
- package/security/authn/oidc/tokenExchange.ts +6 -34
- package/security/authn/oidc/tokenUseTable.ts +27 -0
- package/security/certificateVerification/certificateVerificationSource.ts +2 -3
- package/security/certificateVerification/crlVerification.ts +96 -174
- package/security/certificateVerification/types.ts +0 -3
- package/security/certificateVerification/verificationTables.ts +124 -0
- package/security/certificateVerification/verificationUtils.ts +8 -32
- package/security/jsLoader.ts +9 -1
- package/server/DESIGN.md +27 -1
- package/server/Server.ts +2 -1
- package/server/http.ts +4 -2
- package/server/itc/serverHandlers.js +29 -1
- package/server/jobs/jobOwnership.ts +89 -0
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +15 -4
- package/server/loadRootComponents.js +11 -0
- package/server/serverHelpers/contentTypes.ts +13 -1
- package/server/serverHelpers/serverHandlers.js +11 -2
- package/server/threads/itc.js +32 -6
- package/server/threads/manageThreads.js +268 -122
- package/server/threads/threadServer.js +13 -1
- package/server/threads/windowsProcessTree.ts +409 -0
- package/server/throttle.ts +11 -1
- package/studio/web/assets/{Chat-CveGbxe6.js → Chat-Bq60AO-g.js} +1 -1
- package/studio/web/assets/{FloatingChat-IpCFgYWL.js → FloatingChat-D4560T0p.js} +3 -3
- package/studio/web/assets/{apiToken-CS8fFTgL.js → apiToken-BEvbFPMc.js} +1 -1
- package/studio/web/assets/{applications-D7F4Jopl.js → applications-CSUVTH4E.js} +1 -1
- package/studio/web/assets/{cssMode-Daw_oLPq.js → cssMode-DJyc9mHj.js} +1 -1
- package/studio/web/assets/{editor-B3Bs1zWX.js → editor-DDIsQfj6.js} +1 -1
- package/studio/web/assets/{html-CDaQwWiE.js → html-g1BJHxLy.js} +1 -1
- package/studio/web/assets/{htmlMode-Cb6emO4U.js → htmlMode-Vh5IglCQ.js} +1 -1
- package/studio/web/assets/{index-B4g2Fu_2.js → index-DGRAzvO4.js} +5 -5
- package/studio/web/assets/{index.lazy-NwhvF1q1.js → index.lazy-vgSic7et.js} +1 -1
- package/studio/web/assets/{javascript-2_86gB9Q.js → javascript-5VIolqf7.js} +1 -1
- package/studio/web/assets/{jsonMode-DX-i86uQ.js → jsonMode-DG4zQraI.js} +1 -1
- package/studio/web/assets/{languageServices-B85WN-Ej.js → languageServices-t5FBpX-i.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-JCWHIsKg.js → lspLanguageFeatures-DgVnNPh-.js} +1 -1
- package/studio/web/assets/{notifications-D9ejzt52.js → notifications-BeWcX04m.js} +1 -1
- package/studio/web/assets/{notifications-B7hjKhai.js → notifications-DUGWrWoO.js} +1 -1
- package/studio/web/assets/{profile-D8B6No3F.js → profile-BvUEDBzW.js} +1 -1
- package/studio/web/assets/{regions-DFazIfTK.js → regions-CB-_2m7M.js} +1 -1
- package/studio/web/assets/{register-CwFLviLF.js → register-1w1Zr3b2.js} +2 -2
- package/studio/web/assets/{setComponentFile-VnJ80SSJ.js → setComponentFile-z4-qJCbA.js} +1 -1
- package/studio/web/assets/{setup-BYQxFy-C.js → setup-CS50uJ4x.js} +2 -2
- package/studio/web/assets/{status-Cn5XjmsL.js → status-CI0wOdOE.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-g9xIh6TX.js → toggleHighContrast-BsAE4jPe.js} +1 -1
- package/studio/web/assets/{tsMode-EC-n0Wqc.js → tsMode-D83Gg3cq.js} +1 -1
- package/studio/web/assets/{typescript-DHmM9JCO.js → typescript-Dfp-GrNq.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-BUdnVSVv.js → useEntityRestURL-BKetlINf.js} +1 -1
- package/studio/web/assets/{workers-BVl9bBB-.js → workers-CCvmDLH3.js} +1 -1
- package/studio/web/assets/{xml-C4_sqOkX.js → xml-06pZp00U.js} +1 -1
- package/studio/web/assets/{yaml-CVkbDqtW.js → yaml-iSnWMVlV.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +61 -54
- package/utility/DESIGN.md +5 -1
- package/utility/errors/hdbError.ts +56 -8
- package/utility/fsync.ts +16 -0
- package/utility/hdbTerms.ts +3 -0
- package/utility/signalling.ts +33 -4
- package/utility/when.ts +22 -0
- package/validation/configValidator.ts +36 -7
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import * as pkijs from 'pkijs';
|
|
6
6
|
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
7
|
-
import { table } from '../../resources/databases.ts';
|
|
8
7
|
import { Resource } from '../../resources/Resource.ts';
|
|
8
|
+
import { transaction } from '../../resources/transaction.ts';
|
|
9
9
|
import type { SourceContext } from '../../resources/ResourceInterface.ts';
|
|
10
10
|
import {
|
|
11
11
|
extractCRLDistributionPoints,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
getCertificateCacheTable as getSharedCertificateCacheTable,
|
|
18
18
|
pemToBuffer,
|
|
19
19
|
} from './verificationUtils.ts';
|
|
20
|
+
import { declareCRLCacheTable, declareRevokedCertificatesTable } from './verificationTables.ts';
|
|
20
21
|
import { ERROR_CACHE_TTL, CRL_DEFAULT_VALIDITY_PERIOD, CRL_USER_AGENT } from './verificationConfig.ts';
|
|
21
22
|
import type {
|
|
22
23
|
CertificateVerificationResult,
|
|
@@ -68,17 +69,12 @@ class CertificateRevocationListSource extends Resource {
|
|
|
68
69
|
const { distributionPoint, issuerPem: issuerPemStr, config } = requestContext;
|
|
69
70
|
|
|
70
71
|
try {
|
|
71
|
-
const
|
|
72
|
+
const { entry } = await downloadAndParseCRLOnce(distributionPoint, issuerPemStr, config);
|
|
72
73
|
|
|
73
74
|
// Set expiration - use the CRL's nextUpdate time or configured TTL, whichever is sooner
|
|
74
|
-
|
|
75
|
-
const configExpiry = Date.now() + config.cacheTtl;
|
|
76
|
-
const expiresAt = Math.min(crlExpiry, configExpiry);
|
|
75
|
+
context.expiresAt = Math.min(entry.next_update, Date.now() + config.cacheTtl);
|
|
77
76
|
|
|
78
|
-
return
|
|
79
|
-
...result,
|
|
80
|
-
expiresAt,
|
|
81
|
-
};
|
|
77
|
+
return entry;
|
|
82
78
|
} catch (error) {
|
|
83
79
|
logger.error?.(`CRL fetch error for: ${distributionPoint} - ${error}`);
|
|
84
80
|
|
|
@@ -89,7 +85,7 @@ class CertificateRevocationListSource extends Resource {
|
|
|
89
85
|
// Check failure mode
|
|
90
86
|
if (config.failureMode === 'fail-closed') {
|
|
91
87
|
// Cache the error for faster recovery
|
|
92
|
-
|
|
88
|
+
context.expiresAt = Date.now() + ERROR_CACHE_TTL;
|
|
93
89
|
|
|
94
90
|
return {
|
|
95
91
|
crl_id: id,
|
|
@@ -97,9 +93,8 @@ class CertificateRevocationListSource extends Resource {
|
|
|
97
93
|
issuer_dn: 'unknown',
|
|
98
94
|
crl_blob: Buffer.alloc(0),
|
|
99
95
|
this_update: Date.now(),
|
|
100
|
-
next_update: expiresAt,
|
|
96
|
+
next_update: context.expiresAt,
|
|
101
97
|
signature_valid: false,
|
|
102
|
-
expiresAt,
|
|
103
98
|
};
|
|
104
99
|
}
|
|
105
100
|
|
|
@@ -111,42 +106,12 @@ class CertificateRevocationListSource extends Resource {
|
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
// Lazy-load Harper tables
|
|
114
|
-
let crlCacheTable: ReturnType<typeof
|
|
115
|
-
let revokedCertificateTable: ReturnType<typeof
|
|
109
|
+
let crlCacheTable: ReturnType<typeof declareCRLCacheTable>;
|
|
110
|
+
let revokedCertificateTable: ReturnType<typeof declareRevokedCertificatesTable>;
|
|
116
111
|
|
|
117
112
|
function getCRLCacheTable() {
|
|
118
113
|
if (!crlCacheTable) {
|
|
119
|
-
crlCacheTable =
|
|
120
|
-
table: 'hdb_crl_cache',
|
|
121
|
-
database: 'system',
|
|
122
|
-
attributes: [
|
|
123
|
-
{
|
|
124
|
-
name: 'distribution_point',
|
|
125
|
-
isPrimaryKey: true,
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'issuer_dn',
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: 'crl_blob',
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'this_update',
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
name: 'next_update',
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
name: 'signature_valid',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'expiresAt',
|
|
144
|
-
expiresAt: true,
|
|
145
|
-
indexed: true,
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
});
|
|
149
|
-
|
|
114
|
+
crlCacheTable = declareCRLCacheTable();
|
|
150
115
|
// Configure the caching source
|
|
151
116
|
(crlCacheTable as any).sourcedFrom(CertificateRevocationListSource);
|
|
152
117
|
}
|
|
@@ -154,44 +119,7 @@ function getCRLCacheTable() {
|
|
|
154
119
|
}
|
|
155
120
|
|
|
156
121
|
function getRevokedCertificateTable() {
|
|
157
|
-
|
|
158
|
-
revokedCertificateTable = table({
|
|
159
|
-
table: 'hdb_revoked_certificates',
|
|
160
|
-
database: 'system',
|
|
161
|
-
attributes: [
|
|
162
|
-
{
|
|
163
|
-
name: 'composite_id',
|
|
164
|
-
isPrimaryKey: true,
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
name: 'serial_number',
|
|
168
|
-
indexed: true,
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: 'issuer_key_id',
|
|
172
|
-
indexed: true,
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
name: 'revocation_date',
|
|
176
|
-
},
|
|
177
|
-
{
|
|
178
|
-
name: 'revocation_reason',
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: 'crl_source',
|
|
182
|
-
indexed: true, // Links to CRL cache
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
name: 'crl_next_update',
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
name: 'expiresAt',
|
|
189
|
-
expiresAt: true,
|
|
190
|
-
indexed: true,
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
});
|
|
194
|
-
}
|
|
122
|
+
revokedCertificateTable ??= declareRevokedCertificatesTable();
|
|
195
123
|
return revokedCertificateTable;
|
|
196
124
|
}
|
|
197
125
|
|
|
@@ -345,9 +273,15 @@ export async function performCRLCheck(
|
|
|
345
273
|
const crlStatus = await checkCRLFreshness(distributionPoints, issuerPem, config);
|
|
346
274
|
|
|
347
275
|
if (crlStatus.upToDate) {
|
|
348
|
-
// CRL
|
|
349
|
-
//
|
|
350
|
-
|
|
276
|
+
// A CRL this check downloaded decides it: by now another thread may have replaced the stored set with
|
|
277
|
+
// a different generation of that CRL
|
|
278
|
+
if (crlStatus.revokedIds) {
|
|
279
|
+
return crlStatus.revokedIds.has(compositeId)
|
|
280
|
+
? { status: 'revoked', reason: 'unspecified', source: crlStatus.source }
|
|
281
|
+
: { status: 'good', source: crlStatus.source };
|
|
282
|
+
}
|
|
283
|
+
// Outside the verdict fill's transaction: on LMDB its read snapshot predates rows committed since
|
|
284
|
+
const revokedEntryFresh = await (revokedTable as any).get(compositeId, {});
|
|
351
285
|
if (revokedEntryFresh) {
|
|
352
286
|
const entry = revokedEntryFresh as any;
|
|
353
287
|
const now = Date.now();
|
|
@@ -392,7 +326,7 @@ async function checkCRLFreshness(
|
|
|
392
326
|
distributionPoints: string[],
|
|
393
327
|
issuerPem: string,
|
|
394
328
|
config: CRLConfig
|
|
395
|
-
): Promise<{ upToDate: boolean; reason?: string; source?: string }> {
|
|
329
|
+
): Promise<{ upToDate: boolean; reason?: string; source?: string; revokedIds?: Set<string> }> {
|
|
396
330
|
const now = Date.now();
|
|
397
331
|
|
|
398
332
|
// Check each distribution point
|
|
@@ -416,8 +350,9 @@ async function checkCRLFreshness(
|
|
|
416
350
|
}
|
|
417
351
|
|
|
418
352
|
// If no valid cached CRL, download and parse fresh
|
|
353
|
+
let revokedIds: Set<string> | undefined;
|
|
419
354
|
if (!crlData) {
|
|
420
|
-
crlData = await
|
|
355
|
+
({ entry: crlData, revokedIds } = await downloadAndParseCRLOnce(distributionPoint, issuerPem, config));
|
|
421
356
|
}
|
|
422
357
|
|
|
423
358
|
// Check if CRL is current
|
|
@@ -426,15 +361,15 @@ async function checkCRLFreshness(
|
|
|
426
361
|
// Store in cache for future use (only if we downloaded it fresh)
|
|
427
362
|
if (!cachedCRL) {
|
|
428
363
|
try {
|
|
429
|
-
await (crlTable as any).put(distributionPoint, crlData);
|
|
364
|
+
await (crlTable as any).put(distributionPoint, crlData, { expiresAt: crlExpiry });
|
|
430
365
|
} catch {
|
|
431
366
|
// Failed to cache, but continue anyway
|
|
432
367
|
}
|
|
433
368
|
}
|
|
434
369
|
|
|
435
|
-
return { upToDate: true, source: distributionPoint };
|
|
370
|
+
return { upToDate: true, source: distributionPoint, revokedIds };
|
|
436
371
|
} else if (crlExpiry + config.gracePeriod > now) {
|
|
437
|
-
return { upToDate: true, source: distributionPoint };
|
|
372
|
+
return { upToDate: true, source: distributionPoint, revokedIds };
|
|
438
373
|
} else {
|
|
439
374
|
return { upToDate: false, reason: 'crl-expired' };
|
|
440
375
|
}
|
|
@@ -451,23 +386,43 @@ async function checkCRLFreshness(
|
|
|
451
386
|
return { upToDate: false, reason: 'no-current-crl-data' };
|
|
452
387
|
}
|
|
453
388
|
|
|
389
|
+
// Concurrent checks of certificates from one CA share one download and replacement per distribution point on
|
|
390
|
+
// this thread; each replacing the same rows would make their commits conflict, and past the retry limit that
|
|
391
|
+
// fails the check. The key holds every input the download uses, so no check inherits another's deadline.
|
|
392
|
+
const crlDownloads = new Map<string, Promise<DownloadedCRL>>();
|
|
393
|
+
|
|
394
|
+
function downloadAndParseCRLOnce(distributionPoint: string, issuerPemStr: string, config: CRLConfig) {
|
|
395
|
+
const key = `${distributionPoint}\n${config.gracePeriod}\n${config.timeout}\n${issuerPemStr}`;
|
|
396
|
+
let download = crlDownloads.get(key);
|
|
397
|
+
if (!download) {
|
|
398
|
+
download = downloadAndParseCRL(distributionPoint, issuerPemStr, config).finally(() => crlDownloads.delete(key));
|
|
399
|
+
crlDownloads.set(key, download);
|
|
400
|
+
}
|
|
401
|
+
return download;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
interface DownloadedCRL {
|
|
405
|
+
entry: CRLCacheEntry;
|
|
406
|
+
revokedIds: Set<string>;
|
|
407
|
+
}
|
|
408
|
+
|
|
454
409
|
/**
|
|
455
410
|
* Download and parse a CRL from a distribution point
|
|
456
411
|
* @param distributionPoint - CRL URL
|
|
457
412
|
* @param issuerPemStr - Issuer certificate for signature verification
|
|
458
|
-
* @param
|
|
459
|
-
* @returns Parsed CRL entry for caching
|
|
413
|
+
* @param config - CRL configuration (download timeout, grace period)
|
|
414
|
+
* @returns Parsed CRL entry for caching, and the certificates it revokes
|
|
460
415
|
*/
|
|
461
416
|
async function downloadAndParseCRL(
|
|
462
417
|
distributionPoint: string,
|
|
463
418
|
issuerPemStr: string,
|
|
464
|
-
|
|
465
|
-
): Promise<
|
|
419
|
+
config: CRLConfig
|
|
420
|
+
): Promise<DownloadedCRL> {
|
|
466
421
|
// Download the CRL
|
|
467
422
|
// Note: Using fetch here since CRL downloads are cached and infrequent
|
|
468
423
|
// (typically one per CA), so this is not a hot path
|
|
469
424
|
const controller = new AbortController();
|
|
470
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
425
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
|
471
426
|
|
|
472
427
|
try {
|
|
473
428
|
const response = await fetch(distributionPoint, {
|
|
@@ -477,8 +432,6 @@ async function downloadAndParseCRL(
|
|
|
477
432
|
},
|
|
478
433
|
});
|
|
479
434
|
|
|
480
|
-
clearTimeout(timeoutId);
|
|
481
|
-
|
|
482
435
|
if (!response.ok) {
|
|
483
436
|
throw new Error(`CRL download failed: ${response.status}`);
|
|
484
437
|
}
|
|
@@ -523,93 +476,80 @@ async function downloadAndParseCRL(
|
|
|
523
476
|
this_update: thisUpdate,
|
|
524
477
|
next_update: nextUpdate,
|
|
525
478
|
signature_valid: signatureValid,
|
|
526
|
-
expiresAt: nextUpdate,
|
|
527
479
|
};
|
|
528
480
|
|
|
529
481
|
// Process revoked certificates before returning so the revoked table is populated
|
|
530
482
|
// before any subsequent lookup in performCRLCheck
|
|
531
|
-
await processRevokedCertificates(
|
|
532
|
-
|
|
533
|
-
|
|
483
|
+
const revokedIds = await processRevokedCertificates(
|
|
484
|
+
crl,
|
|
485
|
+
issuerPemStr,
|
|
486
|
+
distributionPoint,
|
|
487
|
+
nextUpdate,
|
|
488
|
+
config.gracePeriod ?? 0
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
return { entry: cacheEntry, revokedIds };
|
|
534
492
|
} finally {
|
|
535
493
|
clearTimeout(timeoutId);
|
|
536
494
|
}
|
|
537
495
|
}
|
|
538
496
|
|
|
539
497
|
/**
|
|
540
|
-
*
|
|
498
|
+
* Replace this CRL's rows in the revoked certificates table with its current revocations
|
|
541
499
|
* @param crl - Parsed CRL object
|
|
542
500
|
* @param issuerPemStr - Issuer certificate PEM
|
|
543
501
|
* @param distributionPoint - CRL distribution point URL
|
|
544
502
|
* @param nextUpdate - When this CRL expires
|
|
503
|
+
* @param gracePeriod - How long past nextUpdate performCRLCheck still honors a revocation
|
|
504
|
+
* @returns The composite ids of the revoked certificates
|
|
545
505
|
*/
|
|
546
506
|
async function processRevokedCertificates(
|
|
547
507
|
crl: pkijs.CertificateRevocationList,
|
|
548
508
|
issuerPemStr: string,
|
|
549
509
|
distributionPoint: string,
|
|
550
|
-
nextUpdate: number
|
|
551
|
-
|
|
510
|
+
nextUpdate: number,
|
|
511
|
+
gracePeriod: number
|
|
512
|
+
): Promise<Set<string>> {
|
|
552
513
|
const revokedTable = getRevokedCertificateTable();
|
|
553
514
|
const issuerKeyId = extractIssuerKeyId(issuerPemStr);
|
|
554
515
|
const cacheKey = distributionPoint;
|
|
516
|
+
const revokedIds = new Set<string>();
|
|
555
517
|
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
|
|
518
|
+
// All or nothing, so a failure leaves the previous revocations rather than a partial set that would read
|
|
519
|
+
// as good. Its own transaction, not the verdict fill's, so the rows are committed before performCRLCheck
|
|
520
|
+
// reads them back; the context's expiresAt is what each row stores as its expiry.
|
|
521
|
+
await transaction({ expiresAt: nextUpdate + gracePeriod }, async () => {
|
|
559
522
|
await clearExistingCRLEntries(revokedTable, cacheKey);
|
|
560
|
-
} catch (error) {
|
|
561
|
-
logger.warn?.(`Failed to clear existing CRL entries: ${error}`);
|
|
562
|
-
// Continue with processing - partial cleanup is better than no update
|
|
563
|
-
}
|
|
564
523
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const serialHex = revokedCert.userCertificate.valueBlock.valueHexView;
|
|
572
|
-
|
|
573
|
-
if (!serialHex) {
|
|
574
|
-
logger.warn?.('Could not extract serial number from revoked certificate');
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
524
|
+
for (const revokedCert of crl.revokedCertificates ?? []) {
|
|
525
|
+
// Extract serial number using PKI.js - same method as extractSerialNumber() function
|
|
526
|
+
// This gives us the clean serial number without ASN.1 encoding
|
|
527
|
+
const serialHex = revokedCert.userCertificate.valueBlock.valueHexView;
|
|
528
|
+
if (!serialHex)
|
|
529
|
+
throw new Error(`A revoked certificate in the CRL from ${distributionPoint} has no serial number`);
|
|
577
530
|
|
|
578
|
-
|
|
579
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
580
|
-
.join('');
|
|
581
|
-
|
|
582
|
-
const compositeId = createRevokedCertificateId(issuerKeyId, serialNumber);
|
|
583
|
-
|
|
584
|
-
// Extract revocation date
|
|
585
|
-
const revocationDate = revokedCert.revocationDate.value.getTime();
|
|
531
|
+
const serialNumber = Buffer.from(serialHex).toString('hex');
|
|
586
532
|
|
|
533
|
+
const entry: RevokedCertificateEntry = {
|
|
534
|
+
composite_id: createRevokedCertificateId(issuerKeyId, serialNumber),
|
|
535
|
+
serial_number: serialNumber,
|
|
536
|
+
issuer_key_id: issuerKeyId,
|
|
537
|
+
revocation_date: revokedCert.revocationDate.value.getTime(),
|
|
587
538
|
// For now, skip complex extension parsing and just use default reason
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
serial_number: serialNumber,
|
|
593
|
-
issuer_key_id: issuerKeyId,
|
|
594
|
-
revocation_date: revocationDate,
|
|
595
|
-
revocation_reason: revocationReason,
|
|
596
|
-
crl_source: cacheKey,
|
|
597
|
-
crl_next_update: nextUpdate,
|
|
598
|
-
expiresAt: nextUpdate,
|
|
599
|
-
};
|
|
539
|
+
revocation_reason: 'unspecified',
|
|
540
|
+
crl_source: cacheKey,
|
|
541
|
+
crl_next_update: nextUpdate,
|
|
542
|
+
};
|
|
600
543
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
logger.warn?.(`Failed to process revoked certificate: ${error}`);
|
|
604
|
-
// Continue with other certificates
|
|
605
|
-
}
|
|
544
|
+
revokedIds.add(entry.composite_id);
|
|
545
|
+
await (revokedTable as any).put(entry.composite_id, entry);
|
|
606
546
|
}
|
|
607
|
-
}
|
|
547
|
+
});
|
|
548
|
+
return revokedIds;
|
|
608
549
|
}
|
|
609
550
|
|
|
610
551
|
/**
|
|
611
|
-
*
|
|
612
|
-
* This ensures data consistency when CRLs are updated and certificates are removed
|
|
552
|
+
* Delete the revoked certificate entries stored for a specific CRL source
|
|
613
553
|
* @param revokedTable - Harper table for revoked certificates
|
|
614
554
|
* @param crlSource - CRL cache key to identify entries to remove
|
|
615
555
|
*/
|
|
@@ -617,27 +557,9 @@ async function clearExistingCRLEntries(
|
|
|
617
557
|
revokedTable: ReturnType<typeof getRevokedCertificateTable>,
|
|
618
558
|
crlSource: string
|
|
619
559
|
): Promise<void> {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
// Note: This assumes the crl_source field is indexed (which it is)
|
|
624
|
-
const existingEntries = (revokedTable as any).search([
|
|
625
|
-
{
|
|
626
|
-
attribute: 'crl_source',
|
|
627
|
-
value: crlSource,
|
|
628
|
-
},
|
|
629
|
-
]);
|
|
630
|
-
|
|
631
|
-
for await (const entry of existingEntries) {
|
|
632
|
-
try {
|
|
633
|
-
await (revokedTable as any).delete((entry as any).composite_id);
|
|
634
|
-
} catch (deleteError) {
|
|
635
|
-
logger.warn?.(`Failed to delete revoked certificate entry: ${deleteError}`);
|
|
636
|
-
// Continue with other entries
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
} catch (searchError) {
|
|
640
|
-
logger.error?.(`Failed to search for existing CRL entries: ${searchError}`);
|
|
641
|
-
throw searchError;
|
|
560
|
+
const compositeIds: string[] = [];
|
|
561
|
+
for await (const entry of (revokedTable as any).search([{ attribute: 'crl_source', value: crlSource }])) {
|
|
562
|
+
compositeIds.push((entry as any).composite_id);
|
|
642
563
|
}
|
|
564
|
+
for (const compositeId of compositeIds) await (revokedTable as any).delete(compositeId);
|
|
643
565
|
}
|
|
@@ -34,7 +34,6 @@ export interface CertificateCacheEntry {
|
|
|
34
34
|
status: CertificateStatus;
|
|
35
35
|
reason?: string;
|
|
36
36
|
checked_at: number;
|
|
37
|
-
expiresAt: number;
|
|
38
37
|
method: VerificationMethod;
|
|
39
38
|
}
|
|
40
39
|
|
|
@@ -45,7 +44,6 @@ export interface CRLCacheEntry {
|
|
|
45
44
|
this_update: number;
|
|
46
45
|
next_update: number;
|
|
47
46
|
signature_valid: boolean;
|
|
48
|
-
expiresAt: number;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
export interface RevokedCertificateEntry {
|
|
@@ -56,7 +54,6 @@ export interface RevokedCertificateEntry {
|
|
|
56
54
|
revocation_reason?: string;
|
|
57
55
|
crl_source: string; // Links to CRLCacheEntry.crl_id
|
|
58
56
|
crl_next_update: number;
|
|
59
|
-
expiresAt: number;
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
export interface CertificateChainEntry {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tables certificate verification caches into. Every node declares them in this shape at every writable
|
|
3
|
+
* start, whether or not it ever verifies a certificate: they replicate, and only an `expiration` stored in the
|
|
4
|
+
* node's own catalog arms the cleanup scan that removes the rows replicated to it. See dataLayer/DESIGN.md,
|
|
5
|
+
* "System table bootstrap".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { databases, table, type Table } from '../../resources/databases.ts';
|
|
9
|
+
import { ServerError } from '../../utility/errors/hdbError.ts';
|
|
10
|
+
import { SYSTEM_SCHEMA_NAME } from '../../utility/hdbTerms.ts';
|
|
11
|
+
import { CRL_DEFAULTS, OCSP_DEFAULTS } from './configValidation.ts';
|
|
12
|
+
import { CRL_DEFAULT_VALIDITY_PERIOD } from './verificationConfig.ts';
|
|
13
|
+
|
|
14
|
+
export const CERTIFICATE_CACHE_TABLE = 'hdb_certificate_cache';
|
|
15
|
+
export const CRL_CACHE_TABLE = 'hdb_crl_cache';
|
|
16
|
+
export const REVOKED_CERTIFICATES_TABLE = 'hdb_revoked_certificates';
|
|
17
|
+
|
|
18
|
+
// A table's expiration bounds only a row written without an expiry of its own; every writer here sets one.
|
|
19
|
+
// For a verdict, the shorter of the two methods' default lifetimes.
|
|
20
|
+
const CERTIFICATE_CACHE_EXPIRATION_SECONDS = Math.min(OCSP_DEFAULTS.cacheTtl, CRL_DEFAULTS.cacheTtl) / 1000;
|
|
21
|
+
const CRL_CACHE_EXPIRATION_SECONDS = CRL_DEFAULTS.cacheTtl / 1000;
|
|
22
|
+
// Never shorter than a revocation's own window, which would admit a revoked certificate early.
|
|
23
|
+
const REVOKED_CERTIFICATES_EXPIRATION_SECONDS = (CRL_DEFAULT_VALIDITY_PERIOD + CRL_DEFAULTS.gracePeriod) / 1000;
|
|
24
|
+
|
|
25
|
+
const MAX_OUTSTANDING_EVICTIONS = 100;
|
|
26
|
+
|
|
27
|
+
export function declareCertificateCacheTable(): Table {
|
|
28
|
+
return table<Table>({
|
|
29
|
+
table: CERTIFICATE_CACHE_TABLE,
|
|
30
|
+
database: SYSTEM_SCHEMA_NAME,
|
|
31
|
+
schemaDefined: true,
|
|
32
|
+
expiration: CERTIFICATE_CACHE_EXPIRATION_SECONDS,
|
|
33
|
+
attributes: [
|
|
34
|
+
{ name: 'certificate_id', isPrimaryKey: true },
|
|
35
|
+
{ name: 'status' },
|
|
36
|
+
{ name: 'reason' },
|
|
37
|
+
{ name: 'checked_at' },
|
|
38
|
+
{ name: 'method' },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function declareCRLCacheTable(): Table {
|
|
44
|
+
return table<Table>({
|
|
45
|
+
table: CRL_CACHE_TABLE,
|
|
46
|
+
database: SYSTEM_SCHEMA_NAME,
|
|
47
|
+
schemaDefined: true,
|
|
48
|
+
expiration: CRL_CACHE_EXPIRATION_SECONDS,
|
|
49
|
+
attributes: [
|
|
50
|
+
{ name: 'distribution_point', isPrimaryKey: true },
|
|
51
|
+
{ name: 'issuer_dn' },
|
|
52
|
+
{ name: 'crl_blob' },
|
|
53
|
+
{ name: 'this_update' },
|
|
54
|
+
{ name: 'next_update' },
|
|
55
|
+
{ name: 'signature_valid' },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function declareRevokedCertificatesTable(): Table {
|
|
61
|
+
return table<Table>({
|
|
62
|
+
table: REVOKED_CERTIFICATES_TABLE,
|
|
63
|
+
database: SYSTEM_SCHEMA_NAME,
|
|
64
|
+
schemaDefined: true,
|
|
65
|
+
expiration: REVOKED_CERTIFICATES_EXPIRATION_SECONDS,
|
|
66
|
+
attributes: [
|
|
67
|
+
{ name: 'composite_id', isPrimaryKey: true },
|
|
68
|
+
{ name: 'serial_number', indexed: true },
|
|
69
|
+
{ name: 'issuer_key_id', indexed: true },
|
|
70
|
+
{ name: 'revocation_date' },
|
|
71
|
+
{ name: 'revocation_reason' },
|
|
72
|
+
{ name: 'crl_source', indexed: true },
|
|
73
|
+
{ name: 'crl_next_update' },
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Declares the three tables and waits for any index backfill that starts, reading its outcome back from the
|
|
80
|
+
* catalog because a failed backfill still settles. After the first declaration on a node, it reclaims the
|
|
81
|
+
* verdicts cached before a verdict carried its own expiry.
|
|
82
|
+
*/
|
|
83
|
+
export async function ensureCertificateVerificationTables(): Promise<void> {
|
|
84
|
+
const reclaimLegacyVerdicts = databases.system?.[CERTIFICATE_CACHE_TABLE]?.expirationMS === undefined;
|
|
85
|
+
const declared = [declareCertificateCacheTable(), declareCRLCacheTable(), declareRevokedCertificatesTable()];
|
|
86
|
+
if (reclaimLegacyVerdicts) await evictVerdictsWithoutExpiry(declared[0]);
|
|
87
|
+
const incomplete: string[] = [];
|
|
88
|
+
for (const Table of declared) {
|
|
89
|
+
await Table.indexingOperation;
|
|
90
|
+
for (const { name, indexed } of Table.attributes) {
|
|
91
|
+
if (!indexed) continue;
|
|
92
|
+
const descriptor = Table.dbisDB.getSync(`${Table.tableName}/${name}`);
|
|
93
|
+
if (descriptor?.indexingFailed || descriptor?.indexingPID) incomplete.push(`${Table.tableName}.${name}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (incomplete.length > 0) {
|
|
97
|
+
throw new ServerError(
|
|
98
|
+
`The index backfill of system.${incomplete.join(', system.')} did not complete; the next start retries it`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Those verdicts were stored with no expiry, so they would never be removed. Their keys are never read again
|
|
105
|
+
* (createCacheKey hashes a key version), so this only reclaims their space, with local evictions.
|
|
106
|
+
*/
|
|
107
|
+
async function evictVerdictsWithoutExpiry(CertificateCache: any): Promise<void> {
|
|
108
|
+
const outstanding: unknown[] = new Array(MAX_OUTSTANDING_EVICTIONS);
|
|
109
|
+
let evictions = 0;
|
|
110
|
+
for (const { key, value, version, expiresAt } of CertificateCache.primaryStore.getRange({
|
|
111
|
+
start: false,
|
|
112
|
+
versions: true,
|
|
113
|
+
snapshot: false,
|
|
114
|
+
lazy: true,
|
|
115
|
+
})) {
|
|
116
|
+
if (value == null || expiresAt != undefined) continue;
|
|
117
|
+
const slot = evictions++ % MAX_OUTSTANDING_EVICTIONS;
|
|
118
|
+
await outstanding[slot];
|
|
119
|
+
outstanding[slot] = CertificateCache.evict(key, value, version);
|
|
120
|
+
// yields between evictions, as the table's own cleanup scan does, so LMDB can renew its read transaction
|
|
121
|
+
await new Promise(setImmediate);
|
|
122
|
+
}
|
|
123
|
+
await Promise.all(outstanding);
|
|
124
|
+
}
|
|
@@ -6,7 +6,7 @@ import { createHash } from 'node:crypto';
|
|
|
6
6
|
import * as pkijs from 'pkijs';
|
|
7
7
|
import * as asn1js from 'asn1js';
|
|
8
8
|
import { loggerWithTag } from '../../utility/logging/logger.ts';
|
|
9
|
-
import {
|
|
9
|
+
import { declareCertificateCacheTable } from './verificationTables.ts';
|
|
10
10
|
import type { PeerCertificate, CertificateChainEntry } from './types.ts';
|
|
11
11
|
|
|
12
12
|
const logger = loggerWithTag('cert-verification-utils');
|
|
@@ -297,6 +297,10 @@ export function pemToBuffer(pem: string): ArrayBuffer {
|
|
|
297
297
|
return buffer;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
// A verdict cached before verdicts carried their own expiry has none and would read as fresh forever, so the
|
|
301
|
+
// keys it was stored under are never read again.
|
|
302
|
+
const VERDICT_KEY_VERSION = 2;
|
|
303
|
+
|
|
300
304
|
/**
|
|
301
305
|
* Create a cache key for certificate verification
|
|
302
306
|
* @param certPem - Certificate in PEM format
|
|
@@ -316,6 +320,7 @@ export function createCacheKey(
|
|
|
316
320
|
issuerPem,
|
|
317
321
|
method,
|
|
318
322
|
...additionalData,
|
|
323
|
+
keyVersion: VERDICT_KEY_VERSION,
|
|
319
324
|
};
|
|
320
325
|
const cacheKeyHash = createHash('sha256').update(JSON.stringify(cacheData)).digest('hex');
|
|
321
326
|
return `${method}:${cacheKeyHash}`;
|
|
@@ -410,38 +415,9 @@ export function extractIssuerKeyId(certPem: string): string {
|
|
|
410
415
|
* Get shared certificate verification cache table
|
|
411
416
|
* @returns Harper table instance for certificate verification cache
|
|
412
417
|
*/
|
|
413
|
-
|
|
414
|
-
let certificateCacheTable: ReturnType<typeof table> | null = null;
|
|
418
|
+
let certificateCacheTable: ReturnType<typeof declareCertificateCacheTable> | null = null;
|
|
415
419
|
|
|
416
420
|
export function getCertificateCacheTable() {
|
|
417
|
-
|
|
418
|
-
certificateCacheTable = table({
|
|
419
|
-
table: 'hdb_certificate_cache',
|
|
420
|
-
database: 'system',
|
|
421
|
-
attributes: [
|
|
422
|
-
{
|
|
423
|
-
name: 'certificate_id',
|
|
424
|
-
isPrimaryKey: true,
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
name: 'status', // 'good', 'revoked', 'unknown'
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
name: 'reason',
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: 'checked_at',
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: 'expiresAt',
|
|
437
|
-
expiresAt: true,
|
|
438
|
-
indexed: true,
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
name: 'method', // 'ocsp' or 'crl'
|
|
442
|
-
},
|
|
443
|
-
],
|
|
444
|
-
});
|
|
445
|
-
}
|
|
421
|
+
certificateCacheTable ??= declareCertificateCacheTable();
|
|
446
422
|
return certificateCacheTable;
|
|
447
423
|
}
|
package/security/jsLoader.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
realpathSync,
|
|
33
33
|
} from 'node:fs';
|
|
34
34
|
import { EventEmitter } from 'node:events';
|
|
35
|
-
import { whenComponentsLoaded } from '../server/threads/threadServer.js';
|
|
35
|
+
import { whenComponentsLoaded, bootLoadsComponents } from '../server/threads/threadServer.js';
|
|
36
36
|
import { thisThreadOwnsApplication } from '../server/threads/isolatedApplications.ts';
|
|
37
37
|
|
|
38
38
|
type Lockdown = 'none' | 'freeze' | 'ses' | 'freeze-after-load';
|
|
@@ -88,6 +88,14 @@ export async function scopedImport(filePath: string | URL, scope?: ApplicationSc
|
|
|
88
88
|
return runWithComponentBinding(scope?.name, () => importScoped(moduleUrl, scope));
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* True where `freeze-after-load` freezes this thread's intrinsics once its boot load finishes, so no later
|
|
93
|
+
* load here matches a boot load.
|
|
94
|
+
*/
|
|
95
|
+
export function laterLoadsMeetFrozenIntrinsics(): boolean {
|
|
96
|
+
return APPLICATIONS_LOCKDOWN === 'freeze-after-load' && bootLoadsComponents();
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
async function importScoped(moduleUrl: string, scope?: ApplicationScope) {
|
|
92
100
|
try {
|
|
93
101
|
const loaderMode = scope?.mode;
|