diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py index 2f3db68..33e6555 100644 --- a/IPython/consoleapp.py +++ b/IPython/consoleapp.py @@ -265,6 +265,8 @@ class IPythonConsoleApp(Configurable): setattr(self, name, cfg[name]) if 'key' in cfg: self.config.Session.key = str_to_bytes(cfg['key']) + if 'signature_scheme' in cfg: + self.config.Session.signature_scheme = cfg['signature_scheme'] def init_ssh(self): """set up ssh tunnels, if needed.""" diff --git a/IPython/kernel/connect.py b/IPython/kernel/connect.py index ae8e406..657f5cc 100644 --- a/IPython/kernel/connect.py +++ b/IPython/kernel/connect.py @@ -50,7 +50,9 @@ from IPython.utils.traitlets import ( #----------------------------------------------------------------------------- def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0, - control_port=0, ip=LOCALHOST, key=b'', transport='tcp'): + control_port=0, ip=LOCALHOST, key=b'', transport='tcp', + signature_scheme='hmac-sha256', + ): """Generates a JSON config file, including the selection of random ports. Parameters @@ -78,7 +80,15 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, The ip address the kernel will bind to. key : str, optional - The Session key used for HMAC authentication. + The Session key used for message authentication. + + signature_scheme : str, optional + The scheme used for message authentication. + This has the form 'digest-hash', where 'digest' + is the scheme used for digests, and 'hash' is the name of the hash function + used by the digest scheme. + Currently, 'hmac' is the only supported digest scheme, + and 'sha256' is the default hash function. """ # default to temporary connector file @@ -129,6 +139,7 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, cfg['ip'] = ip cfg['key'] = bytes_to_str(key) cfg['transport'] = transport + cfg['signature_scheme'] = signature_scheme with open(fname, 'w') as f: f.write(json.dumps(cfg, indent=2)) @@ -380,6 +391,7 @@ class ConnectionFileMixin(HasTraits): _connection_file_written = Bool(False) transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True) + signature_scheme = Unicode('') ip = Unicode(LOCALHOST, config=True, help="""Set the kernel\'s IP address [default localhost]. @@ -427,6 +439,7 @@ class ConnectionFileMixin(HasTraits): stdin_port=self.stdin_port, hb_port=self.hb_port, control_port=self.control_port, + signature_scheme=self.signature_scheme, ) def cleanup_connection_file(self): @@ -463,6 +476,7 @@ class ConnectionFileMixin(HasTraits): stdin_port=self.stdin_port, iopub_port=self.iopub_port, shell_port=self.shell_port, hb_port=self.hb_port, control_port=self.control_port, + signature_scheme=self.signature_scheme, ) # write_connection_file also sets default ports: for name in port_names: @@ -479,7 +493,10 @@ class ConnectionFileMixin(HasTraits): self.ip = cfg['ip'] for name in port_names: setattr(self, name, cfg[name]) - self.session.key = str_to_bytes(cfg['key']) + if 'key' in cfg: + self.session.key = str_to_bytes(cfg['key']) + if cfg.get('signature_scheme'): + self.session.signature_scheme = cfg['signature_scheme'] #-------------------------------------------------------------------------- # Creating connected sockets diff --git a/IPython/kernel/zmq/session.py b/IPython/kernel/zmq/session.py index 234dd9d..b71d043 100644 --- a/IPython/kernel/zmq/session.py +++ b/IPython/kernel/zmq/session.py @@ -24,6 +24,7 @@ Authors: # Imports #----------------------------------------------------------------------------- +import hashlib import hmac import logging import os @@ -50,7 +51,9 @@ from IPython.utils.importstring import import_item from IPython.utils.jsonutil import extract_dates, squash_dates, date_default from IPython.utils.py3compat import str_to_bytes, str_to_unicode from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set, - DottedObjectName, CUnicode, Dict, Integer) + DottedObjectName, CUnicode, Dict, Integer, + TraitError, +) from IPython.kernel.zmq.serialize import MAX_ITEMS, MAX_BYTES #----------------------------------------------------------------------------- @@ -308,10 +311,26 @@ class Session(Configurable): help="""execution key, for extra authentication.""") def _key_changed(self, name, old, new): if new: - self.auth = hmac.HMAC(new) + self.auth = hmac.HMAC(new, digestmod=self.digest_mod) else: self.auth = None + signature_scheme = Unicode('hmac-sha256', config=True, + help="""The digest scheme used to construct the message signatures. + Must have the form 'hmac-HASH'.""") + def _signature_scheme_changed(self, name, old, new): + if not new.startswith('hmac-'): + raise TraitError("signature_scheme must start with 'hmac-', got %r" % new) + hash_name = new.split('-', 1)[1] + try: + self.digest_mod = getattr(hashlib, hash_name) + except AttributeError: + raise TraitError("hashlib has no such attribute: %s" % hash_name) + + digest_mod = Any() + def _digest_mod_default(self): + return hashlib.sha256 + auth = Instance(hmac.HMAC) digest_history = Set() @@ -387,6 +406,11 @@ class Session(Configurable): key : bytes The key used to initialize an HMAC signature. If unset, messages will not be signed or checked. + signature_scheme : str + The message digest scheme. Currently must be of the form 'hmac-HASH', + where 'HASH' is a hashing function available in Python's hashlib. + The default is 'hmac-sha256'. + This is ignored if 'key' is empty. keyfile : filepath The file containing a key. If this is set, `key` will be initialized to the contents of the file.