|
1 | 1 | import type { Logger } from '@objectstack/spec/contracts'; |
2 | 2 | import type { PluginMetadata } from '../plugin-loader.js'; |
3 | 3 |
|
| 4 | +// Conditionally import crypto for Node.js environments |
| 5 | +let cryptoModule: typeof import('crypto') | null = null; |
| 6 | +if (typeof window === 'undefined') { |
| 7 | + try { |
| 8 | + // Dynamic import for Node.js crypto module |
| 9 | + // eslint-disable-next-line @typescript-eslint/no-var-requires |
| 10 | + cryptoModule = require('crypto'); |
| 11 | + } catch { |
| 12 | + // Crypto module not available (e.g., browser environment) |
| 13 | + } |
| 14 | +} |
| 15 | + |
4 | 16 | /** |
5 | 17 | * Plugin Signature Configuration |
6 | 18 | * Controls how plugin signatures are verified |
@@ -217,18 +229,15 @@ export class PluginSignatureVerifier { |
217 | 229 | } |
218 | 230 |
|
219 | 231 | private computePluginHashNode(plugin: PluginMetadata): string { |
220 | | - // Import crypto dynamically to avoid breaking browser builds |
221 | | - try { |
222 | | - // eslint-disable-next-line @typescript-eslint/no-var-requires |
223 | | - const crypto = require('crypto'); |
224 | | - |
225 | | - // Compute hash of plugin code |
226 | | - const pluginCode = this.serializePluginCode(plugin); |
227 | | - return crypto.createHash('sha256').update(pluginCode).digest('hex'); |
228 | | - } catch (error) { |
229 | | - this.logger.warn('crypto module not available, using fallback hash', error as Error); |
| 232 | + // Use pre-loaded crypto module |
| 233 | + if (!cryptoModule) { |
| 234 | + this.logger.warn('crypto module not available, using fallback hash'); |
230 | 235 | return this.computePluginHashFallback(plugin); |
231 | 236 | } |
| 237 | + |
| 238 | + // Compute hash of plugin code |
| 239 | + const pluginCode = this.serializePluginCode(plugin); |
| 240 | + return cryptoModule.createHash('sha256').update(pluginCode).digest('hex'); |
232 | 241 | } |
233 | 242 |
|
234 | 243 | private computePluginHashBrowser(plugin: PluginMetadata): string { |
@@ -291,17 +300,32 @@ export class PluginSignatureVerifier { |
291 | 300 | signature: string, |
292 | 301 | publicKey: string |
293 | 302 | ): boolean { |
| 303 | + if (!cryptoModule) { |
| 304 | + this.logger.error('Crypto module not available for signature verification'); |
| 305 | + return false; |
| 306 | + } |
| 307 | + |
294 | 308 | try { |
295 | | - // eslint-disable-next-line @typescript-eslint/no-var-requires |
296 | | - const crypto = require('crypto'); |
297 | | - |
298 | 309 | // Create verify object based on algorithm |
299 | | - const algorithm = this.config.algorithm === 'ES256' ? 'sha256' : 'SHA256'; |
300 | | - const verify = crypto.createVerify(algorithm); |
301 | | - verify.update(data); |
302 | | - |
303 | | - // Verify signature |
304 | | - return verify.verify(publicKey, signature, 'base64'); |
| 310 | + if (this.config.algorithm === 'ES256') { |
| 311 | + // ECDSA verification |
| 312 | + const verify = cryptoModule.createVerify('SHA256'); |
| 313 | + verify.update(data); |
| 314 | + return verify.verify( |
| 315 | + { |
| 316 | + key: publicKey, |
| 317 | + format: 'pem', |
| 318 | + type: 'spki', |
| 319 | + }, |
| 320 | + signature, |
| 321 | + 'base64' |
| 322 | + ); |
| 323 | + } else { |
| 324 | + // RSA verification (RS256) |
| 325 | + const verify = cryptoModule.createVerify('RSA-SHA256'); |
| 326 | + verify.update(data); |
| 327 | + return verify.verify(publicKey, signature, 'base64'); |
| 328 | + } |
305 | 329 | } catch (error) { |
306 | 330 | this.logger.error('Signature verification failed', error as Error); |
307 | 331 | return false; |
|
0 commit comments