initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bin/
|
||||
obj/
|
||||
.idea/
|
||||
*.user
|
||||
16
Cryptomator.sln
Normal file
16
Cryptomator.sln
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cryptomator", "Cryptomator\Cryptomator.csproj", "{C8109960-0F0D-40D7-861D-1C809E160111}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{C8109960-0F0D-40D7-861D-1C809E160111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C8109960-0F0D-40D7-861D-1C809E160111}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C8109960-0F0D-40D7-861D-1C809E160111}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C8109960-0F0D-40D7-861D-1C809E160111}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
24
Cryptomator/Commands/CreateKeyPair.cs
Normal file
24
Cryptomator/Commands/CreateKeyPair.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Security.Cryptography;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Cryptomator.Commands;
|
||||
|
||||
public sealed class CreateKeyPair: AsyncCommand<CreateKeyPair.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<./>")]
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
using var rsa = RSA.Create();
|
||||
var privateKey = rsa.ExportRSAPrivateKeyPem();
|
||||
var t1 = File.WriteAllTextAsync($"{settings.Path}/priv.pem", privateKey);
|
||||
var pem = rsa.ExportRSAPublicKeyPem();
|
||||
var t2 = File.WriteAllTextAsync($"{settings.Path}/pub.pem", pem);
|
||||
await Task.WhenAll(t1, t2);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
77
Cryptomator/Commands/Decrypt.cs
Normal file
77
Cryptomator/Commands/Decrypt.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Security.Cryptography;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Cryptomator.Commands;
|
||||
|
||||
public sealed class Decrypt: AsyncCommand<Decrypt.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<./>")]
|
||||
[CommandOption("-k|--key")]
|
||||
public string PrivateKeyPath { get; set; }
|
||||
[CommandArgument(1, "<./>")]
|
||||
[CommandOption("-i|--input")]
|
||||
public string EncryptedDirectory { get; set; }
|
||||
[CommandArgument(2, "<./>")]
|
||||
[CommandOption("-o|--output")]
|
||||
public string OutputDirectory { get; set; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
using var rsa = await LoadPrivateKey(settings.PrivateKeyPath);
|
||||
var files = GetEncryptedFiles(settings.EncryptedDirectory);
|
||||
await Parallel.ForEachAsync(files, async (file, ct) =>
|
||||
{
|
||||
await DecryptFile(file, settings.EncryptedDirectory, settings.OutputDirectory, rsa);
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static async Task<RSA> LoadPrivateKey(string privateKeyPath)
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(privateKeyPath);
|
||||
var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(content);
|
||||
return rsa;
|
||||
}
|
||||
|
||||
private string[] GetEncryptedFiles(string encryptedDirectory)
|
||||
{
|
||||
var files = Directory.GetFiles(encryptedDirectory)
|
||||
.GroupBy(Path.GetFileNameWithoutExtension)
|
||||
.Select(g => g.First())
|
||||
.ToArray();
|
||||
return files;
|
||||
}
|
||||
|
||||
private async Task DecryptFile(string encryptedFile, string inputDirectory, string outputDirectory, RSA rsa)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(encryptedFile);
|
||||
if (!File.Exists($"{inputDirectory}/{name}.key"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var key = File.ReadAllBytesAsync($"{inputDirectory}/{name}.key");
|
||||
var iv = File.ReadAllBytesAsync($"{inputDirectory}/{name}.iv");
|
||||
var keyBytes = await key;
|
||||
var decryptedKey = rsa.Decrypt(keyBytes, RSAEncryptionPadding.OaepSHA512);
|
||||
var ivBytes = await iv;
|
||||
var decryptedIv = rsa.Decrypt(ivBytes, RSAEncryptionPadding.OaepSHA512);
|
||||
|
||||
var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.Key = decryptedKey;
|
||||
aes.IV = decryptedIv;
|
||||
|
||||
await using var inFs = File.OpenRead($"{inputDirectory}/{name}.enc");
|
||||
await using var outFs = File.OpenWrite($"{outputDirectory}/{name}");
|
||||
await using var encStream = new CryptoStream(
|
||||
inFs,
|
||||
aes.CreateDecryptor(),
|
||||
CryptoStreamMode.Read
|
||||
);
|
||||
await encStream.CopyToAsync(outFs);
|
||||
}
|
||||
}
|
||||
42
Cryptomator/Commands/Encrypt/EncryptCommand.cs
Normal file
42
Cryptomator/Commands/Encrypt/EncryptCommand.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Cryptomator.Commands.Encrypt;
|
||||
|
||||
public sealed class EncryptCommand : AsyncCommand<EncryptCommand.Settings>
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("-p|--pubkey")]
|
||||
[CommandArgument(0, "<./pubkey.asc>")]
|
||||
[Description("Path to the public key to encrypt with")]
|
||||
public string PublikKeyPath { get; set; }
|
||||
|
||||
[CommandOption("-i|--input")]
|
||||
[CommandArgument(1, "<./input-dir>")]
|
||||
[Description("Path to the directory to encrypt")]
|
||||
public string InputDir { get; set; }
|
||||
|
||||
[CommandOption("-o|--output")]
|
||||
[CommandArgument(2, "<./output-dir>")]
|
||||
[Description("Path to the directory to write the encrypted files to")]
|
||||
public string OutputDir { get; set; }
|
||||
}
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
||||
{
|
||||
var config = await Step1Config.FromSettings(settings)
|
||||
.ContinueWith(Step2Rsa.FromConfig)
|
||||
.ContinueWith(Step3Encrypt.FromStep2);
|
||||
var result = config.Match(
|
||||
_ => 0,
|
||||
err =>
|
||||
{
|
||||
Console.WriteLine(err);
|
||||
return 1;
|
||||
}
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
46
Cryptomator/Commands/Encrypt/Step1Config.cs
Normal file
46
Cryptomator/Commands/Encrypt/Step1Config.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using OneOf;
|
||||
|
||||
namespace Cryptomator.Commands.Encrypt;
|
||||
|
||||
public record Step1Config(
|
||||
string PublicKeyPath,
|
||||
string InputDirectory,
|
||||
string OutputDirectory
|
||||
)
|
||||
{
|
||||
public static OneOf<Step1Config, string> FromSettings(EncryptCommand.Settings settings)
|
||||
{
|
||||
|
||||
return new Step1Config(
|
||||
settings.PublikKeyPath,
|
||||
settings.InputDir,
|
||||
settings.OutputDir
|
||||
);
|
||||
}
|
||||
|
||||
public static OneOf<Step1Config, string> FromArgs(string[] args)
|
||||
{
|
||||
var pubkey = args[0];
|
||||
if (!File.Exists(pubkey))
|
||||
{
|
||||
return "Pubkey not found";
|
||||
}
|
||||
|
||||
var inputDir = args[1];
|
||||
|
||||
if (!Directory.Exists(inputDir))
|
||||
{
|
||||
return "Input directory not found: " + inputDir;
|
||||
}
|
||||
|
||||
var outputDir = args[2];
|
||||
|
||||
if (!Directory.Exists(outputDir))
|
||||
{
|
||||
return "Output directory not found: " + outputDir;
|
||||
}
|
||||
|
||||
var config = new Step1Config(pubkey, inputDir, outputDir);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
42
Cryptomator/Commands/Encrypt/Step2Rsa.cs
Normal file
42
Cryptomator/Commands/Encrypt/Step2Rsa.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Security.Cryptography;
|
||||
using OneOf;
|
||||
|
||||
namespace Cryptomator.Commands.Encrypt;
|
||||
|
||||
public sealed record Step2Rsa(
|
||||
RSA Rsa,
|
||||
string InputDirectory,
|
||||
string OutputDirectory
|
||||
)
|
||||
{
|
||||
public static async Task<OneOf<Step2Rsa, string>> FromConfig(Step1Config step1Config)
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(step1Config.PublicKeyPath);
|
||||
try
|
||||
{
|
||||
var rsa = LoadPublicKeyFromPem(content);
|
||||
var config2 = new Step2Rsa(rsa, step1Config.InputDirectory, step1Config.OutputDirectory);
|
||||
return config2;
|
||||
}catch(Exception e)
|
||||
{
|
||||
return e.Message;
|
||||
}
|
||||
}
|
||||
|
||||
private static RSA LoadPublicKeyFromPem(string pemFileContent)
|
||||
{
|
||||
// Remove header and footer lines
|
||||
// pemFileContent = pemFileContent.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "").Replace("\n", "");
|
||||
|
||||
// Convert the PEM content to a byte array
|
||||
// byte[] publicKeyBytes = Convert.FromBase64String(pemFileContent);
|
||||
|
||||
// Create an RSA instance
|
||||
RSA rsa = RSA.Create();
|
||||
rsa.ImportFromPem(pemFileContent);
|
||||
// Import the public key
|
||||
// rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
|
||||
|
||||
return rsa;
|
||||
}
|
||||
}
|
||||
51
Cryptomator/Commands/Encrypt/Step3Encrypt.cs
Normal file
51
Cryptomator/Commands/Encrypt/Step3Encrypt.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Security.Cryptography;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
|
||||
namespace Cryptomator.Commands.Encrypt;
|
||||
|
||||
public class Step3Encrypt
|
||||
{
|
||||
public static async Task<OneOf<None, string>> FromStep2(Step2Rsa step2Rsa)
|
||||
{
|
||||
var files = Directory.GetFiles(step2Rsa.InputDirectory);
|
||||
await Parallel.ForEachAsync(files, async (file, ct) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var fi = new FileInfo(file);
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.GenerateKey();
|
||||
aes.GenerateIV();
|
||||
var keyPath = Path.Combine(step2Rsa.OutputDirectory, $"{fi.Name}.key");
|
||||
var ivPath = Path.Combine(step2Rsa.OutputDirectory, $"{fi.Name}.iv");
|
||||
var encFile = Path.Combine(step2Rsa.OutputDirectory, $"{fi.Name}.enc");
|
||||
|
||||
var rsa = step2Rsa.Rsa;
|
||||
var encKey = rsa.Encrypt(aes.Key, RSAEncryptionPadding.OaepSHA512);
|
||||
await File.WriteAllBytesAsync(keyPath, encKey, ct);
|
||||
|
||||
var encIv = rsa.Encrypt(aes.IV, RSAEncryptionPadding.OaepSHA512);
|
||||
await File.WriteAllBytesAsync(ivPath, encIv, ct);
|
||||
|
||||
await using var fs = File.OpenRead(file);
|
||||
await using var encFs = File.OpenWrite(encFile);
|
||||
await using var encStream = new CryptoStream(
|
||||
encFs,
|
||||
aes.CreateEncryptor(),
|
||||
CryptoStreamMode.Write
|
||||
);
|
||||
await fs.CopyToAsync(encStream, ct);
|
||||
await encStream.FlushAsync(ct);
|
||||
|
||||
Console.WriteLine($"Encrypted {fi.Name}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
});
|
||||
return new None();
|
||||
}
|
||||
}
|
||||
15
Cryptomator/Cryptomator.csproj
Normal file
15
Cryptomator/Cryptomator.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OneOf" Version="3.0.271" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
21
Cryptomator/Program.cs
Normal file
21
Cryptomator/Program.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// See https://aka.ms/new-console-template for more information
|
||||
|
||||
using Cryptomator.Commands;
|
||||
using Cryptomator.Commands.Encrypt;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
var app = new CommandApp();
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.AddCommand<EncryptCommand>("encrypt");
|
||||
config.AddCommand<Decrypt>("decrypt");
|
||||
config.AddCommand<CreateKeyPair>("create-keypair");
|
||||
});
|
||||
await app.RunAsync(args);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
58
Cryptomator/RoP.cs
Normal file
58
Cryptomator/RoP.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using OneOf;
|
||||
|
||||
namespace Cryptomator;
|
||||
|
||||
public static class RoP
|
||||
{
|
||||
public static OneOf<TRes, TErr> ContinueWith<TOk, TErr, TRes>(this OneOf<TOk, TErr> value, Func<TOk, OneOf<TRes, TErr>> func)
|
||||
{
|
||||
var result = value.Match(
|
||||
func,
|
||||
err => err
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<OneOf<TRes, TErr>> ContinueWith<TOk, TErr, TRes>(
|
||||
this OneOf<TOk, TErr> value,
|
||||
Func<TOk, Task<OneOf<TRes, TErr>>> func
|
||||
)
|
||||
{
|
||||
var result = await value.Match(
|
||||
func,
|
||||
err => Task.FromResult<OneOf<TRes, TErr>>(err)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<OneOf<TRes, TErr>> ContinueWith<TOk, TErr, TRes>(
|
||||
this Task<OneOf<TOk, TErr>> value,
|
||||
Func<TOk, Task<OneOf<TRes, TErr>>> func
|
||||
)
|
||||
{
|
||||
var valRes = await value;
|
||||
var result = await valRes.Match(
|
||||
func,
|
||||
err => Task.FromResult<OneOf<TRes, TErr>>(err)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task<OneOf<TRes, TErr>> ContinueWith<TOk, TErr, TRes>(
|
||||
this Task<OneOf<TOk, TErr>> value,
|
||||
Func<TOk, OneOf<TRes, TErr>> func
|
||||
)
|
||||
{
|
||||
var valRes = await value;
|
||||
var result = valRes.Match(
|
||||
func,
|
||||
err => err
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user