Skip to content

Commit

Permalink
Resolve VCSWP-19101 (#42)
Browse files Browse the repository at this point in the history
Add signing secret verification util classes
  • Loading branch information
amahadaya authored Apr 4, 2023
1 parent ef6bf81 commit 9fc81b1
Show file tree
Hide file tree
Showing 10 changed files with 442 additions and 13 deletions.
1 change: 0 additions & 1 deletion .openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

None

<a name="4.3.0"></a>

## [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

<a name="4.2.3"></a>

## [4.2.3] 2023-03-13
Expand Down
46 changes: 43 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down Expand Up @@ -316,7 +316,7 @@ Class | Method | HTTP request | Description
<a name="documentation-for-serialization-deserialization"></a>
## 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.

Expand Down Expand Up @@ -396,4 +396,44 @@ Class | Method | HTTP request | Description
}

}
```
```

<a name="documentation-for-verify-request-signature"></a>

## 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)
}
}

}
```
10 changes: 5 additions & 5 deletions freeclimb.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
155 changes: 155 additions & 0 deletions src/freeclimb.Test/Utils/RequestVerifierTests.cs
Original file line number Diff line number Diff line change
@@ -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.
}

/// <summary>
/// Test an instance of SignatureInformation
/// </summary>
[Fact]
public void RequestVerifierInstanceTests()
{
// TODO uncomment below to test "IsType" AccountRequest
//Assert.IsType<RequestVerifier>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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<Exception>(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);
}
}
}
Loading

0 comments on commit 9fc81b1

Please sign in to comment.