4460bf277722273572c22e29b7b55d98f70bb653
[src/app-framework-main.git] / src / wgtpkg-digsig.c
1 /*
2  Copyright (C) 2015-2018 IoT.bzh
3
4  author: José Bollo <jose.bollo@iot.bzh>
5
6  Licensed under the Apache License, Version 2.0 (the "License");
7  you may not use this file except in compliance with the License.
8  You may obtain a copy of the License at
9
10      http://www.apache.org/licenses/LICENSE-2.0
11
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 */
18
19
20 #include <string.h>
21 #include <assert.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24
25 #include <libxml/parser.h>
26 #include <libxml/tree.h>
27 #include <libxml/uri.h>
28 #include <libxml/xmlsave.h>
29
30
31 #include "verbose.h"
32 #include "wgtpkg-files.h"
33 #include "wgtpkg-workdir.h"
34 #include "wgtpkg-certs.h"
35 #include "wgtpkg-xmlsec.h"
36 #include "wgtpkg-digsig.h"
37
38
39
40 static const char uri_role_author[] = "http://www.w3.org/ns/widgets-digsig#role-author";
41 static const char uri_role_distributor[] = "http://www.w3.org/ns/widgets-digsig#role-distributor";
42 static const char uri_profile[] = "http://www.w3.org/ns/widgets-digsig#profile";
43
44
45 /* global data */
46 static xmlDocPtr document;  /* the document */
47
48 /* facility to get the first element node (skip text nodes) starting with 'node' */
49 static xmlNodePtr next_element(xmlNodePtr node)
50 {
51         while (node && node->type != XML_ELEMENT_NODE)
52                 node = node->next;
53         return node;
54 }
55
56 /* is the 'node' an element node of 'name'? */
57 static int is_element(xmlNodePtr node, const char *name)
58 {
59         return node->type == XML_ELEMENT_NODE
60                 && !strcmp(name, node->name);
61 }
62
63 /* is the 'node' an element node of 'name'? */
64 static int is_node(xmlNodePtr node, const char *name)
65 {
66         return node != NULL && is_element(node, name);
67 }
68
69 #if 0
70 /* facility to get the first element node (skip text nodes) starting with 'node' */
71 static xmlNodePtr next_element_type(xmlNodePtr node, const char *name)
72 {
73         while (node && node->type != XML_ELEMENT_NODE && strcmp(name, node->name))
74                 node = node->next;
75         return node;
76 }
77
78 /* search the element node of id. NOTE : not optimized at all */
79 static xmlNodePtr search_for(const char *attrname, const char *value)
80 {
81         char *val;
82         xmlNodePtr iter, next;
83         xmlNodePtr result;
84
85         result = NULL;
86         iter = xmlDocGetRootElement(document);
87         while (iter != NULL) {
88                 val = xmlGetProp(iter, attrname);
89                 if (val != NULL && !strcmp(val, value)) {
90                         if (result != NULL) {
91                                 ERROR("duplicated %s %s", attrname, value);
92                                 free(val);
93                                 return NULL;
94                         }
95                         result = iter;
96                 }
97                 free(val);
98                 next = next_element(iter->children);
99                 if (next == NULL) {
100                         /* no child, try sibling */
101                         next = next_element(iter->next);
102                         if (next == NULL) {
103                                 iter = iter->parent;
104                                 while (iter != NULL && next == NULL) {
105                                         next = next_element(iter->next);
106                                         iter = iter->parent;
107                                 }
108                         }
109                 }
110                 iter = next;
111         }
112         if (result == NULL)
113                 ERROR("node of %s '%s' not found", attrname, value);
114         return result;
115 }
116
117 /* search the element node of id. NOTE : not optimized at all */
118 static xmlNodePtr search_id(const char *id)
119 {
120         return search_for("Id", id);
121 }
122 #endif
123
124 /* check the digest of one element */
125 static int check_one_reference(xmlNodePtr ref)
126 {
127         int rc;
128         char *uri;
129         xmlURIPtr u;
130         struct filedesc *fdesc;
131
132         /* start */
133         rc = -1;
134
135         /* get the uri */
136         uri = xmlGetProp(ref, "URI");
137         if (uri == NULL) {
138                 ERROR("attribute URI of element <Reference> not found");
139                 goto error;
140         }
141
142         /* parse the uri */
143         u = xmlParseURI(uri);
144         if (!u) {
145                 ERROR("error while parsing URI %s", uri);
146                 goto error2;
147         }
148
149         /* check that unexpected parts are not there */
150         if (u->scheme || u->opaque || u->authority || u->server || u->user || u->query) {
151                 ERROR("unexpected uri component in %s", uri);
152                 goto error3;
153         }
154
155         /* check path and fragment */
156         if (!u->path && !u->fragment) {
157                 ERROR("invalid uri %s", uri);
158                 goto error3;
159         }
160         if (u->path && u->fragment) {
161                 ERROR("not allowed to sign foreign fragment in %s", uri);
162                 goto error3;
163         }
164
165         if (u->path) {
166                 /* check that the path is valid */
167                 fdesc = file_of_name(u->path);
168                 if (fdesc == NULL) {
169                         ERROR("reference to unknown file %s", u->path);
170                         goto error3;
171                 }
172                 if (fdesc->type != type_file) {
173                         ERROR("reference to directory %s", u->path);
174                         goto error3;
175                 }
176                 if ((fdesc->flags & flag_distributor_signature) != 0) {
177                         ERROR("reference to signature %s", u->path);
178                         goto error3;
179                 }
180                 fdesc->flags |= flag_referenced;
181                 rc = 0;
182         } else {
183                 rc = 0;
184         }
185
186 error3:
187         xmlFreeURI(u);
188 error2:
189         xmlFree(uri);
190 error:
191         return rc;
192 }
193
194 static int check_references(xmlNodePtr sinfo)
195 {
196         unsigned int i, n, flags;
197         struct filedesc *f;
198         int result;
199         xmlNodePtr elem;
200
201         result = 0;
202         elem = sinfo->children;
203         while (elem != NULL) {
204                 if (is_element(elem, "Reference"))
205                         if (check_one_reference(elem))
206                                 result = -1;
207                 elem = elem->next;
208         }
209
210         n = file_count();
211         i = 0;
212         while(i < n) {
213                 f = file_of_index(i++);
214                 if (f->type == type_file) {
215                         flags = f->flags;
216                         if (!(flags & (flag_signature | flag_referenced))) {
217                                 ERROR("file not referenced in signature: %s", f->name);
218                                 result = -1;
219                         }
220                 }
221         }
222
223         return result;
224 }
225
226
227 static int get_certificates(xmlNodePtr kinfo)
228 {
229         xmlNodePtr n1, n2;
230         char *b;
231         int rc;
232
233         n1 = kinfo->children;
234         while (n1) {
235                 if (is_element(n1, "X509Data")) {
236                         n2 = n1->children;
237                         while (n2) {
238                                 if (is_element(n2, "X509Certificate")) {
239                                         b = xmlNodeGetContent(n2);
240                                         if (b == NULL) {
241                                                 ERROR("xmlNodeGetContent of X509Certificate failed");
242                                                 return -1;
243                                         }
244                                         rc = add_certificate_b64(b);
245                                         xmlFree(b);
246                                         if (rc)
247                                                 return rc;
248                                 }
249                                 n2 = n2->next;
250                         }
251                 }
252                 n1 = n1->next;
253         }
254         return 0;
255 }
256
257 /* checks the current document */
258 static int checkdocument()
259 {
260         int rc;
261         xmlNodePtr sinfo, svalue, kinfo, objs, rootsig;
262
263         rc = -1;
264
265         rootsig = xmlDocGetRootElement(document);
266         if (!is_node(rootsig, "Signature")) {
267                 ERROR("root element <Signature> not found");
268                 goto error;
269         }
270
271         sinfo = next_element(rootsig->children);
272         if (!is_node(sinfo, "SignedInfo")) {
273                 ERROR("element <SignedInfo> not found");
274                 goto error;
275         }
276
277         svalue = next_element(sinfo->next);
278         if (!is_node(svalue, "SignatureValue")) {
279                 ERROR("element <SignatureValue> not found");
280                 goto error;
281         }
282
283         kinfo = next_element(svalue->next);
284         if (is_node(kinfo, "KeyInfo")) {
285                 objs = kinfo->next;
286         } else {
287                 objs = kinfo;
288                 kinfo = NULL;
289         }
290
291         rc = check_references(sinfo);
292         if (rc)
293                 goto error;
294
295         rc = xmlsec_verify(rootsig);
296         if (rc)
297                 goto error;
298
299         rc = get_certificates(kinfo);
300
301 error:
302         return rc;
303 }
304
305 /* verify the digital signature of the file described by 'fdesc' */
306 int verify_digsig(struct filedesc *fdesc)
307 {
308         int res, fd;
309
310         assert ((fdesc->flags & flag_signature) != 0);
311         DEBUG("-- checking file %s",fdesc->name);
312
313         /* reset the flags */
314         file_clear_flags();
315         clear_certificates();
316
317         /* reads and xml parses the signature file */
318         fd = openat(workdirfd, fdesc->name, O_RDONLY);
319         if (fd < 0) {
320                 ERROR("cant't open file %s", fdesc->name);
321                 return -1;
322         }
323         document = xmlReadFd(fd, fdesc->name, NULL, 0);
324         close(fd);
325         if (document == NULL) {
326                 ERROR("xml parse of file %s failed", fdesc->name);
327                 return -1;
328         }
329
330         res = checkdocument();
331         if (res)
332                 ERROR("previous error was during check of file %s", fdesc->name);
333
334         xmlFreeDoc(document);
335         return res;
336 }
337
338 /* check all the signature files */
339 int check_all_signatures()
340 {
341         int rc, irc;
342         unsigned int i, n;
343         struct filedesc *fdesc;
344
345         n = signature_count();
346         rc = 0;
347         for (i = n ; i-- > 0 ; ) {
348                 fdesc = signature_of_index(i);
349                 irc = verify_digsig(fdesc);
350                 if (!irc)
351                         rc = irc;
352         }
353
354         return rc;
355 }
356
357 /* create a signature of 'index' (0 for author, other values for distributors)
358 using the private 'key' (filename) and the certificates 'certs' (filenames)
359 as trusted chain */
360 int create_digsig(unsigned int index, const char *key, const char **certs)
361 {
362         struct filedesc *fdesc;
363         xmlDocPtr doc;
364         int rc, fd;
365         long len;
366         xmlSaveCtxtPtr ctx;
367
368         rc = -1;
369
370         /* create the doc */
371         doc = xmlsec_create(index, key, certs);
372         if (doc == NULL)
373                 goto error;
374
375         /* instanciate the filename */
376         fdesc = create_signature(index);
377         if (fdesc == NULL)
378                 goto error2;
379
380         /* save the doc as file */
381         fd = openat(workdirfd, fdesc->name, O_WRONLY|O_CREAT|O_TRUNC, 0644);
382         if (fd < 0) {
383                 ERROR("cant open %s for write", fdesc->name);
384                 goto error2;
385         }
386         ctx = xmlSaveToFd(fd, NULL, XML_SAVE_FORMAT);
387         if (!ctx) {
388                 ERROR("xmlSaveToFd failed for %s", fdesc->name);
389                 goto error3;
390         }
391         len = xmlSaveDoc(ctx, doc);
392         if (len < 0) {
393                 ERROR("xmlSaveDoc to %s failed", fdesc->name);
394                 goto error4;
395         }
396
397         rc = 0;
398 error4:
399         xmlSaveClose(ctx);
400 error3:
401         close(fd);
402 error2:
403         xmlFreeDoc(doc);
404 error:
405         return rc;
406 }
407
408