d8ec58eb382005f58be5feaabb9903be39be30c8
[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 && !allow_none) {
347                 ERROR("no signature found");
348                 return -1;
349         }
350         rc = 0;
351         for (i = n ; i-- > 0 ; ) {
352                 fdesc = signature_of_index(i);
353                 irc = verify_digsig(fdesc);
354                 if (irc < 0)
355                         rc = irc;
356         }
357
358         return rc;
359 }
360
361 /* create a signature of 'index' (0 for author, other values for distributors)
362 using the private 'key' (filename) and the certificates 'certs' (filenames)
363 as trusted chain */
364 int create_digsig(unsigned int index, const char *key, const char **certs)
365 {
366         struct filedesc *fdesc;
367         xmlDocPtr doc;
368         int rc, fd;
369         long len;
370         xmlSaveCtxtPtr ctx;
371
372         rc = -1;
373
374         /* create the doc */
375         doc = xmlsec_create(index, key, certs);
376         if (doc == NULL)
377                 goto error;
378
379         /* instanciate the filename */
380         fdesc = create_signature(index);
381         if (fdesc == NULL)
382                 goto error2;
383
384         /* save the doc as file */
385         fd = openat(workdirfd, fdesc->name, O_WRONLY|O_CREAT|O_TRUNC, 0644);
386         if (fd < 0) {
387                 ERROR("cant open %s for write", fdesc->name);
388                 goto error2;
389         }
390         ctx = xmlSaveToFd(fd, NULL, XML_SAVE_FORMAT);
391         if (!ctx) {
392                 ERROR("xmlSaveToFd failed for %s", fdesc->name);
393                 goto error3;
394         }
395         len = xmlSaveDoc(ctx, doc);
396         if (len < 0) {
397                 ERROR("xmlSaveDoc to %s failed", fdesc->name);
398                 goto error4;
399         }
400
401         rc = 0;
402 error4:
403         xmlSaveClose(ctx);
404 error3:
405         close(fd);
406 error2:
407         xmlFreeDoc(doc);
408 error:
409         return rc;
410 }
411
412