import { promises as fs } from "fs"; import { CryptContainer } from "./CryptContainer"; import * as crypto from "crypto"; import * as path from "path"; import * as bcrypt from "bcrypt"; import { CryptSettings } from "./CryptSettings"; import { Files } from "../files/Files"; import { RJLog } from "../log/RJLog"; export class CryptIO { static readonly encryptionSuffix = ".crypt"; static readonly debugOutput = true; static readonly debugOutputSuffix = ".crypt.json"; private static _cryptSettings:CryptSettings; static set cryptSettings( cryptSettings:CryptSettings ) { this._cryptSettings = cryptSettings; } static createUUID() { return crypto.randomUUID(); } static isDebugFilePath( filePath:string ):boolean { return filePath.endsWith( this.debugOutputSuffix ); } static async hash( data:string ):Promise { let hash = await bcrypt.hash( data, 10 ); return Promise.resolve( hash ); } static async verifyHash( data:string, hashed:string ):Promise { let isVerified = await bcrypt.compare( data, hashed ); return Promise.resolve( isVerified ); } static async encrypt( data:string, settings?:CryptSettings ):Promise { settings = settings || CryptIO._cryptSettings; const algorithm = 'aes-192-cbc'; const key = crypto.scryptSync( settings.publicKey, 'salt', 24 ); const cipher = crypto.createCipheriv( algorithm, key, settings.iv ); let encrypted = cipher.update( data, 'utf8', 'hex'); encrypted += cipher.final('hex'); return Promise.resolve( encrypted ); } static async decrypt( data:string, settings?:CryptSettings ) { settings = settings || CryptIO._cryptSettings; const algorithm = 'aes-192-cbc'; const key = crypto.scryptSync( settings.publicKey, 'salt', 24 ); const decipher = crypto.createDecipheriv( algorithm, key, settings.iv ); let decrypted = decipher.update( data, 'hex', 'utf8' ); decrypted += decipher.final('utf8'); return decrypted; } static async createWithRandomUUID( parentPath:string, data:any, onUUID?:( uuid:string, data:T ) => T ):Promise { let uuid = CryptIO.createUUID(); let fullPath = path.join( parentPath, uuid + this.encryptionSuffix ); let pathExists = await Files.exists( fullPath ); while ( pathExists ) { uuid = CryptIO.createUUID(); fullPath = path.join( parentPath, uuid + this.encryptionSuffix ); pathExists = await Files.exists( fullPath ); } if ( onUUID ) { data = onUUID( uuid, data ); } await CryptIO.saveJSON( fullPath, data ); return Promise.resolve( uuid ); } static async loadWithRandomUUID( parentPath:string, uuid:string ):Promise { let fullPath = path.join( parentPath, uuid + this.encryptionSuffix ); let fileExsits = await Files.exists( fullPath ); if ( ! fileExsits ) { return Promise.resolve( null ); } let data = await this.loadJSON( fullPath ); return data; } static async deleteWithRandomUUID( parentPath:string, uuid:string ):Promise { let fullPath = path.join( parentPath, uuid + this.encryptionSuffix ); let exists = await Files.exists( fullPath ); if ( ! exists ) { return Promise.resolve( false ); } await Files.deleteFile( fullPath ); return Promise.resolve( true ); } static async updateWithRandomUUID( parentPath:string, uuid:string, data:any ):Promise { let fullPath = path.join( parentPath, uuid + this.encryptionSuffix ); await this.saveJSON( fullPath, data ); return Promise.resolve(); } static async saveJSON( path:string, data:any ):Promise { let jsonString = JSON.stringify( data ); RJLog.log( "Saving ", data, ">>", jsonString, ); let container:CryptContainer = { key:"", data: await this.encrypt( jsonString ) }; let containerString = await this.encrypt( JSON.stringify( container ) ); await fs.writeFile( path, containerString ); if ( this.debugOutput ) { await fs.writeFile( path + this.debugOutputSuffix, jsonString ); } return Promise.resolve(); } static async saveUTF8( path:string, jsonString:string ):Promise { RJLog.log( "Saving ", jsonString, ); let container:CryptContainer = { key:"", data: await this.encrypt( jsonString ) }; let containerString = await this.encrypt( JSON.stringify( container ) ); await fs.writeFile( path, containerString ); if ( this.debugOutput ) { await fs.writeFile( path + this.debugOutputSuffix, jsonString ); } return Promise.resolve(); } static async loadJSON( path:string ):Promise { let encryptedContainerString = await fs.readFile( path ); let containerString = await this.decrypt( encryptedContainerString.toString() ); let container = JSON.parse( containerString ) as CryptContainer; let encryptedData = container.data; let jsonString = await this.decrypt( encryptedData ); let json = JSON.parse( jsonString ) as T; return Promise.resolve( json ); } static async loadUTF8( path:string ):Promise { let encryptedContainerString = await fs.readFile( path ); let containerString = await this.decrypt( encryptedContainerString.toString() ); let container = JSON.parse( containerString ) as CryptContainer; let encryptedData = container.data; let jsonString = await this.decrypt( encryptedData ); return Promise.resolve( jsonString ); } }