From 98aa10c5b4af899b8e16db88f70d31e656880bc5 2014-01-29 02:44:49 From: MinRK <benjaminrk@gmail.com> Date: 2014-01-29 02:44:49 Subject: [PATCH] Notaries sign notebooks now --- diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index bc8405d..40300f3 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -262,7 +262,7 @@ class NotebookTrustApp(BaseIPythonApplication): self.exit(1) with io.open(notebook_path, encoding='utf8') as f: nb = current.read(f, 'json') - sign.trust_notebook(nb, self.notary.secret, self.notary.signature_scheme) + self.notary.sign(nb) with io.open(notebook_path, 'w', encoding='utf8') as f: current.write(nb, f, 'json') diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py index 0ec6b63..8bcee57 100644 --- a/IPython/html/services/notebooks/filenbmanager.py +++ b/IPython/html/services/notebooks/filenbmanager.py @@ -214,7 +214,10 @@ class FileNotebookManager(NotebookManager): except Exception as e: raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) model['content'] = nb - sign.mark_trusted_cells(nb, self.notary.secret, self.notary.scheme) + trusted = self.notary.check_signature(nb) + if not trusted: + self.log.warn("Notebook %s/%s is not trusted", model['path'], model['name']) + self.notary.mark_cells(nb, trusted) return model def save_notebook_model(self, model, name='', path=''): @@ -238,8 +241,11 @@ class FileNotebookManager(NotebookManager): os_path = self.get_os_path(new_name, new_path) nb = current.to_notebook_json(model['content']) - if sign.check_trusted_cells(nb): - sign.trust_notebook(nb, self.notary.secret, self.notary.scheme) + if self.notary.check_cells(nb): + self.notary.sign(nb) + else: + self.log.warn("Saving untrusted notebook %s/%s", new_path, new_name) + if 'name' in nb['metadata']: nb['metadata']['name'] = u'' diff --git a/IPython/nbformat/sign.py b/IPython/nbformat/sign.py index ed93cb1..002242f 100644 --- a/IPython/nbformat/sign.py +++ b/IPython/nbformat/sign.py @@ -19,7 +19,7 @@ import os from IPython.utils.py3compat import string_types, unicode_type, cast_bytes from IPython.config import LoggingConfigurable -from IPython.utils.traitlets import Instance, Bytes, Enum +from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode #----------------------------------------------------------------------------- # Code @@ -62,104 +62,8 @@ def signature_removed(nb): nb['metadata']['signature'] = save_signature -def notebook_signature(nb, secret, scheme): - """Compute a notebook's signature - - by hashing the entire contents of the notebook via HMAC digest. - scheme is the hashing scheme, which must be an attribute of the hashlib module, - as listed in hashlib.algorithms. - """ - hmac = HMAC(secret, digestmod=getattr(hashlib, scheme)) - # don't include the previous hash in the content to hash - with signature_removed(nb): - # sign the whole thing - for b in yield_everything(nb): - hmac.update(b) - - return hmac.hexdigest() - - -def check_notebook_signature(nb, secret, scheme): - """Check a notebook's stored signature - - If a signature is stored in the notebook's metadata, - a new signature is computed and compared with the stored value. - - Returns True if the signature is found and matches, False otherwise. - - The following conditions must all be met for a notebook to be trusted: - - a signature is stored in the form 'scheme:hexdigest' - - the stored scheme matches the requested scheme - - the requested scheme is available from hashlib - - the computed hash from notebook_signature matches the stored hash - """ - stored_signature = nb['metadata'].get('signature', None) - if not stored_signature \ - or not isinstance(stored_signature, string_types) \ - or ':' not in stored_signature: - return False - stored_scheme, sig = stored_signature.split(':', 1) - if scheme != stored_scheme: - return False - try: - my_signature = notebook_signature(nb, secret, scheme) - except AttributeError: - return False - return my_signature == sig - - -def trust_notebook(nb, secret, scheme): - """Re-sign a notebook, indicating that its output is trusted - - stores 'scheme:hmac-hexdigest' in notebook.metadata.signature - - e.g. 'sha256:deadbeef123...' - """ - signature = notebook_signature(nb, secret, scheme) - nb['metadata']['signature'] = "%s:%s" % (scheme, signature) - - -def mark_trusted_cells(nb, secret, scheme): - """Mark cells as trusted if the notebook's signature can be verified - - Sets ``cell.trusted = True | False`` on all code cells, - depending on whether the stored signature can be verified. - """ - if not nb['worksheets']: - # nothing to mark if there are no cells - return True - trusted = check_notebook_signature(nb, secret, scheme) - for cell in nb['worksheets'][0]['cells']: - if cell['cell_type'] == 'code': - cell['trusted'] = trusted - return trusted - - -def check_trusted_cells(nb): - """Return whether all code cells are trusted - - If there are no code cells, return True. - """ - if not nb['worksheets']: - return True - for cell in nb['worksheets'][0]['cells']: - if cell['cell_type'] != 'code': - continue - if not cell.get('trusted', False): - return False - return True - - class NotebookNotary(LoggingConfigurable): - """A class for configuring notebook signatures - - It stores the secret with which to sign notebooks, - and the hashing scheme to use for notebook signatures. - """ - - scheme = Enum(hashlib.algorithms, default_value='sha256', config=True, - help="""The hashing algorithm used to sign notebooks.""" - ) + """A class for computing and verifying notebook signatures.""" profile_dir = Instance("IPython.core.profiledir.ProfileDir") def _profile_dir_default(self): @@ -172,28 +76,128 @@ class NotebookNotary(LoggingConfigurable): app.initialize() return app.profile_dir + algorithm = Enum(hashlib.algorithms, default_value='sha256', config=True, + help="""The hashing algorithm used to sign notebooks.""" + ) + def _algorithm_changed(self, name, old, new): + self.digestmod = getattr(hashlib, self.algorithm) + + digestmod = Any() + def _digestmod_default(self): + return getattr(hashlib, self.algorithm) + + secret_file = Unicode() + def _secret_file_default(self): + if self.profile_dir is None: + return '' + return os.path.join(self.profile_dir.security_dir, 'notebook_secret') + secret = Bytes(config=True, help="""The secret key with which notebooks are signed.""" ) def _secret_default(self): # note : this assumes an Application is running - profile_dir = self.profile_dir - secret_file = os.path.join(profile_dir.security_dir, 'notebook_secret') - if os.path.exists(secret_file): - with io.open(secret_file, 'rb') as f: + if os.path.exists(self.secret_file): + with io.open(self.secret_file, 'rb') as f: return f.read() else: secret = base64.encodestring(os.urandom(1024)) - self.log.info("Writing output secret to %s", secret_file) - with io.open(secret_file, 'wb') as f: - f.write(secret) - try: - os.chmod(secret_file, 0o600) - except OSError: - self.log.warn( - "Could not set permissions on %s", - secret_file - ) + self._write_secret_file(secret) return secret + + def _write_secret_file(self, secret): + """write my secret to my secret_file""" + self.log.info("Writing output secret to %s", self.secret_file) + with io.open(self.secret_file, 'wb') as f: + f.write(secret) + try: + os.chmod(self.secret_file, 0o600) + except OSError: + self.log.warn( + "Could not set permissions on %s", + self.secret_file + ) + return secret + + def compute_signature(self, nb): + """Compute a notebook's signature + + by hashing the entire contents of the notebook via HMAC digest. + """ + hmac = HMAC(self.secret, digestmod=self.digestmod) + # don't include the previous hash in the content to hash + with signature_removed(nb): + # sign the whole thing + for b in yield_everything(nb): + hmac.update(b) + + return hmac.hexdigest() + + def check_signature(self, nb): + """Check a notebook's stored signature + + If a signature is stored in the notebook's metadata, + a new signature is computed and compared with the stored value. + + Returns True if the signature is found and matches, False otherwise. + + The following conditions must all be met for a notebook to be trusted: + - a signature is stored in the form 'scheme:hexdigest' + - the stored scheme matches the requested scheme + - the requested scheme is available from hashlib + - the computed hash from notebook_signature matches the stored hash + """ + stored_signature = nb['metadata'].get('signature', None) + if not stored_signature \ + or not isinstance(stored_signature, string_types) \ + or ':' not in stored_signature: + return False + stored_algo, sig = stored_signature.split(':', 1) + if self.algorithm != stored_algo: + return False + my_signature = self.compute_signature(nb) + return my_signature == sig + + def sign(self, nb): + """Sign a notebook, indicating that its output is trusted + + stores 'algo:hmac-hexdigest' in notebook.metadata.signature + + e.g. 'sha256:deadbeef123...' + """ + signature = self.compute_signature(nb) + nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) + + def mark_cells(self, nb, trusted): + """Mark cells as trusted if the notebook's signature can be verified + + Sets ``cell.trusted = True | False`` on all code cells, + depending on whether the stored signature can be verified. + + This function is the inverse of check_cells + """ + if not nb['worksheets']: + # nothing to mark if there are no cells + return + for cell in nb['worksheets'][0]['cells']: + if cell['cell_type'] == 'code': + cell['trusted'] = trusted + + def check_cells(self, nb): + """Return whether all code cells are trusted + + If there are no code cells, return True. + + This function is the inverse of mark_cells. + """ + if not nb['worksheets']: + return True + for cell in nb['worksheets'][0]['cells']: + if cell['cell_type'] != 'code': + continue + if not cell.get('trusted', False): + return False + return True + \ No newline at end of file