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