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