Skip to content

Commit 546a017

Browse files
committed
add response validation; fix README.md
1 parent 8fc0969 commit 546a017

File tree

7 files changed

+130
-28
lines changed

7 files changed

+130
-28
lines changed

.idea/.idea.BunqSdkCsharp/riderModule.iml

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

BunqSdkCsharp.csproj

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,24 @@
99
<Folder Include="Http" />
1010
<Folder Include="Model\Generated\Object" />
1111
<Folder Include="Samples\Assets" />
12+
<Folder Include="Security" />
13+
<Folder Include="Tests\BunqSdkCsharpTest\bin\Debug\netcoreapp1.1\obj\Debug\netcoreapp1.1" />
14+
<Folder Include="Tests\BunqSdkCsharpTest\bin\Debug\netcoreapp1.1\tmp" />
15+
<Folder Include="Tests\BunqSdkCsharpTest\obj\Debug\netcoreapp1.1" />
1216
</ItemGroup>
1317
<ItemGroup>
1418
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
1519
</ItemGroup>
16-
</Project>
20+
<ItemGroup>
21+
<Content Include="project.json" />
22+
</ItemGroup>
23+
<ItemGroup>
24+
<Compile Remove="Tests\BunqSdkCsharpTest\Recourses\**" />
25+
</ItemGroup>
26+
<ItemGroup>
27+
<EmbeddedResource Remove="Tests\BunqSdkCsharpTest\Recourses\**" />
28+
</ItemGroup>
29+
<ItemGroup>
30+
<None Remove="Tests\BunqSdkCsharpTest\Resoucres\**" />
31+
</ItemGroup>
32+
</Project>

BunqSdkCsharp.sln

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
2-
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 2013
4-
VisualStudioVersion = 12.0.0.0
5-
MinimumVisualStudioVersion = 10.0.0.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BunqSdkCsharp", "BunqSdkCsharp.csproj", "{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}"
7-
EndProject
8-
Global
9-
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10-
Debug|Any CPU = Debug|Any CPU
11-
Release|Any CPU = Release|Any CPU
12-
EndGlobalSection
13-
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14-
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15-
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
16-
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
17-
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Release|Any CPU.Build.0 = Release|Any CPU
18-
EndGlobalSection
19-
GlobalSection(SolutionProperties) = preSolution
20-
HideSolutionNode = FALSE
21-
EndGlobalSection
22-
EndGlobal
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 2013
4+
VisualStudioVersion = 12.0.0.0
5+
MinimumVisualStudioVersion = 10.0.0.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BunqSdkCsharp", "BunqSdkCsharp.csproj", "{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BunqSdkCsharpTest", "Tests\BunqSdkCsharpTest\BunqSdkCsharpTest.csproj", "{B9F49991-943E-48E4-A151-B3CD12AFA231}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
18+
{4A5F65F6-41AC-4693-9A91-500A7B8DE1F7}.Release|Any CPU.Build.0 = Release|Any CPU
19+
{B9F49991-943E-48E4-A151-B3CD12AFA231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20+
{B9F49991-943E-48E4-A151-B3CD12AFA231}.Debug|Any CPU.Build.0 = Debug|Any CPU
21+
{B9F49991-943E-48E4-A151-B3CD12AFA231}.Release|Any CPU.ActiveCfg = Release|Any CPU
22+
{B9F49991-943E-48E4-A151-B3CD12AFA231}.Release|Any CPU.Build.0 = Release|Any CPU
23+
EndGlobalSection
24+
GlobalSection(SolutionProperties) = preSolution
25+
HideSolutionNode = FALSE
26+
EndGlobalSection
27+
EndGlobal

Http/ApiClient.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,22 @@ private HttpResponseMessage SendRequest(HttpRequestMessage requestMessage,
8686
SetSessionHeaders(requestMessage);
8787
InitializeHttpClientIfNeeded(apiContext);
8888
var responseMessage = client.SendAsync(requestMessage).Result;
89+
ValidateResponse(responseMessage);
8990
AssertResponseSuccess(responseMessage);
9091

9192
return responseMessage;
9293
}
9394

95+
private void ValidateResponse(HttpResponseMessage responseMessage)
96+
{
97+
var installationContext = apiContext.InstallationContext;
98+
99+
if (installationContext != null)
100+
{
101+
SecurityUtils.ValidateResponse(responseMessage, installationContext.PublicKeyServer);
102+
}
103+
}
104+
94105
private static HttpRequestMessage CreateHttpRequestMessage(HttpMethod method, string uriRelative,
95106
byte[] requestBodyBytes)
96107
{

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ We're very happy to introduce yet another unique product: complete banking SDKs!
1010
Now you can build even bigger and better apps and integrate them with your bank of the free! 🌈
1111

1212
Before you dive into this brand new SDK, please consider:
13-
- Checking out our new developer’s page [bunq.com/developers](https://bunq.com/developers) 🙌
13+
- Checking out our new developer’s page [https://bunq.com/en/developer](https://bunq.com/en/developer) 🙌
1414
- Grabbing your production API key from the bunq app or asking our support for a Sandbox API key 🗝
1515
- Visiting [together.bunq.com](https://together.bunq.com) where you can share your creations,
1616
questions and experience 🎤

Security/SecurityUtils.cs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@
77
using System.Security.Cryptography;
88
using System.Text;
99
using Bunq.Sdk.Context;
10+
using Bunq.Sdk.Exception;
1011
using Bunq.Sdk.Http;
1112

1213
namespace Bunq.Sdk.Security
1314
{
1415
public class SecurityUtils
1516
{
17+
/// <summary>
18+
/// Error constants.
19+
/// </summary>
20+
private const string ERROR_COULD_NOT_VERIFY_RESPONSE = "Could not verify server response.";
21+
1622
/// <summary>
1723
/// Constants for formatting the request textual representation for signing.
1824
/// </summary>
@@ -48,6 +54,7 @@ public class SecurityUtils
4854
private const string HEADER_CLIENT_ENCRYPTION_HMAC = "X-Bunq-Client-Encryption-Hmac";
4955
private const string HEADER_CLIENT_ENCRYPTION_IV = "X-Bunq-Client-Encryption-Iv";
5056
private const string HEADER_CLIENT_ENCRYPTION_KEY = "X-Bunq-Client-Encryption-Key";
57+
private const string HEADER_SERVER_SIGNATURE = "X-Bunq-Server-Signature";
5158

5259
/// <summary>
5360
/// Padding modes for the encrypted key and body.
@@ -95,7 +102,7 @@ private static byte[] GetRequestBodyBytes(HttpRequestMessage requestMessage)
95102
private static byte[] GenerateRequestHeadBytes(HttpRequestMessage requestMessage)
96103
{
97104
var requestHeadString = GenerateMethodAndEndpointString(requestMessage) + NEWLINE +
98-
GenerateHeadersSortedString(requestMessage) + NEWLINE +
105+
GenerateRequestHeadersSortedString(requestMessage) + NEWLINE +
99106
NEWLINE;
100107

101108
return Encoding.UTF8.GetBytes(requestHeadString);
@@ -109,13 +116,21 @@ private static string GenerateMethodAndEndpointString(HttpRequestMessage request
109116
return string.Format(FORMAT_METHOD_AND_ENDPOINT_STRING, method, endpoint);
110117
}
111118

112-
private static string GenerateHeadersSortedString(HttpRequestMessage requestMessage)
119+
private static string GenerateRequestHeadersSortedString(HttpRequestMessage requestMessage)
113120
{
114-
return requestMessage.Headers.Where(x =>
121+
return GenerateHeadersSortedString(
122+
requestMessage.Headers.Where(x =>
115123
x.Key.StartsWith(HEADER_NAME_PREFIX_X_BUNQ) ||
116124
x.Key.Equals(ApiClient.HEADER_CACHE_CONTROL) ||
117125
x.Key.Equals(ApiClient.HEADER_USER_AGENT)
118126
)
127+
);
128+
}
129+
130+
private static string GenerateHeadersSortedString(
131+
IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers)
132+
{
133+
return headers
119134
.Select(x => new KeyValuePair<string, string>(x.Key, string.Join(DELIMITER_HEADER_VALUE, x.Value)))
120135
.ToImmutableSortedDictionary()
121136
.Select(x => string.Format(FORMAT_HEADER_STRING, x.Key, x.Value))
@@ -263,5 +278,40 @@ private static byte[] HashHmac(byte[] iv, byte[] bytes, byte[] key)
263278
return hmacSha1.ComputeHash(buffer);
264279
}
265280
}
281+
282+
public static void ValidateResponse(HttpResponseMessage responseMessage, RSA serverPublicKey)
283+
{
284+
var headBytes = GenerateResponseHeadBytes(responseMessage);
285+
var bodyBytes = responseMessage.Content.ReadAsByteArrayAsync().Result;
286+
var responseBytes = ConcatenateByteArrays(headBytes, bodyBytes);
287+
var serverSignatureHeader = responseMessage.Headers.First(h => h.Key == HEADER_SERVER_SIGNATURE).Value
288+
.First();
289+
var serverSignature = Convert.FromBase64String(serverSignatureHeader);
290+
291+
if (!serverPublicKey.VerifyData(responseBytes, serverSignature, HashAlgorithmName.SHA256,
292+
RSASignaturePadding.Pkcs1))
293+
{
294+
throw new BunqException(ERROR_COULD_NOT_VERIFY_RESPONSE);
295+
}
296+
}
297+
298+
private static byte[] GenerateResponseHeadBytes(HttpResponseMessage responseMessage)
299+
{
300+
var requestHeadString = (int)responseMessage.StatusCode + NEWLINE +
301+
GenerateResponseHeadersSortedString(responseMessage) + NEWLINE +
302+
NEWLINE;
303+
304+
return Encoding.UTF8.GetBytes(requestHeadString);
305+
}
306+
307+
private static string GenerateResponseHeadersSortedString(HttpResponseMessage responseMessage)
308+
{
309+
return GenerateHeadersSortedString(
310+
responseMessage.Headers.Where(x =>
311+
x.Key.StartsWith(HEADER_NAME_PREFIX_X_BUNQ) &&
312+
!x.Key.Equals(HEADER_SERVER_SIGNATURE)
313+
)
314+
);
315+
}
266316
}
267317
}

project.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"buildOptions": {
3+
"copyToOutput": {
4+
"include": [
5+
"BunqSdkCsharpTest.xunit.runner.json"
6+
]
7+
}
8+
}
9+
}

0 commit comments

Comments
 (0)