Skip to content

Commit 8b8e7c7

Browse files
jnpblrdrgzlordkyzr
andauthored
Better Error Handling for Missing Entity Keys When Using Single Purpose Keys (#154)
* Updated JOSE auth decrypt error message to accurately present correct error when entity keys are not found. - changes to step to match new error message - added new EntityKeyNotFound error - logic changes to account for missing entity keys and raising new error Co-authored-by: Branden Jordan <[email protected]>
1 parent 6b70d87 commit 8b8e7c7

File tree

5 files changed

+48
-14
lines changed

5 files changed

+48
-14
lines changed

Pipfile.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

features/steps/key_steps.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from launchkey import LAUNCHKEY_PRODUCTION
22
from launchkey.factories import OrganizationFactory
33
from behave import given, when, then
4-
from hamcrest import assert_that, equal_to
4+
from hamcrest import assert_that, equal_to, string_contains_in_order
55

66
from managers import DirectoryManager
77

@@ -64,7 +64,8 @@ def attempt_basic_api_call(context):
6464

6565
@then("no valid key will be available to decrypt response")
6666
def no_key_exists_to_decrypt_response(context):
67-
assert_that(str(context.current_exception), equal_to("Incorrect decryption."),
68-
"Expected an Incorrect Decryption error but received a different error.")
6967

70-
context.execute_steps("Then a ValueError error occurs")
68+
assert_that(str(context.current_exception),
69+
string_contains_in_order("The key id:", "could not be found in the entities available keys."))
70+
71+
context.execute_steps("Then a EntityKeyNotFound error occurs")

launchkey/exceptions/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ class NoIssuerKey(LaunchKeyAPIException):
165165
"""Issuer key was not loaded"""
166166

167167

168+
class EntityKeyNotFound(LaunchKeyAPIException):
169+
"""The key id with this current response could not be found within the
170+
entities available keys"""
171+
172+
168173
class InvalidJWTResponse(LaunchKeyAPIException):
169174
"""JWT Response is not in a valid format"""
170175

launchkey/transports/jose_auth.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ..exceptions import InvalidEntityID, InvalidPrivateKey, \
2424
InvalidIssuer, InvalidAlgorithm, LaunchKeyAPIException, \
2525
JWTValidationFailure, UnexpectedAPIResponse, NoIssuerKey, \
26-
InvalidJWTResponse, UnexpectedKeyID
26+
InvalidJWTResponse, UnexpectedKeyID, EntityKeyNotFound
2727
from .. import VALID_JWT_ISSUER_LIST, API_CACHE_TIME, \
2828
JOSE_SUPPORTED_CONTENT_HASH_ALGS, JOSE_SUPPORTED_JWE_ALGS, \
2929
JOSE_SUPPORTED_JWE_ENCS, JOSE_SUPPORTED_JWT_ALGS, \
@@ -486,11 +486,17 @@ def decrypt_response(self, response):
486486
:return: Decrypted string
487487
"""
488488
package = JWEnc().unpack(response)
489-
keys = list(self.issuer_private_keys)
489+
keys = list()
490490
if 'kid' in package.headers:
491491
for key in self.issuer_private_keys:
492492
if key.kid == package.headers['kid']:
493493
keys = [key]
494+
if len(keys) == 0:
495+
raise EntityKeyNotFound("The key id: %s could not be found in "
496+
"the entities available keys."
497+
% package.headers["kid"])
498+
else:
499+
keys = list(self.issuer_private_keys)
494500
return JWE().decrypt(response, keys=keys).decode('utf-8')
495501

496502
def decrypt_rsa_response(self, response, key_id):

tests/test_jose_auth_transport.py

+26-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from launchkey.exceptions import InvalidAlgorithm, UnexpectedAPIResponse, \
1313
InvalidEntityID, InvalidIssuer, InvalidPrivateKey, NoIssuerKey, \
1414
InvalidJWTResponse, JWTValidationFailure, LaunchKeyAPIException, \
15-
UnexpectedKeyID
15+
UnexpectedKeyID, EntityKeyNotFound
1616
from launchkey import JOSE_SUPPORTED_JWT_ALGS, JOSE_SUPPORTED_JWE_ALGS, JOSE_SUPPORTED_JWE_ENCS, \
1717
JOSE_SUPPORTED_CONTENT_HASH_ALGS, API_CACHE_TIME, VALID_JWT_ISSUER_LIST, JOSE_JWT_LEEWAY, SDK_VERSION
1818

@@ -161,9 +161,9 @@ def setUp(self):
161161
self._transport.get.return_value = public_key
162162
self._transport._server_time_difference = 0, time()
163163

164-
self._jwt_patch = patch("launchkey.transports.jose_auth.JWT", return_value=MagicMock(spec=JWT)).start()
165-
self._jwt_patch.return_value.unpack.return_value.headers = faux_jwt_headers
166-
164+
self._jwenc_patch = patch("launchkey.transports.jose_auth.JWEnc",
165+
return_value=MagicMock(spec=JWT)).start()
166+
self._jwenc_patch.return_value.unpack.return_value.headers = faux_jwt_headers
167167
self.addCleanup(patch.stopall)
168168

169169
def _encrypt_decrypt(self):
@@ -192,6 +192,28 @@ def test_supported_jwe_encryptions_success(self):
192192
self._transport.jwe_claims_encryption = enc
193193
self._encrypt_decrypt()
194194

195+
def test_no_valid_keys_returns_error(self):
196+
with self.assertRaises(EntityKeyNotFound):
197+
self._jwenc_patch.return_value.unpack.return_value.headers = \
198+
{"alg": "RS512",
199+
"typ": "JWT",
200+
"kid": "ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff"
201+
}
202+
self._encrypt_decrypt()
203+
204+
@patch("launchkey.transports.jose_auth.JWE")
205+
def test_no_kid_in_headers_uses_all_keys_to_decrypt(self, jwe_patch):
206+
self._jwenc_patch.return_value.unpack.return_value.headers = \
207+
{"alg": "RS512",
208+
"typ": "JWT",
209+
}
210+
jwe_patch.return_value.decrypt.return_value = b'{"tobe": "encrypted"}'
211+
jwe_patch.return_value.encrypt.return_value = \
212+
"this.value.is.encrypted.correctly"
213+
self._encrypt_decrypt()
214+
jwe_patch.return_value.decrypt.assert_called_once_with(
215+
ANY, keys=self._transport.issuer_private_keys)
216+
195217

196218
class TestHashlibSupportedAlgs(unittest.TestCase):
197219

0 commit comments

Comments
 (0)