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