Skip to content

Commit 8a26c1d

Browse files
authored
Merge pull request #8 from akreisman-epam/layer-7-python-sdk
Added layer 7 encryption for requests/responses of Hyperwallet client
2 parents 13619a7 + eb6c00e commit 8a26c1d

11 files changed

Lines changed: 516 additions & 7 deletions

File tree

CHANGELOG.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
Changelog
22
=========
33

4-
1.1.2 (current)
4+
1.1.3 (current)
5+
------------------
6+
7+
- Added Layer 7 encryption for Hyperwallet client
8+
9+
1.1.2 (2018-08-03)
510
------------------
611

712
- Added bank card endpoint

hyperwallet/api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class Api(object):
3636
The token for the program this user is accessing. **REQUIRED**
3737
:param server:
3838
Your UAT or Production API URL if applicable.
39+
:param encryptionData:
40+
Dictionary with params for encrypted requests (keys: clientPrivateKeySetLocation, hyperwalletKeySetLocation, etc).
3941
4042
.. note::
4143
**server** defaults to the Hyperwallet Sandbox URL if not provided.
@@ -46,7 +48,8 @@ def __init__(self,
4648
username=None,
4749
password=None,
4850
programToken=None,
49-
server=SERVER):
51+
server=SERVER,
52+
encryptionData=None):
5053
'''
5154
Create an instance of the API interface.
5255
This is the main interface the user will call to interact with the API.
@@ -66,7 +69,7 @@ def __init__(self,
6669
self.programToken = programToken
6770
self.server = server
6871

69-
self.apiClient = ApiClient(self.username, self.password, self.server)
72+
self.apiClient = ApiClient(self.username, self.password, self.server, encryptionData)
7073

7174
'''
7275
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"keys": [
3+
{
4+
"p": "5zx2tRWX6hKlVAsjkfqCyrSFeUVqgzNdtEwS7ZsOOvEKy4Ber3DId_IaGHMEWNu5W2Y5p5BukaCXg1b8UCOgXEHKC4A0SzJT5Y1ObSrAZn3gzo4NjMwzM3PRZdxA5UnqnHpvh3j47OcLYbHQGhcKRb1NktmyBwhoTnxoQUGW5As",
5+
"kty": "RSA",
6+
"q": "xl3k5jfvezJbyxzHAtZYxZd3V0o9jjX7VE6LUZFz4oY4EYL39-WqywoYLaWGFDBHWZ6woBRQHUFQDN2U1GzeXtjo9Tk4VLdU6a8PAMWc1x6EizA3Yj5K9MEcSMhjK--TfXSkL7Mg9x5ppIGI7Pj6Vh5syalTxWsqNYKhq5JohcM",
7+
"d": "iBkyW3X3WFQGKD4IzU7qj9jVJ0i7Qrnb9X1aed7t_2f895uGYgjtAB9twMHyjJ5PbK0VB3_5EAPVUCRS2VjUnoKczNfDmzkNO2xVP0By_M1QgYZpmOKiMNQnoZEZ5ghY6OpKY89DYRAvLnv76yDoyVtlblfrxkqjENr4lIhbIuBFyGCvT6hm0BeXNePsZvmKn_anC4C31mNXeTXYnOcdIkFxpfrmB21lnc3MA2jj0BGFe5SiKTG9qNNlS1KWTaoZnncR2tnlZ1ntHIk56fMBe9DOHaZ1LafY1G4k0mnViJZWcJEe-OU9uDfSBzx6HIQCslIlUJjcFMDdRcgAjInfEQ",
8+
"e": "AQAB",
9+
"use": "enc",
10+
"kid": "2018_enc_rsa_RSA-OAEP-256",
11+
"alg": "RSA-OAEP-256",
12+
"qi": "NjwtLMBXGwfL9TKl8rD2gOvDLFZ4LBYvzoefoKZq9jAJ_qNTTpUahfzdVFCDqEyRABv4VMa7ReKFIBxZxtOWAslS6lFU1odqQvfIF-IwvPCzFEMdXl4rCZgOjwh6NeVD91UI4-sWbWRaszDSSLmomE4x9WuQ8OUeBI746T1UzvM",
13+
"dp": "2oUkNTrDxjt6q7KfGbvgUAlKvXDhGD25hsIBfTNzvjW-GtQkJq1xdRCAoqxG5mY2g25We8idBNf7du4EIQOZ7rVpZ3bvdESKTjs8ayPkkLbSdMB_g5gRpsUDlLwRQ92XbeqybRbgPpiVA-zSmWU-musrXOSHPegvEkS1DT4bh0M",
14+
"dq": "GEJ9bwZiPG_hOArx78_lFW07xCopMw45CYt9kGE4ifiePM4Go4OsCp7WbCa1KhzpbPVyZnF1hs5pCtnCjOQvoevnOa5gzEOLl_S34gFI-CocTaV88H_rzNkdK22Oa14mbI5qUgcXPgGzK9JHu6uLeiLIbTVPMHK1u8uZGBGUxN8",
15+
"n": "sy2TiPsOk2JsDuojkCLYhNIXffvGizTWwLYXCPSVKzVSGEaWcK6D50wJyuKpsweR9o-MvxWp5EvPJRjYD4dyRAsVfk5LLhTR15uJfbxVpDdHmZgtFkIrqx4Y1mO-YCh6MgC3ZZszSLGl73pDTVlwrSSWtWCMXS0ePG0SfYasZcQNYfIUDbJ2Xb4ULcuIGPzlZ57NZ4Ww-Oz8faQeZgrmHh8xjgxaX_Nr2slvbaAAHEKkwOuAohx_1I9JG2ibUbyKpKDHbLx1ym4QASJIAK5t91ROuuYOeSrb-U9Q98essDmTcDIjDezgFEvfymOrYomIClMTMSnHqrTGo0J8ROFrYQ"
16+
},
17+
{
18+
"p": "2g6lVmoodmar_eao8bU_7YEL97tWQ7tqxGEWhWDR-6fP8Gmn4tgZ2L5Q2YSXiL1ImSFBtUy7LbUfKAH3QovFf2tMHEhDVVjyEtIBhjWz9JPr_QXm_G3gzBktMPRjusEBXUVZ6rn9cfgAcs7VOBN2Gq7Ed3RNs1vHt39w9ZPiQlU",
19+
"kty": "RSA",
20+
"q": "ngDddzAi_TuRLTzqpcE6IDrTRIzKSxdbnq5-PZ9g1kOVAIxOmLeIwKJ_6j9-RmOD1x1yv3LW-4yJXyD2NNVIzHp9loA8DGzqGO5vs7TtI9ptDBPp2tPGlvax9Oknfnyn_drs5TyX4pnHSRoepHRbzLYOHmzQl7vj28_VKgJE5ME",
21+
"d": "CFElMB9H3YGuL0KpqBqc6k3qlrjAOfF2Ao59wIk_GgzrZ0fUvTM1NNs-CwDpsPXNf5w23G4oAP8pwFPtPdoJB1GZNv3xiDZYP_cAZcxudWydF7xNq20XCoGKjzUFAVbkwbp5FykDcuieLevHk46AGdtfgmtUnbvHZUTZq5sYJLOoXCgYAvFqOlng2kMbBw3LH03IaZ5hOXECiV7lYYw1jkfnqB7aPX00fWBW1owZYLdkcfqI_66wB6-yXBIFVVgG8RaUiow8b561hkcLUqSJwbb6xFKJ5P0YC6oMTJrSobRhZUow1JPoiR2KL6vvgmJI6BrprfLv3vs2uvSHzT1OAQ",
22+
"e": "AQAB",
23+
"use": "sig",
24+
"kid": "2018_sig_rsa_RS256_2048",
25+
"alg": "RS256",
26+
"qi": "Lru1wRh_htXyKIH7UOiCnUiHqMrgHBbNux8G-G2nfzNFJftpkIgkSAo4DqDSce13ozdm2STVaLKw-91n3Jc-OovZEtVzBxlk7imbVxi6DLM52h8FUKW5n-Rmt0c7Lc-m-kbHdBBw3yvpCu3svU0UAkLSGpGNkZ2ve7CjFkjapz4",
27+
"dp": "sK7ZTRGrQ3Shu5LgJSlFaT384ngKx8reEczRILV4rz4kAJq7i9Sp7LMYc4c5-XPVlS4bPbm0mK5_Vj1xiZwTJNFd1DTBSjBNxO4gigyNiYkp19SmerbVRMrJkTcUb8ffQSHmX4jgUS4vvtbUcSFjuu8NBfVY2BFv28EJWBLBbBk",
28+
"dq": "iEa8vQkSlJFk5LyusaoYBSZXg79e1yddSV682U92iTce86sQOx3JYESHyTVcJz-7vbTTfJaDH9EVxqu6TtVKhbp8SWtu31StEDXOuBOrmQnSleEzCR8xIJHD6TWTb7_6cLP7MLhzU-lIfh9-IF-Psd-wC8PUoZpXrAX0l9f_LcE",
29+
"n": "hpXGr4AP-okE_WYUJjIga-N8T5pKwgjYrz_cin4k3sDBb1PRSjR9aZa_y1fn2IZpxra00iyG9ndC2Afv7kFqJtm6gadJVVNW_xFXqNKhrobsLJaHQIWnu8Un3pk78hZh42FlHNSM06GBvaCFdRHNtSBSyrQ2rjs8XlMd_YUaqWVIAmRYut_xR7qnKmaKk_wGN9IR5K9Wzt7pN14Ryc_nMGKpaKhgyBwWUVF5O_2IIbtx-Z2CalgdxqJ2by7Jo2LDTvBcAQ5swya0WPDCLV7AFGIrEEpYDF4AoQHpOaX58BqSOWhylmyxjXyYtyahYuRxV6ndXGqjtSGiHIhqSTu2FQ"
30+
}
31+
]
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"keys": [
3+
{
4+
"p": "8_PYykGGgfX0xwyzIdBQThStS6VUjpyJNub5X3l83ellWv0x_sAYtWb8xSmJfMbjHYn4-yzp98fgtk6fHZzuBqR0ey6JLyhDx0wGK6niaUHFGqQuaXg-2-T3bcBuZEWK-1lEwiMCYaqUY-BETF0Cg9GJJwzYkxZDmGl_62VdQtc",
5+
"kty": "RSA",
6+
"q": "qC_ugGj7UnLHgZiocRBZ5WmsCMGIDIe4TAZYC029wJ7IWu2YswAt_qGtpsI2NM2Bp_MmQt5nHPJ7KfSZl132V_vq1mCuxFOC2Y1LyqlY8LdIAUFHxbQuWCP-nNvbJbNs6Ph7XDgidygpv2vgQriqPbjbtVp5GQfGTYPzSk3JKrE",
7+
"d": "KLkNEtG3WcHuB5f4x8IXMxyIDAUgcULruPy45NBDqrY3yS4CKl_anpMp8_YJDriLAz79vePeVJ7_mKDcTysMVMJMK77HgxB-ixDyxw8Q1qHUu4IyzcmzPHWoDNmtoG7szPcIzwbUkLOWo1pg2whl0zYDsuQsJiIsuKf03HZpHKydHtcGO9nOA9k2_uatxQYZJbr_PM-Gri0gDbENCRhWYwePcrgpAekhju3TOA9N_ndSLre3XhdUKQbu4KoBoX8jUB2L3jKIsLZ5t2iSJ6kHRXDllinxXem3WTtqxDsNZqIhDgkSeej_mmotPFv73y0S2rYCZdV8FL9qFjFTBprVgQ",
8+
"e": "AQAB",
9+
"use": "enc",
10+
"kid": "2018_enc_rsa_RSA-OAEP-256",
11+
"alg": "RSA-OAEP-256",
12+
"qi": "S0vgkBYTYbuQRUzM7NLhDf93TUCkzx8OjRkCrkwbdpJo5gY1AybksqM2O8iu3sEQf2A01qHpZl0YxvPJhIs5BqqzoorPB6D3LmFjAmMZBs7alCuI_lgGoSgTIt6wqm6Y1nQ3mvPRBjB_8t7oSJPaJGwPgBue_TJsH1RkyRVXSW0",
13+
"dp": "KSh97fnKKMkHaEHTQyQzOEkyx614K6trVxD3B82mbIZBLG3FbpaYVJqwkM8mPCAOF2C82hvEyaI2Xmu7WrKsUgCTCmlaidNARDKmY92AroODLrB-iBraeB0URbcOqOo2vZtdB2gCsdmmuYcP3tZeY0EJ48W-EGrUMrWx-FQcvPc",
14+
"dq": "gW0JS7X-GW-MifVxQjjEBSAxrDdKO-JBd_e1z1UO_ejy485NoQo1WusOV_LChhXTfexGeFTv4r3S-_FoNKyxQvnwuPKD6z8cxc_PEHELqYpRle2njsPemiNw70LdPQD7gbieLdRg6XN11QHt_Upgb8kPAltSL2nlN4egNIDxmrE",
15+
"n": "oEWzUJmlScBJ8HkGst54PU41I69D7RyMY8ATNbPBMuJ1sML83qPGK99qjkwVeRtEv0cKd7E6vAIIW4Kgvv2n0IFl3ZvQjg0vOIFZuFOB5tQVW-rR8NGWcqOFS9lL-koej5YSqhZQ60dib5DiIrOy8R2vVWnRDc92qmkd5IA9S6urMooPXlEINmvkqIUH9TOsgubXsFQFD4eGqPQcZ_7miCWhqL23AonJARerCvcR6Bt6_T5UNbpFQJYrxWsLXyOo1p-UZkznS8wzO95wVT-jvgT6D2S2dVgxES6tcQX7ZMKiF7uTbNieFlokoLdJlVnkpoKUZD4VzG2fqhrciEF8pw"
16+
},
17+
{
18+
"p": "_z4REUkCaGuU3k4CrlYu4MwCFj5YbIeW8MFjIl95PY-DfNTp7V_XPFrHSN5IGnyfuvxesysKmb0edwjiFv5Xpn4Sx07tqghmYYt3eNT6q7zu9PPjTXUThFKceuPgnBLJZVdkhlcm2WZ8NcsyXtRpmUW6QtMfM1b4cqYu9JfRkYM",
19+
"kty": "RSA",
20+
"q": "9er99XCYiUycDXjQZRCsMW84XtslUhnvNsd_x_XgTQtk-8nmgNCMiScF9rQpKZuQXiCnNpnl9n3IjR4Yod_pL9-oAcnCoyG84I0i21jKkg_rPqid_aGy6f-H1WmmsfIgurihMzQBMVNg6N_8X47BJ5jgsU0HEVSYdeZNzQlqHfE",
21+
"d": "xYP2-OlUD6cW5vUbRXDOLaWYRoN8ged2v_p6AicoN05D8Tztj05TVu08-D8eExFbAVwrFKeptGiza9vNvbHF2jBzPToxsNuIN_yAny_Ii11YxzyVEiUKHRF0fAvoTFsB8f3gIr_cin2JTbo4ACNR92rw9jRmYptwTQZGse2EvSBquHWqAIUIPGmGDPmu7yPiMPKO5so7-_a2rQZaMACaFA5Ioey6mV8qehIMm0yx8COwbz36mcU07TSwi1Yj1sSCpzK8jxoPbbVfLnxPCJmkKChQ5qRjEr1ibctj_z6WqW2NGJvKhgVifQP8axt9sdywx1alCZrl8CxxYniVR3VgwQ",
22+
"e": "AQAB",
23+
"use": "sig",
24+
"kid": "2018_sig_rsa_RS256_2048",
25+
"alg": "RS256",
26+
"qi": "0dO4SMNaxUHxZsP5OVitZB0BjVGRJ0Hl5puLPe3uRjYFnhDQCkItpTBE5xnT3lM3BQjMDpewqqIxqXwRbx0K4tW2v3wcrHYwfmMqZUb0rdqS08lZBaAi3uD11MlGhQzsRqo8bfQmpHR11k1JGRK86ioFYrNFraJopsAL7K4X3vc",
27+
"dp": "s_KSHdmXNP8DyWa-RSLFkf7CSeRSetFs_PeaaJVe6KPRU6TX915mZEqrzRfJRcMu6akbKr3hj1nhrJI6s3NFYD_qBVIEBKg_Ze3poOqmf4WIAnIfgnBT_iov4APgSqiEDEp8uKmg3gx-7X4AWRLwD_s0wgAOMyfRqSK4YADY4vk",
28+
"dq": "VZdWkMiBrrflUKMOFT76T7JgMlOf57VzFuPUy6n-SZJ_sUsSWR886reUUcte0EZ-tuQyjsR9z47z8HnbJOwj4y-67_RjNBgX_yfgS-vZhYDY5dQWOSLAfMUdZ9__zstxLMv5_zJIf_x_LE5ZLoEnJTsGaW9f2F5TOiXQSl7OemE",
29+
"n": "9TCyRjIzsgrzqg5EWE-ei9hYHgEyo--iAOrlnVZkaAiWvpJCr2Een3VgEQy0vm228qlAvRY_xl3Hj1fruAm3G-6R3fxx_I2steuAUE1PzSIR7MP_4KCU-Px3vVtUEuhqbnXNl6sKnIR-eorigAzPyOZjpL9sY_oAVPysFWUdhiqrXzvmx8xtbOdfgZQ43pKdGiHpLq1ipUzLHtpHvze-eTz20AxWhDp59JI_oIfFgnUhutKKrbFG9BXRYgtA2-N9mSgNcpo9qnxjRcOo4F-ZfpdZJcSipgwFMJXs8v18KabhoI6ZYkTWg3g1aADsmHW0BQ4rJNpNEHACqRFAhQPTUw"
30+
}
31+
]
32+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"keys": [
3+
{
4+
"kty": "RSA",
5+
"e": "AQAB",
6+
"use": "enc",
7+
"kid": "2018_enc_rsa_RSA-OAEP-256",
8+
"alg": "RSA-OAEP-256",
9+
"n": "sy2TiPsOk2JsDuojkCLYhNIXffvGizTWwLYXCPSVKzVSGEaWcK6D50wJyuKpsweR9o-MvxWp5EvPJRjYD4dyRAsVfk5LLhTR15uJfbxVpDdHmZgtFkIrqx4Y1mO-YCh6MgC3ZZszSLGl73pDTVlwrSSWtWCMXS0ePG0SfYasZcQNYfIUDbJ2Xb4ULcuIGPzlZ57NZ4Ww-Oz8faQeZgrmHh8xjgxaX_Nr2slvbaAAHEKkwOuAohx_1I9JG2ibUbyKpKDHbLx1ym4QASJIAK5t91ROuuYOeSrb-U9Q98essDmTcDIjDezgFEvfymOrYomIClMTMSnHqrTGo0J8ROFrYQ"
10+
},
11+
{
12+
"kty": "RSA",
13+
"e": "AQAB",
14+
"use": "sig",
15+
"kid": "2018_sig_rsa_RS256_2048",
16+
"alg": "RS256",
17+
"n": "hpXGr4AP-okE_WYUJjIga-N8T5pKwgjYrz_cin4k3sDBb1PRSjR9aZa_y1fn2IZpxra00iyG9ndC2Afv7kFqJtm6gadJVVNW_xFXqNKhrobsLJaHQIWnu8Un3pk78hZh42FlHNSM06GBvaCFdRHNtSBSyrQ2rjs8XlMd_YUaqWVIAmRYut_xR7qnKmaKk_wGN9IR5K9Wzt7pN14Ryc_nMGKpaKhgyBwWUVF5O_2IIbtx-Z2CalgdxqJ2by7Jo2LDTvBcAQ5swya0WPDCLV7AFGIrEEpYDF4AoQHpOaX58BqSOWhylmyxjXyYtyahYuRxV6ndXGqjtSGiHIhqSTu2FQ"
18+
}
19+
]
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"keys": [
3+
{
4+
"kty": "RSA",
5+
"e": "AQAB",
6+
"use": "enc",
7+
"kid": "2018_enc_rsa_RSA-OAEP-256",
8+
"alg": "RSA-OAEP-256",
9+
"n": "oEWzUJmlScBJ8HkGst54PU41I69D7RyMY8ATNbPBMuJ1sML83qPGK99qjkwVeRtEv0cKd7E6vAIIW4Kgvv2n0IFl3ZvQjg0vOIFZuFOB5tQVW-rR8NGWcqOFS9lL-koej5YSqhZQ60dib5DiIrOy8R2vVWnRDc92qmkd5IA9S6urMooPXlEINmvkqIUH9TOsgubXsFQFD4eGqPQcZ_7miCWhqL23AonJARerCvcR6Bt6_T5UNbpFQJYrxWsLXyOo1p-UZkznS8wzO95wVT-jvgT6D2S2dVgxES6tcQX7ZMKiF7uTbNieFlokoLdJlVnkpoKUZD4VzG2fqhrciEF8pw"
10+
},
11+
{
12+
"kty": "RSA",
13+
"e": "AQAB",
14+
"use": "sig",
15+
"kid": "2018_sig_rsa_RS256_2048",
16+
"alg": "RS256",
17+
"n": "9TCyRjIzsgrzqg5EWE-ei9hYHgEyo--iAOrlnVZkaAiWvpJCr2Een3VgEQy0vm228qlAvRY_xl3Hj1fruAm3G-6R3fxx_I2steuAUE1PzSIR7MP_4KCU-Px3vVtUEuhqbnXNl6sKnIR-eorigAzPyOZjpL9sY_oAVPysFWUdhiqrXzvmx8xtbOdfgZQ43pKdGiHpLq1ipUzLHtpHvze-eTz20AxWhDp59JI_oIfFgnUhutKKrbFG9BXRYgtA2-N9mSgNcpo9qnxjRcOo4F-ZfpdZJcSipgwFMJXs8v18KabhoI6ZYkTWg3g1aADsmHW0BQ4rJNpNEHACqRFAhQPTUw"
18+
}
19+
]
20+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env python
2+
3+
import unittest
4+
import time
5+
import json
6+
import os.path
7+
8+
from jwcrypto import jwk, jws as cryptoJWS
9+
from jwcrypto.common import json_encode
10+
from hyperwallet.exceptions import HyperwalletException
11+
from hyperwallet.utils.encryption import Encryption
12+
from django.core.validators import URLValidator
13+
from django.core.exceptions import ValidationError
14+
15+
16+
class EncryptionTest(unittest.TestCase):
17+
18+
def test_should_successfully_encrypt_and_decrypt_text_message(self):
19+
20+
localDir = os.path.abspath(os.path.dirname(__file__))
21+
clientPath = os.path.join(localDir, 'resources', 'private-jwkset1')
22+
hyperwalletPath = os.path.join(localDir, 'resources', 'public-jwkset1')
23+
encryption = Encryption(clientPath, hyperwalletPath)
24+
testMessage = 'Message for test'
25+
encryptedMessage = encryption.encrypt(testMessage)
26+
decryptedMessage = encryption.decrypt(encryptedMessage)
27+
self.assertEqual(decryptedMessage, testMessage)
28+
29+
def test_should_fail_decryption_when_wrong_private_key_is_used(self):
30+
31+
localDir = os.path.abspath(os.path.dirname(__file__))
32+
clientPath1 = os.path.join(localDir, 'resources', 'private-jwkset1')
33+
hyperwalletPath1 = os.path.join(localDir, 'resources', 'public-jwkset1')
34+
clientPath2 = os.path.join(localDir, 'resources', 'private-jwkset2')
35+
hyperwalletPath2 = os.path.join(localDir, 'resources', 'public-jwkset2')
36+
encryption1 = Encryption(clientPath1, hyperwalletPath1)
37+
encryption2 = Encryption(clientPath2, hyperwalletPath2)
38+
testMessage = 'Message for test'
39+
encryptedMessage = encryption1.encrypt(testMessage)
40+
41+
with self.assertRaises(HyperwalletException) as exc:
42+
encryption2.decrypt(encryptedMessage)
43+
44+
self.assertEqual(exc.exception.message, 'No recipient matched the provided key["Failed: [ValueError(\'Decryption failed.\',)]"]')
45+
46+
def test_should_fail_signature_verification_when_wrong_public_key_is_used(self):
47+
48+
localDir = os.path.abspath(os.path.dirname(__file__))
49+
clientPath1 = os.path.join(localDir, 'resources', 'private-jwkset1')
50+
hyperwalletPath1 = os.path.join(localDir, 'resources', 'public-jwkset1')
51+
clientPath2 = os.path.join(localDir, 'resources', 'private-jwkset2')
52+
hyperwalletPath2 = os.path.join(localDir, 'resources', 'public-jwkset2')
53+
encryption1 = Encryption(clientPath1, hyperwalletPath1)
54+
encryption2 = Encryption(clientPath1, hyperwalletPath2)
55+
testMessage = 'Message for test'
56+
encryptedMessage = encryption1.encrypt(testMessage)
57+
58+
with self.assertRaises(HyperwalletException) as exc:
59+
encryption2.decrypt(encryptedMessage)
60+
61+
self.assertEqual(exc.exception.message, 'Signature verification failed.')
62+
63+
def test_should_throw_exception_when_wrong_jwk_key_set_location_is_given(self):
64+
65+
localDir = os.path.abspath(os.path.dirname(__file__))
66+
clientPath = 'wrong_keyset_path'
67+
hyperwalletPath = os.path.join(localDir, 'resources', 'public-jwkset1')
68+
encryption = Encryption(clientPath, hyperwalletPath)
69+
testMessage = 'Message for test'
70+
71+
with self.assertRaises(HyperwalletException) as exc:
72+
encryptedMessage = encryption.encrypt(testMessage)
73+
74+
self.assertEqual(exc.exception.message, 'Wrong JWK key set location path = wrong_keyset_path')
75+
76+
def test_should_throw_exception_when_not_supported_encryption_algorithm_is_given(self):
77+
78+
localDir = os.path.abspath(os.path.dirname(__file__))
79+
clientPath = os.path.join(localDir, 'resources', 'private-jwkset1')
80+
hyperwalletPath = os.path.join(localDir, 'resources', 'public-jwkset1')
81+
encryption = Encryption(clientPath, hyperwalletPath, 'unsupported_encryption_algorithm')
82+
testMessage = 'Message for test'
83+
84+
with self.assertRaises(HyperwalletException) as exc:
85+
encryptedMessage = encryption.encrypt(testMessage)
86+
87+
self.assertEqual(exc.exception.message, 'JWK set doesn\'t contain key with algorithm = unsupported_encryption_algorithm')
88+
89+
def test_should_throw_exception_when_jws_signature_does_not_contain_exp_header_param(self):
90+
91+
localDir = os.path.abspath(os.path.dirname(__file__))
92+
clientPath = os.path.join(localDir, 'resources', 'private-jwkset1')
93+
hyperwalletPath = '/public-jwkset1'
94+
encryption = Encryption(clientPath, hyperwalletPath)
95+
96+
jwsKeySet = self.__getJwkKeySet(location=clientPath)
97+
jwkSignKey = self.__findJwkKeyByAlgorithm(jwkKeySet=jwsKeySet, algorithm='RS256')
98+
privateKeyToSign = jwk.JWK(**jwkSignKey)
99+
body = "Test message"
100+
jwsToken = cryptoJWS.JWS(body.encode('utf-8'))
101+
jwsToken.add_signature(privateKeyToSign, None, json_encode({
102+
"alg": "RS256",
103+
"kid": jwkSignKey['kid']
104+
}))
105+
signedBody = jwsToken.serialize(True)
106+
107+
with self.assertRaises(HyperwalletException) as exc:
108+
encryption.checkJwsExpiration(signedBody)
109+
110+
self.assertEqual(exc.exception.message, 'While trying to verify JWS signature no [exp] header is found')
111+
112+
def test_should_throw_exception_when_jws_signature_exp_header_param_is_not_integer(self):
113+
114+
localDir = os.path.abspath(os.path.dirname(__file__))
115+
clientPath = os.path.join(localDir, 'resources', 'private-jwkset1')
116+
hyperwalletPath = '/public-jwkset1'
117+
encryption = Encryption(clientPath, hyperwalletPath)
118+
119+
jwsKeySet = self.__getJwkKeySet(location=clientPath)
120+
jwkSignKey = self.__findJwkKeyByAlgorithm(jwkKeySet=jwsKeySet, algorithm='RS256')
121+
privateKeyToSign = jwk.JWK(**jwkSignKey)
122+
body = "Test message"
123+
jwsToken = cryptoJWS.JWS(body.encode('utf-8'))
124+
jwsToken.add_signature(privateKeyToSign, None, json_encode({
125+
"alg": "RS256",
126+
"exp": "153356exp",
127+
"kid": jwkSignKey['kid']
128+
}))
129+
signedBody = jwsToken.serialize(True)
130+
131+
with self.assertRaises(HyperwalletException) as exc:
132+
encryption.checkJwsExpiration(signedBody)
133+
134+
self.assertEqual(exc.exception.message, 'Wrong value in [exp] header of JWS signature, must be integer')
135+
136+
def test_should_throw_exception_when_jws_signature_has_expired(self):
137+
138+
localDir = os.path.abspath(os.path.dirname(__file__))
139+
clientPath = os.path.join(localDir, 'resources', 'private-jwkset1')
140+
hyperwalletPath = '/public-jwkset1'
141+
encryption = Encryption(clientPath, hyperwalletPath)
142+
143+
jwsKeySet = self.__getJwkKeySet(location=clientPath)
144+
jwkSignKey = self.__findJwkKeyByAlgorithm(jwkKeySet=jwsKeySet, algorithm='RS256')
145+
privateKeyToSign = jwk.JWK(**jwkSignKey)
146+
body = "Test message"
147+
jwsToken = cryptoJWS.JWS(body.encode('utf-8'))
148+
jwsToken.add_signature(privateKeyToSign, None, json_encode({
149+
"alg": "RS256",
150+
"exp": int(time.time()) - 6000,
151+
"kid": jwkSignKey['kid']
152+
}))
153+
signedBody = jwsToken.serialize(True)
154+
155+
with self.assertRaises(HyperwalletException) as exc:
156+
encryption.checkJwsExpiration(signedBody)
157+
158+
self.assertEqual(exc.exception.message, 'JWS signature has expired, checked by [exp] JWS header')
159+
160+
def __getJwkKeySet(self, location):
161+
162+
try:
163+
URLValidator()(location)
164+
except ValidationError:
165+
if os.path.isfile(location):
166+
with open(location) as f:
167+
return f.read()
168+
else:
169+
raise HyperwalletException('Wrong JWK key set location path = ' + location)
170+
171+
return requests.get(location).text
172+
173+
def __findJwkKeyByAlgorithm(self, jwkKeySet, algorithm):
174+
175+
try:
176+
keySet = json.loads(jwkKeySet)
177+
except ValueError:
178+
raise HyperwalletException('Wrong JWK key set' + jwkKeySet)
179+
180+
for key in keySet['keys']:
181+
if key['alg'] == algorithm:
182+
return key
183+
184+
raise HyperwalletException('JWK set doesn\'t contain key with algorithm = ' + algorithm)
185+
186+
187+
if __name__ == '__main__':
188+
unittest.main()

0 commit comments

Comments
 (0)