def get_wrapped_key_as_jwe(priv_dek: bytearray, tenant: str, jwe_kid: str, nonce: str = '') -> str: """Creates a JWE.""" trace_enter(inspect.currentframe()) logger.info('Creating JWE token for request with kid "%s"...', jwe_kid) # Generate a 256 bit AES content encryption key (32 bytes * 8). try: cek = bytearray(get_random_bytes(32)) except Exception as exc: ret = '' logger.error('Failed to get random bytes: %s', exc) trace_exit(inspect.currentframe(), ret) return ret if config.get_config_by_keypath('DEV_MODE'): logger.debug('Generated cek (BYOK AES key): %s (hex)', cek.hex()) if not (b64_cek_ciphertext := _encrypt_cek_with_key_consumer_key( tenant, jwe_kid, cek)): logger.error( 'Cannot encrypt content encryption key with key consumer ' 'key of %s/%s.', tenant, jwe_kid) trace_exit(inspect.currentframe(), '') return ''
def _create_jwe_token_json(jwe_kid: str, b64_protected_header: bytes, b64_cek_ciphertext: bytes, b64_iv: bytes, b64_encrypted_dek: bytes, b64_tag: bytes) -> str: """ Creates JWE token according to: https://tools.ietf.org/html/rfc7516#section-3.3 Compact Serialization representation: BASE64URL(UTF8(JWE Protected Header)) || '.' || BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector) || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag) """ trace_enter(inspect.currentframe()) try: jwe = b64_protected_header + b'.' + b64_cek_ciphertext + b'.' + \ b64_iv + b'.' + b64_encrypted_dek + b'.' + b64_tag jwe_token = {'kid': jwe_kid, 'jwe': jwe.decode()} json_jwe_token = json.dumps(jwe_token) except Exception as exc: ret = '' logger.error('Failed to create JWE token: %s', exc) trace_exit(inspect.currentframe(), ret) return ret logger.debug('Created JWE token: %s', json_jwe_token) trace_exit(inspect.currentframe(), json_jwe_token) return json_jwe_token
def _encrypt_dek_with_cek(priv_cek: bytearray, initialization_vector: bytes, priv_dek: bytearray, ascii_b64_protected_header: bytes) \ -> Tuple[bytes, bytes]: """ Wrap dek with cek: - Perform authenticated encryption on dek with the AES GCM algorithm. - Use cek as encryption key, the initialization vector, and the protected header as Additional Authenticated Data value. - Request a 128-bit Authentication Tag output. """ trace_enter(inspect.currentframe()) try: # mac_len=16 bytes: 128 bit authentication tag dek_cipher = AES.new(priv_cek, AES.MODE_GCM, nonce=initialization_vector, mac_len=16) # add additional authenticated data (aad) dek_cipher.update(ascii_b64_protected_header) # TODO: Autom. padding helpful? Might replace pycryptodome anyway. # from Cryptodome.Util.Padding import pad # encrypted_dek, tag = \ # dek_cipher.encrypt_and_digest(pad(dek, AES.block_size)) encrypted_dek, tag = dek_cipher.encrypt_and_digest(priv_dek) # Remove sensitive data from memory del priv_dek[:] del priv_cek[:] b64_encrypted_dek = base64.urlsafe_b64encode(encrypted_dek) b64_tag = base64.urlsafe_b64encode(tag) except Exception as exc: ret = (b'', b'') logger.error('Failed to encrypt dek: %s', exc) trace_exit(inspect.currentframe(), ret) return ret if config.get_config_by_keypath('DEV_MODE'): logger.debug('Additional authenticated data (aad): ' '%s', ascii_b64_protected_header.decode()) logger.debug('Encrypted dek: "%s" (hex), ' 'tag :"%s" (hex).', encrypted_dek.hex(), tag.hex()) trace_exit(inspect.currentframe(), (b64_encrypted_dek, b64_tag)) return b64_encrypted_dek, b64_tag
def __authenticate_vault_client(client: hvac.Client, tenant: str, priv_jwt_token: str) -> hvac.Client: trace_enter(inspect.currentframe()) vault_auth_jwt_path = config.get_vault_auth_jwt_path(tenant) if not vault_auth_jwt_path: logger.error('Failed to load auth jwt path for tenant "%s"', tenant) return None if config.get_config_by_keypath('DEV_MODE'): logger.debug('Attempting to authenticate against Vault using JWT: %s', priv_jwt_token) cache_id = utils.get_vault_token_cache_id(tenant, priv_jwt_token) if cache_id in __VAULT_TOKEN_CACHE: logger.debug('Cache hit: Found token for "%s".', cache_id) client.token = __VAULT_TOKEN_CACHE[cache_id] else: logger.debug('Cache miss: Token for "%s" not found.', cache_id) token = __get_vault_token(client, tenant, priv_jwt_token, vault_auth_jwt_path) if not token: ret = None logger.error('Failed to get Vault token.') trace_exit(inspect.currentframe(), ret) return ret client.token = token __VAULT_TOKEN_CACHE[cache_id] = token if not client.is_authenticated(): # token might be invalid/has expired del __VAULT_TOKEN_CACHE[cache_id] ret = None logger.error('Failed to validate Vault client. ' 'Review configuration (config/config.json). ' 'Retry as token might have expired.') trace_exit(inspect.currentframe(), ret) return ret logger.debug('Successfully authenticated Vault client ' 'for tenant "%s"', tenant) trace_exit(inspect.currentframe(), client) return client
def get_kid_from_jwt(priv_token: str) -> str: """ Extracts KID from JWT token. Only use this method after JWT token has been validated. """ trace_enter(inspect.currentframe()) try: protected_header_unverified = jwt.get_unverified_header(priv_token) except jwt.DecodeError as exc: ret = '' logger.error('Cannot decode JWT to get kid: %s', exc) logger.debug('JWT: %s', priv_token) trace_exit(inspect.currentframe(), ret) return ret ret = protected_header_unverified.get('kid', '') trace_exit(inspect.currentframe(), ret) return ret
def __get_vault_token(client: hvac.Client, tenant: str, priv_jwt_token: str, vault_auth_jwt_path: str) -> str: trace_enter(inspect.currentframe()) default_role = config.get_vault_default_role(tenant) if not default_role: logger.error('Failed to load Vault default role for tenant "%s"', tenant) return '' try: response = client.auth.jwt.jwt_login(role=default_role, jwt=priv_jwt_token, path=vault_auth_jwt_path) except Exception as exc: ret = '' logger.error('Failed to authenticate against Vault: %s', exc) trace_exit(inspect.currentframe(), ret) return ret if config.get_config_by_keypath('DEV_MODE'): logger.debug('Vault login response: %s', response) try: vault_token = response['auth']['client_token'] except KeyError as exc: ret = '' logger.error( 'Failed to access the Vault token from auth response: ' 'KeyError on key %s. ' 'This is most likely a permission issue.', exc) trace_exit(inspect.currentframe(), ret) return ret if config.get_config_by_keypath('DEV_MODE'): logger.debug('Vault client token returned: %s', vault_token) logger.debug('Retrieved new Vault token.') trace_exit(inspect.currentframe(), CAMOUFLAGE_SIGN) return vault_token
trace_exit(inspect.currentframe(), '') return '' # Generate an initialization vector (IV) # (BASE64URL(IV)) (12 bytes * 8 = 96 bit) try: initialization_vector = get_random_bytes(12) b64_iv = base64.urlsafe_b64encode(initialization_vector) except Exception as exc: ret = '' logger.error('Failed to create initialization vector: %s', exc) trace_exit(inspect.currentframe(), ret) return ret if config.get_config_by_keypath('DEV_MODE'): logger.debug('Generated IV/Nonce "%s" ' '(base64 encoded, bytes).', b64_iv.decode()) b64_protected_header = _get_jwe_protected_header(jwe_kid, nonce) if not b64_protected_header: ret = '' logger.error('Failed to get JWE protected header.') trace_exit(inspect.currentframe(), ret) return ret # Encode JWE protected header # (ASCII(BASE64URL(UTF8(JWE Protected Header)))) try: ascii_b64_protected_header = \ b64_protected_header.decode().encode('ascii', errors='strict') except Exception as exc: