Skip to content

Commit

Permalink
Feat/remove libsodium (#71)
Browse files Browse the repository at this point in the history
* Remove libsodium dependencies

* Added crypto tests;

* Impemented ConvertEd25519PublicKeyToCurve25519PublicKey;

* Removed hardcoded PUBLIC_KEY_BYTES value;

* SealedPublicKeyBox

* Fix README.md

* Fix SealedPublicKeyBox keys

* Decreased BYTES_MIN param to 0 in GenericHash;

* 1.0.18

---------

Co-authored-by: Igor Matsak <[email protected]>
  • Loading branch information
k-karuna and matsakiv authored Feb 15, 2023
1 parent 4922512 commit 49ec82e
Show file tree
Hide file tree
Showing 35 changed files with 1,597 additions and 1,163 deletions.
14 changes: 9 additions & 5 deletions Beacon.Sdk.Tests/Beacon.Sdk.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4"/>
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7"/>
<PackageReference Include="MSTest.TestFramework" Version="2.2.3"/>
<PackageReference Include="coverlet.collector" Version="3.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Beacon.Sdk\Beacon.Sdk.csproj"/>
<ProjectReference Include="..\Beacon.Sdk\Beacon.Sdk.csproj" />
</ItemGroup>

</Project>
72 changes: 72 additions & 0 deletions Beacon.Sdk.Tests/CryptoTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Beacon.Sdk.Tests
{
using Core.Infrastructure.Cryptography;
using Core.Infrastructure.Cryptography.NaCl;
using Core.Infrastructure.Cryptography.BouncyCastle;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Text;

[TestClass]
public class CryptoTests
{
private byte[] randomPubKeyBytes { get; } =
{
0x58, 0x96, 0xB4, 0xDE, 0x36, 0x93, 0x5F, 0x5, 0x79, 0x4E, 0xB8, 0x7, 0x3F, 0xB1, 0x7F, 0xE6, 0xAD,
0x64, 0xFF, 0xE3, 0x48, 0x60, 0x81, 0x1C, 0x9D, 0xDF, 0xB9, 0xE4, 0xF3, 0x2F, 0xF7, 0x5B,
};

private byte[] convertedPubKeyBytes { get; } =
{
0x50, 0x94, 0xD3, 0xF3, 0x88, 0xFD, 0x19, 0xF3, 0x3D, 0xBF, 0xA7, 0x3A, 0x6C, 0x9E, 0xCD, 0x80, 0x7C,
0x1C, 0x92, 0x74, 0xE3, 0xA0, 0x71, 0x8F, 0xEC, 0xB8, 0xDD, 0x8D, 0x3E, 0x78, 0x66, 0x15
};

[TestMethod]
public void TestSodiumConvertEd25519PublicKeyToCurve25519PublicKey()
{
var actual = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(randomPubKeyBytes);
CollectionAssert.AreEqual(convertedPubKeyBytes, actual);
}

[TestMethod]
public void TestConvertEd25519PublicKeyToCurve25519PublicKey()
{
var actual = new byte[32];
MontgomeryCurve25519.EdwardsToMontgomery(actual, randomPubKeyBytes);
CollectionAssert.AreEqual(convertedPubKeyBytes, actual);
}

[TestMethod]
public void CanEncryptAndDecryptBySecretBox()
{
var key = SecureRandom.GetRandomBytes(32);
var nonce = SecureRandom.GetRandomBytes(24);
var message = Encoding.UTF8.GetBytes("Test message for secret box");

var cipher = SecretBox.Create(message, nonce, key);

var decrypted = SecretBox.Open(cipher, nonce, key);

CollectionAssert.AreEqual(message, decrypted);
}

[TestMethod]
public void CanEncryptAndDecryptBySealedSecretBox()
{
var seed = SecureRandom.GetRandomBytes(32);

var ed25519keyPair = PublicKeyAuth.GenerateKeyPair(seed);

var curve25519sk = Ed25519Extensions.ConvertEd25519SecretKeyToCurve25519SecretKey(ed25519keyPair.PrivateKey);
var curve25519pk = Ed25519Extensions.ConvertEd25519PublicKeyToCurve25519PublicKey(ed25519keyPair.PublicKey);

var message = Encoding.UTF8.GetBytes("Test message for secret box");

var cipher = SealedPublicKeyBox.Create(message, curve25519pk);

var decrypted = SealedPublicKeyBox.Open(cipher, curve25519sk, curve25519pk);

CollectionAssert.AreEqual(message, decrypted);
}
}
}
6 changes: 3 additions & 3 deletions Beacon.Sdk/Beacon.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
<Authors>Mikhail Tatarenko</Authors>
<Product>Beacon.Sdk</Product>
<Description>Beacon .NET SDK for Tezos wallet / dApps developers.</Description>
<Version>1.0.16</Version>
<Version>1.0.18</Version>
<Copyright>Copyright © Baking Bad 2019-2022</Copyright>
<Nullable>enable</Nullable>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LiteDB" Version="4.1.4" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="NaCl.Net" Version="0.1.13" />
<PackageReference Include="Netezos" Version="2.4.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="libsodium" Version="1.0.18.2" />
<PackageReference Include="Matrix.Sdk" Version="1.0.7" />
</ItemGroup>
</Project>
8 changes: 2 additions & 6 deletions Beacon.Sdk/Core/Domain/Entities/PeerFactory.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace Beacon.Sdk.Core.Domain.Entities
{
using System;
using Infrastructure.Cryptography.Libsodium;
using global::Beacon.Sdk.Core.Infrastructure.Cryptography;
using Interfaces;
using Netezos.Encoding;
using Utils;
Expand All @@ -17,10 +16,7 @@ public PeerFactory(ICryptographyService cryptographyService)

public static byte[] Hash(byte[] message, int bufferLength)
{
var buffer = new byte[bufferLength];
Sodium.CryptoGenericHash(buffer, bufferLength, message, (ulong)message.Length, Array.Empty<byte>(), 0);

return buffer;
return GenericHash.Hash(message, bufferLength);
}

public Peer Create(HexString hexPublicKey, string name, string version, string relayServer, bool isActive = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Beacon.Sdk.Core.Domain.Interfaces.Data
{
using Infrastructure.Cryptography.Libsodium;
using Utils;

public interface ISessionKeyPairRepository
Expand Down
1 change: 0 additions & 1 deletion Beacon.Sdk/Core/Domain/Interfaces/ICryptographyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Beacon.Sdk.Core.Domain.Interfaces
{
using Infrastructure.Cryptography.Libsodium;
using Utils;

public interface ICryptographyService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ChannelOpeningMessageBuilder(
public void BuildRecipientId(string relayServer, HexString hexPublicKey)
{
byte[] publicKeyByteArray = hexPublicKey.ToByteArray();
byte[] hash = GenericHash.Hash(publicKeyByteArray, null, publicKeyByteArray.Length)!;
byte[] hash = GenericHash.Hash(publicKeyByteArray, publicKeyByteArray.Length)!;

if (!HexString.TryParse(hash, out HexString hexHash))
throw new InvalidOperationException("Can not parse hash");
Expand Down
2 changes: 1 addition & 1 deletion Beacon.Sdk/Core/Domain/Services/P2PMessageService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Beacon.Sdk.Core.Domain.Services
{
using System.Text;
using Infrastructure.Cryptography.Libsodium;
using global::Beacon.Sdk.Core.Infrastructure.Cryptography;
using Interfaces;
using Interfaces.Data;
using Netezos.Encoding;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Org.BouncyCastle.Crypto.Digests;
using System;

namespace Beacon.Sdk.Core.Infrastructure.Cryptography.BouncyCastle
{
using NaCl;

public static class Ed25519Extensions
{
private const int PUBLIC_KEY_BYTES = 32;
private const int SCALAR_BYTES = 32;
private const int SECRET_KEY_BYTES = 32 + 32;

/// <summary>Converts the ed25519 public key to curve25519 public key.</summary>
/// <param name="ed25519PublicKey">Ed25519 public key.</param>
/// <returns>The curve25519 public key.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte[] ConvertEd25519PublicKeyToCurve25519PublicKey(byte[] ed25519PublicKey)
{
if (ed25519PublicKey == null || ed25519PublicKey.Length != PUBLIC_KEY_BYTES)
throw new ArgumentOutOfRangeException(nameof(ed25519PublicKey), ed25519PublicKey?.Length ?? 0, $"ed25519PublicKey must be {PUBLIC_KEY_BYTES} bytes in length.");

var result = new byte[PUBLIC_KEY_BYTES];
MontgomeryCurve25519.EdwardsToMontgomery(result, ed25519PublicKey);
return result;
}

/// <summary>Converts the ed25519 secret key to curve25519 secret key.</summary>
/// <param name="ed25519SecretKey">Ed25519 secret key.</param>
/// <returns>The curve25519 secret key.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte[] ConvertEd25519SecretKeyToCurve25519SecretKey(byte[] ed25519SecretKey)
{
// key can be appended with the public key or not (both are allowed)
if (ed25519SecretKey == null || (ed25519SecretKey.Length != PUBLIC_KEY_BYTES && ed25519SecretKey.Length != SECRET_KEY_BYTES))
throw new ArgumentOutOfRangeException(nameof(ed25519SecretKey), ed25519SecretKey?.Length ?? 0, $"ed25519SecretKey must be either {PUBLIC_KEY_BYTES} or {SECRET_KEY_BYTES} bytes in length.");

var sha512 = new Sha512Digest();

byte[] h = new byte[sha512.GetDigestSize()];

sha512.BlockUpdate(ed25519SecretKey, 0, SCALAR_BYTES);
sha512.DoFinal(h, 0);

PruneScalar(h);

return h.AsSpan(0, 32).ToArray();
}

private static void PruneScalar(byte[] n)
{
n[0] &= 0xF8;
n[SCALAR_BYTES - 1] &= 0x7F;
n[SCALAR_BYTES - 1] |= 0x40;
}
}
}
40 changes: 13 additions & 27 deletions Beacon.Sdk/Core/Infrastructure/Cryptography/CryptographyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ namespace Beacon.Sdk.Core.Infrastructure.Cryptography
using System.Linq;
using System.Text;
using Domain.Interfaces;
using Libsodium;
using Utils;
using Sodium = Libsodium.Sodium;

public class CryptographyService : ICryptographyService
{
private static readonly int MacBytes = Sodium.CryptoBoxMacBytes();
private static readonly int NonceBytes = Sodium.CryptoBoxNonceBytes();
private static readonly int MacBytes = 16;
private static readonly int NonceBytes = 24;

public SessionKeyPair CreateClientSessionKeyPair(byte[] clientPublicKey, byte[] serverPrivateKey)
{
Expand All @@ -20,7 +18,9 @@ public SessionKeyPair CreateClientSessionKeyPair(byte[] clientPublicKey, byte[]
byte[] serverSecretKeyCurve = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(serverPrivateKey)!;
byte[] clientPublicKeyCurve = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(clientPublicKey)!;

return KeyExchange.CreateClientSessionKeyPair(serverPublicKeyCurve, serverSecretKeyCurve,
return KeyExchange.CreateClientSessionKeyPair(
serverPublicKeyCurve,
serverSecretKeyCurve,
clientPublicKeyCurve);
}

Expand All @@ -31,25 +31,19 @@ public SessionKeyPair CreateServerSessionKeyPair(byte[] clientPublicKey, byte[]
byte[] serverSecretKeyCurve = PublicKeyAuth.ConvertEd25519SecretKeyToCurve25519SecretKey(serverPrivateKey)!;
byte[] clientPublicKeyCurve = PublicKeyAuth.ConvertEd25519PublicKeyToCurve25519PublicKey(clientPublicKey)!;

return KeyExchange.CreateServerSessionKeyPair(serverPublicKeyCurve, serverSecretKeyCurve,
return KeyExchange.CreateServerSessionKeyPair(
serverPublicKeyCurve,
serverSecretKeyCurve,
clientPublicKeyCurve);
}

public byte[] Hash(byte[] input) => GenericHash.Hash(input, null, input.Length);
public byte[] Hash(byte[] input) => GenericHash.Hash(input, input.Length);

public byte[] Hash(byte[] message, int bufferLength)
{
var buffer = new byte[bufferLength];

Sodium.Initialize();
Sodium.CryptoGenericHash(buffer, bufferLength, message, (ulong)message.Length, Array.Empty<byte>(), 0);

return buffer;
}
public byte[] Hash(byte[] message, int resultLength) => GenericHash.Hash(message, resultLength);

public KeyPair GenerateEd25519KeyPair(string seed)
{
byte[] hash = GenericHash.Hash(seed, (byte[]?)null, 32);
byte[] hash = GenericHash.Hash(seed, 32);

return PublicKeyAuth.GenerateKeyPair(hash);
}
Expand All @@ -67,14 +61,6 @@ public HexString Encrypt(string input, byte[] key)
return hexPayload;
}

// public static class ArrayExtensions
// {
// public byte this[Range input]
// {
// get;
// set;
// }
// }
public string Decrypt(HexString hexInput, byte[] key)
{
byte[] bytes = hexInput.ToByteArray();
Expand Down Expand Up @@ -136,7 +122,7 @@ public byte[] GenerateLoginDigest()
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds() * 1000;
var message = $"login:{now / 1000 / (5 * 60)}";

return GenericHash.Hash(message, (byte[]?)null, 32);
return GenericHash.Hash(message, 32);
}

public string GenerateHexSignature(byte[] loginDigest, byte[] secretKey)
Expand All @@ -148,7 +134,7 @@ public string GenerateHexSignature(byte[] loginDigest, byte[] secretKey)

public string GenerateHexId(byte[] publicKey)
{
byte[] hash = GenericHash.Hash(publicKey, null, publicKey.Length);
byte[] hash = GenericHash.Hash(publicKey, publicKey.Length);

return ToHexString(hash);
}
Expand Down
42 changes: 42 additions & 0 deletions Beacon.Sdk/Core/Infrastructure/Cryptography/GenericHash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Text;

using Org.BouncyCastle.Crypto.Digests;

namespace Beacon.Sdk.Core.Infrastructure.Cryptography
{
public static partial class GenericHash
{
private const int BYTES_MIN = 0;
private const int BYTES_MAX = 64;

/// <summary>Hashes a message, using the BLAKE2b primitive.</summary>
/// <param name="message">The message to be hashed.</param>
/// <param name="resultLength">The size (in bytes) of the desired result.</param>
/// <returns>Returns a byte array.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte[] Hash(string message, int resultLength)
{
return Hash(Encoding.UTF8.GetBytes(message), resultLength);
}

/// <summary>Hashes a message, using the BLAKE2b primitive.</summary>
/// <param name="message">The message to be hashed.</param>
/// <param name="resultLength">The size (in bytes) of the desired result.</param>
/// <returns>Returns a byte array.</returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte[] Hash(byte[] message, int resultLength)
{
if (resultLength > BYTES_MAX || resultLength < BYTES_MIN)
throw new ArgumentOutOfRangeException(nameof(resultLength), resultLength, $"bytes must be between {BYTES_MIN} and {BYTES_MAX} bytes in length.");

var array = new byte[resultLength];

var blake2bDigest = new Blake2bDigest(resultLength * 8);
blake2bDigest.BlockUpdate(message, 0, message.Length);
blake2bDigest.DoFinal(array, 0);

return array;
}
}
}
Loading

0 comments on commit 49ec82e

Please sign in to comment.