rinseoff/rinseoffcli/RinseOffCLI.cs

226 lines
8.5 KiB
C#
Raw Permalink Normal View History

2020-12-20 01:01:40 +00:00
using System;
using System.IO;
2020-12-20 09:07:19 +00:00
using System.Collections.Generic;
using rinseoff;
2020-12-21 08:14:27 +00:00
/*
RinseOff - Encrypt or load data with a key file to help with secure erasure
Copyright (C) <2020> Kevin Froman
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2020-12-20 01:01:40 +00:00
namespace rinseoffcli
{
class Program
{
public static string version = "2.0.0";
2020-12-21 04:16:58 +00:00
enum ErrorCode : byte
{
2020-12-21 08:14:27 +00:00
// Exit error codes indexed from 1
2020-12-21 04:16:58 +00:00
InvaildArgs = 1,
NoDataFileFound,
NoPermissionToReadDataFile,
FailedToReadDataFile,
FailedToWriteDataFile,
InvalidKeyFileSize,
InvalidKeyFile,
KeyFileNotFound,
NoPermissionToWriteKeyFile,
FailedToReadKeyFile,
FailedToWriteKeyFile
}
2020-12-20 01:01:40 +00:00
2020-12-21 04:16:58 +00:00
static void showHelp(ErrorCode exitCode = ErrorCode.InvaildArgs){
Console.WriteLine("RinseOff " + version + " High level secure-erasure utility");
2020-12-29 07:35:26 +00:00
Console.WriteLine("For stdout output, specify 'stdout' as the data file for the store command");
2020-12-20 09:07:19 +00:00
Console.WriteLine("Must specify keygen <key path> or store/load, then a file name followed by a key file.\nFormat: <verb> <data file> <key file>");
2020-12-21 04:16:58 +00:00
Environment.Exit((int) exitCode);
2020-12-20 01:01:40 +00:00
}
2020-12-20 09:07:19 +00:00
static void validateArgCount(string[] args, int count){
if (args.Length != count){
stderrWrite("Invalid number of arguments");
2020-12-21 04:16:58 +00:00
showHelp(ErrorCode.InvaildArgs);
2020-12-20 09:07:19 +00:00
}
}
static void stderrWrite(string msg){
var stderrStream = Console.Error;
stderrStream.WriteLine(msg);
stderrStream.Flush();
}
static byte[] readUntilClose(){
// Read binary from STDIN until close
var readData = new List<byte>();
Stream inputStream = Console.OpenStandardInput();
int inp;
while(true){
inp = inputStream.ReadByte();
if (inp != -1){
readData.Add((byte) inp);
}
else{
return readData.ToArray();
}
}
}
2020-12-20 09:07:19 +00:00
static void loadData(string filepath, string keypath){
2020-12-20 09:41:14 +00:00
// Load in an encrypted file and use a key file to decrypt it, then log bytes back to stdout
2020-12-20 09:07:19 +00:00
byte[] plaintext = {};
byte[] ciphertext = {};
2020-12-20 09:07:19 +00:00
byte[] readBytes(string file){
2020-12-20 09:41:14 +00:00
// Read bytes in from a file, exit with error message if not possible
2020-12-20 09:07:19 +00:00
byte[] bytesToRead = {};
try{
bytesToRead = File.ReadAllBytes(file);
}
catch(FileNotFoundException){
stderrWrite(file + " is not found");
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.NoDataFileFound);
2020-12-20 09:07:19 +00:00
}
catch(UnauthorizedAccessException){
stderrWrite("No permssion to read " + file);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.NoPermissionToReadDataFile);
2020-12-20 09:07:19 +00:00
}
catch(IOException){
stderrWrite("Failed to read " + file);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.FailedToReadDataFile);
2020-12-20 09:07:19 +00:00
}
return bytesToRead;
}
var stdout = Console.OpenStandardOutput();
if (filepath.Equals("stdin")){
ciphertext = readUntilClose();
}
else{
ciphertext = readBytes(filepath);
}
2020-12-20 09:07:19 +00:00
try{
2020-12-20 09:41:14 +00:00
// Decrypt a file using a key file
2020-12-20 09:07:19 +00:00
plaintext = RinseOff.decrypt_secret_bytes(
ciphertext,
2020-12-20 09:07:19 +00:00
readBytes(keypath)
);
}
catch(Sodium.Exceptions.KeyOutOfRangeException){
stderrWrite("Keyfile is not appropriate size for key");
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.InvalidKeyFileSize);
2020-12-20 09:07:19 +00:00
}
catch(System.Security.Cryptography.CryptographicException){
stderrWrite("Could not decrypt " + filepath + " with " + keypath);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.InvalidKeyFile);
2020-12-20 09:07:19 +00:00
}
ciphertext = null;
2020-12-20 09:41:14 +00:00
// print the plaintext and exit
2020-12-20 09:07:19 +00:00
foreach(byte b in plaintext){
stdout.WriteByte(b);
2020-12-20 01:01:40 +00:00
}
2020-12-20 09:07:19 +00:00
stdout.Flush();
}
static void storeData(string filepath, string keypath){
byte[] encryptedInput = new byte[0];
2020-12-20 01:01:40 +00:00
2020-12-29 07:35:26 +00:00
void writeStdoutBytes(byte[] data){
var stdout = Console.OpenStandardOutput();
foreach (byte b in data){
stdout.WriteByte(b);
}
stdout.Flush();
}
2020-12-20 09:07:19 +00:00
// Encrypt stdin with keyfile data then write out to output file
try{
encryptedInput = RinseOff.encrypt_secret_bytes(readUntilClose(), File.ReadAllBytes(keypath));
}
catch(FileNotFoundException){
stderrWrite("Key file " + keypath + " does not exist");
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.KeyFileNotFound);
2020-12-20 09:07:19 +00:00
}
catch(Sodium.Exceptions.KeyOutOfRangeException){
stderrWrite("Keyfile is not appropriate size for key");
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.InvalidKeyFileSize);
2020-12-20 09:07:19 +00:00
}
catch(IOException){
stderrWrite("Failed to read key file " + keypath);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.FailedToReadKeyFile);
2020-12-20 09:07:19 +00:00
}
2020-12-29 07:35:26 +00:00
if (filepath == "stdout"){
writeStdoutBytes(encryptedInput);
return;
}
2020-12-20 09:07:19 +00:00
try{
File.WriteAllBytes(filepath, encryptedInput);
}
catch(ArgumentNullException)
{
Environment.Exit(0);
}
catch(DirectoryNotFoundException){
stderrWrite("Output path " + filepath + " not found");
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.NoDataFileFound);
2020-12-20 09:07:19 +00:00
}
catch(IOException){
stderrWrite("Could not write to " + filepath);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.FailedToWriteDataFile);
2020-12-20 09:07:19 +00:00
}
2020-12-20 01:01:40 +00:00
}
static void Main(string[] args)
{
if (args.Length == 0){
showHelp();
}
var cmd = args[0].ToLower();
switch(cmd){
2020-12-20 09:07:19 +00:00
case "keygen":
validateArgCount(args, 2);
try{
RinseOff.generateKeyFile(args[1]);
}
catch(UnauthorizedAccessException){
stderrWrite("Cannot write to key file due to lack of perms " + args[1]);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.NoPermissionToWriteKeyFile);
2020-12-20 09:07:19 +00:00
}
catch(DirectoryNotFoundException){
stderrWrite("Path not found " + args[1]);
2020-12-21 04:16:58 +00:00
Environment.Exit((int)ErrorCode.KeyFileNotFound);
2020-12-20 09:07:19 +00:00
}
catch(IOException){
2020-12-21 04:16:58 +00:00
stderrWrite("Error writing keyfile " + args[1]);
Environment.Exit((int)ErrorCode.FailedToWriteKeyFile);
2020-12-20 01:01:40 +00:00
}
2020-12-20 09:07:19 +00:00
break;
case "store":
validateArgCount(args, 3);
2020-12-20 01:01:40 +00:00
storeData(args[1], args[2]);
break;
2020-12-20 09:07:19 +00:00
case "load":
validateArgCount(args, 3);
loadData(args[1], args[2]);
break;
2020-12-20 01:01:40 +00:00
default:
2020-12-20 09:07:19 +00:00
stderrWrite("Invalid command");
2020-12-20 01:01:40 +00:00
showHelp();
break;
case "help":
case "--help":
case "-h":
showHelp();
break;
}
}
}
}