diff --git a/src/lib/security/sri-hash-calculator.ts b/src/lib/security/sri-hash-calculator.ts new file mode 100644 index 0000000..2cabedd --- /dev/null +++ b/src/lib/security/sri-hash-calculator.ts @@ -0,0 +1,29 @@ +import { createHash } from 'node:crypto'; + +export type SriHashAlgorithm = 'sha384' | 'sha512'; + +/** + * Generates a Subresource Integrity (SRI) hash for content. + * + * @param content - The content to hash, as a string or Buffer. + * @param algorithm - The hashing algorithm. Defaults to SHA-384. + * @returns The SRI value in algorithm-Base64 format. + */ +export function generateSriHash( + content: string | Buffer, + algorithm: SriHashAlgorithm = 'sha384', +): string { + if (algorithm !== 'sha384' && algorithm !== 'sha512') { + throw new TypeError( + `Unsupported SRI hash algorithm: ${String(algorithm)}`, + ); + } + + if (typeof content !== 'string' && !Buffer.isBuffer(content)) { + throw new TypeError('SRI content must be a string or Buffer.'); + } + + const digest = createHash(algorithm).update(content).digest('base64'); + + return `${algorithm}-${digest}`; +} \ No newline at end of file diff --git a/tests/sri-hash-calculator.test.ts b/tests/sri-hash-calculator.test.ts new file mode 100644 index 0000000..4abda3e --- /dev/null +++ b/tests/sri-hash-calculator.test.ts @@ -0,0 +1,61 @@ + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { generateSriHash } from '../src/lib/security/sri-hash-calculator'; + +describe('generateSriHash', () => { + it('generates a SHA-384 hash by default', () => { + assert.strictEqual( + generateSriHash('hello'), + 'sha384-WeF0h3dEjGnea4ANejO7+5/xtGPkQ1TDVTvNucZm+pASWjx5+QOXvfX2oT3oKGhP', + ); + }); + + it('generates a SHA-512 hash when requested', () => { + assert.strictEqual( + generateSriHash('hello', 'sha512'), + 'sha512-m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw==', + ); + }); + + it('accepts Buffer content', () => { + assert.strictEqual( + generateSriHash(Buffer.from('hello')), + 'sha384-WeF0h3dEjGnea4ANejO7+5/xtGPkQ1TDVTvNucZm+pASWjx5+QOXvfX2oT3oKGhP', + ); + }); + + it('handles empty content', () => { + assert.strictEqual( + generateSriHash(''), + 'sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb', + ); + }); + + it('handles an empty Buffer', () => { + assert.strictEqual( + generateSriHash(Buffer.alloc(0)), + 'sha384-OLBgp1GsljhM2TJ+sbHjaiH9txEUvgdDTAzHv2P24donTt6/529l+9Ua0vFImLlb', + ); + }); + + it('rejects unsupported algorithms', () => { + assert.throws( + () => + generateSriHash( + 'hello', + 'sha256' as 'sha384', + ), + { + name: 'TypeError', + message: 'Unsupported SRI hash algorithm: sha256', + }, + ); + }); + + it('returns the selected algorithm in the SRI prefix', () => { + assert.ok(generateSriHash('hello').startsWith('sha384-')); + assert.ok(generateSriHash('hello', 'sha512').startsWith('sha512-')); + }); +});