116 lines
3 KiB
TypeScript
116 lines
3 KiB
TypeScript
// Copyright 2019-2020 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import { createHash } from 'crypto';
|
|
import {
|
|
createReadStream,
|
|
readFile as readFileCallback,
|
|
writeFile as writeFileCallback,
|
|
} from 'fs';
|
|
import { basename, dirname, join, resolve as resolvePath } from 'path';
|
|
|
|
import pify from 'pify';
|
|
|
|
import { sign, verify } from './curve';
|
|
|
|
const readFile = pify(readFileCallback);
|
|
const writeFile = pify(writeFileCallback);
|
|
|
|
export async function generateSignature(
|
|
updatePackagePath: string,
|
|
version: string,
|
|
privateKeyPath: string
|
|
): Promise<Buffer> {
|
|
const privateKey = await loadHexFromPath(privateKeyPath);
|
|
const message = await generateMessage(updatePackagePath, version);
|
|
|
|
return sign(privateKey, message);
|
|
}
|
|
|
|
export async function verifySignature(
|
|
updatePackagePath: string,
|
|
version: string,
|
|
publicKey: Buffer
|
|
): Promise<boolean> {
|
|
const signaturePath = getSignaturePath(updatePackagePath);
|
|
const signature = await loadHexFromPath(signaturePath);
|
|
const message = await generateMessage(updatePackagePath, version);
|
|
|
|
return verify(publicKey, message, signature);
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
async function generateMessage(
|
|
updatePackagePath: string,
|
|
version: string
|
|
): Promise<Buffer> {
|
|
const hash = await _getFileHash(updatePackagePath);
|
|
const messageString = `${Buffer.from(hash).toString('hex')}-${version}`;
|
|
|
|
return Buffer.from(messageString);
|
|
}
|
|
|
|
export async function writeSignature(
|
|
updatePackagePath: string,
|
|
version: string,
|
|
privateKeyPath: string
|
|
): Promise<void> {
|
|
const signaturePath = getSignaturePath(updatePackagePath);
|
|
const signature = await generateSignature(
|
|
updatePackagePath,
|
|
version,
|
|
privateKeyPath
|
|
);
|
|
await writeHexToPath(signaturePath, signature);
|
|
}
|
|
|
|
export async function _getFileHash(updatePackagePath: string): Promise<Buffer> {
|
|
const hash = createHash('sha256');
|
|
const stream = createReadStream(updatePackagePath);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
stream.on('data', data => {
|
|
hash.update(data);
|
|
});
|
|
stream.on('close', () => {
|
|
resolve(hash.digest());
|
|
});
|
|
stream.on('error', error => {
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
export function getSignatureFileName(fileName: string): string {
|
|
return `${fileName}.sig`;
|
|
}
|
|
|
|
export function getSignaturePath(updatePackagePath: string): string {
|
|
const updateFullPath = resolvePath(updatePackagePath);
|
|
const updateDir = dirname(updateFullPath);
|
|
const updateFileName = basename(updateFullPath);
|
|
|
|
return join(updateDir, getSignatureFileName(updateFileName));
|
|
}
|
|
|
|
export function hexToBinary(target: string): Buffer {
|
|
return Buffer.from(target, 'hex');
|
|
}
|
|
|
|
export function binaryToHex(data: Buffer): string {
|
|
return Buffer.from(data).toString('hex');
|
|
}
|
|
|
|
export async function loadHexFromPath(target: string): Promise<Buffer> {
|
|
const hexString = await readFile(target, 'utf8');
|
|
|
|
return hexToBinary(hexString);
|
|
}
|
|
|
|
export async function writeHexToPath(
|
|
target: string,
|
|
data: Buffer
|
|
): Promise<void> {
|
|
await writeFile(target, binaryToHex(data));
|
|
}
|