Show More
@@ -262,7 +262,7 class NotebookTrustApp(BaseIPythonApplication): | |||
|
262 | 262 | self.exit(1) |
|
263 | 263 | with io.open(notebook_path, encoding='utf8') as f: |
|
264 | 264 | nb = current.read(f, 'json') |
|
265 | sign.trust_notebook(nb, self.notary.secret, self.notary.signature_scheme) | |
|
265 | self.notary.sign(nb) | |
|
266 | 266 | with io.open(notebook_path, 'w', encoding='utf8') as f: |
|
267 | 267 | current.write(nb, f, 'json') |
|
268 | 268 |
@@ -214,7 +214,10 class FileNotebookManager(NotebookManager): | |||
|
214 | 214 | except Exception as e: |
|
215 | 215 | raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) |
|
216 | 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 | 221 | return model |
|
219 | 222 | |
|
220 | 223 | def save_notebook_model(self, model, name='', path=''): |
@@ -238,8 +241,11 class FileNotebookManager(NotebookManager): | |||
|
238 | 241 | os_path = self.get_os_path(new_name, new_path) |
|
239 | 242 | nb = current.to_notebook_json(model['content']) |
|
240 | 243 | |
|
241 |
if s |
|
|
242 | sign.trust_notebook(nb, self.notary.secret, self.notary.scheme) | |
|
244 | if self.notary.check_cells(nb): | |
|
245 | self.notary.sign(nb) | |
|
246 | else: | |
|
247 | self.log.warn("Saving untrusted notebook %s/%s", new_path, new_name) | |
|
248 | ||
|
243 | 249 | |
|
244 | 250 | if 'name' in nb['metadata']: |
|
245 | 251 | nb['metadata']['name'] = u'' |
@@ -19,7 +19,7 import os | |||
|
19 | 19 | |
|
20 | 20 | from IPython.utils.py3compat import string_types, unicode_type, cast_bytes |
|
21 | 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 | 25 | # Code |
@@ -62,14 +62,69 def signature_removed(nb): | |||
|
62 | 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 | 123 | """Compute a notebook's signature |
|
67 | 124 | |
|
68 | 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= |
|
|
127 | hmac = HMAC(self.secret, digestmod=self.digestmod) | |
|
73 | 128 | # don't include the previous hash in the content to hash |
|
74 | 129 | with signature_removed(nb): |
|
75 | 130 | # sign the whole thing |
@@ -78,8 +133,7 def notebook_signature(nb, secret, scheme): | |||
|
78 | 133 | |
|
79 | 134 | return hmac.hexdigest() |
|
80 | 135 | |
|
81 | ||
|
82 | def check_notebook_signature(nb, secret, scheme): | |
|
136 | def check_signature(self, nb): | |
|
83 | 137 | """Check a notebook's stored signature |
|
84 | 138 | |
|
85 | 139 | If a signature is stored in the notebook's metadata, |
@@ -98,47 +152,43 def check_notebook_signature(nb, secret, scheme): | |||
|
98 | 152 | or not isinstance(stored_signature, string_types) \ |
|
99 | 153 | or ':' not in stored_signature: |
|
100 | 154 | return False |
|
101 |
stored_ |
|
|
102 |
if s |
|
|
103 | return False | |
|
104 | try: | |
|
105 | my_signature = notebook_signature(nb, secret, scheme) | |
|
106 | except AttributeError: | |
|
155 | stored_algo, sig = stored_signature.split(':', 1) | |
|
156 | if self.algorithm != stored_algo: | |
|
107 | 157 | return False |
|
158 | my_signature = self.compute_signature(nb) | |
|
108 | 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): | |
|
112 | """Re-sign a notebook, indicating that its output is trusted | |
|
113 | ||
|
114 | stores 'scheme:hmac-hexdigest' in notebook.metadata.signature | |
|
164 | stores 'algo:hmac-hexdigest' in notebook.metadata.signature | |
|
115 | 165 | |
|
116 | 166 | e.g. 'sha256:deadbeef123...' |
|
117 | 167 | """ |
|
118 |
signature = |
|
|
119 |
nb['metadata']['signature'] = "%s:%s" % (s |
|
|
168 | signature = self.compute_signature(nb) | |
|
169 | nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) | |
|
120 | 170 | |
|
121 | ||
|
122 | def mark_trusted_cells(nb, secret, scheme): | |
|
171 | def mark_cells(self, nb, trusted): | |
|
123 | 172 | """Mark cells as trusted if the notebook's signature can be verified |
|
124 | 173 | |
|
125 | 174 | Sets ``cell.trusted = True | False`` on all code cells, |
|
126 | 175 | depending on whether the stored signature can be verified. |
|
176 | ||
|
177 | This function is the inverse of check_cells | |
|
127 | 178 | """ |
|
128 | 179 | if not nb['worksheets']: |
|
129 | 180 | # nothing to mark if there are no cells |
|
130 |
return |
|
|
131 | trusted = check_notebook_signature(nb, secret, scheme) | |
|
181 | return | |
|
132 | 182 | for cell in nb['worksheets'][0]['cells']: |
|
133 | 183 | if cell['cell_type'] == 'code': |
|
134 | 184 | cell['trusted'] = trusted |
|
135 | return trusted | |
|
136 | 185 | |
|
137 | ||
|
138 | def check_trusted_cells(nb): | |
|
186 | def check_cells(self, nb): | |
|
139 | 187 | """Return whether all code cells are trusted |
|
140 | 188 | |
|
141 | 189 | If there are no code cells, return True. |
|
190 | ||
|
191 | This function is the inverse of mark_cells. | |
|
142 | 192 | """ |
|
143 | 193 | if not nb['worksheets']: |
|
144 | 194 | return True |
@@ -150,50 +200,4 def check_trusted_cells(nb): | |||
|
150 | 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 | 203 | No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now