summaryrefslogtreecommitdiff
path: root/wrappers/csharp
diff options
context:
space:
mode:
Diffstat (limited to 'wrappers/csharp')
-rw-r--r--wrappers/csharp/Discovery.cs86
-rw-r--r--wrappers/csharp/EduVpnCommon.csproj57
-rw-r--r--wrappers/csharp/EduVpnCommon.props7
-rw-r--r--wrappers/csharp/EduVpnCommonTests/VerifyTests.cs27
-rw-r--r--wrappers/csharp/Makefile10
-rw-r--r--wrappers/csharp/README.md15
6 files changed, 121 insertions, 81 deletions
diff --git a/wrappers/csharp/Discovery.cs b/wrappers/csharp/Discovery.cs
index 183d94f..da4a6ea 100644
--- a/wrappers/csharp/Discovery.cs
+++ b/wrappers/csharp/Discovery.cs
@@ -1,8 +1,10 @@
using System;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
+// Make InsecureTestingSetExtraKey visible to tests
[assembly: InternalsVisibleTo("EduVpnCommonTests")]
namespace EduVpnCommon
@@ -11,12 +13,12 @@ namespace EduVpnCommon
{
/// <summary>
/// Verifies the signature on the JSON server_list.json/organization_list.json file.
- /// If the function returns the signature is valid for the given file type.
+ /// If the function returns, the signature is valid for the given file type.
/// </summary>
/// <param name="signatureFileContent">.minisig signature file contents.</param>
/// <param name="signedJson">Signed .json file contents.</param>
/// <param name="expectedFileName">The file type to be verified, one of <c>"server_list.json"</c> or <c>"organization_list.json"</c>.</param>
- /// <param name="minSignTime">Minimum time for signature. Should be set to at least the time in a previously retrieved file.</param>
+ /// <param name="minSignTime">Minimum time for signature. Should be set to at least the time of the previous signature.</param>
/// <exception cref="ArgumentException">If <c>expectedFileName</c> is not one of the allowed values.</exception>
/// <exception cref="VerifyException">If signature verification fails.</exception>
public static void Verify(
@@ -35,11 +37,20 @@ namespace EduVpnCommon
(ulong) minSignTime.ToUnixTimeSeconds());
}
- if (result != VerifyReturnCode.Ok)
+ switch (result)
{
- if (result == VerifyReturnCode.ErrUnknownExpectedFileName)
- throw new ArgumentException("unknown name", nameof(expectedFileName));
- throw new VerifyException((VerifyErrorCode) result);
+ case VerifyReturnCode.Ok:
+ return;
+ case VerifyReturnCode.ErrUnknownExpectedFileName:
+ throw new ArgumentException("unknown expected file name", nameof(expectedFileName));
+ case VerifyReturnCode.ErrInvalidSignature:
+ throw new InvalidSignatureException();
+ case VerifyReturnCode.ErrInvalidSignatureUnknownKey:
+ throw new InvalidSignatureUnknownKeyException();
+ case VerifyReturnCode.ErrTooOld:
+ throw new SignatureTooOldException();
+ default:
+ throw new UnknownVerifyException((sbyte) result);
}
}
@@ -50,13 +61,17 @@ namespace EduVpnCommon
InsecureTestingSetExtraKey(keyHandle.Slice);
}
- const string VerifyLibName = "eduvpn_verify";
+ const string LibName = "eduvpn_common";
- [DllImport(VerifyLibName)]
+ [DllImport(LibName)]
static extern VerifyReturnCode Verify(GoSlice signatureFileContent, GoSlice signedJson, GoSlice expectedFileName, ulong minSignTime);
- [DllImport(VerifyLibName)] static extern void InsecureTestingSetExtraKey(GoSlice keyStr);
+ [DllImport(LibName)] static extern void InsecureTestingSetExtraKey(GoSlice keyStr);
+ /// <summary>
+ /// Safe auto-disposing Go slice handle.
+ /// Non-copying alternative to `Marshal.AllocHGlobal` etc.
+ /// </summary>
class GoSliceHandle : IDisposable
{
GCHandle gcHandle_;
@@ -68,6 +83,7 @@ namespace EduVpnCommon
GoSliceHandle(Array array, int offset, int count)
{
+ Debug.Assert(offset <= array.Length && /*prevent overflow:*/ count <= array.Length && offset <= array.Length - count);
gcHandle_ = GCHandle.Alloc(array, GCHandleType.Pinned);
var elemSize = Marshal.SizeOf(array.GetType().GetElementType()!);
slice_ = new GoSlice(gcHandle_.AddrOfPinnedObject() + offset * elemSize, count * elemSize);
@@ -76,12 +92,14 @@ namespace EduVpnCommon
public static GoSliceHandle FromArray<T>(ArraySegment<T> segment) where T : struct =>
new GoSliceHandle(segment.Array!, segment.Offset, segment.Count);
+ /// <summary>From string as UTF-8.</summary>
public static GoSliceHandle FromString(string str) =>
FromArray(new ArraySegment<byte>(Encoding.UTF8.GetBytes(str)));
public void Dispose() => gcHandle_.Free();
}
+ // C-compatible structure
readonly struct GoSlice
{
readonly IntPtr data_;
@@ -98,38 +116,42 @@ namespace EduVpnCommon
}
}
- public class VerifyException : Exception
+ /// <summary>Verification failed, do not trust the file.</summary>
+ public abstract class VerifyException : Exception
{
- public VerifyErrorCode Code { get; }
-
- internal VerifyException(VerifyErrorCode code) : base(GetMessage(code)) => Code = code;
+ protected VerifyException(string message) : base(message) { }
+ }
- static string GetMessage(VerifyErrorCode code) => code switch
- {
- VerifyErrorCode.ErrInvalidSignature => "invalid signature",
- VerifyErrorCode.ErrInvalidSignatureUnknownKey => "invalid signature (unknown key)",
- VerifyErrorCode.ErrTooOld => "replay of previous signature (rollback)",
- _ => $"unknown verify error ({code})"
- };
+ /// <summary>Signature is invalid (for the expected file type).</summary>
+ public sealed class InvalidSignatureException : VerifyException
+ {
+ public InvalidSignatureException() : base("invalid signature") { }
}
- public enum VerifyErrorCode
+ /// <summary>Signature was created with an unknown key and has not been verified.</summary>
+ public sealed class InvalidSignatureUnknownKeyException : VerifyException
{
- /// <summary>Signature is invalid (for the expected file type).</summary>
- ErrInvalidSignature = VerifyReturnCode.ErrUnknownExpectedFileName + 1,
+ public InvalidSignatureUnknownKeyException() : base("invalid signature (unknown key)") { }
+ }
- /// <summary>Signature was created with an unknown key and has not been verified.</summary>
- ErrInvalidSignatureUnknownKey,
+ /// <summary>Signature timestamp smaller than specified minimum signing time (rollback).</summary>
+ public sealed class SignatureTooOldException : VerifyException
+ {
+ public SignatureTooOldException() : base("replay of previous signature (rollback)") { }
+ }
- /// <summary>Signature has a timestamp lower than the specified minimum signing time.</summary>
- ErrTooOld
+ /// <summary>Other unknown error.</summary>
+ public sealed class UnknownVerifyException : VerifyException
+ {
+ public UnknownVerifyException(sbyte code) : base($"unknown verify error ({code})") => Debug.Assert(code != 0);
}
- enum VerifyReturnCode
+ enum VerifyReturnCode : sbyte
{
Ok,
- ErrUnknownExpectedFileName
-
- //...
+ ErrUnknownExpectedFileName,
+ ErrInvalidSignature,
+ ErrInvalidSignatureUnknownKey,
+ ErrTooOld
}
-} \ No newline at end of file
+}
diff --git a/wrappers/csharp/EduVpnCommon.csproj b/wrappers/csharp/EduVpnCommon.csproj
index 4643da2..587601c 100644
--- a/wrappers/csharp/EduVpnCommon.csproj
+++ b/wrappers/csharp/EduVpnCommon.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
@@ -13,58 +13,59 @@
<ItemGroup>
<Compile Remove="EduVpnCommonTests/**" />
- </ItemGroup>
-
- <ItemGroup>
<EmbeddedResource Remove="EduVpnCommonTests/**" />
+ <None Remove="EduVpnCommonTests/**" />
</ItemGroup>
+ <!-- Include EXPORTS_LIB_PATH, LIB_NAME definitions -->
+ <ImportGroup Label="PropertySheets">
+ <Import Project="EduVpnCommon.props" />
+ </ImportGroup>
+
<Target Name="Build library for current OS" BeforeTargets="PrepareForBuild"
- Condition="!(Exists('../../exports/windows/amd64/eduvpn_verify.dll')
- Or Exists('../../exports/windows/386/eduvpn_verify.dll')
- Or Exists('../../exports/windows/arm/eduvpn_verify.dll')
- Or Exists('../../exports/windows/arm64/eduvpn_verify.dll')
- Or Exists('../../exports/linux/amd64/libeduvpn_verify.so')
- Or Exists('../../exports/linux/arm/libeduvpn_verify.so')
- Or Exists('../../exports/linux/arm64/libeduvpn_verify.so'))">
- <Message Text="Shared eduvpn_verify library not found, you should build that one first" Importance="high" />
+ Condition="!(Exists('$(EXPORTS_LIB_PATH)/windows/amd64/$(LIB_NAME).dll')
+ Or Exists('$(EXPORTS_LIB_PATH)/windows/386/$(LIB_NAME).dll')
+ Or Exists('$(EXPORTS_LIB_PATH)/windows/arm/$(LIB_NAME).dll')
+ Or Exists('$(EXPORTS_LIB_PATH)/windows/arm64/$(LIB_NAME).dll')
+ Or Exists('$(EXPORTS_LIB_PATH)/linux/amd64/lib$(LIB_NAME).so')
+ Or Exists('$(EXPORTS_LIB_PATH)/linux/arm/lib$(LIB_NAME).so')
+ Or Exists('$(EXPORTS_LIB_PATH)/linux/arm64/lib$(LIB_NAME).so'))">
+ <Message Text="!! Shared $(LIB_NAME) library not found, you should build that one first" Importance="high" />
</Target>
<ItemGroup>
- <None Remove="EduVpnCommonTests/**" />
-
<!--
See https://docs.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks#architecture-specific-folders
and https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
-->
- <None Condition="Exists('../../exports/windows/amd64/eduvpn_verify.dll')"
- Include="../../exports/windows/amd64/eduvpn_verify.dll" Pack="true" PackagePath="runtimes/win-x64/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/windows/amd64/$(LIB_NAME).dll')"
+ Include="$(EXPORTS_LIB_PATH)/windows/amd64/$(LIB_NAME).dll" Pack="true" PackagePath="runtimes/win-x64/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/windows/386/eduvpn_verify.dll')"
- Include="../../exports/windows/386/eduvpn_verify.dll" Pack="true" PackagePath="runtimes/win-x86/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/windows/386/$(LIB_NAME).dll')"
+ Include="$(EXPORTS_LIB_PATH)/windows/386/$(LIB_NAME).dll" Pack="true" PackagePath="runtimes/win-x86/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/windows/arm/eduvpn_verify.dll')"
- Include="../../exports/windows/arm/eduvpn_verify.dll" Pack="true" PackagePath="runtimes/win-arm/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/windows/arm/$(LIB_NAME).dll')"
+ Include="$(EXPORTS_LIB_PATH)/windows/arm/$(LIB_NAME).dll" Pack="true" PackagePath="runtimes/win-arm/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/windows/arm64/eduvpn_verify.dll')"
- Include="../../exports/windows/arm64/eduvpn_verify.dll" Pack="true" PackagePath="runtimes/win-arm64/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/windows/arm64/$(LIB_NAME).dll')"
+ Include="$(EXPORTS_LIB_PATH)/windows/arm64/$(LIB_NAME).dll" Pack="true" PackagePath="runtimes/win-arm64/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/linux/amd64/libeduvpn_verify.so')"
- Include="../../exports/linux/amd64/libeduvpn_verify.so" Pack="true" PackagePath="runtimes/linux-x64/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/linux/amd64/lib$(LIB_NAME).so')"
+ Include="$(EXPORTS_LIB_PATH)/linux/amd64/lib$(LIB_NAME).so" Pack="true" PackagePath="runtimes/linux-x64/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/linux/arm/libeduvpn_verify.so')"
- Include="../../exports/linux/arm/libeduvpn_verify.so" Pack="true" PackagePath="runtimes/linux-arm/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/linux/arm/lib$(LIB_NAME).so')"
+ Include="$(EXPORTS_LIB_PATH)/linux/arm/lib$(LIB_NAME).so" Pack="true" PackagePath="runtimes/linux-arm/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
- <None Condition="Exists('../../exports/linux/arm64/libeduvpn_verify.so')"
- Include="../../exports/linux/arm64/libeduvpn_verify.so" Pack="true" PackagePath="runtimes/linux-arm64/native/">
+ <None Condition="Exists('$(EXPORTS_LIB_PATH)/linux/arm64/lib$(LIB_NAME).so')"
+ Include="$(EXPORTS_LIB_PATH)/linux/arm64/lib$(LIB_NAME).so" Pack="true" PackagePath="runtimes/linux-arm64/native/">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
diff --git a/wrappers/csharp/EduVpnCommon.props b/wrappers/csharp/EduVpnCommon.props
new file mode 100644
index 0000000..97c2289
--- /dev/null
+++ b/wrappers/csharp/EduVpnCommon.props
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <EXPORTS_LIB_PATH Condition="'$(EXPORTS_LIB_PATH)' == ''">$(SolutionDir)../../exports/lib</EXPORTS_LIB_PATH>
+ <LIB_NAME Condition="'$(LIB_NAME)' == ''">eduvpn_common</LIB_NAME>
+ </PropertyGroup>
+</Project>
diff --git a/wrappers/csharp/EduVpnCommonTests/VerifyTests.cs b/wrappers/csharp/EduVpnCommonTests/VerifyTests.cs
index 41faf87..2d4a565 100644
--- a/wrappers/csharp/EduVpnCommonTests/VerifyTests.cs
+++ b/wrappers/csharp/EduVpnCommonTests/VerifyTests.cs
@@ -14,11 +14,11 @@ namespace EduVpnCommonTests
[OneTimeSetUp]
public void OneTimeSetUp() =>
- Discovery.InsecureTestingSetExtraKey(File.ReadLines($"{testDataDir_}/dummy/public.key").Last());
+ Discovery.InsecureTestingSetExtraKey(File.ReadLines($"{testDataDir_}/public.key").Last());
[Test]
- [TestCase("dummy/server_list.json.minisig", "dummy/server_list.json", "server_list.json")]
- [TestCase("dummy/organization_list.json.minisig", "dummy/organization_list.json", "organization_list.json")]
+ [TestCase("server_list.json.minisig", "server_list.json", "server_list.json")]
+ [TestCase("organization_list.json.minisig", "organization_list.json", "organization_list.json")]
public void TestValid(string sigFile, string jsonFile, string expectedFileName) =>
Discovery.Verify(
File.ReadAllBytes($"{testDataDir_}/{sigFile}"),
@@ -27,7 +27,7 @@ namespace EduVpnCommonTests
DateTimeOffset.UnixEpoch);
[Test]
- [TestCase("dummy/server_list.json.minisig", "dummy/server_list.json", "server_list.json")]
+ [TestCase("server_list.json.minisig", "server_list.json", "server_list.json")]
public void TestValidSegment(string sigFile, string jsonFile, string expectedFileName)
{
var bytes = new byte[] { 1, 2, 3 }.Concat(File.ReadAllBytes($"{testDataDir_}/{jsonFile}"))
@@ -40,10 +40,9 @@ namespace EduVpnCommonTests
}
[Test]
- [TestCase("dummy/random.txt", "dummy/server_list.json", "server_list.json")]
+ [TestCase("random.txt", "server_list.json", "server_list.json")]
public void TestInvalidSignature(string sigFile, string jsonFile, string expectedFileName) =>
- Assert.Throws(Is.TypeOf<VerifyException>()
- .And.Property(nameof(VerifyException.Code)).EqualTo(VerifyErrorCode.ErrInvalidSignature),
+ Assert.Throws<InvalidSignatureException>(
() => Discovery.Verify(
File.ReadAllBytes($"{testDataDir_}/{sigFile}"),
File.ReadAllBytes($"{testDataDir_}/{jsonFile}"),
@@ -51,10 +50,9 @@ namespace EduVpnCommonTests
DateTimeOffset.UnixEpoch));
[Test]
- [TestCase("dummy/server_list.json.wrong_key.minisig", "dummy/server_list.json", "server_list.json")]
+ [TestCase("server_list.json.wrong_key.minisig", "server_list.json", "server_list.json")]
public void TestWrongKey(string sigFile, string jsonFile, string expectedFileName) =>
- Assert.Throws(Is.TypeOf<VerifyException>()
- .And.Property(nameof(VerifyException.Code)).EqualTo(VerifyErrorCode.ErrInvalidSignatureUnknownKey),
+ Assert.Throws<InvalidSignatureUnknownKeyException>(
() => Discovery.Verify(
File.ReadAllBytes($"{testDataDir_}/{sigFile}"),
File.ReadAllBytes($"{testDataDir_}/{jsonFile}"),
@@ -62,10 +60,9 @@ namespace EduVpnCommonTests
DateTimeOffset.UnixEpoch));
[Test]
- [TestCase("dummy/server_list.json.minisig", "dummy/server_list.json", "server_list.json")]
+ [TestCase("server_list.json.minisig", "server_list.json", "server_list.json")]
public void TestOldSignature(string sigFile, string jsonFile, string expectedFileName) =>
- Assert.Throws(Is.TypeOf<VerifyException>()
- .And.Property(nameof(VerifyException.Code)).EqualTo(VerifyErrorCode.ErrTooOld),
+ Assert.Throws<SignatureTooOldException>(
() => Discovery.Verify(
File.ReadAllBytes($"{testDataDir_}/{sigFile}"),
File.ReadAllBytes($"{testDataDir_}/{jsonFile}"),
@@ -73,7 +70,7 @@ namespace EduVpnCommonTests
DateTimeOffset.MaxValue));
[Test]
- [TestCase("dummy/other_list.json.minisig", "dummy/other_list.json", "other_list.json")]
+ [TestCase("other_list.json.minisig", "other_list.json", "other_list.json")]
public void TestUnknownExpectedFile(string sigFile, string jsonFile, string expectedFileName) =>
Assert.Throws<ArgumentException>(
() => Discovery.Verify(
@@ -82,4 +79,4 @@ namespace EduVpnCommonTests
expectedFileName,
DateTimeOffset.UnixEpoch));
}
-} \ No newline at end of file
+}
diff --git a/wrappers/csharp/Makefile b/wrappers/csharp/Makefile
index 1761edc..29f9682 100644
--- a/wrappers/csharp/Makefile
+++ b/wrappers/csharp/Makefile
@@ -1,5 +1,9 @@
.PHONY: build pack test clean
+EXPORTS_PATH ?= ../../exports
+# Export, see EduVpnCommon.props
+export EXPORTS_LIB_PATH ?= $(EXPORTS_PATH)/lib
+
build:
dotnet publish EduVpnCommon.csproj --configuration Release
@@ -7,7 +11,11 @@ pack:
dotnet pack EduVpnCommon.csproj --configuration Release
test:
- $(MAKE) -C ../../exports
+ifneq ($(EXPORTS_PATH),)
+ifneq ($(wildcard $(EXPORTS_PATH)/Makefile),)
+ $(MAKE) -C "$(EXPORTS_PATH)"
+endif
+endif
dotnet test
clean:
diff --git a/wrappers/csharp/README.md b/wrappers/csharp/README.md
index d4d1d97..32d2330 100644
--- a/wrappers/csharp/README.md
+++ b/wrappers/csharp/README.md
@@ -3,33 +3,38 @@
## Requirements
You will need to install the [.NET SDK](https://dotnet.microsoft.com/download), which includes the `dotnet` tool. The
-wrapper targets .NET Standard 2.0, so which means that at least .NET Core 2.0 is required (.NET 5+ is also fine). For
-the tests, .NET 5 or newer is required.
+wrapper targets .NET Standard 2.0, which means that at least .NET Core 2.0 is required (.NET 5+ is also fine). For the
+tests, .NET 5 is required.
## Build & test
First build the shared Go library. Next:
-Build `EduVpnCommon`:
+Build `EduVpnCommon` (Release):
```shell
make
```
-Build as nupkg, including eduvpn_verify library:
+Build as nupkg, including shared Go library for all platforms built in `exports/lib/`:
```shell
make pack
```
+If you do not build this as part of the full repository, specify `EXPORTS_PATH="" EXPORTS_LIB_PATH="path/to/lib-folder"`
+when calling make.
+
The wrapper targets .NET Standard 2.0, which means that it can be referenced by projects using either .NET Framework
4.6.1+, .NET Core 2.0+, or .NET 5+.
Currently, directly referencing the project may not work (with `System.BadImageFormatException`) if you have multiple
-dynamic libraries compiled in the `exports` folder. If you instead add the `.nupkg`, e.g. with one of the
+dynamic libraries compiled in the `exports/lib/` folder. If you instead add the `.nupkg`, e.g. with one of the
methods [here](https://stackoverflow.com/q/43400069) or [here](https://stackoverflow.com/q/10240029), it automatically
copies the correct library.
+This also means that tests may fail if you have multiple dynamic libraries compiled!
+
Test:
```shell