diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES
index cc739df5..51211f6a 100644
--- a/.openapi-generator/FILES
+++ b/.openapi-generator/FILES
@@ -131,7 +131,6 @@ docs/UpdateConferenceRequest.md
docs/UpdateConferenceRequestStatus.md
freeclimb.sln
git_push.sh
-src/freeclimb.Test/Api/DefaultApiTests.cs
src/freeclimb.Test/Model/AccountStatusTests.cs
src/freeclimb.Test/Model/AccountTypeTests.cs
src/freeclimb.Test/Model/AnsweredByTests.cs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3bf04a3..d97ee3c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
None
+
+
+## [4.3.0] 2023-04-03
+
+### Added
+
+- Introduce signing secret verification class (RequestVerifier) - https://docs.freeclimb.com/docs/validating-requests-from-freeclimb#how-to-verify-requests-manually
+
## [4.2.3] 2023-03-13
diff --git a/README.md b/README.md
index fb3d36b8..95908097 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ FreeClimb is a cloud-based application programming interface (API) that puts the
This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.0.0
-- SDK version: 4.2.3
+- SDK version: 4.3.0
- Build package: org.openapitools.codegen.languages.CSharpNetCoreClientCodegen
For more information, please visit [https://www.freeclimb.com/support/](https://www.freeclimb.com/support/)
@@ -316,7 +316,7 @@ Class | Method | HTTP request | Description
## Documentation for Serialization/Deserialization for Enums
-###These methods are not required unless being used for debugging/logging purposes
+### These methods are not required unless being used for debugging/logging purposes
- To serialize (turn value into enum), we would need to use the reflection method GetEnumByValue where you pass the enum as a type and value into the method to get the associated enum.
@@ -396,4 +396,44 @@ Class | Method | HTTP request | Description
}
}
- ```
\ No newline at end of file
+ ```
+
+
+
+## Documentation for verifying request signature
+
+- To verify the request signature, we will need to use the verifyRequestSignature method within the Request Verifier class
+
+ RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance)
+
+ This is a method that you can call directly from the request verifier class, it will throw exceptions depending on whether all parts of the request signature is valid otherwise it will throw a specific error message depending on which request signature part is causing issues
+
+ This method requires a requestBody of type string, a requestHeader of type string, a signingSecret of type string, and a tolerance value of type int
+
+ Example code down below
+
+ ```csharp
+
+ using freeclimb.Utils;
+ using System;
+
+
+ namespace Example
+ {
+ public class verifySignatureRequestExample
+ {
+ public static void Main()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60;
+
+ RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance)
+ }
+ }
+
+ }
+ ```
\ No newline at end of file
diff --git a/freeclimb.sln b/freeclimb.sln
index b2381362..24001613 100644
--- a/freeclimb.sln
+++ b/freeclimb.sln
@@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
VisualStudioVersion = 12.0.0.0
MinimumVisualStudioVersion = 10.0.0.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "freeclimb", "src\freeclimb\freeclimb.csproj", "{6D009CFA-77F3-4A59-BABE-221167D9A270}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "freeclimb", "src\freeclimb\freeclimb.csproj", "{1FC7C4DB-B627-4A2A-9881-BC4BCB079A7A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "freeclimb.Test", "src\freeclimb.Test\freeclimb.Test.csproj", "{19F1DEBC-DE5E-4517-8062-F000CD499087}"
EndProject
@@ -12,10 +12,10 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6D009CFA-77F3-4A59-BABE-221167D9A270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6D009CFA-77F3-4A59-BABE-221167D9A270}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6D009CFA-77F3-4A59-BABE-221167D9A270}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6D009CFA-77F3-4A59-BABE-221167D9A270}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1FC7C4DB-B627-4A2A-9881-BC4BCB079A7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FC7C4DB-B627-4A2A-9881-BC4BCB079A7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FC7C4DB-B627-4A2A-9881-BC4BCB079A7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FC7C4DB-B627-4A2A-9881-BC4BCB079A7A}.Release|Any CPU.Build.0 = Release|Any CPU
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19F1DEBC-DE5E-4517-8062-F000CD499087}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/src/freeclimb.Test/Utils/RequestVerifierTests.cs b/src/freeclimb.Test/Utils/RequestVerifierTests.cs
new file mode 100644
index 00000000..f42c1c96
--- /dev/null
+++ b/src/freeclimb.Test/Utils/RequestVerifierTests.cs
@@ -0,0 +1,155 @@
+using Xunit;
+
+using freeclimb.Utils;
+using System;
+
+namespace freeclimb.Test.Utils
+{
+ public class RequestVerifierTests : IDisposable
+ {
+ private RequestVerifier instance;
+
+
+ public RequestVerifierTests()
+ {
+ RequestVerifier instance = new RequestVerifier();
+ }
+
+ public void Dispose()
+ {
+ // Cleanup when everything is done.
+ }
+
+ ///
+ /// Test an instance of SignatureInformation
+ ///
+ [Fact]
+ public void RequestVerifierInstanceTests()
+ {
+ // TODO uncomment below to test "IsType" AccountRequest
+ //Assert.IsType(instance);
+ }
+
+ [Fact]
+ public void checkRequestBodyTest()
+ {
+ string requestBody = "";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679931346,v1=4945505e46930b6e31df721c069f10cd3a4cfb3c8e2ec67d2663fae49f95644f,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60 * 1000;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Request Body cannot be empty or null", exception.Message);
+ }
+ [Fact]
+ public void checkRequestHeaderTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,";
+ int tolerance = 5 * 60 * 1000;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Error with request header, signatures are not present", exception.Message);
+ }
+ [Fact]
+ public void checkRequestHeaderTest2()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60 * 1000;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Error with request header, timestamp is not present", exception.Message);
+ }
+ [Fact]
+ public void checkRequestHeaderTest3()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "";
+ int tolerance = 5 * 60;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Error with request header, Request header is empty", exception.Message);
+ }
+ [Fact]
+ public void checkSigningSecretTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "";
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Signing secret cannot be empty or null", exception.Message);
+ }
+ [Fact]
+ public void checkToleranceTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = int.MaxValue;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Tolerance value must be a positive integer", exception.Message);
+ }
+ [Fact]
+ public void checkToleranceTest2()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = -5;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Tolerance value must be a positive integer", exception.Message);
+ }
+ [Fact]
+ public void checkToleranceTest3()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 0;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Tolerance value must be a positive integer", exception.Message);
+ }
+ [Fact]
+ public void verifyToleranceTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1900871395,v1=1d798c86e977ff734dec3a8b8d67fe8621dcc1df46ef4212e0bfe2e122b01bfd,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60;
+ int currentTime = (int)((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds();
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Request time exceeded tolerance threshold. Request: 1900871395, CurrentTime: " + currentTime.ToString() + ", tolerance: 300", exception.Message);
+ }
+ [Fact]
+ public void verifySignatureTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,v1=1d798c86e977ff734dec3a8b8d67fe8621dcc1df46ef4212e0bfe2e122b01bfd,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60;
+ Action act = () => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance);
+ Exception exception = Assert.Throws(act);
+ Assert.Equal("Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret", exception.Message);
+ }
+ [Fact]
+ public void verifyRequestSignatureTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ int tolerance = 5 * 60;
+ var exception = Record.Exception(() => RequestVerifier.verifyRequestSignature(requestBody, requestHeader, signingSecret, tolerance));
+ Assert.Null(exception);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/freeclimb.Test/Utils/SignatureInformationTests.cs b/src/freeclimb.Test/Utils/SignatureInformationTests.cs
new file mode 100644
index 00000000..9f9bc90a
--- /dev/null
+++ b/src/freeclimb.Test/Utils/SignatureInformationTests.cs
@@ -0,0 +1,74 @@
+using Xunit;
+
+using freeclimb.Utils;
+using System;
+
+namespace freeclimb.Test.Utils
+{
+ public class SignatureInformationTests : IDisposable
+ {
+ // TODO uncomment below to declare an instance variable for AccountRequest
+ private SignatureInformation instance;
+
+ public SignatureInformationTests()
+ {
+ string requestHeader = "t=1679944186,v1=c3957749baf61df4b1506802579cc69a74c77a1ae21447b930e5a704f9ec4120,v1=1ba18712726898fbbe48cd862dd096a709f7ad761a5bab14bda9ac24d963a6a8";
+ instance = new SignatureInformation(requestHeader);
+ }
+
+ public void Dispose()
+ {
+ // Cleanup when everything is done.
+ }
+
+ ///
+ /// Test an instance of SignatureInformation
+ ///
+ [Fact]
+ public void SignatureInformationInstanceTests()
+ {
+ // TODO uncomment below to test "IsType" AccountRequest
+ //Assert.IsType(instance);
+ }
+
+
+ ///
+ /// Test the method 'isRequestTimeValid'
+ ///
+ [Fact]
+ public void isRequestTimeValidTest()
+ {
+ //For test purposes, this relates to three days, we also want to ensure that the signature header remains the same during tests
+ int tolerance = 5 * 60;
+ Boolean isRequestTimeValid = instance.isRequestTimeValid(tolerance);
+ Assert.Equal(isRequestTimeValid, true);
+ }
+ [Fact]
+ public void isRequestTimeValidTest2()
+ {
+ //For test purposes, this relates to three days, we also want to ensure that the signature header remains the same during tests
+ int tolerance = 5 * 60 * 10000;
+ Boolean isRequestTimeValid = instance.isRequestTimeValid(tolerance);
+ Assert.Equal(isRequestTimeValid, false);
+ }
+ ///
+ /// Test the method 'isSignatureSafe'
+ ///
+ [Fact]
+ public void isSignatureSafeTest()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7793";
+ Boolean isSignatureSafe = instance.isSignatureSafe(requestBody, signingSecret);
+ Assert.Equal(isSignatureSafe, true);
+ }
+ [Fact]
+ public void isSignatureSafeTest2()
+ {
+ string requestBody = "{\"accountId\":\"AC1334ffb694cd8d969f51cddf5f7c9b478546d50c\",\"callId\":\"CAccb0b00506553cda09b51c5477f672a49e0b2213\",\"callStatus\":\"ringing\",\"conferenceId\":null,\"direction\":\"inbound\",\"from\":\"+13121000109\",\"parentCallId\":null,\"queueId\":null,\"requestType\":\"inboundCall\",\"to\":\"+13121000096\"}";
+ string signingSecret = "sigsec_ead6d3b6904196c60835d039e91b3341c77a7794";
+ Boolean isSignatureSafe = instance.isSignatureSafe(requestBody, signingSecret);
+ Assert.Equal(isSignatureSafe, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/freeclimb/Client/Configuration.cs b/src/freeclimb/Client/Configuration.cs
index 25f6fa0c..61717ac4 100644
--- a/src/freeclimb/Client/Configuration.cs
+++ b/src/freeclimb/Client/Configuration.cs
@@ -32,7 +32,7 @@ public class Configuration : IReadableConfiguration
/// Version of the package.
///
/// Version of the package.
- public const string Version = "4.2.3";
+ public const string Version = "4.3.0";
///
/// Identifier for ISO 8601 DateTime Format
@@ -107,7 +107,7 @@ public class Configuration : IReadableConfiguration
public Configuration()
{
Proxy = null;
- UserAgent = "OpenAPI-Generator/4.2.3/csharp";
+ UserAgent = "OpenAPI-Generator/4.3.0/csharp";
BasePath = "https://www.freeclimb.com/apiserver";
DefaultHeaders = new ConcurrentDictionary();
ApiKey = new ConcurrentDictionary();
@@ -452,7 +452,7 @@ public static string ToDebugReport()
report += " OS: " + System.Environment.OSVersion + "\n";
report += " .NET Framework Version: " + System.Environment.Version + "\n";
report += " Version of the API: 1.0.0\n";
- report += " SDK Package Version: 4.2.3\n";
+ report += " SDK Package Version: 4.3.0\n";
return report;
}
diff --git a/src/freeclimb/Utils/RequestVerifier.cs b/src/freeclimb/Utils/RequestVerifier.cs
new file mode 100644
index 00000000..f290aea6
--- /dev/null
+++ b/src/freeclimb/Utils/RequestVerifier.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace freeclimb.Utils
+{
+ public class RequestVerifier
+ {
+ public const int DEFAULT_TOLERANCE = 5 * 60 * 1000;
+ public static void verifyRequestSignature(String requestBody, String requestHeader, String signingSecret, int tolerance = DEFAULT_TOLERANCE)
+ {
+ RequestVerifier verifier = new RequestVerifier();
+ verifier.checkRequestBody(requestBody);
+ verifier.checkRequestHeader(requestHeader);
+ verifier.checkSigningSecret(signingSecret);
+ verifier.checkTolerance(tolerance);
+ SignatureInformation info = new SignatureInformation(requestHeader);
+ verifier.verifyTolerance(info, tolerance);
+ verifier.verifySignature(info, requestBody, signingSecret);
+ }
+
+ private void checkRequestBody(String requestBody)
+ {
+ if (requestBody == "" || requestBody == null)
+ {
+ throw new Exception("Request Body cannot be empty or null");
+ }
+ }
+
+ private void checkRequestHeader(String requestHeader)
+ {
+ if (requestHeader == "" || requestHeader == null)
+ {
+ throw new Exception("Error with request header, Request header is empty");
+ }
+ else if (!requestHeader.Contains("t="))
+ {
+ throw new Exception("Error with request header, timestamp is not present");
+ }
+ else if (!requestHeader.Contains("v1="))
+ {
+ throw new Exception("Error with request header, signatures are not present");
+ }
+ }
+
+ private void checkSigningSecret(String signingSecret)
+ {
+ if (signingSecret == "" || signingSecret == null)
+ {
+ throw new Exception("Signing secret cannot be empty or null");
+ }
+ }
+ private void checkTolerance(int tolerance)
+ {
+ if (tolerance <= 0 || tolerance >= int.MaxValue)
+ {
+ throw new Exception("Tolerance value must be a positive integer");
+ }
+ }
+
+ private void verifyTolerance(SignatureInformation info, int tolerance)
+ {
+ int currentTime = info.getCurrentUnixTime();
+ if (!info.isRequestTimeValid(tolerance))
+ {
+ throw new Exception(String.Format("Request time exceeded tolerance threshold. Request: {0}, CurrentTime: {1}, tolerance: {2}", info.requestTimestamp, currentTime, tolerance));
+ }
+ }
+ private void verifySignature(SignatureInformation info, String requestBody, String signingSecret)
+ {
+ if (!info.isSignatureSafe(requestBody, signingSecret))
+ {
+ throw new Exception("Unverified signature request, If this request was unexpected, it may be from a bad actor. Please proceed with caution. If the request was exepected, please check any typos or issues with the signingSecret");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/freeclimb/Utils/SignatureInformation.cs b/src/freeclimb/Utils/SignatureInformation.cs
new file mode 100644
index 00000000..b5236a3c
--- /dev/null
+++ b/src/freeclimb/Utils/SignatureInformation.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace freeclimb.Utils
+{
+ ///
+ /// This is the Signature Information class, this parses and validates the signature information class
+ ///
+ public class SignatureInformation
+ {
+ public int requestTimestamp;
+ public List signatures;
+ ///
+ /// This is the Signature Information constructor, this takes the requestHeader and parses it into the attribute values
+ ///
+ ///
+ public SignatureInformation(string requestHeader)
+ {
+ List constructorSignatures = new List();
+ string[] signatureHeaders = requestHeader.Split(',');
+ foreach (var signatureHeader in signatureHeaders)
+ {
+ var header = signatureHeader.Split('=')[0];
+ var value = signatureHeader.Split('=')[1];
+ if (header == "t")
+ {
+ requestTimestamp = Int32.Parse(value);
+ }
+ else if (header == "v1")
+ {
+ constructorSignatures.Add(value);
+ }
+ }
+ signatures = constructorSignatures;
+ }
+ ///
+ /// This is the timestamp validity function, this checks if the request timestamp is valid
+ ///
+ ///
+ /// true or false if timestamp is valid or invalid based on condition
+ public Boolean isRequestTimeValid(int tolerance)
+ {
+ var currentUnixTimestamp = getCurrentUnixTime();
+ return (requestTimestamp + tolerance) < currentUnixTimestamp;
+ }
+ ///
+ /// This is the signature validity function, this checks if the request timestamp is valid
+ ///
+ ///
+ ///
+ /// true or false if timestamp is valid or invalid based on condition
+ public Boolean isSignatureSafe(String requestBody, String signingSecret)
+ {
+ string hashValue = computeHash(requestBody, signingSecret);
+ return signatures.Contains(hashValue);
+ }
+
+ private String computeHash(String requestBody, String signingSecret)
+ {
+ string hashSeedString = requestTimestamp + "." + requestBody;
+ byte[] hashSeed = Encoding.ASCII.GetBytes(hashSeedString);
+ byte[] signingSecretBytes = Encoding.ASCII.GetBytes(signingSecret);
+ HMACSHA256 hmac = new HMACSHA256(signingSecretBytes);
+ byte[] hashValue = hmac.ComputeHash(hashSeed);
+ return BitConverter.ToString(hashValue).Replace("-", "").ToLower();
+ }
+
+ public int getCurrentUnixTime()
+ {
+ return (int)((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds();
+ }
+ }
+}
diff --git a/src/freeclimb/freeclimb.csproj b/src/freeclimb/freeclimb.csproj
index 6b11a129..b62ea34e 100644
--- a/src/freeclimb/freeclimb.csproj
+++ b/src/freeclimb/freeclimb.csproj
@@ -12,7 +12,7 @@
A library generated from a OpenAPI doc
No Copyright
freeclimb
- 4.2.3
+ 4.3.0
bin\$(Configuration)\$(TargetFramework)\freeclimb.xml
https://github.com/freeclimbapi/csharp-sdk.git
git