##// END OF EJS Templates
Added signature verification for a post
neko259 -
r1237:6c4ec150 decentral
parent child Browse files
Show More
@@ -1,129 +1,160 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2 from django.db import transaction
2 from django.db import transaction
3 from boards.models import KeyPair, GlobalId, Signature, Post
3 from boards.models import KeyPair, GlobalId, Signature, Post
4
4
5 ENCODING_UNICODE = 'unicode'
5 ENCODING_UNICODE = 'unicode'
6
6
7 TAG_MODEL = 'model'
7 TAG_MODEL = 'model'
8 TAG_REQUEST = 'request'
8 TAG_REQUEST = 'request'
9 TAG_RESPONSE = 'response'
9 TAG_RESPONSE = 'response'
10 TAG_ID = 'id'
10 TAG_ID = 'id'
11 TAG_STATUS = 'status'
11 TAG_STATUS = 'status'
12 TAG_MODELS = 'models'
12 TAG_MODELS = 'models'
13 TAG_TITLE = 'title'
13 TAG_TITLE = 'title'
14 TAG_TEXT = 'text'
14 TAG_TEXT = 'text'
15 TAG_THREAD = 'thread'
15 TAG_THREAD = 'thread'
16 TAG_PUB_TIME = 'pub-time'
16 TAG_PUB_TIME = 'pub-time'
17 TAG_SIGNATURES = 'signatures'
17 TAG_SIGNATURES = 'signatures'
18 TAG_SIGNATURE = 'signature'
18 TAG_SIGNATURE = 'signature'
19 TAG_CONTENT = 'content'
19 TAG_CONTENT = 'content'
20 TAG_ATTACHMENTS = 'attachments'
20 TAG_ATTACHMENTS = 'attachments'
21 TAG_ATTACHMENT = 'attachment'
21 TAG_ATTACHMENT = 'attachment'
22
22
23 TYPE_GET = 'get'
23 TYPE_GET = 'get'
24
24
25 ATTR_VERSION = 'version'
25 ATTR_VERSION = 'version'
26 ATTR_TYPE = 'type'
26 ATTR_TYPE = 'type'
27 ATTR_NAME = 'name'
27 ATTR_NAME = 'name'
28 ATTR_VALUE = 'value'
28 ATTR_VALUE = 'value'
29 ATTR_MIMETYPE = 'mimetype'
29 ATTR_MIMETYPE = 'mimetype'
30 ATTR_KEY = 'key'
30
31
31 STATUS_SUCCESS = 'success'
32 STATUS_SUCCESS = 'success'
32
33
33
34
34 class SyncManager:
35 class SyncManager:
35 @staticmethod
36 @staticmethod
36 def generate_response_get(model_list: list):
37 def generate_response_get(model_list: list):
37 response = et.Element(TAG_RESPONSE)
38 response = et.Element(TAG_RESPONSE)
38
39
39 status = et.SubElement(response, TAG_STATUS)
40 status = et.SubElement(response, TAG_STATUS)
40 status.text = STATUS_SUCCESS
41 status.text = STATUS_SUCCESS
41
42
42 models = et.SubElement(response, TAG_MODELS)
43 models = et.SubElement(response, TAG_MODELS)
43
44
44 for post in model_list:
45 for post in model_list:
45 model = et.SubElement(models, TAG_MODEL)
46 model = et.SubElement(models, TAG_MODEL)
46 model.set(ATTR_NAME, 'post')
47 model.set(ATTR_NAME, 'post')
47
48
48 content_tag = et.SubElement(model, TAG_CONTENT)
49 content_tag = et.SubElement(model, TAG_CONTENT)
49
50
50 tag_id = et.SubElement(content_tag, TAG_ID)
51 tag_id = et.SubElement(content_tag, TAG_ID)
51 post.global_id.to_xml_element(tag_id)
52 post.global_id.to_xml_element(tag_id)
52
53
53 title = et.SubElement(content_tag, TAG_TITLE)
54 title = et.SubElement(content_tag, TAG_TITLE)
54 title.text = post.title
55 title.text = post.title
55
56
56 text = et.SubElement(content_tag, TAG_TEXT)
57 text = et.SubElement(content_tag, TAG_TEXT)
57 text.text = post.get_sync_text()
58 text.text = post.get_sync_text()
58
59
59 if not post.is_opening():
60 if not post.is_opening():
60 thread = et.SubElement(content_tag, TAG_THREAD)
61 thread = et.SubElement(content_tag, TAG_THREAD)
61 thread_id = et.SubElement(thread, TAG_ID)
62 thread_id = et.SubElement(thread, TAG_ID)
62 post.get_thread().get_opening_post().global_id.to_xml_element(thread_id)
63 post.get_thread().get_opening_post().global_id.to_xml_element(thread_id)
63 else:
64 else:
64 # TODO Output tags here
65 # TODO Output tags here
65 pass
66 pass
66
67
67 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
68 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
68 pub_time.text = str(post.get_pub_time_str())
69 pub_time.text = str(post.get_pub_time_str())
69
70
70 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
71 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
71 post_signatures = post.signature.all()
72 post_signatures = post.signature.all()
72 if post_signatures:
73 if post_signatures:
73 signatures = post.signatures
74 signatures = post.signatures
74 else:
75 else:
75 # TODO Maybe the signature can be computed only once after
76 # TODO Maybe the signature can be computed only once after
76 # the post is added? Need to add some on_save signal queue
77 # the post is added? Need to add some on_save signal queue
77 # and add this there.
78 # and add this there.
78 key = KeyPair.objects.get(public_key=post.global_id.key)
79 key = KeyPair.objects.get(public_key=post.global_id.key)
79 signatures = [Signature(
80 signatures = [Signature(
80 key_type=key.key_type,
81 key_type=key.key_type,
81 key=key.public_key,
82 key=key.public_key,
82 signature=key.sign(et.tostring(model, ENCODING_UNICODE)),
83 signature=key.sign(et.tostring(content_tag, ENCODING_UNICODE)),
83 )]
84 )]
84 for signature in signatures:
85 for signature in signatures:
85 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
86 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
86 signature_tag.set(ATTR_TYPE, signature.key_type)
87 signature_tag.set(ATTR_TYPE, signature.key_type)
87 signature_tag.set(ATTR_VALUE, signature.signature)
88 signature_tag.set(ATTR_VALUE, signature.signature)
89 signature_tag.set(ATTR_KEY, signature.key)
88
90
89 return et.tostring(response, ENCODING_UNICODE)
91 return et.tostring(response, ENCODING_UNICODE)
90
92
91 @staticmethod
93 @staticmethod
92 @transaction.atomic
94 @transaction.atomic
93 def parse_response_get(response_xml):
95 def parse_response_get(response_xml):
94 tag_root = et.fromstring(response_xml)
96 tag_root = et.fromstring(response_xml)
95 tag_status = tag_root.find(TAG_STATUS)
97 tag_status = tag_root.find(TAG_STATUS)
96 if STATUS_SUCCESS == tag_status.text:
98 if STATUS_SUCCESS == tag_status.text:
97 tag_models = tag_root.find(TAG_MODELS)
99 tag_models = tag_root.find(TAG_MODELS)
98 for tag_model in tag_models:
100 for tag_model in tag_models:
99 tag_content = tag_model.find(TAG_CONTENT)
101 tag_content = tag_model.find(TAG_CONTENT)
102
103 valid = SyncManager.verify_model(tag_content, tag_model)
104
105 if not valid:
106 raise Exception('Invalid model signature')
107
100 tag_id = tag_content.find(TAG_ID)
108 tag_id = tag_content.find(TAG_ID)
101 global_id, exists = GlobalId.from_xml_element(tag_id)
109 global_id, exists = GlobalId.from_xml_element(tag_id)
102
110
103 if exists:
111 if exists:
104 print('Post with same ID already exists')
112 print('Post with same ID already exists')
105 else:
113 else:
106 global_id.save()
114 global_id.save()
107
115
108 title = tag_content.find(TAG_TITLE).text
116 title = tag_content.find(TAG_TITLE).text
109 text = tag_content.find(TAG_TEXT).text
117 text = tag_content.find(TAG_TEXT).text
110 pub_time = tag_content.find(TAG_PUB_TIME).text
118 pub_time = tag_content.find(TAG_PUB_TIME).text
111
119
112 thread = tag_content.find(TAG_THREAD)
120 thread = tag_content.find(TAG_THREAD)
113 if thread:
121 if thread:
114 opening_post = Post.objects.get(
122 opening_post = Post.objects.get(
115 id=thread.find(TAG_ID).text)
123 id=thread.find(TAG_ID).text)
116 else:
124 else:
117 opening_post = None
125 opening_post = None
118 # TODO Get tags here
126 # TODO Get tags here
119
127
120 # TODO Check that the replied posts are already present
128 # TODO Check that the replied posts are already present
121 # before adding new ones
129 # before adding new ones
122
130
123 post = Post.objects.import_post(
131 post = Post.objects.import_post(
124 title=title, text=text, pub_time=pub_time,
132 title=title, text=text, pub_time=pub_time,
125 opening_post=opening_post)
133 opening_post=opening_post)
126 post.global_id = global_id
134 post.global_id = global_id
127 else:
135 else:
128 # TODO Throw an exception?
136 # TODO Throw an exception?
129 pass
137 pass
138
139 @staticmethod
140 def verify_model(tag_content, tag_model):
141 """
142 Verifies all signatures for a single model.
143 """
144
145 valid = True
146
147 tag_signatures = tag_model.find(TAG_SIGNATURES)
148 for tag_signature in tag_signatures:
149 signature_type = tag_signature.get(ATTR_TYPE)
150 signature_value = tag_signature.get(ATTR_VALUE)
151 signature_key = tag_signature.get(ATTR_KEY)
152
153 if not KeyPair.objects.verify(
154 signature_key,
155 et.tostring(tag_content, ENCODING_UNICODE),
156 signature_value, signature_type):
157 valid = False
158 break
159
160 return valid
@@ -1,205 +1,205 b''
1 # 0 Title #
1 # 0 Title #
2
2
3 DIP-1 Common protocol description
3 DIP-1 Common protocol description
4
4
5 # 1 Intro #
5 # 1 Intro #
6
6
7 This document describes the Data Interchange Protocol (DIP), designed to
7 This document describes the Data Interchange Protocol (DIP), designed to
8 exchange filtered data that can be stored as a graph structure between
8 exchange filtered data that can be stored as a graph structure between
9 network nodes.
9 network nodes.
10
10
11 # 2 Purpose #
11 # 2 Purpose #
12
12
13 This protocol will be used to share the models (originally imageboard posts)
13 This protocol will be used to share the models (originally imageboard posts)
14 across multiple servers. The main differnce of this protocol is that the node
14 across multiple servers. The main differnce of this protocol is that the node
15 can specify what models it wants to get and from whom. The nodes can get
15 can specify what models it wants to get and from whom. The nodes can get
16 models from a specific server, or from all except some specific servers. Also
16 models from a specific server, or from all except some specific servers. Also
17 the models can be filtered by timestamps or tags.
17 the models can be filtered by timestamps or tags.
18
18
19 # 3 Protocol description #
19 # 3 Protocol description #
20
20
21 The node requests other node's changes list since some time (since epoch if
21 The node requests other node's changes list since some time (since epoch if
22 this is the start). The other node sends a list of post ids or posts in the
22 this is the start). The other node sends a list of post ids or posts in the
23 XML format.
23 XML format.
24
24
25 Protocol version is the version of the sync api. Model version is the version
25 Protocol version is the version of the sync api. Model version is the version
26 of data models. If at least one of them is different, the sync cannot be
26 of data models. If at least one of them is different, the sync cannot be
27 performed.
27 performed.
28
28
29 The node signs the data with its keys. The receiving node saves the key at the
29 The node signs the data with its keys. The receiving node saves the key at the
30 first sync and checks it every time. If the key has changed, the info won't be
30 first sync and checks it every time. If the key has changed, the info won't be
31 saved from the node (or the node id must be changed). A model can be signed
31 saved from the node (or the node id must be changed). A model can be signed
32 with several keys but at least one of them must be the same as in the global
32 with several keys but at least one of them must be the same as in the global
33 ID to verify the sender.
33 ID to verify the sender.
34
34
35 Each node can have several keys. Nodes can have shared keys to serve as a pool
35 Each node can have several keys. Nodes can have shared keys to serve as a pool
36 (several nodes with the same key).
36 (several nodes with the same key).
37
37
38 Each post has an ID in the unique format: key-type::key::local-id
38 Each post has an ID in the unique format: key-type::key::local-id
39
39
40 All requests pass a request type, protocol and model versions, and a list of
40 All requests pass a request type, protocol and model versions, and a list of
41 optional arguments used for filtering.
41 optional arguments used for filtering.
42
42
43 Each request has its own version. Version consists of 2 numbers: first is
43 Each request has its own version. Version consists of 2 numbers: first is
44 incompatible version (1.3 and 2.0 are not compatible and must not be in sync)
44 incompatible version (1.3 and 2.0 are not compatible and must not be in sync)
45 and the second one is minor and compatible (for example, new optional field
45 and the second one is minor and compatible (for example, new optional field
46 is added which will be igroned by those who don't support it yet).
46 is added which will be igroned by those who don't support it yet).
47
47
48 Post edits and reflinks are not saved to the sync model. The replied post ID
48 Post edits and reflinks are not saved to the sync model. The replied post ID
49 can be got from the post text, and reflinks can be computed when loading
49 can be got from the post text, and reflinks can be computed when loading
50 posts. The edit time is not saved because a foreign post can be 'edited' (new
50 posts. The edit time is not saved because a foreign post can be 'edited' (new
51 replies are added) but the signature must not change (so we can't update the
51 replies are added) but the signature must not change (so we can't update the
52 content). The inner posts can be edited, and the signature will change then
52 content). The inner posts can be edited, and the signature will change then
53 but the local-id won't, so the other node can detect that and replace the post
53 but the local-id won't, so the other node can detect that and replace the post
54 instead of adding a new one.
54 instead of adding a new one.
55
55
56 ## 3.1 Requests ##
56 ## 3.1 Requests ##
57
57
58 There is no constraint on how the server should calculate the request. The
58 There is no constraint on how the server should calculate the request. The
59 server can return any information by any filter and the requesting node is
59 server can return any information by any filter and the requesting node is
60 responsible for validating it.
60 responsible for validating it.
61
61
62 The server is required to return the status of request. See 3.2 for details.
62 The server is required to return the status of request. See 3.2 for details.
63
63
64 ### 3.1.1 pull ###
64 ### 3.1.1 pull ###
65
65
66 "pull" request gets the desired model id list by the given filter (e.g. thread, tags,
66 "pull" request gets the desired model id list by the given filter (e.g. thread, tags,
67 author)
67 author)
68
68
69 Sample request is as follows:
69 Sample request is as follows:
70
70
71 <?xml version="1.1" encoding="UTF-8" ?>
71 <?xml version="1.1" encoding="UTF-8" ?>
72 <request version="1.0" type="pull">
72 <request version="1.0" type="pull">
73 <model version="1.0" name="post">
73 <model version="1.0" name="post">
74 <timestamp_from>0</timestamp_from>
74 <timestamp_from>0</timestamp_from>
75 <timestamp_to>0</timestamp_to>
75 <timestamp_to>0</timestamp_to>
76 <tags>
76 <tags>
77 <tag>tag1</tag>
77 <tag>tag1</tag>
78 </tags>
78 </tags>
79 <sender>
79 <sender>
80 <allow>
80 <allow>
81 <key>abcehy3h9t</key>
81 <key>abcehy3h9t</key>
82 <key>ehoehyoe</key>
82 <key>ehoehyoe</key>
83 </allow>
83 </allow>
84 <!-- There can be only allow block (all other are denied) or deny block (all other are allowed) -->
84 <!-- There can be only allow block (all other are denied) or deny block (all other are allowed) -->
85 </sender>
85 </sender>
86 </model>
86 </model>
87 </request>
87 </request>
88
88
89 Under the <model> tag there are filters. Filters for the "post" model can
89 Under the <model> tag there are filters. Filters for the "post" model can
90 be found in DIP-2.
90 be found in DIP-2.
91
91
92 Sample response:
92 Sample response:
93
93
94 <?xml version="1.1" encoding="UTF-8" ?>
94 <?xml version="1.1" encoding="UTF-8" ?>
95 <response>
95 <response>
96 <status>success</status>
96 <status>success</status>
97 <models>
97 <models>
98 <id key="id1" type="ecdsa" local-id="1" />
98 <id key="id1" type="ecdsa" local-id="1" />
99 <id key="id1" type="ecdsa" local-id="2" />
99 <id key="id1" type="ecdsa" local-id="2" />
100 <id key="id2" type="ecdsa" local-id="1" />
100 <id key="id2" type="ecdsa" local-id="1" />
101 <id key="id2" type="ecdsa" local-id="5" />
101 <id key="id2" type="ecdsa" local-id="5" />
102 </models>
102 </models>
103 </response>
103 </response>
104
104
105 ### 3.1.2 get ###
105 ### 3.1.2 get ###
106
106
107 "get" gets models by id list.
107 "get" gets models by id list.
108
108
109 Sample request:
109 Sample request:
110
110
111 <?xml version="1.1" encoding="UTF-8" ?>
111 <?xml version="1.1" encoding="UTF-8" ?>
112 <request version="1.0" type="get">
112 <request version="1.0" type="get">
113 <model version="1.0" name="post">
113 <model version="1.0" name="post">
114 <id key="id1" type="ecdsa" local-id="1" />
114 <id key="id1" type="ecdsa" local-id="1" />
115 <id key="id1" type="ecdsa" local-id="2" />
115 <id key="id1" type="ecdsa" local-id="2" />
116 </model>
116 </model>
117 </request>
117 </request>
118
118
119 Id consists of a key, key type and local id. This key is used for signing and
119 Id consists of a key, key type and local id. This key is used for signing and
120 validating of data in the model content.
120 validating of data in the model content.
121
121
122 Sample response:
122 Sample response:
123
123
124 <?xml version="1.1" encoding="UTF-8" ?>
124 <?xml version="1.1" encoding="UTF-8" ?>
125 <response>
125 <response>
126 <!--
126 <!--
127 Valid statuses are 'success' and 'error'.
127 Valid statuses are 'success' and 'error'.
128 -->
128 -->
129 <status>success</status>
129 <status>success</status>
130 <models>
130 <models>
131 <model name="post">
131 <model name="post">
132 <!--
132 <!--
133 Content tag is the data that is signed by signatures and must
133 Content tag is the data that is signed by signatures and must
134 not be changed for the post from other node.
134 not be changed for the post from other node.
135 -->
135 -->
136 <content>
136 <content>
137 <id key="id1" type="ecdsa" local-id="1" />
137 <id key="id1" type="ecdsa" local-id="1" />
138 <title>13</title>
138 <title>13</title>
139 <text>Thirteen</text>
139 <text>Thirteen</text>
140 <thread><id key="id1" type="ecdsa" local-id="2" /></thread>
140 <thread><id key="id1" type="ecdsa" local-id="2" /></thread>
141 <pub-time>12</pub-time>
141 <pub-time>12</pub-time>
142 <!--
142 <!--
143 Images are saved as attachments and included in the
143 Images are saved as attachments and included in the
144 signature.
144 signature.
145 -->
145 -->
146 <attachments>
146 <attachments>
147 <attachment mimetype="image/png" name="12345.png">
147 <attachment mimetype="image/png" name="12345.png">
148 TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0
148 TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0
149 aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1
149 aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1
150 c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0
150 c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0
151 aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdl
151 aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdl
152 LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
152 LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
153 </attachment>
153 </attachment>
154 </attachments>
154 </attachments>
155 </content>
155 </content>
156 <!--
156 <!--
157 There can be several signatures for one model. At least one
157 There can be several signatures for one model. At least one
158 signature must be made with the key used in global ID.
158 signature must be made with the key used in global ID.
159 -->
159 -->
160 <signatures>
160 <signatures>
161 <signature key="id1" type="ecdsa" value="dhefhtreh" />
161 <signature key="id1" type="ecdsa" value="dhefhtreh" />
162 <signature key="id45" type="ecdsa" value="dsgfgdhefhtreh" />
162 <signature key="id45" type="ecdsa" value="dsgfgdhefhtreh" />
163 </signatures>
163 </signatures>
164 </model>
164 </model>
165 <model name="post">
165 <model name="post">
166 <content>
166 <content>
167 <id key="id1" type="ecdsa" local-id="id2" />
167 <id key="id1" type="ecdsa" local-id="id2" />
168 <title>13</title>
168 <title>13</title>
169 <text>Thirteen</text>
169 <text>Thirteen</text>
170 <pub-time>12</pub-time>
170 <pub-time>12</pub-time>
171 <edit-time>13</edit-time>
171 <edit-time>13</edit-time>
172 <tags>
172 <tags>
173 <tag>tag1</tag>
173 <tag>tag1</tag>
174 </tags>
174 </tags>
175 </content>
175 </content>
176 <signatures>
176 <signatures>
177 <signature key="id2" type="ecdsa" value="dehdfh" />
177 <signature key="id2" type="ecdsa" value="dehdfh" />
178 </signatures>
178 </signatures>
179 </model>
179 </model>
180 </models>
180 </models>
181 </response>
181 </response>
182
182
183 ### 3.1.3 put ###
183 ### 3.1.3 put ###
184
184
185 "put" gives a model to the given node (you have no guarantee the node takes
185 "put" gives a model to the given node (you have no guarantee the node takes
186 it, consider you are just advising the node to take your post). This request
186 it, consider you are just advising the node to take your post). This request
187 type is useful in pool where all the nodes try to duplicate all of their data
187 type is useful in pool where all the nodes try to duplicate all of their data
188 across the pool.
188 across the pool.
189
189
190 ## 3.2 Responses ##
190 ## 3.2 Responses ##
191
191
192 ### 3.2.1 "not supported" ###
192 ### 3.2.1 "not supported" ###
193
193
194 If the request if completely not supported, a "not supported" status will be
194 If the request if completely not supported, a "not supported" status will be
195 returned.
195 returned.
196
196
197 ### 3.2.2 "success" ###
197 ### 3.2.2 "success" ###
198
198
199 "success" status means the request was processed and the result is returned.
199 "success" status means the request was processed and the result is returned.
200
200
201 ### 3.2.3 "error" ###
201 ### 3.2.3 "error" ###
202
202
203 If the server knows for sure that the operation cannot be processed, it sends
203 If the server knows for sure that the operation cannot be processed, it sends
204 the "error" status. Additional tags describing the error may be <description>
204 the "error" status. Additional tags describing the error may be <description>
205 and <stack>.
205 and <stack>.
General Comments 0
You need to be logged in to leave comments. Login now