diff --git a/src/app/init/compute/Config.ts b/src/app/init/compute/Config.ts index 0950cdb..e5ad1ce 100644 --- a/src/app/init/compute/Config.ts +++ b/src/app/init/compute/Config.ts @@ -195,7 +195,7 @@ const Config = BaseModel.extend({ execSync(`Icacls "${this._config.privateKey}" /Grant:r "%username%":"(F)"`) // Source :: https://stackoverflow.com/questions/2928738/how-to-grant-permission-to-users-for-a-directory-using-command-line-in-windows }else{ - chmodSync(this._config.privateKey, 0o400); + chmodSync(this._config.privateKey, 0o600); } // console.log(this._config.privateKey); if (hasPasspharse == true) { diff --git a/src/app/load_save/compute/FolderEncrypt.ts b/src/app/load_save/compute/FolderEncrypt.ts new file mode 100644 index 0000000..7d17954 --- /dev/null +++ b/src/app/load_save/compute/FolderEncrypt.ts @@ -0,0 +1,119 @@ +import * as crypto from 'crypto'; +import * as tar from 'tar-fs'; +import * as fs from 'fs'; +import * as PATH from 'path'; +import { Writable } from 'stream'; + +const algo = 'aes-256-ctr'; + +export function encrypt(options: { + password?: string; + input: string; + output?: string | Writable; + cipher?: { + algo: string; + key: Buffer | string; + iv: Buffer | string; + } +}) { + return new Promise(async (resolve, reject) => { + if (!options.password && !options.cipher) return reject(`Missing password`); + if (!options.output) { + const input = PATH.parse(options.input); + options.output = PATH.join(input.dir, input.name + input.ext + '.encrypted'); + } + + try { + + let iv: Buffer, cipher: crypto.Cipher; + + if (options.cipher) { + iv = typeof options.cipher.iv === 'string' ? Buffer.from(options.cipher.iv) : options.cipher.iv; + cipher = crypto.createCipheriv(options.cipher.algo, options.cipher.key, options.cipher.iv); + } else { + iv = crypto.randomBytes(16) + cipher = crypto.createCipheriv(algo, crypto.createHash('sha256').update(options.password).digest(), iv); + } + + const writeStream = options.output instanceof Writable ? options.output : fs.createWriteStream(options.output as string), + isFile = await new Promise((resolve: (isFile: boolean) => void) => { + fs.stat(options.input, (err, stats) => { + if (err) return reject(err); + resolve(stats.isFile()); + }); + }); + + writeStream.write(Buffer.concat([Buffer.from(isFile ? 'F' : 'D'), iv]), (err) => { + if (err) return reject(err); + writeStream.on('finish', resolve); + (isFile ? fs.createReadStream : tar.pack)(options.input).on('error', reject).pipe(cipher).pipe(writeStream) + }); + } catch (err) { + reject(err); + } + }); +} + +export function decrypt(options: { + password?: string; + input: string; + output?: string; + cipher?: { + algo: string; + key: Buffer | string; + ivLength: number; + } +}) { + return new Promise(async (resolve, reject) => { + if (!options.password && !options.cipher) return reject(`Missing password`); + if (!options.output) { + const input = PATH.parse(options.input); + options.output = PATH.join(input.dir, input.name); + } + + try { + let cipher: crypto.Decipher; + + const head = await parseHead(options.input, options.cipher ? options.cipher.ivLength : 16); + + if (options.cipher) cipher = crypto.createDecipheriv(options.cipher.algo, options.cipher.key, head.iv); + else cipher = crypto.createDecipheriv(algo, crypto.createHash('sha256').update(options.password).digest(), head.iv); + + const readStream = fs.createReadStream(options.input, { start: options.cipher ? options.cipher.ivLength + 1 : 17 }), + outputStream = head.isFile ? fs.createWriteStream(options.output, { mode: 0o755 }) : tar.extract(options.output, { + readable: true, // all dirs and files should be readable + writable: true, // all dirs and files should be writable + }); + + outputStream.on('finish', resolve); + readStream.pipe(cipher).pipe(outputStream).on('error', reject); + } catch (err) { + reject(err); + } + }); +} + +function parseHead(path: string, ivLength: number) { + return new Promise((resolve: (head: { isFile: boolean; iv: Buffer }) => void, reject) => { + fs.open(path, 'r', (err, fd) => { + if (err) return reject(err); + let buffer = Buffer.alloc(ivLength + 1); + fs.read(fd, buffer, 0, ivLength + 1, 0, (err) => { + if (err) return reject(err); + const type = buffer.toString()[0]; + let head = { isFile: null, iv: buffer.slice(1) } + switch (type) { + case 'D': + head.isFile = false; + break; + case 'F': + head.isFile = true; + break; + default: + return reject(`Unknown type: ${type}`); + } + resolve(head); + }); + }); + }); +} \ No newline at end of file diff --git a/src/app/load_save/services/LoadSaveDataService.ts b/src/app/load_save/services/LoadSaveDataService.ts index b5dc812..1dfea4c 100644 --- a/src/app/load_save/services/LoadSaveDataService.ts +++ b/src/app/load_save/services/LoadSaveDataService.ts @@ -9,7 +9,7 @@ import Config, { ConfigInterface } from "../compute/Config"; import YAML from 'yaml'; import readdirp from "readdirp"; import filendir from 'filendir'; -import * as folderEncrypt from 'folder-encrypt'; +import * as folderEncrypt from '../compute/FolderEncrypt'; export interface LoadSaveServiceInterface extends BaseServiceInterface { _completeData: {