Refactor ALLOW_NO_SIGNATURE compile flag
[src/app-framework-main.git] / src / wgtpkg-digsig.c
1 /*
2  Copyright (C) 2015-2020 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(int allow_none)
340 {
341         int rc, irc;
342         unsigned int i, n;
343         struct filedesc *fdesc;
344
345         n = signature_count();
346         if (n == 0) {
347                 if (!allow_none) {
348                         ERROR("no signature found");
349                         return -1;
350                 }
351                 return 0;
352         }
353
354         rc = xmlsec_init();
355         if (rc < 0) {
356                 ERROR("can't check signature");
357                 return rc;
358         }
359
360         rc = 0;
361         for (i = n ; i ; ) {
362                 fdesc = signature_of_index(--i);
363                 irc = verify_digsig(fdesc);
364                 if (irc < 0)
365                         rc = irc;
366         }
367
368         return rc;
369 }
370
371 /* create a signature of 'index' (0 for author, other values for distributors)
372 using the private 'key' (filename) and the certificates 'certs' (filenames)
373 as trusted chain */
374 int create_digsig(unsigned int index, const char *key, const char **certs)
375 {
376         struct filedesc *fdesc;
377         xmlDocPtr doc;
378         int rc, fd;
379         long len;
380         xmlSaveCtxtPtr ctx;
381
382         rc = -1;
383
384         /* create the doc */
385         doc = xmlsec_create(index, key, certs);
386         if (doc == NULL)
387                 goto error;
388
389         /* instanciate the filename */
390         fdesc = create_signature(index);
391         if (fdesc == NULL)
392                 goto error2;
393
394         /* save the doc as file */
395         fd = openat(workdirfd, fdesc->name, O_WRONLY|O_CREAT|O_TRUNC, 0644);
396         if (fd < 0) {
397                 ERROR("cant open %s for write", fdesc->name);
398                 goto error2;
399         }
400         ctx = xmlSaveToFd(fd, NULL, XML_SAVE_FORMAT);
401         if (!ctx) {
402                 ERROR("xmlSaveToFd failed for %s", fdesc->name);
403                 goto error3;
404         }
405         len = xmlSaveDoc(ctx, doc);
406         if (len < 0) {
407                 ERROR("xmlSaveDoc to %s failed", fdesc->name);
408                 goto error4;
409         }
410
411         rc = 0;
412 error4:
413         xmlSaveClose(ctx);
414 error3:
415         close(fd);
416 error2:
417         xmlFreeDoc(doc);
418 error:
419         return rc;
420 }
421
422