2023-03-20 22:59:09 +00:00
|
|
|
import std.stdio;
|
|
|
|
import std.path;
|
|
|
|
import std.file;
|
|
|
|
import std.algorithm : endsWith;
|
|
|
|
import std.string : strip;
|
2023-03-21 07:48:12 +00:00
|
|
|
import std.getopt;
|
2023-03-20 22:59:09 +00:00
|
|
|
|
|
|
|
import botan.block.block_cipher : BlockCipher;
|
|
|
|
import botan.block.aes : AES256;
|
|
|
|
import botan.hash.hash : HashFunction;
|
|
|
|
import botan.hash.sha2_32 : SHA256;
|
|
|
|
|
|
|
|
import cipher_utils;
|
2023-03-21 11:53:18 +00:00
|
|
|
import cli_utils;
|
2023-03-20 22:59:09 +00:00
|
|
|
|
|
|
|
int main(string[] args) {
|
2023-03-21 13:30:09 +00:00
|
|
|
if (args.length >= 2 && (args[1] == "-h" || args[1] == "--help")) {
|
|
|
|
printUsage();
|
|
|
|
return 0;
|
|
|
|
}
|
2023-03-21 07:48:12 +00:00
|
|
|
Params params;
|
|
|
|
int result = parseParams(args, params);
|
|
|
|
if (result != 0) {
|
|
|
|
printUsage();
|
|
|
|
return result;
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
string passphrase;
|
|
|
|
if (params.passphraseFile !is null && exists(params.passphraseFile) && isFile(params.passphraseFile)) {
|
|
|
|
if (params.verbose) {
|
|
|
|
writefln!"Reading passphrase from \"%s\""(params.passphraseFile);
|
|
|
|
}
|
|
|
|
passphrase = readText(params.passphraseFile).strip();
|
|
|
|
} else {
|
|
|
|
write("Enter passphrase: ");
|
|
|
|
passphrase = readPassphrase();
|
|
|
|
writeln();
|
|
|
|
}
|
|
|
|
if (passphrase is null || passphrase.length == 0) {
|
|
|
|
stderr.writeln("Invalid or missing passphrase.");
|
2023-03-21 07:48:12 +00:00
|
|
|
return 2;
|
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
|
|
|
|
HashFunction hash = new SHA256();
|
2023-03-21 13:30:09 +00:00
|
|
|
auto secureKeyVector = hash.process(passphrase);
|
|
|
|
BlockCipher cipher = new AES256();
|
2023-03-20 22:59:09 +00:00
|
|
|
cipher.setKey(secureKeyVector);
|
|
|
|
|
|
|
|
ubyte[] buffer = new ubyte[cipher.blockSize];
|
2023-03-21 07:48:12 +00:00
|
|
|
if (isDir(params.target)) {
|
|
|
|
if (params.action == Action.ENCRYPT) {
|
2023-03-21 13:30:09 +00:00
|
|
|
encryptDir(params.target, cipher, buffer, params.recursive, params.verbose);
|
2023-03-20 22:59:09 +00:00
|
|
|
} else {
|
2023-03-21 13:30:09 +00:00
|
|
|
bool success = decryptDir(params.target, cipher, buffer, params.recursive, params.verbose);
|
|
|
|
if (!success) {
|
|
|
|
stderr.writeln("Decryption failed.");
|
|
|
|
return 3;
|
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
2023-03-21 07:48:12 +00:00
|
|
|
} else if (isFile(params.target)) {
|
|
|
|
if (params.action == Action.ENCRYPT) {
|
2023-03-21 13:30:09 +00:00
|
|
|
encryptAndRemoveFile(params.target, cipher, buffer, params.verbose);
|
2023-03-20 22:59:09 +00:00
|
|
|
} else {
|
2023-03-21 13:30:09 +00:00
|
|
|
bool success = decryptAndRemoveFile(params.target, cipher, buffer, params.verbose);
|
|
|
|
if (!success) {
|
|
|
|
stderr.writeln("Decryption failed.");
|
|
|
|
return 3;
|
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-21 07:48:12 +00:00
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
void encryptAndRemoveFile(string filename, BlockCipher cipher, ref ubyte[] buffer, bool verbose) {
|
|
|
|
string encryptedFilename = filename ~ ENCRYPTED_SUFFIX;
|
|
|
|
encryptFile(filename, encryptedFilename, cipher, buffer, verbose);
|
2023-03-20 22:59:09 +00:00
|
|
|
std.file.remove(filename);
|
|
|
|
}
|
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
bool decryptAndRemoveFile(string filename, BlockCipher cipher, ref ubyte[] buffer, bool verbose) {
|
|
|
|
string decryptedFilename = filename[0 .. $-ENCRYPTED_SUFFIX.length];
|
|
|
|
bool success = decryptFile(filename, decryptedFilename, cipher, buffer, verbose);
|
|
|
|
if (!success) return false;
|
2023-03-20 22:59:09 +00:00
|
|
|
std.file.remove(filename);
|
2023-03-21 13:30:09 +00:00
|
|
|
return true;
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
void encryptDir(string dirname, BlockCipher cipher, ref ubyte[] buffer, bool recursive, bool verbose) {
|
2023-03-20 22:59:09 +00:00
|
|
|
string[] dirsToTraverse;
|
|
|
|
foreach (DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
|
2023-03-21 13:30:09 +00:00
|
|
|
if (entry.isFile && !endsWith(entry.name, ENCRYPTED_SUFFIX)) {
|
|
|
|
encryptAndRemoveFile(entry.name, cipher, buffer, verbose);
|
2023-03-20 22:59:09 +00:00
|
|
|
} else if (entry.isDir && recursive) {
|
|
|
|
dirsToTraverse ~= entry.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (recursive) {
|
|
|
|
foreach (string childDirname; dirsToTraverse) {
|
2023-03-21 13:30:09 +00:00
|
|
|
encryptDir(childDirname, cipher, buffer, recursive, verbose);
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
bool decryptDir(string dirname, BlockCipher cipher, ref ubyte[] buffer, bool recursive, bool verbose) {
|
2023-03-20 22:59:09 +00:00
|
|
|
string[] dirsToTraverse;
|
|
|
|
foreach (DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
|
2023-03-21 13:30:09 +00:00
|
|
|
if (entry.isFile && endsWith(entry.name, ENCRYPTED_SUFFIX)) {
|
|
|
|
bool success = decryptAndRemoveFile(entry.name, cipher, buffer, verbose);
|
|
|
|
if (!success) return false;
|
2023-03-20 22:59:09 +00:00
|
|
|
} else if (entry.isDir && recursive) {
|
|
|
|
dirsToTraverse ~= entry.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (recursive) {
|
|
|
|
foreach (string childDirname; dirsToTraverse) {
|
2023-03-21 13:30:09 +00:00
|
|
|
bool success = decryptDir(childDirname, cipher, buffer, recursive, verbose);
|
|
|
|
if (!success) return false;
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-21 13:30:09 +00:00
|
|
|
return true;
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|