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
4 changes: 4 additions & 0 deletions .github/workflows/owasp-dc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ on:
branches: [ main ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
depscan:
runs-on: ubuntu-latest
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/pr-tests-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ on:
pull_request:
branches: [ main ]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:

build:
runs-on: ubuntu-latest
strategy:
Expand Down Expand Up @@ -53,4 +58,4 @@ jobs:
- name: Run tests (override framework)
run: |
echo "Running tests for framework ${{ matrix.tfm }}"
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -c Release -f ${{ matrix.tfm }} --logger "trx"
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -c Release -f ${{ matrix.tfm }} --logger "trx"
6 changes: 5 additions & 1 deletion .github/workflows/pr-tests-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on:
pull_request:
branches: [ main ]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: windows-latest
Expand Down Expand Up @@ -55,4 +59,4 @@ jobs:
- name: Run tests (override framework)
run: |
echo "Running tests for framework ${{ matrix.tfm }}"
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -c Release -f ${{ matrix.tfm }} --logger "trx"
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -c Release -f ${{ matrix.tfm }} --logger "trx"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CAS .NET SDK is a comprehensive cryptographic toolkit for .NET, designed to prov
- Unified interface: no need to manage multiple cryptography packages or surf disparate documentation
- Built on trusted, open-source cryptography libraries
- Cross-platform support: Windows x64, Linux x64
- Multi-framework support: .NET 6, 7, 8, 9
- Multi-framework support: .NET 6, 7, 8, 9, 10

## Documentation & References
We build on the work of leading cryptography projects. For in-depth algorithm details and implementation notes, please refer to:
Expand Down
284 changes: 284 additions & 0 deletions cas-dotnet-sdk-tests/AES/AESWrapperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
using CasDotnetSdk.KeyExchange;
using CasDotnetSdk.KeyExchange.Types;
using CasDotnetSdk.Symmetric;
using System.Text;
using Xunit;

namespace CasDotnetSdkTests.Tests
{
public class AESWrapperTests
{
private readonly AESWrapper _aESWrapper;
private readonly X25519Wrapper _x25519Wrapper;
private const string ProjectDataDirectory = "cas-dotnet-sdk-tests/AES/Data";
private const string OutputDataDirectory = "AES/Data";

public AESWrapperTests()
{
this._aESWrapper = new AESWrapper();
this._x25519Wrapper = new X25519Wrapper();
}

[Fact]
public void AesNonce()
{
byte[] nonceKey = this._aESWrapper.GenerateAESNonce();
Assert.True(nonceKey.Length == 12);
}

[Fact]
public void Aes128Key()
{
byte[] key = this._aESWrapper.Aes128Key();
Assert.NotEmpty(key);
}

[Fact]
public void Aes256Key()
{
byte[] key = this._aESWrapper.Aes256Key();
Assert.NotEmpty(key);
}

[Fact]
public void Aes256X25519DiffieHellmanKeyAndNonce()
{
X25519SecretPublicKey aliceSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SecretPublicKey bobSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SharedSecret aliceSharedSecet = this._x25519Wrapper.GenerateSharedSecret(aliceSecretAndPublicKey.SecretKey, bobSecretAndPublicKey.PublicKey);
X25519SharedSecret bobSharedSecet = this._x25519Wrapper.GenerateSharedSecret(bobSecretAndPublicKey.SecretKey, aliceSecretAndPublicKey.PublicKey);
byte[] aliceAesKey = this._aESWrapper.Aes256KeyNonceX25519DiffieHellman(aliceSharedSecet.SharedSecret);
byte[] bobAesKey = this._aESWrapper.Aes256KeyNonceX25519DiffieHellman(bobSharedSecet.SharedSecret);

Assert.Equal(aliceAesKey, bobAesKey);
}

[Fact]
public void Aes128X25519DiffieHellmanKeyAndNonce()
{
X25519SecretPublicKey aliceSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SecretPublicKey bobSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SharedSecret aliceSharedSecet = this._x25519Wrapper.GenerateSharedSecret(aliceSecretAndPublicKey.SecretKey, bobSecretAndPublicKey.PublicKey);
X25519SharedSecret bobSharedSecet = this._x25519Wrapper.GenerateSharedSecret(bobSecretAndPublicKey.SecretKey, aliceSecretAndPublicKey.PublicKey);
byte[] aliceAesKey = this._aESWrapper.Aes128KeyNonceX25519DiffieHellman(aliceSharedSecet.SharedSecret);
byte[] bobAesKey = this._aESWrapper.Aes128KeyNonceX25519DiffieHellman(bobSharedSecet.SharedSecret);

Assert.Equal(aliceAesKey, bobAesKey);
}

[Fact]
public void Aes256X25519DiffieHellmanEncrypt()
{
X25519SecretPublicKey aliceSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SecretPublicKey bobSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SharedSecret aliceSharedSecet = this._x25519Wrapper.GenerateSharedSecret(aliceSecretAndPublicKey.SecretKey, bobSecretAndPublicKey.PublicKey);
X25519SharedSecret bobSharedSecet = this._x25519Wrapper.GenerateSharedSecret(bobSecretAndPublicKey.SecretKey, aliceSecretAndPublicKey.PublicKey);
byte[] aliceAesKey = this._aESWrapper.Aes256KeyNonceX25519DiffieHellman(aliceSharedSecet.SharedSecret);
byte[] bobAesKey = this._aESWrapper.Aes256KeyNonceX25519DiffieHellman(bobSharedSecet.SharedSecret);

Assert.Equal(aliceAesKey, bobAesKey);
byte[] nonce = this._aESWrapper.GenerateAESNonce();
byte[] toEncrypt = Encoding.UTF8.GetBytes("EncryptThisText");
byte[] encrypted = this._aESWrapper.Aes256Encrypt(nonce, aliceAesKey, toEncrypt);
byte[] plaintext = this._aESWrapper.Aes256Decrypt(nonce, bobAesKey, encrypted);
Assert.Equal(toEncrypt, plaintext);
}

[Fact]
public void Aes128X25519DiffieHellmanEncrypt()
{
X25519SecretPublicKey aliceSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SecretPublicKey bobSecretAndPublicKey = this._x25519Wrapper.GenerateSecretAndPublicKey();
X25519SharedSecret aliceSharedSecet = this._x25519Wrapper.GenerateSharedSecret(aliceSecretAndPublicKey.SecretKey, bobSecretAndPublicKey.PublicKey);
X25519SharedSecret bobSharedSecet = this._x25519Wrapper.GenerateSharedSecret(bobSecretAndPublicKey.SecretKey, aliceSecretAndPublicKey.PublicKey);
byte[] aliceAesKey = this._aESWrapper.Aes128KeyNonceX25519DiffieHellman(aliceSharedSecet.SharedSecret);
byte[] bobAesKey = this._aESWrapper.Aes128KeyNonceX25519DiffieHellman(bobSharedSecet.SharedSecret);

Assert.Equal(aliceAesKey, bobAesKey);
byte[] nonce = this._aESWrapper.GenerateAESNonce();
byte[] toEncrypt = Encoding.UTF8.GetBytes("EncryptThisText");
byte[] encrypted = this._aESWrapper.Aes128Encrypt(nonce, aliceAesKey, toEncrypt);
byte[] plaintext = this._aESWrapper.Aes128Decrypt(nonce, bobAesKey, encrypted);
Assert.Equal(toEncrypt, plaintext);
}

[Theory]
[MemberData(nameof(AES128NistVectors))]
public void Aes128MatchesNistEncryptVectors(byte[] nonce, byte[] key, byte[] plaintext, byte[] expectedCiphertext)
{
byte[] encrypted = this._aESWrapper.Aes128Encrypt(nonce, key, plaintext);

Assert.Equal(expectedCiphertext, encrypted.Take(expectedCiphertext.Length).ToArray());
}

[Theory]
[MemberData(nameof(AES256NistVectors))]
public void Aes256MatchesNistEncryptVectors(byte[] nonce, byte[] key, byte[] plaintext, byte[] expectedCiphertext)
{
byte[] encrypted = this._aESWrapper.Aes256Encrypt(nonce, key, plaintext);

Assert.Equal(expectedCiphertext, encrypted.Take(expectedCiphertext.Length).ToArray());
}

public static IEnumerable<object[]> AES128NistVectors()
{
return LoadGcmEncryptVectors("gcmDecrypt128.rsp");
}

public static IEnumerable<object[]> AES256NistVectors()
{
return LoadGcmEncryptVectors("gcmDecrypt256.rsp");
}

private static IEnumerable<object[]> LoadGcmEncryptVectors(string fileName)
{
string path = ResolveVectorPath(fileName);

string? keyHex = null;
string? ivHex = null;
string? ctHex = null;
string? ptHex = null;
int? ivBitLength = null;
int? aadBitLength = null;
bool isFailureCase = false;

foreach (string rawLine in File.ReadLines(path))
{
string line = rawLine.Trim();
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#", StringComparison.Ordinal))
{
continue;
}

if (line.StartsWith("[IVlen =", StringComparison.Ordinal))
{
ivBitLength = ParseBracketValue(line, "IVlen");
continue;
}

if (line.StartsWith("[AADlen =", StringComparison.Ordinal))
{
aadBitLength = ParseBracketValue(line, "AADlen");
continue;
}

if (line.StartsWith("[", StringComparison.Ordinal))
{
continue;
}

if (line.StartsWith("Count =", StringComparison.Ordinal))
{
ResetVectorState(
ref keyHex,
ref ivHex,
ref ctHex,
ref ptHex);
isFailureCase = false;
continue;
}

if (line.StartsWith("Key =", StringComparison.Ordinal))
{
keyHex = line["Key =".Length..].Trim();
continue;
}

if (line.StartsWith("IV =", StringComparison.Ordinal))
{
ivHex = line["IV =".Length..].Trim();
continue;
}

if (line.StartsWith("CT =", StringComparison.Ordinal))
{
ctHex = line["CT =".Length..].Trim();
continue;
}

if (line.StartsWith("AAD =", StringComparison.Ordinal))
{
continue;
}

if (line.Equals("FAIL", StringComparison.Ordinal))
{
isFailureCase = true;
continue;
}

if (!line.StartsWith("PT =", StringComparison.Ordinal))
{
continue;
}

ptHex = line["PT =".Length..].Trim();

if (isFailureCase || ivBitLength != 96 || aadBitLength != 0)
{
continue;
}

if (string.IsNullOrEmpty(keyHex) || string.IsNullOrEmpty(ivHex) || ctHex is null || ptHex is null)
{
continue;
}

yield return new object[]
{
HexToBytes(ivHex),
HexToBytes(keyHex),
HexToBytes(ptHex),
HexToBytes(ctHex)
};
}
}

private static string ResolveVectorPath(string fileName)
{
string projectRoot = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", ".."));
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string[] candidatePaths =
{
Path.Combine(AppContext.BaseDirectory, OutputDataDirectory, fileName),
Path.Combine(projectRoot, ProjectDataDirectory, fileName),
Path.Combine(userProfile, "Downloads", "gcmtestvectors", fileName)
};

foreach (string candidatePath in candidatePaths)
{
if (File.Exists(candidatePath))
{
return candidatePath;
}
}

throw new FileNotFoundException(
$"Missing AES-GCM vector file: {fileName}. Checked: {string.Join(", ", candidatePaths)}");
}

private static byte[] HexToBytes(string value)
{
return string.IsNullOrEmpty(value) ? Array.Empty<byte>() : Convert.FromHexString(value);
}

private static void ResetVectorState(
ref string? keyHex,
ref string? ivHex,
ref string? ctHex,
ref string? ptHex)
{
keyHex = null;
ivHex = null;
ctHex = null;
ptHex = null;
}

private static int ParseBracketValue(string line, string fieldName)
{
string prefix = $"[{fieldName} =";
string value = line[prefix.Length..].TrimEnd(']').Trim();
return int.Parse(value);
}
}
}
Loading
Loading