##// END OF EJS Templates
Notaries sign notebooks now
MinRK -
Show More
@@ -262,7 +262,7 class NotebookTrustApp(BaseIPythonApplication):
262 self.exit(1)
262 self.exit(1)
263 with io.open(notebook_path, encoding='utf8') as f:
263 with io.open(notebook_path, encoding='utf8') as f:
264 nb = current.read(f, 'json')
264 nb = current.read(f, 'json')
265 sign.trust_notebook(nb, self.notary.secret, self.notary.signature_scheme)
265 self.notary.sign(nb)
266 with io.open(notebook_path, 'w', encoding='utf8') as f:
266 with io.open(notebook_path, 'w', encoding='utf8') as f:
267 current.write(nb, f, 'json')
267 current.write(nb, f, 'json')
268
268
@@ -214,7 +214,10 class FileNotebookManager(NotebookManager):
214 except Exception as e:
214 except Exception as e:
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
216 model['content'] = nb
216 model['content'] = nb
217 sign.mark_trusted_cells(nb, self.notary.secret, self.notary.scheme)
217 trusted = self.notary.check_signature(nb)
218 if not trusted:
219 self.log.warn("Notebook %s/%s is not trusted", model['path'], model['name'])
220 self.notary.mark_cells(nb, trusted)
218 return model
221 return model
219
222
220 def save_notebook_model(self, model, name='', path=''):
223 def save_notebook_model(self, model, name='', path=''):
@@ -238,8 +241,11 class FileNotebookManager(NotebookManager):
238 os_path = self.get_os_path(new_name, new_path)
241 os_path = self.get_os_path(new_name, new_path)
239 nb = current.to_notebook_json(model['content'])
242 nb = current.to_notebook_json(model['content'])
240
243
241 if sign.check_trusted_cells(nb):
244 if self.notary.check_cells(nb):
242 sign.trust_notebook(nb, self.notary.secret, self.notary.scheme)
245 self.notary.sign(nb)
246 else:
247 self.log.warn("Saving untrusted notebook %s/%s", new_path, new_name)
248
243
249
244 if 'name' in nb['metadata']:
250 if 'name' in nb['metadata']:
245 nb['metadata']['name'] = u''
251 nb['metadata']['name'] = u''
@@ -19,7 +19,7 import os
19
19
20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
21 from IPython.config import LoggingConfigurable
21 from IPython.config import LoggingConfigurable
22 from IPython.utils.traitlets import Instance, Bytes, Enum
22 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Code
25 # Code
@@ -62,14 +62,69 def signature_removed(nb):
62 nb['metadata']['signature'] = save_signature
62 nb['metadata']['signature'] = save_signature
63
63
64
64
65 def notebook_signature(nb, secret, scheme):
65 class NotebookNotary(LoggingConfigurable):
66 """A class for computing and verifying notebook signatures."""
67
68 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
69 def _profile_dir_default(self):
70 from IPython.core.application import BaseIPythonApplication
71 if BaseIPythonApplication.initialized():
72 app = BaseIPythonApplication.instance()
73 else:
74 # create an app, without the global instance
75 app = BaseIPythonApplication()
76 app.initialize()
77 return app.profile_dir
78
79 algorithm = Enum(hashlib.algorithms, default_value='sha256', config=True,
80 help="""The hashing algorithm used to sign notebooks."""
81 )
82 def _algorithm_changed(self, name, old, new):
83 self.digestmod = getattr(hashlib, self.algorithm)
84
85 digestmod = Any()
86 def _digestmod_default(self):
87 return getattr(hashlib, self.algorithm)
88
89 secret_file = Unicode()
90 def _secret_file_default(self):
91 if self.profile_dir is None:
92 return ''
93 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
94
95 secret = Bytes(config=True,
96 help="""The secret key with which notebooks are signed."""
97 )
98 def _secret_default(self):
99 # note : this assumes an Application is running
100 if os.path.exists(self.secret_file):
101 with io.open(self.secret_file, 'rb') as f:
102 return f.read()
103 else:
104 secret = base64.encodestring(os.urandom(1024))
105 self._write_secret_file(secret)
106 return secret
107
108 def _write_secret_file(self, secret):
109 """write my secret to my secret_file"""
110 self.log.info("Writing output secret to %s", self.secret_file)
111 with io.open(self.secret_file, 'wb') as f:
112 f.write(secret)
113 try:
114 os.chmod(self.secret_file, 0o600)
115 except OSError:
116 self.log.warn(
117 "Could not set permissions on %s",
118 self.secret_file
119 )
120 return secret
121
122 def compute_signature(self, nb):
66 """Compute a notebook's signature
123 """Compute a notebook's signature
67
124
68 by hashing the entire contents of the notebook via HMAC digest.
125 by hashing the entire contents of the notebook via HMAC digest.
69 scheme is the hashing scheme, which must be an attribute of the hashlib module,
70 as listed in hashlib.algorithms.
71 """
126 """
72 hmac = HMAC(secret, digestmod=getattr(hashlib, scheme))
127 hmac = HMAC(self.secret, digestmod=self.digestmod)
73 # don't include the previous hash in the content to hash
128 # don't include the previous hash in the content to hash
74 with signature_removed(nb):
129 with signature_removed(nb):
75 # sign the whole thing
130 # sign the whole thing
@@ -78,8 +133,7 def notebook_signature(nb, secret, scheme):
78
133
79 return hmac.hexdigest()
134 return hmac.hexdigest()
80
135
81
136 def check_signature(self, nb):
82 def check_notebook_signature(nb, secret, scheme):
83 """Check a notebook's stored signature
137 """Check a notebook's stored signature
84
138
85 If a signature is stored in the notebook's metadata,
139 If a signature is stored in the notebook's metadata,
@@ -98,47 +152,43 def check_notebook_signature(nb, secret, scheme):
98 or not isinstance(stored_signature, string_types) \
152 or not isinstance(stored_signature, string_types) \
99 or ':' not in stored_signature:
153 or ':' not in stored_signature:
100 return False
154 return False
101 stored_scheme, sig = stored_signature.split(':', 1)
155 stored_algo, sig = stored_signature.split(':', 1)
102 if scheme != stored_scheme:
156 if self.algorithm != stored_algo:
103 return False
104 try:
105 my_signature = notebook_signature(nb, secret, scheme)
106 except AttributeError:
107 return False
157 return False
158 my_signature = self.compute_signature(nb)
108 return my_signature == sig
159 return my_signature == sig
109
160
161 def sign(self, nb):
162 """Sign a notebook, indicating that its output is trusted
110
163
111 def trust_notebook(nb, secret, scheme):
164 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
112 """Re-sign a notebook, indicating that its output is trusted
113
114 stores 'scheme:hmac-hexdigest' in notebook.metadata.signature
115
165
116 e.g. 'sha256:deadbeef123...'
166 e.g. 'sha256:deadbeef123...'
117 """
167 """
118 signature = notebook_signature(nb, secret, scheme)
168 signature = self.compute_signature(nb)
119 nb['metadata']['signature'] = "%s:%s" % (scheme, signature)
169 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
120
170
121
171 def mark_cells(self, nb, trusted):
122 def mark_trusted_cells(nb, secret, scheme):
123 """Mark cells as trusted if the notebook's signature can be verified
172 """Mark cells as trusted if the notebook's signature can be verified
124
173
125 Sets ``cell.trusted = True | False`` on all code cells,
174 Sets ``cell.trusted = True | False`` on all code cells,
126 depending on whether the stored signature can be verified.
175 depending on whether the stored signature can be verified.
176
177 This function is the inverse of check_cells
127 """
178 """
128 if not nb['worksheets']:
179 if not nb['worksheets']:
129 # nothing to mark if there are no cells
180 # nothing to mark if there are no cells
130 return True
181 return
131 trusted = check_notebook_signature(nb, secret, scheme)
132 for cell in nb['worksheets'][0]['cells']:
182 for cell in nb['worksheets'][0]['cells']:
133 if cell['cell_type'] == 'code':
183 if cell['cell_type'] == 'code':
134 cell['trusted'] = trusted
184 cell['trusted'] = trusted
135 return trusted
136
185
137
186 def check_cells(self, nb):
138 def check_trusted_cells(nb):
139 """Return whether all code cells are trusted
187 """Return whether all code cells are trusted
140
188
141 If there are no code cells, return True.
189 If there are no code cells, return True.
190
191 This function is the inverse of mark_cells.
142 """
192 """
143 if not nb['worksheets']:
193 if not nb['worksheets']:
144 return True
194 return True
@@ -150,50 +200,4 def check_trusted_cells(nb):
150 return True
200 return True
151
201
152
202
153 class NotebookNotary(LoggingConfigurable):
154 """A class for configuring notebook signatures
155
156 It stores the secret with which to sign notebooks,
157 and the hashing scheme to use for notebook signatures.
158 """
159
160 scheme = Enum(hashlib.algorithms, default_value='sha256', config=True,
161 help="""The hashing algorithm used to sign notebooks."""
162 )
163
164 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
165 def _profile_dir_default(self):
166 from IPython.core.application import BaseIPythonApplication
167 if BaseIPythonApplication.initialized():
168 app = BaseIPythonApplication.instance()
169 else:
170 # create an app, without the global instance
171 app = BaseIPythonApplication()
172 app.initialize()
173 return app.profile_dir
174
175 secret = Bytes(config=True,
176 help="""The secret key with which notebooks are signed."""
177 )
178 def _secret_default(self):
179 # note : this assumes an Application is running
180 profile_dir = self.profile_dir
181 secret_file = os.path.join(profile_dir.security_dir, 'notebook_secret')
182 if os.path.exists(secret_file):
183 with io.open(secret_file, 'rb') as f:
184 return f.read()
185 else:
186 secret = base64.encodestring(os.urandom(1024))
187 self.log.info("Writing output secret to %s", secret_file)
188 with io.open(secret_file, 'wb') as f:
189 f.write(secret)
190 try:
191 os.chmod(secret_file, 0o600)
192 except OSError:
193 self.log.warn(
194 "Could not set permissions on %s",
195 secret_file
196 )
197 return secret
198
199 No newline at end of file
203
General Comments 0
You need to be logged in to leave comments. Login now