Decrypt file
This commit is contained in:
parent
1f4cdf348f
commit
551ca404b4
|
@ -7,9 +7,11 @@ import { join } from "path"
|
||||||
const ALGO = "aes-256-ctr"
|
const ALGO = "aes-256-ctr"
|
||||||
const SEPARATOR = "-"
|
const SEPARATOR = "-"
|
||||||
const ITERATIONS = 10000
|
const ITERATIONS = 10000
|
||||||
const RANDOM_BYTES = 16
|
|
||||||
const STRETCH_LENGTH = 32
|
const STRETCH_LENGTH = 32
|
||||||
|
|
||||||
|
const SALT_LENGTH = 16
|
||||||
|
const IV_LENGTH = 16
|
||||||
|
|
||||||
export enum SecretOption {
|
export enum SecretOption {
|
||||||
API = "api",
|
API = "api",
|
||||||
ENCRYPTION = "encryption",
|
ENCRYPTION = "encryption",
|
||||||
|
@ -42,7 +44,7 @@ export function encrypt(
|
||||||
input: string,
|
input: string,
|
||||||
secretOption: SecretOption = SecretOption.API
|
secretOption: SecretOption = SecretOption.API
|
||||||
) {
|
) {
|
||||||
const salt = crypto.randomBytes(RANDOM_BYTES)
|
const salt = crypto.randomBytes(SALT_LENGTH)
|
||||||
const stretched = stretchString(getSecret(secretOption), salt)
|
const stretched = stretchString(getSecret(secretOption), salt)
|
||||||
const cipher = crypto.createCipheriv(ALGO, stretched, salt)
|
const cipher = crypto.createCipheriv(ALGO, stretched, salt)
|
||||||
const base = cipher.update(input)
|
const base = cipher.update(input)
|
||||||
|
@ -74,13 +76,15 @@ export async function encryptFile(
|
||||||
const inputFile = fs.createReadStream(filePath)
|
const inputFile = fs.createReadStream(filePath)
|
||||||
const outputFile = fs.createWriteStream(join(dir, outputFileName))
|
const outputFile = fs.createWriteStream(join(dir, outputFileName))
|
||||||
|
|
||||||
const salt = crypto.randomBytes(RANDOM_BYTES)
|
const salt = crypto.randomBytes(SALT_LENGTH)
|
||||||
|
const iv = crypto.randomBytes(IV_LENGTH)
|
||||||
const stretched = stretchString(secret, salt)
|
const stretched = stretchString(secret, salt)
|
||||||
const cipher = crypto.createCipheriv(ALGO, stretched, salt)
|
const cipher = crypto.createCipheriv(ALGO, stretched, iv)
|
||||||
|
|
||||||
const encrypted = inputFile.pipe(cipher).pipe(zlib.createGzip())
|
outputFile.write(salt)
|
||||||
|
outputFile.write(iv)
|
||||||
|
|
||||||
encrypted.pipe(outputFile)
|
inputFile.pipe(cipher).pipe(outputFile)
|
||||||
|
|
||||||
return new Promise<{ filename: string; dir: string }>(r => {
|
return new Promise<{ filename: string; dir: string }>(r => {
|
||||||
outputFile.on("finish", () => {
|
outputFile.on("finish", () => {
|
||||||
|
@ -91,3 +95,54 @@ export async function encryptFile(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function decryptFile(
|
||||||
|
inputPath: string,
|
||||||
|
outputPath: string,
|
||||||
|
secret: string
|
||||||
|
) {
|
||||||
|
const inputFile = fs.createReadStream(inputPath)
|
||||||
|
const outputFile = fs.createWriteStream(outputPath)
|
||||||
|
|
||||||
|
const salt = await readBytes(inputFile, SALT_LENGTH)
|
||||||
|
const iv = await readBytes(inputFile, IV_LENGTH)
|
||||||
|
|
||||||
|
const stretched = stretchString(secret, salt)
|
||||||
|
const decipher = crypto.createDecipheriv(ALGO, stretched, iv)
|
||||||
|
|
||||||
|
fs.createReadStream(inputPath, { start: SALT_LENGTH + IV_LENGTH })
|
||||||
|
.pipe(decipher)
|
||||||
|
.pipe(outputFile)
|
||||||
|
|
||||||
|
return new Promise<void>(r => {
|
||||||
|
outputFile.on("finish", () => {
|
||||||
|
r()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBytes(stream: fs.ReadStream, length: number) {
|
||||||
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
let bytesRead = 0
|
||||||
|
const data: Buffer[] = []
|
||||||
|
|
||||||
|
stream.on("readable", () => {
|
||||||
|
let chunk
|
||||||
|
|
||||||
|
while ((chunk = stream.read(length - bytesRead)) !== null) {
|
||||||
|
data.push(chunk)
|
||||||
|
bytesRead += chunk.length
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(Buffer.concat(data))
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on("end", () => {
|
||||||
|
reject(new Error("Insufficient data in the stream."))
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on("error", (error: any) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@ import { DocumentType } from "../../db/utils"
|
||||||
import { isQsTrue } from "../../utilities"
|
import { isQsTrue } from "../../utilities"
|
||||||
|
|
||||||
export async function exportAppDump(ctx: any) {
|
export async function exportAppDump(ctx: any) {
|
||||||
let { appId, excludeRows = false, encryptPassword } = ctx.query
|
let { appId, excludeRows = false, encryptPassword = "password" } = ctx.query
|
||||||
// remove the 120 second limit for the request
|
// remove the 120 second limit for the request
|
||||||
ctx.req.setTimeout(0)
|
ctx.req.setTimeout(0)
|
||||||
const appName = decodeURI(ctx.query.appname)
|
const appName = decodeURI(ctx.query.appname)
|
||||||
excludeRows = isQsTrue(excludeRows)
|
excludeRows = isQsTrue(excludeRows)
|
||||||
const extension = encryptPassword ? "enc.tar.gz" : "tar.gz"
|
const backupIdentifier = `${appName}-export-${new Date().getTime()}${
|
||||||
const backupIdentifier = `${appName}-export-${new Date().getTime()}.${extension}`
|
encryptPassword ? "-enc" : ""
|
||||||
|
}.tar.gz`
|
||||||
ctx.attachment(backupIdentifier)
|
ctx.attachment(backupIdentifier)
|
||||||
ctx.body = await sdk.backups.streamExportApp({
|
ctx.body = await sdk.backups.streamExportApp({
|
||||||
appId,
|
appId,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { db as dbCore, objectStore } from "@budibase/backend-core"
|
import { db as dbCore, encryption, objectStore } from "@budibase/backend-core"
|
||||||
import { Database, Row } from "@budibase/types"
|
import { Database, Row } from "@budibase/types"
|
||||||
import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils"
|
import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils"
|
||||||
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
||||||
|
@ -20,6 +20,7 @@ type TemplateType = {
|
||||||
file?: {
|
file?: {
|
||||||
type: string
|
type: string
|
||||||
path: string
|
path: string
|
||||||
|
password?: string
|
||||||
}
|
}
|
||||||
key?: string
|
key?: string
|
||||||
}
|
}
|
||||||
|
@ -123,6 +124,15 @@ export function untarFile(file: { path: string }) {
|
||||||
return tmpPath
|
return tmpPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function decryptFiles(path: string) {
|
||||||
|
for (let file of fs.readdirSync(path)) {
|
||||||
|
const inputPath = join(path, file)
|
||||||
|
const outputPath = inputPath.replace(/\.enc$/, "")
|
||||||
|
await encryption.decryptFile(inputPath, outputPath, "password")
|
||||||
|
fs.rmSync(inputPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function getGlobalDBFile(tmpPath: string) {
|
export function getGlobalDBFile(tmpPath: string) {
|
||||||
return fs.readFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), "utf8")
|
return fs.readFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), "utf8")
|
||||||
}
|
}
|
||||||
|
@ -143,6 +153,9 @@ export async function importApp(
|
||||||
template.file && fs.lstatSync(template.file.path).isDirectory()
|
template.file && fs.lstatSync(template.file.path).isDirectory()
|
||||||
if (template.file && (isTar || isDirectory)) {
|
if (template.file && (isTar || isDirectory)) {
|
||||||
const tmpPath = isTar ? untarFile(template.file) : template.file.path
|
const tmpPath = isTar ? untarFile(template.file) : template.file.path
|
||||||
|
if (isTar && template.file.password) {
|
||||||
|
await decryptFiles(tmpPath)
|
||||||
|
}
|
||||||
const contents = fs.readdirSync(tmpPath)
|
const contents = fs.readdirSync(tmpPath)
|
||||||
// have to handle object import
|
// have to handle object import
|
||||||
if (contents.length) {
|
if (contents.length) {
|
||||||
|
|
Loading…
Reference in New Issue