Skip to content

Commit a032484

Browse files
committed
fix README.md add response validation
1 parent f7a9616 commit a032484

File tree

3 files changed

+71
-9
lines changed

3 files changed

+71
-9
lines changed

README.md

Lines changed: 5 additions & 4 deletions
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 🎤
@@ -22,9 +22,10 @@ This SDK is in **beta**. We cannot guarantee constant availability or stability.
2222
Thanks to your feedback we will make improvements on it.
2323

2424
## Installation
25-
In the root of your project, being in the correct virtual environment, run:
25+
From the root of your project, run:
26+
2627
```shell
27-
(bunq_sdk_python) $ pip install bunq_sdk && pip freeze > requirements.txt
28+
(bunq_sdk) $ pip install bunq_sdk --upgrade
2829
```
2930

3031
## Usage
@@ -148,4 +149,4 @@ users = generated.User.list(api_context)
148149
```
149150

150151
##### Example
151-
See [`UserListExample.py`](./examples/user_list_example.py)
152+
See [`UserListExample.py`](./examples/user_list_example.py)

bunq/sdk/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ def _request(self, method, uri_relative, request_bytes, custom_headers):
101101
headers=all_headers
102102
)
103103

104+
if self._api_context.installation_context is not None:
105+
security.validate_response(
106+
self._api_context.installation_context.public_key_server,
107+
response.status_code,
108+
response.content,
109+
response.headers
110+
)
111+
104112
self._assert_response_success(response)
105113

106114
return response

bunq/sdk/security.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
_HEADER_CLIENT_ENCRYPTION_KEY = 'X-Bunq-Client-Encryption-Key'
4343
_HEADER_CLIENT_ENCRYPTION_IV = 'X-Bunq-Client-Encryption-Iv'
4444
_HEADER_CLIENT_ENCRYPTION_HMAC = 'X-Bunq-Client-Encryption-Hmac'
45+
_HEADER_SERVER_SIGNATURE = 'X-Bunq-Server-Signature'
4546

4647

4748
def generate_rsa_private_key():
@@ -97,7 +98,7 @@ def sign_request(private_key, method, endpoint, body_bytes, headers):
9798
:rtype: str
9899
"""
99100

100-
head_bytes = _generate_head_bytes(method, endpoint, headers)
101+
head_bytes = _generate_request_head_bytes(method, endpoint, headers)
101102
bytes_to_sign = head_bytes + body_bytes
102103
signer = PKCS1_v1_5.new(private_key)
103104
digest = SHA256.new()
@@ -107,7 +108,7 @@ def sign_request(private_key, method, endpoint, body_bytes, headers):
107108
return b64encode(sign)
108109

109110

110-
def _generate_head_bytes(method, endpoint, headers):
111+
def _generate_request_head_bytes(method, endpoint, headers):
111112
"""
112113
:type method: str
113114
:type endpoint: str
@@ -116,17 +117,17 @@ def _generate_head_bytes(method, endpoint, headers):
116117
:rtype: bytes
117118
"""
118119

119-
header_tuples = sorted((k, headers[k]) for k in headers)
120120
head_string = _FORMAT_METHOD_AND_ENDPOINT.format(method, endpoint)
121+
header_tuples = sorted((k, headers[k]) for k in headers)
121122

122123
for name, value in header_tuples:
123-
if _should_sign_header(name):
124+
if _should_sign_request_header(name):
124125
head_string += _FORMAT_HEADER_STRING.format(name, value)
125126

126127
return (head_string + _DELIMITER_NEWLINE).encode()
127128

128129

129-
def _should_sign_header(header_name):
130+
def _should_sign_request_header(header_name):
130131
"""
131132
:type header_name: str
132133
@@ -229,3 +230,55 @@ def _add_header_client_encryption_hmac(request_bytes, key, iv, custom_headers):
229230
hashed = hmac.new(key, iv + request_bytes, sha1)
230231
hashed_base64 = base64.b64encode(hashed.digest()).decode()
231232
custom_headers[_HEADER_CLIENT_ENCRYPTION_HMAC] = hashed_base64
233+
234+
235+
def validate_response(public_key_server, status_code, body_bytes, headers):
236+
"""
237+
:type public_key_server: RSA.RsaKey
238+
:type status_code: int
239+
:type body_bytes: bytes
240+
:type headers: dict[str, str]
241+
242+
:rtype: None
243+
"""
244+
245+
head_bytes = _generate_response_head_bytes(status_code, headers)
246+
bytes_signed = head_bytes + body_bytes
247+
signer = PKCS1_v1_5.pkcs1_15.new(public_key_server)
248+
digest = SHA256.new()
249+
digest.update(bytes_signed)
250+
signer.verify(digest, base64.b64decode(headers[_HEADER_SERVER_SIGNATURE]))
251+
252+
253+
def _generate_response_head_bytes(status_code, headers):
254+
"""
255+
:type status_code: int
256+
:type headers: dict[str, str]
257+
258+
:rtype: bytes
259+
"""
260+
261+
head_string = str(status_code) + _DELIMITER_NEWLINE
262+
header_tuples = sorted((k, headers[k]) for k in headers)
263+
264+
for name, value in header_tuples:
265+
if _should_sign_response_header(name):
266+
head_string += _FORMAT_HEADER_STRING.format(name, value)
267+
268+
return (head_string + _DELIMITER_NEWLINE).encode()
269+
270+
271+
def _should_sign_response_header(header_name):
272+
"""
273+
:type header_name: str
274+
275+
:rtype: bool
276+
"""
277+
278+
if header_name == _HEADER_SERVER_SIGNATURE:
279+
return False
280+
281+
if re.match(_PATTERN_HEADER_PREFIX_BUNQ, header_name):
282+
return True
283+
284+
return False

0 commit comments

Comments
 (0)