Skip to content

Commit fcfc92c

Browse files
committed
Added the option to restore corrupted vault config files
1 parent 234fd63 commit fcfc92c

24 files changed

Lines changed: 559 additions & 40 deletions

File tree

src/Core/SecureFolderFS.Core.Cryptography/Constants.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ public static class KeyTraits
99
public const int DEK_KEY_LENGTH = 32;
1010
public const int MAC_KEY_LENGTH = 32;
1111
public const int ARGON2_KEK_LENGTH = 32;
12-
public const int CHALLENGE_KEY_PART_LENGTH_32 = 32;
13-
public const int CHALLENGE_KEY_PART_LENGTH_64 = 64;
14-
public const int CHALLENGE_KEY_PART_LENGTH_128 = 128;
15-
public const int ECIES_SHA256_AESGCM_STDX963_KEY_LENGTH = CHALLENGE_KEY_PART_LENGTH_32;
12+
public const int KEY_PART_LENGTH_32 = 32;
13+
public const int KEY_PART_LENGTH_64 = 64;
14+
public const int KEY_PART_LENGTH_128 = 128;
15+
public const int ECIES_SHA256_AESGCM_STDX963_KEY_LENGTH = KEY_PART_LENGTH_32;
1616
public const int HMAC_SHA1_HASH_LENGTH = 20;
1717
}
1818

src/Core/SecureFolderFS.Core.Cryptography/Helpers/CryptHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace SecureFolderFS.Core.Cryptography.Helpers
1010
{
1111
public static class CryptHelpers
1212
{
13-
public static IKeyBytes GenerateChallenge(string vaultId, int challengeSize = Constants.KeyTraits.CHALLENGE_KEY_PART_LENGTH_128)
13+
public static IKeyBytes GenerateChallenge(string vaultId, int challengeSize = Constants.KeyTraits.KEY_PART_LENGTH_128)
1414
{
1515
var encodedVaultIdLength = Encoding.ASCII.GetByteCount(vaultId);
1616
var challenge = new byte[challengeSize + encodedVaultIdLength];

src/Core/SecureFolderFS.Core.Cryptography/NameCrypt/BaseNameCrypt.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ namespace SecureFolderFS.Core.Cryptography.NameCrypt
1010
internal abstract class BaseNameCrypt : INameCrypt
1111
{
1212
protected const NormalizationForm NORMALIZATION = NormalizationForm.FormC;
13-
protected readonly string fileNameEncodingId;
13+
14+
/// <inheritdoc/>
15+
public virtual string EncodingId { get; }
1416

1517
protected BaseNameCrypt(string fileNameEncodingId)
1618
{
17-
this.fileNameEncodingId = fileNameEncodingId;
19+
EncodingId = fileNameEncodingId;
1820
}
1921

2022
/// <inheritdoc/>
@@ -32,11 +34,11 @@ public virtual string EncryptName(ReadOnlySpan<char> plaintextName, ReadOnlySpan
3234
var ciphertextNameBuffer = EncryptFileName(bytes.Slice(0, count), directoryId);
3335

3436
// Encode string
35-
return fileNameEncodingId switch
37+
return EncodingId switch
3638
{
3739
Constants.CipherId.ENCODING_BASE64URL => Base64Url.EncodeToString(ciphertextNameBuffer),
3840
Constants.CipherId.ENCODING_BASE4K => SecombaBase4K.Encode(ciphertextNameBuffer).Normalize(NORMALIZATION),
39-
_ => throw new ArgumentOutOfRangeException(nameof(fileNameEncodingId))
41+
_ => throw new ArgumentOutOfRangeException(nameof(EncodingId))
4042
};
4143
}
4244

@@ -46,7 +48,7 @@ public virtual string EncryptName(ReadOnlySpan<char> plaintextName, ReadOnlySpan
4648
{
4749
try
4850
{
49-
if (fileNameEncodingId == Constants.CipherId.ENCODING_BASE4K && !ciphertextName.IsNormalized(NORMALIZATION))
51+
if (EncodingId == Constants.CipherId.ENCODING_BASE4K && !ciphertextName.IsNormalized(NORMALIZATION))
5052
{
5153
var normalizedLength = ciphertextName.GetNormalizedLength(NORMALIZATION);
5254
var destination = normalizedLength < 256 ? stackalloc char[normalizedLength] : new char[normalizedLength];
@@ -70,11 +72,11 @@ public virtual string EncryptName(ReadOnlySpan<char> plaintextName, ReadOnlySpan
7072
string? Decode(ReadOnlySpan<char> name, ReadOnlySpan<byte> associatedData)
7173
{
7274
// Decode buffer
73-
var decoded = fileNameEncodingId switch
75+
var decoded = EncodingId switch
7476
{
7577
Constants.CipherId.ENCODING_BASE64URL => Base64Url.DecodeFromChars(name),
7678
Constants.CipherId.ENCODING_BASE4K => SecombaBase4K.Decode(name),
77-
_ => throw new ArgumentOutOfRangeException(nameof(fileNameEncodingId))
79+
_ => throw new ArgumentOutOfRangeException(nameof(EncodingId))
7880
};
7981

8082
// Decrypt

src/Core/SecureFolderFS.Core.Cryptography/NameCrypt/INameCrypt.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ namespace SecureFolderFS.Core.Cryptography.NameCrypt
77
/// </summary>
88
public interface INameCrypt : IDisposable
99
{
10+
/// <summary>
11+
/// Gets the encoding identifier.
12+
/// </summary>
13+
string EncodingId { get; }
14+
1015
/// <summary>
1116
/// Encrypts the <paramref name="plaintextName"/> using associated <paramref name="directoryId"/>.
1217
/// </summary>

src/Core/SecureFolderFS.Core.Cryptography/Security.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics.CodeAnalysis;
23
using SecureFolderFS.Core.Cryptography.ContentCrypt;
34
using SecureFolderFS.Core.Cryptography.HeaderCrypt;
45
using SecureFolderFS.Core.Cryptography.NameCrypt;
@@ -48,35 +49,50 @@ private Security(KeyPair keyPair)
4849
public static Security CreateNew(KeyPair keyPair, string contentCipherId, string fileNameCipherId, string fileNameEncodingId)
4950
{
5051
// Initialize crypt implementation
51-
IHeaderCrypt headerCrypt = contentCipherId switch
52+
var headerCrypt = GetHeaderCrypt(keyPair, contentCipherId);
53+
var contentCrypt = GetContentCrypt(contentCipherId, keyPair);
54+
var nameCrypt = GetNameCrypt(keyPair, fileNameCipherId, fileNameEncodingId);
55+
56+
return new(keyPair)
57+
{
58+
ContentCrypt = contentCrypt,
59+
HeaderCrypt = headerCrypt,
60+
NameCrypt = nameCrypt
61+
};
62+
}
63+
64+
public static IHeaderCrypt GetHeaderCrypt(KeyPair keyPair, string contentCipherId)
65+
{
66+
return contentCipherId switch
5267
{
5368
CipherId.AES_CTR_HMAC => new AesCtrHmacHeaderCrypt(keyPair),
5469
CipherId.AES_GCM => new AesGcmHeaderCrypt(keyPair),
5570
CipherId.XCHACHA20_POLY1305 => new XChaChaHeaderCrypt(keyPair),
5671
CipherId.NONE => new NoHeaderCrypt(keyPair),
5772
_ => throw new ArgumentOutOfRangeException(nameof(contentCipherId))
5873
};
59-
IContentCrypt contentCrypt = contentCipherId switch
74+
}
75+
76+
public static IContentCrypt GetContentCrypt(string contentCipherId, KeyPair keyPair)
77+
{
78+
return contentCipherId switch
6079
{
6180
CipherId.AES_CTR_HMAC => new AesCtrHmacContentCrypt(keyPair.MacKey),
6281
CipherId.AES_GCM => new AesGcmContentCrypt(),
6382
CipherId.XCHACHA20_POLY1305 => new XChaChaContentCrypt(),
6483
CipherId.NONE => new NoContentCrypt(),
6584
_ => throw new ArgumentOutOfRangeException(nameof(contentCipherId))
6685
};
67-
INameCrypt? nameCrypt = fileNameCipherId switch
86+
}
87+
88+
public static INameCrypt? GetNameCrypt(KeyPair keyPair, string fileNameCipherId, string fileNameEncodingId)
89+
{
90+
return fileNameCipherId switch
6891
{
6992
CipherId.AES_SIV => new AesSivNameCrypt(keyPair, fileNameEncodingId),
7093
CipherId.NONE => null,
7194
_ => throw new ArgumentOutOfRangeException(nameof(fileNameCipherId))
7295
};
73-
74-
return new(keyPair)
75-
{
76-
ContentCrypt = contentCrypt,
77-
HeaderCrypt = headerCrypt,
78-
NameCrypt = nameCrypt
79-
};
8096
}
8197

8298
/// <inheritdoc/>

src/Core/SecureFolderFS.Core.FileSystem/Extensions/FileHeaderExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
using System;
22
using System.IO;
33
using System.Runtime.CompilerServices;
4-
using SecureFolderFS.Core.Cryptography;
4+
using SecureFolderFS.Core.Cryptography.HeaderCrypt;
55
using SecureFolderFS.Core.FileSystem.Buffers;
66
using SecureFolderFS.Storage.VirtualFileSystem;
77

88
namespace SecureFolderFS.Core.FileSystem.Extensions
99
{
10-
internal static class FileHeaderExtensions
10+
public static class FileHeaderExtensions
1111
{
1212
[SkipLocalsInit]
13-
public static bool ReadHeader(this HeaderBuffer headerBuffer, Stream ciphertextStream, Security security)
13+
public static bool ReadHeader(this HeaderBuffer headerBuffer, Stream ciphertextStream, IHeaderCrypt headerCrypt)
1414
{
1515
if (headerBuffer.IsHeaderReady)
1616
return true;
@@ -19,7 +19,7 @@ public static bool ReadHeader(this HeaderBuffer headerBuffer, Stream ciphertextS
1919
throw FileSystemExceptions.StreamNotReadable;
2020

2121
// Allocate ciphertext header
22-
Span<byte> ciphertextHeader = stackalloc byte[security.HeaderCrypt.HeaderCiphertextSize];
22+
Span<byte> ciphertextHeader = stackalloc byte[headerCrypt.HeaderCiphertextSize];
2323

2424
// Read header
2525
int read;
@@ -43,7 +43,7 @@ public static bool ReadHeader(this HeaderBuffer headerBuffer, Stream ciphertextS
4343
return false;
4444

4545
// Decrypt header
46-
headerBuffer.IsHeaderReady = security.HeaderCrypt.DecryptHeader(ciphertextHeader, headerBuffer);
46+
headerBuffer.IsHeaderReady = headerCrypt.DecryptHeader(ciphertextHeader, headerBuffer);
4747

4848
return headerBuffer.IsHeaderReady;
4949
}

src/Core/SecureFolderFS.Core.FileSystem/Helpers/Paths/Abstract/AbstractPathHelpers.Directory.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,26 @@ namespace SecureFolderFS.Core.FileSystem.Helpers.Paths.Abstract
1010
{
1111
public static partial class AbstractPathHelpers
1212
{
13+
public static async Task<bool> GetDirectoryIdAsync(
14+
IFolder folderOfDirectoryId,
15+
IFolder contentFolder,
16+
Memory<byte> directoryId,
17+
CancellationToken cancellationToken)
18+
{
19+
if (folderOfDirectoryId.Id == contentFolder.Id)
20+
return false;
21+
22+
var directoryIdFile = await folderOfDirectoryId.GetFileByNameAsync(Constants.Names.DIRECTORY_ID_FILENAME, cancellationToken).ConfigureAwait(false);
23+
await using var directoryIdStream = await directoryIdFile.OpenStreamAsync(FileAccess.Read, FileShare.Read, cancellationToken).ConfigureAwait(false);
24+
25+
var read = await directoryIdStream.ReadAsync(directoryId, cancellationToken).ConfigureAwait(false);
26+
if (read < Constants.DIRECTORY_ID_SIZE)
27+
throw new IOException($"The data inside Directory ID file is of incorrect size: {read}.");
28+
29+
// The Directory ID is not empty - return true
30+
return true;
31+
}
32+
1333
public static async Task<bool> GetDirectoryIdAsync(
1434
IFolder folderOfDirectoryId,
1535
FileSystemSpecifics specifics,

src/Core/SecureFolderFS.Core.FileSystem/Streams/PlaintextStream.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public override int Read(Span<byte> buffer)
100100
}
101101

102102
// Read header if is not ready
103-
if (!_headerBuffer.ReadHeader(Inner, _security))
103+
if (!_headerBuffer.ReadHeader(Inner, _security.HeaderCrypt))
104104
throw new CryptographicException("Could not read header.");
105105

106106
var positionInBuffer = 0;
@@ -173,7 +173,7 @@ public override void SetLength(long value)
173173
return;
174174

175175
// Make sure the header is ready before we can read/modify chunks
176-
if (!TryWriteHeader() && !_headerBuffer.ReadHeader(Inner, _security))
176+
if (!TryWriteHeader() && !_headerBuffer.ReadHeader(Inner, _security.HeaderCrypt))
177177
throw new CryptographicException();
178178

179179
var plaintextChunkSize = _security.ContentCrypt.ChunkPlaintextSize;
@@ -274,7 +274,7 @@ public override void Flush()
274274

275275
private void WriteInternal(ReadOnlySpan<byte> buffer, long position)
276276
{
277-
if (!TryWriteHeader() && !_headerBuffer.ReadHeader(Inner, _security))
277+
if (!TryWriteHeader() && !_headerBuffer.ReadHeader(Inner, _security.HeaderCrypt))
278278
throw new CryptographicException("Could not write nor read the header.");
279279

280280
var plaintextChunkSize = _security.ContentCrypt.ChunkPlaintextSize;

src/Core/SecureFolderFS.Core.FileSystem/Streams/StreamsAccess.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public Stream OpenPlaintextStream(string id, Stream wrappedStream, Stream? heade
8282
// The header needs to be read at this point now as there is existing data in the wrapped stream,
8383
// indicating overhead is present. Since the primary stream is write-only, we must
8484
// use the dedicated read-only stream and store the header.
85-
if (!headerBuffer.ReadHeader(headerReadingStream, _security))
85+
if (!headerBuffer.ReadHeader(headerReadingStream, _security.HeaderCrypt))
8686
throw new InvalidOperationException($"The {nameof(headerReadingStream)} cannot read the header.");
8787

8888
headerReadingStream.Dispose();

src/Core/SecureFolderFS.Core/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static class Names
1717
public static class Authentication
1818
{
1919
public const string AUTH_NONE = "none";
20+
public const string AUTH_RECOVERY_KEY_REQUIREMENT = "recovery_key_requirement";
2021
public const string AUTH_PASSWORD = "password";
2122
public const string AUTH_KEYFILE = "key_file";
2223
public const string AUTH_WINDOWS_HELLO = "windows_hello";

0 commit comments

Comments
 (0)