Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
using OwlCore.Storage;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.FileSystem.Helpers.Paths;
using SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Shared.Models;
using SecureFolderFS.Storage.Extensions;
using SecureFolderFS.Storage.Renamable;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace SecureFolderFS.Core.FileSystem.Helpers.Health
{
public static partial class HealthHelpers
{
public static async Task<IResult> RepairDirectoryAsync(IFolder affected, Security security, CancellationToken cancellationToken)
{
// Return success, if no encryption is used
// Return success if no encryption is used
if (security.NameCrypt is null)
return Result.Success;

Expand All @@ -38,11 +39,8 @@ public static async Task<IResult> RepairDirectoryAsync(IFolder affected, Securit
if (PathHelpers.IsCoreName(item.Name))
continue;

// Encrypt a new name
var encryptedName = security.NameCrypt.EncryptName(item.Name, directoryId);
encryptedName = $"{encryptedName}{Constants.Names.ENCRYPTED_FILE_EXTENSION}";

// Rename
// Encrypt a new name and rename
var encryptedName = AbstractPathHelpers.EncryptNewName(item.Name, directoryId, security);
_ = await renamableFolder.RenameAsync(item, encryptedName, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Shared.Models;
using SecureFolderFS.Storage.Extensions;
using SecureFolderFS.Storage.Renamable;
using System;
using System.IO;
using System;
using System.Threading;
using System.Threading.Tasks;
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Shared.Helpers;
using SecureFolderFS.Shared.Models;
using SecureFolderFS.Storage.Renamable;

namespace SecureFolderFS.Core.FileSystem.Helpers.Health
{
Expand Down Expand Up @@ -49,26 +47,8 @@ private static async Task<IResult> RepairNameAsync(IStorableChild affected, Secu
if (parentFolder is not IRenamableFolder renamableFolder)
return Result.Failure(FolderNotRenamable);

byte[] directoryId;
if (parentFolder.Id != contentFolder.Id) // TODO: Remove code duplication with AbstractPathHelpers
{
var directoryIdFile = await parentFolder.GetFileByNameAsync(Constants.Names.DIRECTORY_ID_FILENAME, cancellationToken);
await using var directoryIdStream = await directoryIdFile.OpenStreamAsync(FileAccess.Read, FileShare.Read, cancellationToken);

directoryId = new byte[Constants.DIRECTORY_ID_SIZE];
var read = await directoryIdStream.ReadAsync(directoryId, cancellationToken);

if (read < Constants.DIRECTORY_ID_SIZE)
throw new IOException($"The data inside Directory ID file is of incorrect size: {read}.");
}
else
directoryId = Array.Empty<byte>();

// Encrypt new name
var encryptedName = security.NameCrypt.EncryptName(newName, directoryId);
encryptedName = $"{encryptedName}{Constants.Names.ENCRYPTED_FILE_EXTENSION}";

// Rename
// Encrypt new name and rename
var encryptedName = await AbstractPathHelpers.EncryptNameAsync(newName, parentFolder, contentFolder, security, cancellationToken);
_ = await renamableFolder.RenameAsync(affected, encryptedName, cancellationToken);
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract
{
public static partial class AbstractPathHelpers
{
public static async Task<bool> GetDirectoryIdAsync(
IFolder folderOfDirectoryId,
IFolder contentFolder,
Memory<byte> directoryId,
CancellationToken cancellationToken)
{
if (folderOfDirectoryId.Id == contentFolder.Id)
return false;

var directoryIdFile = await folderOfDirectoryId.GetFileByNameAsync(Constants.Names.DIRECTORY_ID_FILENAME, cancellationToken).ConfigureAwait(false);
await using var directoryIdStream = await directoryIdFile.OpenStreamAsync(FileAccess.Read, FileShare.Read, cancellationToken).ConfigureAwait(false);

var read = await directoryIdStream.ReadAsync(directoryId, cancellationToken).ConfigureAwait(false);
if (read < Constants.DIRECTORY_ID_SIZE)
throw new IOException($"The data inside Directory ID file is of incorrect size: {read}.");

// The Directory ID is not empty - return true
return true;
}

public static async Task<bool> GetDirectoryIdAsync(
IFolder folderOfDirectoryId,
FileSystemSpecifics specifics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,90 @@
using System.Threading;
using System.Threading.Tasks;
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.FileSystem.FileNames;

namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract
{
public static partial class AbstractPathHelpers
{
/// <inheritdoc cref="EncryptNameAsync(string,OwlCore.Storage.IFolder,SecureFolderFS.Core.FileSystem.FileSystemSpecifics,System.Byte[],System.Threading.CancellationToken)"/>
public static async Task<string> EncryptNameAsync(string plaintextName, IFolder ciphertextParentFolder,
FileSystemSpecifics specifics, CancellationToken cancellationToken = default)
{
if (specifics.Security.NameCrypt is null)
return plaintextName;

var directoryId = AllocateDirectoryId(specifics.Security, plaintextName);
return await EncryptNameAsync(plaintextName, ciphertextParentFolder, specifics, directoryId, cancellationToken);
}

/// <summary>
/// Encrypts the provided <paramref name="plaintextName"/>.
/// </summary>
/// <param name="plaintextName">The name to encrypt.</param>
/// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
/// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
/// <param name="expendableDirectoryId">A buffer of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name.</returns>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name with the appropriate file extension appended.</returns>
public static async Task<string> EncryptNameAsync(string plaintextName, IFolder ciphertextParentFolder,
FileSystemSpecifics specifics, CancellationToken cancellationToken = default)
FileSystemSpecifics specifics, byte[]? expendableDirectoryId = null, CancellationToken cancellationToken = default)
{
if (specifics.Security.NameCrypt is null)
return plaintextName;

var directoryId = AllocateDirectoryId(specifics.Security, plaintextName);
var result = await GetDirectoryIdAsync(ciphertextParentFolder, specifics, directoryId, cancellationToken);
expendableDirectoryId ??= AllocateDirectoryId(specifics.Security, plaintextName);
var result = await GetDirectoryIdAsync(ciphertextParentFolder, specifics, expendableDirectoryId, cancellationToken);

return specifics.Security.NameCrypt.EncryptName(plaintextName, result ? directoryId : ReadOnlySpan<byte>.Empty) + Constants.Names.ENCRYPTED_FILE_EXTENSION;
return specifics.Security.NameCrypt.EncryptName(plaintextName, result ? expendableDirectoryId : ReadOnlySpan<byte>.Empty) + Constants.Names.ENCRYPTED_FILE_EXTENSION;
}

/// <summary>
/// Encrypts the provided <paramref name="plaintextName"/>.
/// </summary>
/// <param name="plaintextName">The name to encrypt.</param>
/// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
/// <param name="contentFolder">The content folder.</param>
/// <param name="security">The <see cref="Security"/> instance associated with the item.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name with the appropriate file extension appended.</returns>
public static async Task<string> EncryptNameAsync(string plaintextName, IFolder ciphertextParentFolder, IFolder contentFolder,
Security security, CancellationToken cancellationToken = default)
{
if (security.NameCrypt is null)
return plaintextName;

var directoryId = AllocateDirectoryId(security, plaintextName);
var result = await GetDirectoryIdAsync(ciphertextParentFolder, contentFolder, directoryId, cancellationToken);

return security.NameCrypt.EncryptName(plaintextName, result ? directoryId : ReadOnlySpan<byte>.Empty) + Constants.Names.ENCRYPTED_FILE_EXTENSION;
}

/// <summary>
/// Encrypts a plaintext name using the specified Directory ID and security parameters.
/// </summary>
/// <param name="plaintextName">The original plaintext name to encrypt.</param>
/// <param name="newDirectoryId">A new Directory ID used for encryption.</param>
/// <param name="security">The <see cref="Security"/> instance associated with the item.</param>
/// <returns>The encrypted name with the appropriate file extension appended.</returns>
public static string EncryptNewName(string plaintextName, byte[] newDirectoryId, Security security)
{
if (security.NameCrypt is null)
return plaintextName;

return security.NameCrypt.EncryptName(plaintextName, newDirectoryId) + Constants.Names.ENCRYPTED_FILE_EXTENSION;
}

/// <inheritdoc cref="DecryptNameAsync(string,OwlCore.Storage.IFolder,SecureFolderFS.Core.FileSystem.FileSystemSpecifics,System.Byte[],System.Threading.CancellationToken)"/>
public static async Task<string?> DecryptNameAsync(string ciphertextName, IFolder ciphertextParentFolder,
FileSystemSpecifics specifics, CancellationToken cancellationToken = default)
{
if (specifics.Security.NameCrypt is null)
return ciphertextName;

var directoryId = AllocateDirectoryId(specifics.Security, ciphertextName);
return await DecryptNameAsync(ciphertextName, ciphertextParentFolder, specifics, directoryId, cancellationToken);
}

/// <summary>
Expand All @@ -34,21 +94,22 @@ public static async Task<string> EncryptNameAsync(string plaintextName, IFolder
/// <param name="ciphertextName">The name to decrypt.</param>
/// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
/// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
/// <param name="expendableDirectoryId">A buffer of size <see cref="Constants.DIRECTORY_ID_SIZE"/> which will be used to hold the Directory ID data.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is a decrypted name.</returns>
public static async Task<string?> DecryptNameAsync(string ciphertextName, IFolder ciphertextParentFolder,
FileSystemSpecifics specifics, CancellationToken cancellationToken = default)
FileSystemSpecifics specifics, byte[]? expendableDirectoryId, CancellationToken cancellationToken = default)
{
if (specifics.Security.NameCrypt is null)
return ciphertextName;

try
{
if (specifics.Security.NameCrypt is null)
return ciphertextName;

var directoryId = AllocateDirectoryId(specifics.Security, ciphertextName);
var result = await GetDirectoryIdAsync(ciphertextParentFolder, specifics, directoryId, cancellationToken);
expendableDirectoryId ??= AllocateDirectoryId(specifics.Security, ciphertextName);
var result = await GetDirectoryIdAsync(ciphertextParentFolder, specifics, expendableDirectoryId, cancellationToken);

var normalizedName = RemoveCiphertextExtension(ciphertextName);
return specifics.Security.NameCrypt.DecryptName(normalizedName, result ? directoryId : ReadOnlySpan<byte>.Empty);
return specifics.Security.NameCrypt.DecryptName(normalizedName, result ? expendableDirectoryId : ReadOnlySpan<byte>.Empty);
}
catch (Exception)
{
Expand Down Expand Up @@ -96,7 +157,7 @@ public static async Task<string> EncryptNameAsync(string plaintextName, IFolder
/// <param name="ciphertextParentFolder">The ciphertext parent folder.</param>
/// <param name="specifics">The <see cref="FileSystemSpecifics"/> instance associated with the item.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that cancels this action.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. The result is an encrypted name, retrieved from the cache if available, or newly encrypted if not.</returns>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation. Value is an encrypted name with the appropriate file extension appended, retrieved from the cache if available, or newly encrypted if not.</returns>
public static async Task<string> CacheEncryptNameAsync(string plaintextName, IFolder ciphertextParentFolder,
FileSystemSpecifics specifics, CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,13 @@ public static partial class AbstractPathHelpers
foreach (var item in folderChain)
{
// Walk through plaintext folder chain and retrieve ciphertext folders
var subResult = await GetDirectoryIdAsync(finalFolder, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
var subCiphertextName = specifics.Security.NameCrypt.EncryptName(item.Name, subResult ? expendableDirectoryId : ReadOnlySpan<byte>.Empty);

finalFolder = await finalFolder.GetFolderByNameAsync($"{subCiphertextName}{Constants.Names.ENCRYPTED_FILE_EXTENSION}", cancellationToken);
var subCiphertextName = await EncryptNameAsync(item.Name, finalFolder, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
finalFolder = await finalFolder.GetFolderByNameAsync(subCiphertextName, cancellationToken);
}

// Encrypt and retrieve the final item
var result = await GetDirectoryIdAsync(finalFolder, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
var ciphertextName = specifics.Security.NameCrypt.EncryptName(plaintextStorable.Name, result ? expendableDirectoryId : ReadOnlySpan<byte>.Empty);

return await finalFolder.GetFirstByNameAsync($"{ciphertextName}{Constants.Names.ENCRYPTED_FILE_EXTENSION}", cancellationToken);
var ciphertextName = await EncryptNameAsync(plaintextStorable.Name, finalFolder, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
return await finalFolder.GetFirstByNameAsync(ciphertextName, cancellationToken);
}

public static async Task<string?> GetPlaintextPathAsync(IStorableChild ciphertextStorable, FileSystemSpecifics specifics, CancellationToken cancellationToken)
Expand All @@ -83,8 +79,7 @@ public static partial class AbstractPathHelpers
if (!currentParent.Id.Contains(specifics.ContentFolder.Id))
break;

var result = await GetDirectoryIdAsync(currentParent, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
var plaintextName = specifics.Security.NameCrypt.DecryptName(Path.GetFileNameWithoutExtension(currentStorable.Name), result ? expendableDirectoryId : ReadOnlySpan<byte>.Empty);
var plaintextName = await DecryptNameAsync(currentStorable.Name, currentParent, specifics, expendableDirectoryId, cancellationToken).ConfigureAwait(false);
if (plaintextName is null)
return null;

Expand Down
Loading
Loading