diff options
Diffstat (limited to 'wrappers/csharp')
| -rw-r--r-- | wrappers/csharp/Discovery.cs | 86 | ||||
| -rw-r--r-- | wrappers/csharp/EduVpnCommon.csproj | 57 | ||||
| -rw-r--r-- | wrappers/csharp/EduVpnCommon.props | 7 | ||||
| -rw-r--r-- | wrappers/csharp/EduVpnCommonTests/VerifyTests.cs | 27 | ||||
| -rw-r--r-- | wrappers/csharp/Makefile | 10 | ||||
| -rw-r--r-- | wrappers/csharp/README.md | 15 |
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 |
