diff options
| author | StevenWdV <stevenwdv@gmail.com> | 2021-11-20 18:09:09 +0100 |
|---|---|---|
| committer | StevenWdV <stevenwdv@gmail.com> | 2021-11-22 12:37:33 +0100 |
| commit | 8878d8705f0b0fcddb3979194340ca39df897580 (patch) | |
| tree | 6c920d0b9d40584dfe6bf7e5b2e865acff72e72f /wrappers/csharp/Discovery.cs | |
| parent | b8d368b93479233a8ecbeba3daf4b10bee8f0a4a (diff) | |
Add C bindings and a C# wrapper
Diffstat (limited to 'wrappers/csharp/Discovery.cs')
| -rw-r--r-- | wrappers/csharp/Discovery.cs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/wrappers/csharp/Discovery.cs b/wrappers/csharp/Discovery.cs new file mode 100644 index 0000000..0d6fef1 --- /dev/null +++ b/wrappers/csharp/Discovery.cs @@ -0,0 +1,135 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +[assembly: InternalsVisibleTo("EduVpnCommonTests")] + +namespace EduVpnCommon +{ + public static class Discovery + { + /// <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. + /// </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> + /// <exception cref="ArgumentException">If <c>expectedFileName</c> is not one of the allowed valued.</exception> + /// <exception cref="VerifyException">If signature verification fails.</exception> + public static void Verify( + ArraySegment<byte> signatureFileContent, + ArraySegment<byte> signedJson, + string expectedFileName, + DateTimeOffset minSignTime) + { + VerifyReturnCode result; + { + using var signatureHandle = GoSliceHandle.FromArray(signatureFileContent); + using var jsonHandle = GoSliceHandle.FromArray(signedJson); + using var expectedFileHandle = GoSliceHandle.FromString(expectedFileName); + + result = Verify(signatureHandle.Slice, jsonHandle.Slice, expectedFileHandle.Slice, + (ulong) minSignTime.ToUnixTimeSeconds()); + } + + if (result != VerifyReturnCode.Ok) + { + if (result == VerifyReturnCode.ErrUnknownExpectedFileName) + throw new ArgumentException("unknown name", nameof(expectedFileName)); + throw new VerifyException((VerifyErrorCode) result); + } + } + + /// <summary>Use for testing only, see Go documentation.</summary> + internal static void InsecureTestingSetExtraKey(string keyString) + { + using var keyHandle = GoSliceHandle.FromString(keyString); + InsecureTestingSetExtraKey(keyHandle.Slice); + } + + const string VerifyLibName = "eduvpn_verify"; + + [DllImport(VerifyLibName)] + static extern VerifyReturnCode Verify(GoSlice signatureFileContent, GoSlice signedJson, GoSlice expectedFileName, ulong minSignTime); + + [DllImport(VerifyLibName)] static extern void InsecureTestingSetExtraKey(GoSlice keyStr); + + class GoSliceHandle : IDisposable + { + GCHandle gcHandle_; + readonly GoSlice slice_; + + public GoSlice Slice => gcHandle_.IsAllocated + ? slice_ + : throw new InvalidOperationException("Handle was disposed"); + + GoSliceHandle(Array array, int offset, int count) + { + gcHandle_ = GCHandle.Alloc(array, GCHandleType.Pinned); + var elemSize = Marshal.SizeOf(array.GetType().GetElementType()!); + slice_ = new GoSlice(gcHandle_.AddrOfPinnedObject() + offset * elemSize, count * elemSize); + } + + public static GoSliceHandle FromArray<T>(ArraySegment<T> segment) where T : struct => + new GoSliceHandle(segment.Array!, segment.Offset, segment.Count); + + public static GoSliceHandle FromString(string str) => + FromArray(new ArraySegment<byte>(Encoding.UTF8.GetBytes(str))); + + public void Dispose() => gcHandle_.Free(); + } + + readonly struct GoSlice + { + readonly IntPtr data_; + readonly long len_, cap_; + + public GoSlice(IntPtr data, long len, long cap) + { + data_ = data; + len_ = len; + cap_ = cap; + } + + public GoSlice(IntPtr data, long len) : this(data, len, len) { } + } + } + + public class VerifyException : Exception + { + public VerifyErrorCode Code { get; } + + internal VerifyException(VerifyErrorCode code) : base(GetMessage(code)) => Code = code; + + 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})" + }; + } + + public enum VerifyErrorCode + { + /// <summary>Signature is invalid (for the expected file type).</summary> + ErrInvalidSignature = VerifyReturnCode.ErrUnknownExpectedFileName + 1, + + /// <summary>Signature was created with an unknown key and has not been verified.</summary> + ErrInvalidSignatureUnknownKey, + + /// <summary>Signature has a timestamp lower than the specified minimum signing time.</summary> + ErrTooOld + } + + enum VerifyReturnCode + { + Ok, + ErrUnknownExpectedFileName + + //... + } +}
\ No newline at end of file |
