def to_string(self, remote_key = None, remote_ip = None): """ Sign and encrypt this response. :param remote_key: Encrypt response to this key :param remote_ip: If no remote_key, look up a key for this remote_ip :type remote_key: str or None :type remote_ip: str or None :rtype: str """ sign_key = self._config.keys.private_key self._logger.debug("Signing response using key {!r}".format(sign_key)) jws = jose.sign(self._data, sign_key.jwk, alg = self._config.jose_alg) signed_claims = {'v1': jose.serialize_compact(jws)} self._logger.debug("Signed response: {!r}".format(signed_claims)) encrypt_key = remote_key if not encrypt_key: # default to the first key found using the remote_ip in case no key was supplied ip_keys = self._config.keys.lookup_by_ip(remote_ip) if not ip_keys: self._logger.warning("Found no key for IP {!r}, can't encrypt response:\n{!r}".format( remote_ip, self._data )) raise EduIDAPIError("No API Key found - can't encrypt response") encrypt_key = ip_keys[0] self._logger.debug("Encrypting claims to key {!r}".format(encrypt_key)) jwe = jose.encrypt(signed_claims, encrypt_key.jwk) return jose.serialize_compact(jwe)
def to_string(self, remote_key=None, remote_ip=None): """ Sign and encrypt this response. :param remote_key: Encrypt response to this key :param remote_ip: If no remote_key, look up a key for this remote_ip :type remote_key: str or None :type remote_ip: str or None :rtype: str """ sign_key = self._config.keys.private_key self._logger.debug("Signing response using key {!r}".format(sign_key)) jws = jose.sign(self._data, sign_key.jwk, alg=self._config.jose_alg) signed_claims = {'v1': jose.serialize_compact(jws)} self._logger.debug("Signed response: {!r}".format(signed_claims)) encrypt_key = remote_key if not encrypt_key: # default to the first key found using the remote_ip in case no key was supplied ip_keys = self._config.keys.lookup_by_ip(remote_ip) if not ip_keys: self._logger.warning( "Found no key for IP {!r}, can't encrypt response:\n{!r}". format(remote_ip, self._data)) raise EduIDAPIError( "No API Key found - can't encrypt response") encrypt_key = ip_keys[0] self._logger.debug("Encrypting claims to key {!r}".format(encrypt_key)) jwe = jose.encrypt(signed_claims, encrypt_key.jwk) return jose.serialize_compact(jwe)
def test_jwe(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} for (alg, jwk), enc in product(self.algs, self.encs): jwe = jose.encrypt(claims, rsa_pub_key, enc=enc, alg=alg) # make sure the body can't be loaded as json (should be encrypted) try: json.loads(jose.b64decode_url(jwe.ciphertext)) self.fail() except ValueError: pass token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertNotIn(jose._TEMP_VER_KEY, claims) self.assertEqual(jwt.claims, claims) # invalid key try: jose.decrypt(jose.deserialize_compact(token), bad_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Incorrect decryption.')
def test_jwe_no_error_with_iat_claim(self): claims = {jose.CLAIM_ISSUED_AT: int(time()) - 15} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, expiry_seconds=20)
def test_jwe(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} for (alg, jwk), enc in product(self.algs, self.encs): jwe = jose.encrypt(claims, rsa_pub_key, enc=enc, alg=alg) # make sure the body can't be loaded as json (should be encrypted) try: json.loads(jose.b64decode_url(jwe.ciphertext)) self.fail() except ValueError: pass token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertNotIn(jose._TEMP_VER_KEY, claims) self.assertEqual(jwt.claims, claims) # invalid key try: jose.decrypt(jose.deserialize_compact(token), bad_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Incorrect decryption.')
def test_jws_asym(self): algs = ('RS256', 'RS384', 'RS512') for alg in algs: st = jose.serialize_compact(jose.sign(claims, rsa_priv_key, alg=alg)) jwt = jose.verify(jose.deserialize_compact(st), rsa_pub_key) self.assertEqual(jwt.claims, claims)
def test_jwe_decrypt_legacy_v1_without_temp_ver_incorrect_jwk(self): jwk_for_encrypt = {'k': PRIVATE_KEY} legacy_legacy_temp_ver = jose.serialize_compact( legacy_encrypt(claims, jwk_for_encrypt) ) jwk_for_decrypt = {'k': RSA.generate(2048).exportKey('PEM')} legacy_patch = mock.patch.object( jose, 'legacy_decrypt', wraps=jose.legacy_decrypt ) spec_patch = mock.patch.object( jose, 'spec_compliant_decrypt', wraps=jose.spec_compliant_decrypt ) with legacy_patch as legacy_mock, spec_patch as spec_mock: with self.assertRaises(jose.Error) as decryption_error: jose.decrypt( jose.deserialize_compact(legacy_legacy_temp_ver), jwk_for_decrypt) self.assertEqual(legacy_mock.call_count, 1) self.assertEqual(spec_mock.call_count, 1) self.assertEqual(decryption_error.exception.message, "Incorrect decryption.")
def test_jwe_direct_encryption(self): symmetric_key = "tisasymmetrickey" jwe = jose.encrypt(claims, "", alg = "dir",enc="A128CBC-HS256", dir_key = symmetric_key) # make sure the body can't be loaded as json (should be encrypted) try: json.loads(jose.b64decode_url(jwe.ciphertext)) self.fail() except ValueError: pass token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token),"", dir_key = symmetric_key) self.assertNotIn(jose._TEMP_VER_KEY, claims) self.assertEqual(jwt.claims, claims) # invalid key badkey = "1234123412341234" try: jose.decrypt(jose.deserialize_compact(token), '', dir_key=badkey) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Mismatched authentication tags')
def internal_auth_temporary(event, context): """AWS_LAMBDA:auth_temporary_jwt Generate a valid token for temporary access. :param tt: Registration token. :type tt: str. :returns: str -- Authentication token used for API calls. """ #generate token now_time = time.time() end_time = time.time() + 31104000 claims = { 'exp': end_time, 'nbf': now_time, 'iss': 'medpass-aws', 'aud': 'webclient', 'iat': now_time, 'type': 'TEMPORARY', 'id': event['user_id'] } jwe_token = jose.encrypt(claims, {'k': PUBLIC_KEY}, enc='A256CBC-HS512') auth_token = jose.serialize_compact(jwe_token) output = {'Authorization': auth_token} return output
def test_mfa_add_request(self): """ Test basic ability to parse an mfa_add request. :return: """ nonce = os.urandom(10) claims = {'version': 1, 'token_type': 'OATH', 'nonce': nonce.encode('hex'), 'OATH': {'digits': 6, 'issuer': 'TestIssuer', 'account': '*****@*****.**', } } priv_jwk = self.config.keys.private_key.jwk server_jwk = self.config.keys.lookup_by_name("self").jwk jwe = self._sign_and_encrypt(claims, priv_jwk, server_jwk) serialized = jose.serialize_compact(jwe) response = self.request('/mfa_add', request=serialized, return_error=True) jwt = self._decrypt_and_verify(response.body[0], priv_jwk, server_jwk) response_claims = jwt.claims self.logger.debug("Response claims:\n{!s}".format(pprint.pformat(response_claims))) self.assertEqual(response_claims, {u'nonce': nonce.encode('hex'), u'reason': u'No API Key found for OATH AEAD service', u'status': u'FAIL', u'version': 1, })
def test_jwe_decrypt_legacy_v1_without_temp_ver_incorrect_jwk(self): jwk_for_encrypt = {'k': PRIVATE_KEY} legacy_legacy_temp_ver = jose.serialize_compact( legacy_encrypt(claims, jwk_for_encrypt) ) jwk_for_decrypt = {'k': RSA.generate(2048).exportKey('PEM')} legacy_patch = mock.patch.object( jose, 'legacy_decrypt', wraps=jose.legacy_decrypt ) spec_patch = mock.patch.object( jose, 'spec_compliant_decrypt', wraps=jose.spec_compliant_decrypt ) with legacy_patch as legacy_mock, spec_patch as spec_mock: with self.assertRaises(jose.Error) as decryption_error: jose.decrypt( jose.deserialize_compact(legacy_legacy_temp_ver), jwk_for_decrypt) self.assertEqual(legacy_mock.call_count, 1) self.assertEqual(spec_mock.call_count, 1) self.assertEqual(decryption_error.exception.message, "Incorrect decryption.")
def test_jwe_decrypt_legacy_v1_without_temp_ver(self): jwk = {'k': PRIVATE_KEY} legacy_patch = mock.patch.object( jose, 'legacy_decrypt', wraps=jose.legacy_decrypt ) spec_patch = mock.patch.object( jose, 'spec_compliant_decrypt', wraps=jose.spec_compliant_decrypt ) legacy_legacy_temp_ver = jose.serialize_compact( legacy_encrypt(claims, jwk) ) with legacy_patch as legacy_mock, spec_patch as spec_mock: jwt = jose.decrypt( jose.deserialize_compact(legacy_legacy_temp_ver), jwk) self.assertEqual(legacy_mock.call_count, 1) self.assertEqual(spec_mock.call_count, 0) self.assertEqual(jwt.claims, claims) expected_header = { 'alg': 'RSA-OAEP', 'enc': 'A128CBC-HS256', } self.assertEqual(jwt.header, expected_header) self.assertNotIn('__v', jwt.header)
def test_jwe_decrypt_legacy_v1_without_temp_ver(self): jwk = {'k': PRIVATE_KEY} legacy_patch = mock.patch.object( jose, 'legacy_decrypt', wraps=jose.legacy_decrypt ) spec_patch = mock.patch.object( jose, 'spec_compliant_decrypt', wraps=jose.spec_compliant_decrypt ) legacy_legacy_temp_ver = jose.serialize_compact( legacy_encrypt(claims, jwk) ) with legacy_patch as legacy_mock, spec_patch as spec_mock: jwt = jose.decrypt( jose.deserialize_compact(legacy_legacy_temp_ver), jwk) self.assertEqual(legacy_mock.call_count, 1) self.assertEqual(spec_mock.call_count, 0) self.assertEqual(jwt.claims, claims) expected_header = { 'alg': 'RSA-OAEP', 'enc': 'A128CBC-HS256', } self.assertEqual(jwt.header, expected_header) self.assertNotIn('__v', jwt.header)
def test_jwe_ignores_expired_token_if_validate_claims_is_false(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) - 5} et = jose.serialize_compact( jose.spec_compliant_encrypt(claims, rsa_pub_key)) jose.spec_compliant_decrypt(jose.deserialize_compact(et), rsa_priv_key, validate_claims=False)
def __init__(self, claims, logger, config, alg = 'RS256'): self._logger = logger self._config = config self._request_result = None self._api_key = None self._claims = claims jws = jose.sign(claims, self._config.keys.private_key.jwk, alg=alg) self.signed_claims = {'v1': jose.serialize_compact(jws)}
def test_jws_sym(self): algs = ('HS256', 'HS384', 'HS512',) jwk = {'k': 'password'} for alg in algs: st = jose.serialize_compact(jose.sign(claims, jwk, alg=alg)) jwt = jose.verify(jose.deserialize_compact(st), jwk, alg) self.assertEqual(jwt.claims, claims)
def test_jwe_compression(self): local_claims = copy(claims) for v in xrange(1000): local_claims['dummy_' + str(v)] = '0' * 100 jwe = jose.serialize_compact(jose.encrypt(local_claims, rsa_pub_key)) _, _, _, uncompressed_ciphertext, _ = jwe.split('.') jwe = jose.serialize_compact(jose.encrypt(local_claims, rsa_pub_key, compression='DEF')) _, _, _, compressed_ciphertext, _ = jwe.split('.') self.assertTrue(len(compressed_ciphertext) < len(uncompressed_ciphertext)) jwt = jose.decrypt(jose.deserialize_compact(jwe), rsa_priv_key) self.assertEqual(jwt.claims, local_claims)
def test_jws_sym(self): algs = ('HS256', 'HS384', 'HS512',) jwk = {'k': 'password'} for alg in algs: st = jose.serialize_compact(jose.sign(claims, jwk, alg=alg)) jwt = jose.verify(jose.deserialize_compact(st), jwk, alg) self.assertEqual(jwt.claims, claims)
def test_jwe_add_header(self): add_header = {'foo': 'bar'} for (alg, jwk), enc in product(self.algs, self.encs): et = jose.serialize_compact( jose.encrypt(claims, rsa_pub_key, add_header=add_header)) jwt = jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.assertEqual(jwt.header['foo'], add_header['foo'])
def test_jwe_compression(self): local_claims = copy(claims) for v in xrange(1000): local_claims['dummy_' + str(v)] = '0' * 100 jwe = jose.serialize_compact(jose.encrypt(local_claims, rsa_pub_key)) _, _, _, uncompressed_ciphertext, _ = jwe.split('.') jwe = jose.serialize_compact( jose.encrypt(local_claims, rsa_pub_key, compression='DEF')) _, _, _, compressed_ciphertext, _ = jwe.split('.') self.assertTrue( len(compressed_ciphertext) < len(uncompressed_ciphertext)) jwt = jose.decrypt(jose.deserialize_compact(jwe), rsa_priv_key) self.assertEqual(jwt.claims, local_claims)
def test_jwe_add_header(self): add_header = {'foo': 'bar'} for (alg, jwk), enc in product(self.algs, self.encs): et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key, add_header=add_header)) jwt = jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.assertEqual(jwt.header['foo'], add_header['foo'])
def test_jwe_invalid_dates_error(self): claims = {'exp': time() - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting expired token except ValueError: pass claims = {'nbf': time() + 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting not valid yet except ValueError: pass
def test_version1(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} jwe = legacy_encrypt(claims, rsa_pub_key, version=1) token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertEqual(jwt.claims, claims) self.assertEqual(jwt.header.get(jose._TEMP_VER_KEY), 1)
def test_version1(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} jwe = legacy_encrypt(claims, rsa_pub_key, version=1) token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertEqual(jwt.claims, claims) self.assertEqual(jwt.header.get(jose._TEMP_VER_KEY), 1)
def encrypt_fluffy(msgtype, key, groupkey="", keyid=""): #print "MsgType: ", msgtype #print "GroupKey: ", groupkey #print "KeyId: ", keyid print '*****************************' print 'Performing Encryption on a ' + msgtype print '*****************************' msg = gsk.Gsk(msgtype, groupkey, keyid) s = gsk.printGsk(msg) j_obj = json.loads(s) #add and map fluffy claims into jwt claims pyld = map_payload(j_obj) print "Pretty output: " print json.dumps(pyld, indent=4) print "--------------" j_obj['header']['msgtyp'] = msgtype header = { 'kid': str(j_obj['payload']['keydata']['kid']), 'typ': str(j_obj['header']['msgtyp']) + ' ' + str(j_obj['header']['pvno']), 'cty': j_obj['payload']['type'], } aad = { 'aut': j_obj['security']['authtime'], 'non': j_obj['security']['nonce'], 'snu': j_obj['security']['seqnum'] } header['aad'] = aad jwe = jose.encrypt(pyld, '', add_header = header, alg='dir', dir_key=str(key)) print(jose.serialize_compact(jwe)) f = open ("ENC_" + header['typ'] +".txt", "w") f.write(jose.serialize_compact(jwe)); f.close();
def test_jwe_expired_error_with_exp_claim(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting expired token except jose.Expired as e: pass self.assertEquals( e.args[0], 'Token expired at {}'.format( jose._format_timestamp(claims[jose.CLAIM_EXPIRATION_TIME])))
def test_jwe_not_yet_valid_error_with_nbf_claim(self): claims = {jose.CLAIM_NOT_BEFORE: int(time()) + 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting not valid yet except jose.NotYetValid as e: pass self.assertEquals( e.args[0], 'Token not valid until {}'.format( jose._format_timestamp(claims[jose.CLAIM_NOT_BEFORE])))
def auth_master_login_jwt(event, context): """AWS_LAMBDA:auth_master_login_jwt Authenticate a valid code/password combination. :param code: User identification code. :type code: str. :param password: User password. :type password: str. :returns: str -- Authentication token used for API calls. """ try: #generate password hash for comparison, a slow hash with multiple iterations is used to difficult attacks binary_hash = hashlib.pbkdf2_hmac('sha256', event['body-json']['password'], DYN_CONF['MASTER_SALT'], 10000) hashed_password = binascii.hexlify(binary_hash) #compare results if hashed_password == DYN_CONF['MASTER_PASSWORD']: #generate token now_time = time.time() end_time = time.time() + 31104000 claims = { 'exp': end_time, 'nbf': now_time, 'iss': 'medpass-aws', 'aud': 'webclient', 'iat': now_time, 'type': 'MASTER', 'id': 'MASTER' } jwe_token = jose.encrypt(claims, {'k': PUBLIC_KEY}, enc='A256CBC-HS512') auth_token = jose.serialize_compact(jwe_token) output = { 'status': 'success', 'Authorization': auth_token, 'type': 'MASTER' } else: output = {'status': 'error', 'message': 'failed login'} except: LOGGER.error(traceback.print_exc()) output = {'status': 'error', 'message': 'failed login'} return output
def test_jwe_expired_error_with_exp_claim(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting expired token except jose.Expired as e: pass self.assertEquals( e.args[0], 'Token expired at {}'.format( jose._format_timestamp(claims[jose.CLAIM_EXPIRATION_TIME]) ) )
def test_jwe_not_yet_valid_error_with_nbf_claim(self): claims = {jose.CLAIM_NOT_BEFORE: int(time()) + 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() # expecting not valid yet except jose.NotYetValid as e: pass self.assertEquals( e.args[0], 'Token not valid until {}'.format( jose._format_timestamp(claims[jose.CLAIM_NOT_BEFORE]) ) )
def test_jwe_adata(self): adata = '42' for (alg, jwk), enc in product(self.algs, self.encs): et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key, adata=adata)) jwt = jose.legacy_decrypt(jose.deserialize_compact(et), rsa_priv_key, adata=adata) # make sure signatures don't match when adata isn't passed in try: hdr, dt = jose.legacy_decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Mismatched authentication tags') self.assertEqual(jwt.claims, claims)
def test_jwe(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} jwe = legacy_encrypt(claims, rsa_pub_key) token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertEqual(jwt.claims, claims) self.assertNotIn(jose._TEMP_VER_KEY, claims) # invalid key try: jose.decrypt(jose.deserialize_compact(token), bad_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Incorrect decryption.')
def test_jwe(self): bad_key = {'k': RSA.generate(2048).exportKey('PEM')} jwe = legacy_encrypt(claims, rsa_pub_key) token = jose.serialize_compact(jwe) jwt = jose.decrypt(jose.deserialize_compact(token), rsa_priv_key) self.assertEqual(jwt.claims, claims) self.assertNotIn(jose._TEMP_VER_KEY, claims) # invalid key try: jose.decrypt(jose.deserialize_compact(token), bad_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Incorrect decryption.')
def test_jwe_adata(self): adata = '42' for (alg, jwk), enc in product(self.algs, self.encs): et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key, adata=adata)) jwt = jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, adata=adata) # make sure signaures don't match when adata isn't passed in try: hdr, dt = jose.decrypt(jose.deserialize_compact(et), rsa_priv_key) self.fail() except jose.Error as e: self.assertEqual(e.message, 'Mismatched authentication tags') self.assertEqual(jwt.claims, claims)
def test_jwe_expired_error_with_iat_claim(self): expiry_seconds = 10 claims = {jose.CLAIM_ISSUED_AT: int(time()) - 15} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, expiry_seconds=expiry_seconds) self.fail() # expecting expired token except jose.Expired as e: pass expiration_time = claims[jose.CLAIM_ISSUED_AT] + expiry_seconds self.assertEquals( e.args[0], 'Token expired at {}'.format( jose._format_timestamp(expiration_time)))
def test_jwe_expired_error_with_iat_claim(self): expiry_seconds = 10 claims = {jose.CLAIM_ISSUED_AT: int(time()) - 15} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) try: jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, expiry_seconds=expiry_seconds) self.fail() # expecting expired token except jose.Expired as e: pass expiration_time = claims[jose.CLAIM_ISSUED_AT] + expiry_seconds self.assertEquals( e.args[0], 'Token expired at {}'.format( jose._format_timestamp(expiration_time) ) )
def test_mfa_add_request(self): """ Test basic ability to parse an mfa_add request. :return: """ nonce = os.urandom(10) claims = { 'version': 1, 'token_type': 'OATH', 'nonce': nonce.encode('hex'), 'OATH': { 'digits': 6, 'issuer': 'TestIssuer', 'account': '*****@*****.**', } } priv_jwk = self.config.keys.private_key.jwk server_jwk = self.config.keys.lookup_by_name("self").jwk jwe = self._sign_and_encrypt(claims, priv_jwk, server_jwk) serialized = jose.serialize_compact(jwe) response = self.request('/mfa_add', request=serialized, return_error=True) jwt = self._decrypt_and_verify(response.body[0], priv_jwk, server_jwk) response_claims = jwt.claims self.logger.debug("Response claims:\n{!s}".format( pprint.pformat(response_claims))) self.assertEqual( response_claims, { u'nonce': nonce.encode('hex'), u'reason': u'No API Key found for OATH AEAD service', u'status': u'FAIL', u'version': 1, })
def send_request(self, url, name, apikey): """ Encrypt the claims and POST it to url. :param url: The URL to POST the data to :param name: The HTTP parameter name to put the data in :param apikey: API Key to encrypt data with before posting :return: :type url: str | unicode :type apikey: eduid_api.keystore.APIKey """ self._logger.debug("Encrypting signed request using {!r}".format(apikey)) if not apikey.keytype == 'jose': raise EduIDAPIError("Non-jose API key unusuable with send_request") self._api_key = apikey jwe = jose.encrypt(self.signed_claims, apikey.jwk) data = {name: jose.serialize_compact(jwe)} self._logger.debug("Sending signed and encrypted request to {!r}".format(url)) self._request_result = requests.post(url, data = data) self._logger.debug("Result of request: {!r}".format(self._request_result)) return self._request_result
def get_freja_state(user): current_app.logger.debug('Getting state for user {!s}.'.format(user)) try: proofing_state = current_app.proofing_statedb.get_state_by_eppn( user.eppn) expire_time = current_app.config['FREJA_EXPIRE_TIME_HOURS'] if helpers.is_proofing_state_expired(proofing_state, expire_time): current_app.proofing_statedb.remove_state(proofing_state) current_app.stats.count(name='freja.proofing_state_expired') raise DocumentDoesNotExist(reason='freja proofing state expired') except DocumentDoesNotExist: return {} # Return request data current_app.logger.debug( 'Returning request data for user {!s}'.format(user)) current_app.stats.count(name='freja.proofing_state_returned') opaque_data = helpers.create_opaque_data(proofing_state.nonce, proofing_state.token) valid_until = helpers.get_proofing_state_valid_until( proofing_state, expire_time) request_data = { "iarp": current_app.config['FREJA_IARP'], "exp": int(valid_until.astimezone(UTC()).strftime('%s')) * 1000, # Milliseconds since 1970 in UTC "proto": current_app.config['FREJA_RESPONSE_PROTOCOL'], "opaque": opaque_data } jwk = {'k': current_app.config['FREJA_JWK_SECRET'].decode('hex')} jws_header = { 'alg': current_app.config['FREJA_JWS_ALGORITHM'], 'kid': current_app.config['FREJA_JWS_KEY_ID'], } jws = jose.sign(request_data, jwk, add_header=jws_header, alg=current_app.config['FREJA_JWS_ALGORITHM']) return {'iaRequestData': jose.serialize_compact(jws)}
def test_jwe_ignores_expired_token_if_validate_claims_is_false(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, validate_claims=False)
def login(event, context): """AWS_LAMBDA:auth_login_jwt Authenticate a valid email/password combination. :param email: User identification email. :type email: str. :param password: User password. :type password: str. :returns: str -- Authentication token used for API calls. """ try: #request user data (password, active_status, salt and id) from user services login_data = 'email' in event['body-json'] and { 'email': event['body-json']['email'] } or { 'cpf': event['body-json']['cpf'] } response = LAMBDA_CLIENT.invoke( FunctionName= 'arn:aws:lambda:us-west-2:566614558620:function:user_get_credentials:' + ENVIRONMENT, Qualifier=ENVIRONMENT, InvocationType='RequestResponse', Payload=json.dumps(login_data), LogType='None') payload = json.loads(response['Payload'].read()) if response['StatusCode'] == 200 and payload['status'] == 'success': #if the user provided a captcha verify it works approved_captcha = False if 'g-captcha-response' in event[ 'body-json'] and payload['failed_login_attempts'] >= 3: #validate recaptcha google_response = requests.post( 'https://www.google.com/recaptcha/api/siteverify', data={ 'secret': '6LdZNSgUAAAAAE8caj0ckJluAdJ3mGPHrE6kZW_n', 'response': event['body-json']['g-captcha-response'], 'remoteip': event['source-ip'] }).json() approved_captcha = google_response['success'] #verify that the user is not locked or did provide a captcha if payload['failed_login_attempts'] < 3 or approved_captcha: #generate password hash for comparison, a slow hash with multiple iterations is used to difficult attacks binary_hash = hashlib.pbkdf2_hmac( 'sha256', event['body-json']['password'], payload['salt'], 10000) hashed_password = binascii.hexlify(binary_hash) if 'token_annonymous' in event['body-json']: annonymous_login = event['body-json']['token_annonymous'] == payload['token_annonymous'] and\ True or False else: annonymous_login = False #compare results and verify if user is active if (hashed_password == payload['password'] and payload['active']) or annonymous_login: #clear old password reset tokens USER_TABLE.update_item( Key={'user_id': payload['user_id']}, UpdateExpression= 'SET password_token=:password_token, failed_login_attempts=:failed_login_attempts', ExpressionAttributeValues={ ':password_token': {}, ':failed_login_attempts': 0 }) #generate token now_time = time.time() end_time = time.time() + 31104000 claims = { 'exp': end_time, 'nbf': now_time, 'iss': 'medpass-aws', 'aud': 'webclient', 'iat': now_time, 'type': payload['type'], 'id': payload['user_id'] } jwe_token = jose.encrypt(claims, {'k': PUBLIC_KEY}, enc='A256CBC-HS512') auth_token = jose.serialize_compact(jwe_token) output = { 'status': 'success', 'Authorization': auth_token, 'type': payload['type'] } #profile dependent information if 'profile_url' in payload: output['profile_url'] = payload['profile_url'] #type dependent information if payload['type'] == 'USER': pendencies_response = REPORT_TABLE.get_item( Key={'user_id': payload['user_id']}, ProjectionExpression='pendencies') if 'Item' in pendencies_response: output['pendencies'] = False else: output['pendencies'] = True elif payload['type'] == 'PRE-MEDIC': #warn user if data is being revisited user_response = USER_TABLE.get_item( Key={'user_id': payload['user_id']}, ProjectionExpression='profile')['Item'] if 'profile' in user_response: output['sent'] = True else: output['sent'] = False output['pendencies'] = True elif payload['type'] == 'MEDIC': output['pendencies'] = False else: #include a failed login attempt to user USER_TABLE.update_item( Key={'user_id': payload['user_id']}, UpdateExpression='ADD failed_login_attempts :one', ExpressionAttributeValues={':one': 1}) LOGGER.info('Incorrect login attempt at email: "' + event['body-json']['email'] + '".') output = {'status': 'error', 'message': 'failed login'} else: LOGGER.info('Locked out user login attempt at email: "' + event['body-json']['email'] + '"') output = { 'status': 'captcha', 'message': 'locked out user need to validate captcha' } else: LOGGER.error("Failed to request user credential." + payload['message']) output = {'status': 'error', 'message': 'failed login'} except: LOGGER.error(traceback.print_exc()) output = {'status': 'error', 'message': 'failed login'} return output
mtb_key = RSA.generate(2048) # JWE encode - encrypt info using the public key MtB info = {'company': 'ACME', 'author': 'pepe', 'experiment': 'ID'} print "\n\t START" print "\t |" print "\t< Secret information:" + str(info) + " >" print "\t |" print "\t< Encrypt information (information, MtB public key) >" print "\t |" try: pub_jwk = {'k': mtb_key.publickey().exportKey('PEM')} jwe = jose.encrypt(info, pub_jwk) # issue the compact serialized version to the clients. this is what will be # transported along with requests to target systems. secret = jose.serialize_compact(jwe) print "\t OK" print "\t |" except: print "\tError encrypting information! " e = sys.exc_info()[0] print "Error: %s" % e exit() # JWT sign - Sign token using the private key External claims = { 'from': 'mindthebyte', 'exp': int(time()) + 3600, 'secret': secret, } print "\t< claims to be signed: " + str(claims)
def _sign_and_encrypt(self, claims, priv_jwk, server_jwk, alg = 'RS256'): jws = jose.sign(claims, priv_jwk, alg=alg) signed_claims = {'v1': jose.serialize_compact(jws)} jwe = jose.encrypt(signed_claims, server_jwk) return jwe
def test_jwe_no_error_with_nbf_claim(self): claims = {jose.CLAIM_NOT_BEFORE: int(time()) - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key)
def test_jwe_no_error_with_iat_claim(self): claims = {jose.CLAIM_ISSUED_AT: int(time()) - 15} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key, expiry_seconds=20)
def _sign_and_encrypt(self, claims, priv_jwk, server_jwk, alg='RS256'): jws = jose.sign(claims, priv_jwk, alg=alg) signed_claims = {'v1': jose.serialize_compact(jws)} jwe = jose.encrypt(signed_claims, server_jwk) return jwe
def test_jwe_no_error_with_nbf_claim(self): claims = {jose.CLAIM_NOT_BEFORE: int(time()) - 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key)
def test_jwe_no_error_with_exp_claim(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) + 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key)
# key = b"""-----BEGIN PRIVATE KEY----- # MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgsegINAr5xcE48BiD # yfXjsfQmEk1ReGtD7bSuKsKx04CgCgYIKoZIzj0DAQehRANCAASauMCp36D8FOF1 # 5OGI1+fe5oeRoCbY5yGQ2Jk0Gi9P92ksyvC8LK7JDqtzKfEf18UsScYc+NWffEtt # v413G73q # -----END PRIVATE KEY-----""" # k = {'k': 'password'} # # signed = jws.sign(data, k, algorithm='ES256') # print(signed) # data = jws.verify(signed, k, algorithms='ES256') # print(data) jwk = {'k': 'password'} jws = jose.sign(data, jwk, alg='HS256') # JWS(header='eyJhbGciOiAiSFMyNTYifQ', # payload='eyJpc3MiOiAiaHR0cDovL3d3dy5leGFtcGxlLmNvbSIsICJzdWIiOiA0MiwgImV4cCI6IDEzOTU2NzQ0Mjd9', # signature='WYApAiwiKd-eDClA1fg7XFrnfHzUTgrmdRQY4M19Vr8') # issue the compact serialized version to the clients. this is what will be # transported along with requests to target systems. jwt = jose.serialize_compact(jws) # 'eyJhbGciOiAiSFMyNTYifQ.eyJpc3MiOiAiaHR0cDovL3d3dy5leGFtcGxlLmNvbSIsICJzdWIiOiA0MiwgImV4cCI6IDEzOTU2NzQ0Mjd9.WYApAiwiKd-eDClA1fg7XFrnfHzUTgrmdRQY4M19Vr8' jose.verify(jose.deserialize_compact(jwt), jwk, 'HS256') # JWT(header={u'alg': u'HS256'}, claims={u'iss': u'http://www.example.com', u'sub': 42, u'exp': 1395674427})
def test_jwe_no_error_with_exp_claim(self): claims = {jose.CLAIM_EXPIRATION_TIME: int(time()) + 5} et = jose.serialize_compact(jose.encrypt(claims, rsa_pub_key)) jose.decrypt(jose.deserialize_compact(et), rsa_priv_key)