// Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { existsSync } from 'fs'; import { join } from 'path'; import { assert } from 'chai'; import { copy } from 'fs-extra'; import { _getFileHash, getSignaturePath, loadHexFromPath, verifySignature, writeHexToPath, writeSignature, } from '../../updater/signature'; import { createTempDir, deleteTempDir } from '../../updater/common'; import { keyPair } from '../../updater/curve'; import * as log from '../../logging/log'; describe('updater/signatures', () => { it('_getFileHash returns correct hash', async () => { const filePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const expected = '7bc77f27d92d00b4a1d57c480ca86dacc43d57bc318339c92119d1fbf6b557a5'; const hash = await _getFileHash(filePath); assert.strictEqual(expected, Buffer.from(hash).toString('hex')); }); it('roundtrips binary file writes', async () => { let tempDir; try { tempDir = await createTempDir(); const path = join(tempDir, 'something.bin'); const { publicKey } = keyPair(); await writeHexToPath(path, publicKey); const fromDisk = await loadHexFromPath(path); assert.strictEqual( Buffer.from(fromDisk).compare(Buffer.from(publicKey)), 0 ); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); it('roundtrips signature', async () => { let tempDir; try { tempDir = await createTempDir(); const version = 'v1.23.2'; const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const updatePath = join(tempDir, 'ghost-kitty.mp4'); await copy(sourcePath, updatePath); const privateKeyPath = join(tempDir, 'private.key'); const { publicKey, privateKey } = keyPair(); await writeHexToPath(privateKeyPath, privateKey); const signature = await writeSignature( updatePath, version, privateKeyPath ); const signaturePath = getSignaturePath(updatePath); assert.strictEqual(existsSync(signaturePath), true); const verified = await verifySignature( updatePath, version, signature, publicKey ); assert.strictEqual(verified, true); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); it('fails signature verification if version changes', async () => { let tempDir; try { tempDir = await createTempDir(); const version = 'v1.23.2'; const brokenVersion = 'v1.23.3'; const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const updatePath = join(tempDir, 'ghost-kitty.mp4'); await copy(sourcePath, updatePath); const privateKeyPath = join(tempDir, 'private.key'); const { publicKey, privateKey } = keyPair(); await writeHexToPath(privateKeyPath, privateKey); const signature = await writeSignature( updatePath, version, privateKeyPath ); const verified = await verifySignature( updatePath, brokenVersion, signature, publicKey ); assert.strictEqual(verified, false); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); it('fails signature verification if signature tampered with', async () => { let tempDir; try { tempDir = await createTempDir(); const version = 'v1.23.2'; const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const updatePath = join(tempDir, 'ghost-kitty.mp4'); await copy(sourcePath, updatePath); const privateKeyPath = join(tempDir, 'private.key'); const { publicKey, privateKey } = keyPair(); await writeHexToPath(privateKeyPath, privateKey); const signature = await writeSignature( updatePath, version, privateKeyPath ); signature[4] += 3; const verified = await verifySignature( updatePath, version, signature, publicKey ); assert.strictEqual(verified, false); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); it('fails signature verification if binary file tampered with', async () => { let tempDir; try { tempDir = await createTempDir(); const version = 'v1.23.2'; const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const updatePath = join(tempDir, 'ghost-kitty.mp4'); await copy(sourcePath, updatePath); const privateKeyPath = join(tempDir, 'private.key'); const { publicKey, privateKey } = keyPair(); await writeHexToPath(privateKeyPath, privateKey); const signature = await writeSignature( updatePath, version, privateKeyPath ); const brokenSourcePath = join( __dirname, '../../../fixtures/pixabay-Soap-Bubble-7141.mp4' ); await copy(brokenSourcePath, updatePath); const verified = await verifySignature( updatePath, version, signature, publicKey ); assert.strictEqual(verified, false); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); it('fails signature verification if signed by different key', async () => { let tempDir; try { tempDir = await createTempDir(); const version = 'v1.23.2'; const sourcePath = join(__dirname, '../../../fixtures/ghost-kitty.mp4'); const updatePath = join(tempDir, 'ghost-kitty.mp4'); await copy(sourcePath, updatePath); const privateKeyPath = join(tempDir, 'private.key'); const { publicKey } = keyPair(); const { privateKey } = keyPair(); await writeHexToPath(privateKeyPath, privateKey); const signature = await writeSignature( updatePath, version, privateKeyPath ); const verified = await verifySignature( updatePath, version, signature, publicKey ); assert.strictEqual(verified, false); } finally { if (tempDir) { await deleteTempDir(log, tempDir); } } }); });