77using System . Security . Cryptography ;
88using System . Text ;
99using Bunq . Sdk . Context ;
10+ using Bunq . Sdk . Exception ;
1011using Bunq . Sdk . Http ;
1112
1213namespace 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}
0 commit comments