All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jetbrains.exodus.crypto.Scytale.kt Maven / Gradle / Ivy

/**
 * Copyright 2010 - 2022 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.exodus.crypto

import jetbrains.exodus.crypto.convert.*
import jetbrains.exodus.crypto.streamciphers.CHACHA_CIPHER_ID
import jetbrains.exodus.crypto.streamciphers.SALSA20_CIPHER_ID
import jetbrains.exodus.entitystore.PersistentEntityStoreImpl
import jetbrains.exodus.env.Reflect
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.lang.Long.parseLong
import java.util.concurrent.atomic.AtomicLong
import java.util.zip.GZIPOutputStream
import kotlin.system.exitProcess

fun main(args: Array) {
    if (args.size < 2) {
        printUsage()
    }
    var sourcePath: String? = null
    var targetPath: String? = null
    var key: ByteArray? = null
    var keyString: String? = null
    var basicIV: Long? = null
    var compress = false
    var gzip = false
    var overwrite = false
    var verbose = false
    var type = "chacha"

    for (arg in args) {
        if (arg.startsWith('-') && arg.length < 3) {
            when (arg.lowercase().substring(1)) {
                "g" -> gzip = true
                "z" -> compress = true
                "o" -> overwrite = true
                "v" -> verbose = true
                else -> {
                    printUsage()
                }
            }
        } else {
            if (sourcePath == null) {
                sourcePath = arg
            } else if (targetPath == null) {
                targetPath = arg
            } else if (key == null) {
                key = toBinaryKey(arg)
                keyString = arg
            } else if (basicIV == null) {
                basicIV = parseIV(arg)
            } else {
                type = arg.lowercase()
                break
            }
        }
    }

    if (sourcePath == null || targetPath == null || key == null || basicIV == null) {
        printUsage()
    }

    val cipherId = when (type) {
        "salsa" -> SALSA20_CIPHER_ID
        "chacha" -> CHACHA_CIPHER_ID
        else -> {
            abort("Unknown cipher id: $type")
        }
    }

    val source = File(sourcePath)
    val target = File(targetPath)

    if (!source.exists()) {
        abort("File not found: ${source.absolutePath}")
    }

    if (target.exists()) {
        val files = target.list()
        files?.let {
            if (files.isNotEmpty()) {
                if (!overwrite) {
                    abort("File exists: ${target.absolutePath}")
                }
                if (!target.deleteRecursively()) {
                    abort("File cannot be fully deleted: ${target.absolutePath}")
                }
            }
        } ?: target.let {
            if (!overwrite) {
                println("Invalid file: ${target.absolutePath}")
            }
        }
    }

    val input = if (source.isDirectory) {
        try {
            val env = Reflect.openEnvironment(source, !overwrite)
            PersistentEntityStoreImpl(env, "ignored")
        } catch (icpe: InvalidCipherParametersException) {
            val env = Reflect.openEnvironment(source, !overwrite,
                    cipherId = cipherId, cipherKey = keyString, cipherBasicIV = basicIV)
            PersistentEntityStoreImpl(env, "ignored")
        }
    } else {
        ArchiveBackupableFactory.newBackupable(source, gzip)
    }

    var archive: TarArchiveOutputStream? = null
    val output = if (compress) {
        archive = TarArchiveOutputStream(GZIPOutputStream(BufferedOutputStream(FileOutputStream(target))))
        ArchiveEncryptListenerFactory.newListener(archive)
    } else {
        target.mkdir()
        DirectoryEncryptListenerFactory.newListener(target)
    }

    val finalOutput = if (verbose) {
        val bytesWritten = AtomicLong()

        object : EncryptListener {
            override fun onFile(header: FileHeader) {
                output.onFile(header)
                println(header.path + header.name)
            }

            override fun onFileEnd(header: FileHeader) {
                output.onFileEnd(header)
                println(String.format("MB written: %.2f", bytesWritten.get().toFloat() / (1024 * 1024)))
            }

            override fun onData(header: FileHeader, size: Int, data: ByteArray) {
                output.onData(header, size, data)
                bytesWritten.addAndGet(size.toLong())
            }
        }
    } else {
        output
    }

    try {
        ScytaleEngine(finalOutput, newCipherProvider(cipherId), key, basicIV).encryptBackupable(input)
    } finally {
        archive?.close()
    }
}

private fun parseIV(arg: String): Long {
    if (arg.startsWith("0x")) {
        return parseLong(arg.substring(2), 16)
    }
    return parseLong(arg)
}

private fun printUsage(): Nothing {
    println("Usage: Scytale [options] source target key basicIV [cipher]")
    println("Source can be archive or folder")
    println("Cipher can be 'Salsa' or 'ChaCha', 'ChaCha' is default")
    println("Options:")
    println("  -g              use gzip compression when opening archive")
    println("  -v              print verbose progress messages")
    println("  -z              make target an archive")
    println("  -o              overwrite target archive or folder")
    exitProcess(1)
}

private fun abort(message: String): Nothing {
    println(message)
    exitProcess(1)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy