##// END OF EJS Templates
Merge pull request #5087 from minrk/master...
Paul Ivanov -
r15312:f7d6d5f4 merge
parent child Browse files
Show More
@@ -1,278 +1,310 b''
1 1 """Functions for signing notebooks"""
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2014, The IPython Development Team
4 4 #
5 5 # Distributed under the terms of the BSD License. The full license is in
6 6 # the file COPYING, distributed as part of this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Imports
11 11 #-----------------------------------------------------------------------------
12 12
13 13 import base64
14 14 from contextlib import contextmanager
15 15 import hashlib
16 16 from hmac import HMAC
17 17 import io
18 18 import os
19 19
20 20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
21 21 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool
22 22 from IPython.config import LoggingConfigurable, MultipleInstanceError
23 23 from IPython.core.application import BaseIPythonApplication, base_flags
24 24
25 25 from .current import read, write
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Code
29 29 #-----------------------------------------------------------------------------
30 30 try:
31 31 # Python 3
32 32 algorithms = hashlib.algorithms_guaranteed
33 33 except AttributeError:
34 34 algorithms = hashlib.algorithms
35 35
36 36 def yield_everything(obj):
37 37 """Yield every item in a container as bytes
38 38
39 39 Allows any JSONable object to be passed to an HMAC digester
40 40 without having to serialize the whole thing.
41 41 """
42 42 if isinstance(obj, dict):
43 43 for key in sorted(obj):
44 44 value = obj[key]
45 45 yield cast_bytes(key)
46 46 for b in yield_everything(value):
47 47 yield b
48 48 elif isinstance(obj, (list, tuple)):
49 49 for element in obj:
50 50 for b in yield_everything(element):
51 51 yield b
52 52 elif isinstance(obj, unicode_type):
53 53 yield obj.encode('utf8')
54 54 else:
55 55 yield unicode_type(obj).encode('utf8')
56 56
57 57
58 58 @contextmanager
59 59 def signature_removed(nb):
60 60 """Context manager for operating on a notebook with its signature removed
61 61
62 62 Used for excluding the previous signature when computing a notebook's signature.
63 63 """
64 64 save_signature = nb['metadata'].pop('signature', None)
65 65 try:
66 66 yield
67 67 finally:
68 68 if save_signature is not None:
69 69 nb['metadata']['signature'] = save_signature
70 70
71 71
72 72 class NotebookNotary(LoggingConfigurable):
73 73 """A class for computing and verifying notebook signatures."""
74 74
75 75 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
76 76 def _profile_dir_default(self):
77 77 from IPython.core.application import BaseIPythonApplication
78 78 app = None
79 79 try:
80 80 if BaseIPythonApplication.initialized():
81 81 app = BaseIPythonApplication.instance()
82 82 except MultipleInstanceError:
83 83 pass
84 84 if app is None:
85 85 # create an app, without the global instance
86 86 app = BaseIPythonApplication()
87 87 app.initialize(argv=[])
88 88 return app.profile_dir
89 89
90 90 algorithm = Enum(algorithms, default_value='sha256', config=True,
91 91 help="""The hashing algorithm used to sign notebooks."""
92 92 )
93 93 def _algorithm_changed(self, name, old, new):
94 94 self.digestmod = getattr(hashlib, self.algorithm)
95 95
96 96 digestmod = Any()
97 97 def _digestmod_default(self):
98 98 return getattr(hashlib, self.algorithm)
99 99
100 100 secret_file = Unicode()
101 101 def _secret_file_default(self):
102 102 if self.profile_dir is None:
103 103 return ''
104 104 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
105 105
106 106 secret = Bytes(config=True,
107 107 help="""The secret key with which notebooks are signed."""
108 108 )
109 109 def _secret_default(self):
110 110 # note : this assumes an Application is running
111 111 if os.path.exists(self.secret_file):
112 112 with io.open(self.secret_file, 'rb') as f:
113 113 return f.read()
114 114 else:
115 115 secret = base64.encodestring(os.urandom(1024))
116 116 self._write_secret_file(secret)
117 117 return secret
118 118
119 119 def _write_secret_file(self, secret):
120 120 """write my secret to my secret_file"""
121 121 self.log.info("Writing notebook-signing key to %s", self.secret_file)
122 122 with io.open(self.secret_file, 'wb') as f:
123 123 f.write(secret)
124 124 try:
125 125 os.chmod(self.secret_file, 0o600)
126 126 except OSError:
127 127 self.log.warn(
128 128 "Could not set permissions on %s",
129 129 self.secret_file
130 130 )
131 131 return secret
132 132
133 133 def compute_signature(self, nb):
134 134 """Compute a notebook's signature
135 135
136 136 by hashing the entire contents of the notebook via HMAC digest.
137 137 """
138 138 hmac = HMAC(self.secret, digestmod=self.digestmod)
139 139 # don't include the previous hash in the content to hash
140 140 with signature_removed(nb):
141 141 # sign the whole thing
142 142 for b in yield_everything(nb):
143 143 hmac.update(b)
144 144
145 145 return hmac.hexdigest()
146 146
147 147 def check_signature(self, nb):
148 148 """Check a notebook's stored signature
149 149
150 150 If a signature is stored in the notebook's metadata,
151 151 a new signature is computed and compared with the stored value.
152 152
153 153 Returns True if the signature is found and matches, False otherwise.
154 154
155 155 The following conditions must all be met for a notebook to be trusted:
156 156 - a signature is stored in the form 'scheme:hexdigest'
157 157 - the stored scheme matches the requested scheme
158 158 - the requested scheme is available from hashlib
159 159 - the computed hash from notebook_signature matches the stored hash
160 160 """
161 161 stored_signature = nb['metadata'].get('signature', None)
162 162 if not stored_signature \
163 163 or not isinstance(stored_signature, string_types) \
164 164 or ':' not in stored_signature:
165 165 return False
166 166 stored_algo, sig = stored_signature.split(':', 1)
167 167 if self.algorithm != stored_algo:
168 168 return False
169 169 my_signature = self.compute_signature(nb)
170 170 return my_signature == sig
171 171
172 172 def sign(self, nb):
173 173 """Sign a notebook, indicating that its output is trusted
174 174
175 175 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
176 176
177 177 e.g. 'sha256:deadbeef123...'
178 178 """
179 179 signature = self.compute_signature(nb)
180 180 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
181 181
182 182 def mark_cells(self, nb, trusted):
183 183 """Mark cells as trusted if the notebook's signature can be verified
184 184
185 185 Sets ``cell.trusted = True | False`` on all code cells,
186 186 depending on whether the stored signature can be verified.
187 187
188 188 This function is the inverse of check_cells
189 189 """
190 190 if not nb['worksheets']:
191 191 # nothing to mark if there are no cells
192 192 return
193 193 for cell in nb['worksheets'][0]['cells']:
194 194 if cell['cell_type'] == 'code':
195 195 cell['trusted'] = trusted
196 196
197 def _check_cell(self, cell):
198 """Do we trust an individual cell?
199
200 Return True if:
201
202 - cell is explicitly trusted
203 - cell has no potentially unsafe rich output
204
205 If a cell has no output, or only simple print statements,
206 it will always be trusted.
207 """
208 # explicitly trusted
209 if cell.pop("trusted", False):
210 return True
211
212 # explicitly safe output
213 safe = {
214 'text/plain', 'image/png', 'image/jpeg',
215 'text', 'png', 'jpg', # v3-style short keys
216 }
217
218 for output in cell['outputs']:
219 output_type = output['output_type']
220 if output_type in ('pyout', 'display_data'):
221 # if there are any data keys not in the safe whitelist
222 output_keys = set(output).difference({"output_type", "prompt_number", "metadata"})
223 if output_keys.difference(safe):
224 return False
225
226 return True
227
197 228 def check_cells(self, nb):
198 229 """Return whether all code cells are trusted
199 230
200 231 If there are no code cells, return True.
201 232
202 233 This function is the inverse of mark_cells.
203 234 """
204 235 if not nb['worksheets']:
205 236 return True
206 237 trusted = True
207 238 for cell in nb['worksheets'][0]['cells']:
208 239 if cell['cell_type'] != 'code':
209 240 continue
210 if not cell.pop('trusted', False):
241 # only distrust a cell if it actually has some output to distrust
242 if not self._check_cell(cell):
211 243 trusted = False
212 244 return trusted
213 245
214 246
215 247 trust_flags = {
216 248 'reset' : (
217 249 {'TrustNotebookApp' : { 'reset' : True}},
218 250 """Generate a new key for notebook signature.
219 251 All previously signed notebooks will become untrusted.
220 252 """
221 253 ),
222 254 }
223 255 trust_flags.update(base_flags)
224 256 trust_flags.pop('init')
225 257
226 258
227 259 class TrustNotebookApp(BaseIPythonApplication):
228 260
229 261 description="""Sign one or more IPython notebooks with your key,
230 262 to trust their dynamic (HTML, Javascript) output.
231 263
232 264 Otherwise, you will have to re-execute the notebook to see output.
233 265 """
234 266
235 267 examples = """ipython trust mynotebook.ipynb and_this_one.ipynb"""
236 268
237 269 flags = trust_flags
238 270
239 271 reset = Bool(False, config=True,
240 272 help="""If True, generate a new key for notebook signature.
241 273 After reset, all previously signed notebooks will become untrusted.
242 274 """
243 275 )
244 276
245 277 notary = Instance(NotebookNotary)
246 278 def _notary_default(self):
247 279 return NotebookNotary(parent=self, profile_dir=self.profile_dir)
248 280
249 281 def sign_notebook(self, notebook_path):
250 282 if not os.path.exists(notebook_path):
251 283 self.log.error("Notebook missing: %s" % notebook_path)
252 284 self.exit(1)
253 285 with io.open(notebook_path, encoding='utf8') as f:
254 286 nb = read(f, 'json')
255 287 if self.notary.check_signature(nb):
256 288 print("Notebook already signed: %s" % notebook_path)
257 289 else:
258 290 print("Signing notebook: %s" % notebook_path)
259 291 self.notary.sign(nb)
260 292 with io.open(notebook_path, 'w', encoding='utf8') as f:
261 293 write(nb, f, 'json')
262 294
263 295 def generate_new_key(self):
264 296 """Generate a new notebook signature key"""
265 297 print("Generating new notebook key: %s" % self.notary.secret_file)
266 298 self.notary._write_secret_file(os.urandom(1024))
267 299
268 300 def start(self):
269 301 if self.reset:
270 302 self.generate_new_key()
271 303 return
272 304 if not self.extra_args:
273 305 self.log.critical("Specify at least one notebook to sign.")
274 306 self.exit(1)
275 307
276 308 for notebook_path in self.extra_args:
277 309 self.sign_notebook(notebook_path)
278 310
@@ -1,143 +1,156 b''
1 1 {
2 2 "metadata": {
3 3 "cell_tags": [
4 4 [
5 5 "<None>",
6 6 null
7 7 ]
8 8 ],
9 9 "name": ""
10 10 },
11 11 "nbformat": 3,
12 12 "nbformat_minor": 0,
13 13 "worksheets": [
14 14 {
15 15 "cells": [
16 16 {
17 17 "cell_type": "heading",
18 18 "level": 1,
19 19 "metadata": {},
20 20 "source": [
21 21 "nbconvert latex test"
22 22 ]
23 23 },
24 24 {
25 25 "cell_type": "markdown",
26 26 "metadata": {},
27 27 "source": [
28 28 "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis."
29 29 ]
30 30 },
31 31 {
32 32 "cell_type": "heading",
33 33 "level": 2,
34 34 "metadata": {},
35 35 "source": [
36 36 "Printed Using Python"
37 37 ]
38 38 },
39 39 {
40 40 "cell_type": "code",
41 41 "collapsed": false,
42 42 "input": [
43 "next_paragraph = \"\"\"\n",
44 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
45 "quis orci condimentum, sed feugiat dui porta.\n",
46 "\"\"\"\n",
47 "\n",
48 "def nifty_print(text):\n",
49 " \"\"\"Used to test syntax highlighting\"\"\"\n",
50 " \n",
51 " print(text * 2)\n",
52 "\n",
53 "nifty_print(next_paragraph)"
43 "print(\"hello\")"
54 44 ],
55 45 "language": "python",
56 46 "metadata": {},
57 47 "outputs": [
58 48 {
59 49 "output_type": "stream",
60 50 "stream": "stdout",
61 51 "text": [
62 "\n",
63 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
64 "quis orci condimentum, sed feugiat dui porta.\n",
65 "\n",
66 "Aenean vitae diam consectetur, tempus arcu quis, ultricies urna. Vivamus venenatis sem \n",
67 "quis orci condimentum, sed feugiat dui porta.\n",
68 "\n"
52 "hello\n"
69 53 ]
70 54 }
71 55 ],
72 "prompt_number": 3
56 "prompt_number": 1
73 57 },
74 58 {
75 59 "cell_type": "heading",
76 60 "level": 2,
77 61 "metadata": {},
78 62 "source": [
79 63 "Pyout"
80 64 ]
81 65 },
82 66 {
83 67 "cell_type": "code",
84 68 "collapsed": false,
85 69 "input": [
86 "Text = \"\"\"\n",
87 "Aliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus \n",
88 "ut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent \n",
89 "et commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. \n",
90 "Curabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus \n",
91 "neque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.\n",
92 "\"\"\"\n",
93 "Text"
70 "from IPython.display import HTML\n",
71 "HTML(\"\"\"\n",
72 "<script>\n",
73 "console.log(\"hello\");\n",
74 "</script>\n",
75 "<b>HTML</b>\n",
76 "\"\"\")"
94 77 ],
95 78 "language": "python",
96 79 "metadata": {},
97 80 "outputs": [
98 81 {
82 "html": [
83 "\n",
84 "<script>\n",
85 "console.log(\"hello\");\n",
86 "</script>\n",
87 "<b>HTML</b>\n"
88 ],
99 89 "metadata": {},
100 90 "output_type": "pyout",
101 "prompt_number": 2,
91 "prompt_number": 3,
92 "text": [
93 "<IPython.core.display.HTML at 0x1112757d0>"
94 ]
95 }
96 ],
97 "prompt_number": 3
98 },
99 {
100 "cell_type": "code",
101 "collapsed": false,
102 "input": [
103 "%%javascript\n",
104 "console.log(\"hi\");"
105 ],
106 "language": "python",
107 "metadata": {},
108 "outputs": [
109 {
110 "javascript": [
111 "console.log(\"hi\");"
112 ],
113 "metadata": {},
114 "output_type": "display_data",
102 115 "text": [
103 "'\\nAliquam blandit aliquet enim, eget scelerisque eros adipiscing quis. Nunc sed metus \\nut lorem condimentum condimentum nec id enim. Sed malesuada cursus hendrerit. Praesent \\net commodo justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. \\nCurabitur et magna ante. Proin luctus tellus sit amet egestas laoreet. Sed dapibus \\nneque ac nulla mollis cursus. Fusce mollis egestas libero mattis facilisis.\\n'"
116 "<IPython.core.display.Javascript at 0x1112b4b50>"
104 117 ]
105 118 }
106 119 ],
107 "prompt_number": 2
120 "prompt_number": 7
108 121 },
109 122 {
110 123 "cell_type": "heading",
111 124 "level": 3,
112 125 "metadata": {},
113 126 "source": [
114 127 "Image"
115 128 ]
116 129 },
117 130 {
118 131 "cell_type": "code",
119 132 "collapsed": false,
120 133 "input": [
121 "from IPython.core.display import Image\n",
122 "Image(data=\"http://ipython.org/_static/IPy_header.png\")"
134 "from IPython.display import Image\n",
135 "Image(\"http://ipython.org/_static/IPy_header.png\")"
123 136 ],
124 137 "language": "python",
125 138 "metadata": {},
126 139 "outputs": [
127 140 {
128 141 "metadata": {},
129 142 "output_type": "pyout",
130 143 "png": "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\n",
131 144 "prompt_number": 6,
132 145 "text": [
133 "<IPython.core.display.Image at 0x2100f90>"
146 "<IPython.core.display.Image at 0x111275490>"
134 147 ]
135 148 }
136 149 ],
137 150 "prompt_number": 6
138 151 }
139 152 ],
140 153 "metadata": {}
141 154 }
142 155 ]
143 156 } No newline at end of file
@@ -1,112 +1,120 b''
1 1 """Test Notebook signing"""
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2014, The IPython Development Team
4 4 #
5 5 # Distributed under the terms of the BSD License. The full license is in
6 6 # the file COPYING, distributed as part of this software.
7 7 #-----------------------------------------------------------------------------
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Imports
11 11 #-----------------------------------------------------------------------------
12 12
13 13 from .. import sign
14 14 from .base import TestsBase
15 15
16 16 from ..current import read
17 17 from IPython.core.getipython import get_ipython
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Classes and functions
21 21 #-----------------------------------------------------------------------------
22 22
23 23 class TestNotary(TestsBase):
24 24
25 25 def setUp(self):
26 26 self.notary = sign.NotebookNotary(
27 27 secret=b'secret',
28 28 profile_dir=get_ipython().profile_dir
29 29 )
30 30 with self.fopen(u'test3.ipynb', u'r') as f:
31 31 self.nb = read(f, u'json')
32 32
33 33 def test_algorithms(self):
34 34 last_sig = ''
35 35 for algo in sign.algorithms:
36 36 self.notary.algorithm = algo
37 37 self.notary.sign(self.nb)
38 38 sig = self.nb.metadata.signature
39 39 print(sig)
40 40 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
41 41 self.assertNotEqual(last_sig, sig)
42 42 last_sig = sig
43 43
44 44 def test_sign_same(self):
45 45 """Multiple signatures of the same notebook are the same"""
46 46 sig1 = self.notary.compute_signature(self.nb)
47 47 sig2 = self.notary.compute_signature(self.nb)
48 48 self.assertEqual(sig1, sig2)
49 49
50 50 def test_change_secret(self):
51 51 """Changing the secret changes the signature"""
52 52 sig1 = self.notary.compute_signature(self.nb)
53 53 self.notary.secret = b'different'
54 54 sig2 = self.notary.compute_signature(self.nb)
55 55 self.assertNotEqual(sig1, sig2)
56 56
57 57 def test_sign(self):
58 58 self.notary.sign(self.nb)
59 59 sig = self.nb.metadata.signature
60 60 self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm)
61 61
62 62 def test_check_signature(self):
63 63 nb = self.nb
64 64 md = nb.metadata
65 65 notary = self.notary
66 66 check_signature = notary.check_signature
67 67 # no signature:
68 68 md.pop('signature', None)
69 69 self.assertFalse(check_signature(nb))
70 70 # hash only, no algo
71 71 md.signature = notary.compute_signature(nb)
72 72 self.assertFalse(check_signature(nb))
73 73 # proper signature, algo mismatch
74 74 notary.algorithm = 'sha224'
75 75 notary.sign(nb)
76 76 notary.algorithm = 'sha256'
77 77 self.assertFalse(check_signature(nb))
78 78 # check correctly signed notebook
79 79 notary.sign(nb)
80 80 self.assertTrue(check_signature(nb))
81 81
82 82 def test_mark_cells_untrusted(self):
83 83 cells = self.nb.worksheets[0].cells
84 84 self.notary.mark_cells(self.nb, False)
85 85 for cell in cells:
86 86 if cell.cell_type == 'code':
87 87 self.assertIn('trusted', cell)
88 88 self.assertFalse(cell.trusted)
89 89 else:
90 90 self.assertNotIn('trusted', cell)
91 91
92 92 def test_mark_cells_trusted(self):
93 93 cells = self.nb.worksheets[0].cells
94 94 self.notary.mark_cells(self.nb, True)
95 95 for cell in cells:
96 96 if cell.cell_type == 'code':
97 97 self.assertIn('trusted', cell)
98 98 self.assertTrue(cell.trusted)
99 99 else:
100 100 self.assertNotIn('trusted', cell)
101 101
102 102 def test_check_cells(self):
103 103 nb = self.nb
104 104 self.notary.mark_cells(nb, True)
105 105 self.assertTrue(self.notary.check_cells(nb))
106 106 for cell in nb.worksheets[0].cells:
107 107 self.assertNotIn('trusted', cell)
108 108 self.notary.mark_cells(nb, False)
109 109 self.assertFalse(self.notary.check_cells(nb))
110 110 for cell in nb.worksheets[0].cells:
111 111 self.assertNotIn('trusted', cell)
112
113 def test_trust_no_output(self):
114 nb = self.nb
115 self.notary.mark_cells(nb, False)
116 for cell in nb.worksheets[0].cells:
117 if cell.cell_type == 'code':
118 cell.outputs = []
119 self.assertTrue(self.notary.check_cells(nb))
112 120
General Comments 0
You need to be logged in to leave comments. Login now