diff options
-rw-r--r-- | LICENSE.txt | 202 | ||||
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | wgtpkg-base64.c | 203 | ||||
-rw-r--r-- | wgtpkg-certs.c | 74 | ||||
-rw-r--r-- | wgtpkg-digsig.c | 336 | ||||
-rw-r--r-- | wgtpkg-files.c | 312 | ||||
-rw-r--r-- | wgtpkg-install.c | 82 | ||||
-rw-r--r-- | wgtpkg-pack.c | 156 | ||||
-rw-r--r-- | wgtpkg-sign.c | 195 | ||||
-rw-r--r-- | wgtpkg-workdir.c | 166 | ||||
-rw-r--r-- | wgtpkg-xmlsec.c | 352 | ||||
-rw-r--r-- | wgtpkg-zip.c | 317 | ||||
-rw-r--r-- | wgtpkg.h | 115 |
13 files changed, 2539 insertions, 0 deletions
diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..521bc19 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: all + +all: wgtpkg-install wgtpkg-pack wgtpkg-sign + +O = -DPREDIR='""' + +INCS = wgtpkg.h + +COMSRCS = \ + wgtpkg-base64.c \ + wgtpkg-certs.c \ + wgtpkg-digsig.c \ + wgtpkg-files.c \ + wgtpkg-workdir.c \ + wgtpkg-xmlsec.c \ + wgtpkg-zip.c + + +INSTALLSRCS = wgtpkg-install.c $(COMSRCS) + +PACKSRCS = wgtpkg-install.c $(COMSRCS) + +XMLSECOPT = $(shell pkg-config --cflags --libs xmlsec1) + +wgtpkg-%: wgtpkg-%.c $(COMSRCS) $(INCS) + gcc $O -g -o wgtpkg-$* wgtpkg-$*.c $(COMSRCS) -lzip -lxml2 -lcrypto $(XMLSECOPT) -Wall -Wno-pointer-sign + + + diff --git a/wgtpkg-base64.c b/wgtpkg-base64.c new file mode 100644 index 0000000..5cf6f8a --- /dev/null +++ b/wgtpkg-base64.c @@ -0,0 +1,203 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "wgtpkg.h" + +static char tob64(char x) +{ + if (x < 26) + return 'A' + x; + if (x < 52) + return 'a' + x - 26; + if (x < 62) + return '0' + x - 52; + return x == 62 ? '+' : '/'; +} + +char *base64encw(const char *buffer, int length, int width) +{ + int remain, in, out; + char *result; + + if (width == 0 || width % 4) { + syslog(LOG_ERR, "bad width in base64enc"); + return NULL; + } + result = malloc(2 + 4 * ((length + 2) / 3) + (length / width)); + if (result == NULL) { + syslog(LOG_ERR, "malloc failed in base64enc"); + return NULL; + } + in = out = 0; + remain = length; + while (remain >= 3) { + if (out % (width + 1) == width) + result[out++] = '\n'; + result[out] = tob64((buffer[in] >> 2) & '\x3f'); + result[out+1] = tob64(((buffer[in] << 4) & '\x30') | ((buffer[in+1] >> 4) & '\x0f')); + result[out+2] = tob64(((buffer[in+1] << 2) & '\x3c') | ((buffer[in+2] >> 6) & '\x03')); + result[out+3] = tob64(buffer[in+2] & '\x3f'); + remain -= 3; + in += 3; + out += 4; + } + if (remain != 0) { + if (out % (width + 1) == width) + result[out++] = '\n'; + result[out] = tob64((buffer[in] >> 2) & '\x3f'); + if (remain == 1) { + result[out+1] = tob64((buffer[in] << 4) & '\x30'); + result[out+2] = '='; + } else { + result[out+1] = tob64(((buffer[in] << 4) & '\x30') | ((buffer[in+1] >> 4) & '\x0f')); + result[out+2] = tob64((buffer[in+1] << 2) & '\x3c'); + } + result[out+3] = '='; + out += 4; + } + result[out] = 0; + return result; +} + +char *base64enc(const char *buffer, int length) +{ + return base64encw(buffer, length, 76); +} + +static char fromb64(char x) +{ + if ('A' <= x && x <= 'Z') + return x - 'A'; + if ('a' <= x && x <= 'z') + return x - 'a' + 26; + if ('0' <= x && x <= '9') + return x - '0' + 52; + if (x == '+') + return 62; + if (x == '/') + return 63; + if (x == '=') + return '@'; + return 'E'; +} + +int base64dec(const char *buffer, char **output) +{ + int len, in, out; + char *result; + unsigned char x0, x1, x2, x3; + + len = strlen(buffer); + result = malloc(3 * ((3 + len) / 4)); + if (result == NULL) { + syslog(LOG_ERR, "malloc failed in base64dec"); + return -1; + } + in = out = 0; + while (buffer[in] == '\r' || buffer[in] == '\n') + in++; + while (buffer[in]) { + if (in + 4 > len) { + syslog(LOG_ERR, "unexpected input size in base64dec"); + free(result); + return -1; + } + x0 = (unsigned char)fromb64(buffer[in]); + x1 = (unsigned char)fromb64(buffer[in+1]); + x2 = (unsigned char)fromb64(buffer[in+2]); + x3 = (unsigned char)fromb64(buffer[in+3]); + in += 4; + if (x0 == 'E' || x1 == 'E' || x2 == 'E' || x3 == 'E') { + syslog(LOG_ERR, "unexpected input character in base64dec"); + free(result); + return -1; + } + if (x0 == '@' || x1 == '@' || (x2 == '@' && x3 != '@')) { + syslog(LOG_ERR, "unexpected termination character in base64dec"); + free(result); + return -1; + } + result[out] = (char)((x0 << 2) | (x1 >> 4)); + result[out+1] = (char)((x1 << 4) | ((x2 >> 2) & 15)); + result[out+2] = (char)((x2 << 6) | (x3 & 63)); + while (buffer[in] == '\r' || buffer[in] == '\n') + in++; + if (x3 != '@') + out += 3; + else if (!buffer[in]) + out += 1 + (x2 != '@'); + else { + syslog(LOG_ERR, "unexpected continuation in base64dec"); + free(result); + return -1; + } + } + *output = result; + return out; +} + +int base64eq(const char *buf1, const char *buf2) +{ + for(;;) { + while(*buf1 == '\n' || *buf1 == '\r') + buf1++; + while(*buf2 == '\n' || *buf2 == '\r') + buf2++; + if (*buf1 != *buf2) + return 0; + if (!*buf1) + return 1; + buf1++; + buf2++; + } +} + +#ifdef TESTBASE64 +#include <fcntl.h> +#include <string.h> + +int main(int ac, char **av) +{ + char buffer[32768]; + int fd, l0, l1, l2; + char *p1, *p2; + + while(*++av) { + fd = open(*av, O_RDONLY); + if (fd < 0) continue; + l0 = read(fd, buffer, sizeof buffer); + if (l0 <= 0) continue; + close(fd); + p1 = base64enc(buffer, l0); + if (!p1) continue; + l1 = strlen(p1); + l2 = base64dec(p1, &p2); + if (l2 <= 0) continue; +printf("[[[%.*s]]]\n",l2,p2); + if (l0 != l2) printf("length mismatch\n"); + else if (memcmp(buffer, p2, l0)) printf("content mismatch\n"); + free(p1); + free(p2); + } + return 0; +} + +#endif diff --git a/wgtpkg-certs.c b/wgtpkg-certs.c new file mode 100644 index 0000000..c103c51 --- /dev/null +++ b/wgtpkg-certs.c @@ -0,0 +1,74 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <syslog.h> +#include <openssl/x509.h> + +#include "wgtpkg.h" + +struct x509l { + int count; + X509 **certs; +}; + +static struct x509l certificates = { .count = 0, .certs = NULL }; + +static int add_certificate_x509(X509 *x) +{ + X509 **p = realloc(certificates.certs, (certificates.count + 1) * sizeof(X509*)); + if (!p) { + syslog(LOG_ERR, "reallocation failed for certificate"); + return -1; + } + certificates.certs = p; + p[certificates.count++] = x; + return 0; +} + +static int add_certificate_bin(const char *bin, int len) +{ + int rc; + const unsigned char *b = (const unsigned char *)bin; + X509 *x = d2i_X509(NULL, &b, len); + if (x == NULL) { + syslog(LOG_ERR, "d2i_X509 failed"); + return -1; + } + rc = add_certificate_x509(x); + if (rc) + X509_free(x); + return rc; +} + +int add_certificate_b64(const char *b64) +{ + char *d; + int l = base64dec(b64, &d); + if (l > 0) { + l = add_certificate_bin(d, l); + free(d); + } + return l; +} + +void clear_certificates() +{ + while(certificates.count) + X509_free(certificates.certs[--certificates.count]); +} + + diff --git a/wgtpkg-digsig.c b/wgtpkg-digsig.c new file mode 100644 index 0000000..96f4280 --- /dev/null +++ b/wgtpkg-digsig.c @@ -0,0 +1,336 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <string.h> +#include <syslog.h> +#include <assert.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/uri.h> + + +#include "wgtpkg.h" + + + +static const char uri_role_author[] = "http://www.w3.org/ns/widgets-digsig#role-author"; +static const char uri_role_distributor[] = "http://www.w3.org/ns/widgets-digsig#role-distributor"; +static const char uri_profile[] = "http://www.w3.org/ns/widgets-digsig#profile"; + + +/* global data */ +static xmlDocPtr document; /* the document */ + +/* facility to get the first element node (skip text nodes) starting with 'node' */ +static xmlNodePtr next_element(xmlNodePtr node) +{ + while (node && node->type != XML_ELEMENT_NODE) + node = node->next; + return node; +} + +/* is the 'node' an element node of 'name'? */ +static int is_element(xmlNodePtr node, const char *name) +{ + return node->type == XML_ELEMENT_NODE + && !strcmp(name, node->name); +} + +/* is the 'node' an element node of 'name'? */ +static int is_node(xmlNodePtr node, const char *name) +{ + return node != NULL && is_element(node, name); +} + +#if 0 +/* facility to get the first element node (skip text nodes) starting with 'node' */ +static xmlNodePtr next_element_type(xmlNodePtr node, const char *name) +{ + while (node && node->type != XML_ELEMENT_NODE && strcmp(name, node->name)) + node = node->next; + return node; +} + +/* search the element node of id. NOTE : not optimized at all */ +static xmlNodePtr search_id(const char *id) +{ + char *val; + xmlNodePtr iter, next; + xmlNodePtr result; + + result = NULL; + iter = xmlDocGetRootElement(document); + while (iter != NULL) { + val = xmlGetProp(iter, "Id"); + if (val != NULL && !strcmp(val, id)) { + if (result != NULL) { + syslog(LOG_ERR, "duplicated Id %s", id); + free(val); + return NULL; + } + result = iter; + } + free(val); + next = next_element(iter->children); + if (next == NULL) { + /* no child, try sibling */ + next = next_element(iter->next); + if (next == NULL) { + iter = iter->parent; + while (iter != NULL && next == NULL) { + next = next_element(iter->next); + iter = iter->parent; + } + } + } + iter = next; + } + if (result == NULL) + syslog(LOG_ERR, "node of Id '%s' not found", id); + return result; +} +#endif + +/* check the digest of one element */ +static int check_one_reference(xmlNodePtr ref) +{ + int rc; + char *uri; + xmlURIPtr u; + struct filedesc *fdesc; + + /* start */ + rc = -1; + + /* get the uri */ + uri = xmlGetProp(ref, "URI"); + if (uri == NULL) { + syslog(LOG_ERR, "attribute URI of element <Reference> not found"); + goto error; + } + + /* parse the uri */ + u = xmlParseURI(uri); + if (u == NULL) { + syslog(LOG_ERR, "error while parsing URI %s", uri); + goto error2; + } + + if (u->scheme || u->opaque || u->authority || u->server || u->user || u->query) { + syslog(LOG_ERR, "unexpected uri component in %s", uri); + goto error3; + } + + if (u->path && u->fragment) { + syslog(LOG_ERR, "not allowed to sign foreign fragment in %s", uri); + goto error3; + } + + if (u->path) { + fdesc = file_of_name(u->path); + if (fdesc == NULL) { + syslog(LOG_ERR, "reference to unknown file %s", u->path); + goto error3; + } + if (fdesc->type != type_file) { + syslog(LOG_ERR, "reference to directory %s", u->path); + goto error3; + } + if ((fdesc->flags & flag_distributor_signature) != 0) { + syslog(LOG_ERR, "reference to signature %s", u->path); + goto error3; + } + fdesc->flags |= flag_referenced; + rc = 0; + } else { + rc = 0; + } + +error3: + xmlFreeURI(u); +error2: + xmlFree(uri); +error: + return rc; +} + +static int check_references(xmlNodePtr sinfo) +{ + xmlNodePtr elem; + + elem = sinfo->children; + while (elem != NULL) { + if (is_element(elem, "Reference")) + if (check_one_reference(elem)) + return -1; + elem = elem->next; + } + return 0; +} + +static int get_certificates(xmlNodePtr kinfo) +{ + xmlNodePtr n1, n2; + char *b; + int rc; + + n1 = kinfo->children; + while (n1) { + if (is_element(n1, "X509Data")) { + n2 = n1->children; + while (n2) { + if (is_element(n2, "X509Certificate")) { + b = xmlNodeGetContent(n2); + if (b == NULL) { + syslog(LOG_ERR, "xmlNodeGetContent of X509Certificate failed"); + return -1; + } + rc = add_certificate_b64(b); + xmlFree(b); + if (rc) + return rc; + } + n2 = n2->next; + } + } + n1 = n1->next; + } + return 0; +} + +static int checkdocument() +{ + int rc; + xmlNodePtr sinfo, svalue, kinfo, objs, rootsig; + + rc = -1; + + rootsig = xmlDocGetRootElement(document); + if (!is_node(rootsig, "Signature")) { + syslog(LOG_ERR, "root element <Signature> not found"); + goto error; + } + + sinfo = next_element(rootsig->children); + if (!is_node(sinfo, "SignedInfo")) { + syslog(LOG_ERR, "element <SignedInfo> not found"); + goto error; + } + + svalue = next_element(sinfo->next); + if (!is_node(svalue, "SignatureValue")) { + syslog(LOG_ERR, "element <SignatureValue> not found"); + goto error; + } + + kinfo = next_element(svalue->next); + if (is_node(kinfo, "KeyInfo")) { + objs = kinfo->next; + } else { + objs = kinfo; + kinfo = NULL; + } + + rc = check_references(sinfo); + if (rc) + goto error; + + rc = xmlsec_verify(rootsig); + if (rc) + goto error; + + rc = get_certificates(kinfo); + +error: + return rc; +} + +int verify_digsig(struct filedesc *fdesc) +{ + int res; + + assert ((fdesc->flags & flag_signature) != 0); +printf("\n\nchecking file %s\n\n",fdesc->name); + + /* reset the flags */ + file_clear_flags(); + clear_certificates(); + + /* reads and xml parses the signature file */ + document = xmlReadFile(fdesc->name, NULL, 0); + if (document == NULL) { + syslog(LOG_ERR, "xml parse of file %s failed", fdesc->name); + return -1; + } + + res = checkdocument(); + if (res) + syslog(LOG_ERR, "previous error was during check of file %s", fdesc->name); + + xmlFreeDoc(document); + return res; +} + +int check_all_signatures() +{ + int rc, irc; + unsigned int i, n; + struct filedesc *fdesc; + + n = signature_count(); + rc = 0; + for (i = n ; i-- > 0 ; ) { + fdesc = signature_of_index(i); + assert ((fdesc->flags & flag_signature) != 0); + irc = verify_digsig(fdesc); + if (!irc) + rc = irc; + } + + return rc; +} + +int create_digsig(int index, const char *key, const char **certs) +{ + struct filedesc *fdesc; + xmlDocPtr doc; + int rc, len; + + rc = -1; + doc = xmlsec_create(index, key, certs); + if (doc == NULL) + goto error; + + fdesc = create_signature(index); + if (fdesc == NULL) + goto error2; + + len = xmlSaveFormatFileEnc(fdesc->name, doc, NULL, 0); + if (len < 0) { + syslog(LOG_ERR, "xmlSaveFormatFileEnc to %s failed", fdesc->name); + goto error2; + } + + rc = 0; +error2: + xmlFreeDoc(doc); +error: + return rc; +} + + diff --git a/wgtpkg-files.c b/wgtpkg-files.c new file mode 100644 index 0000000..17e909a --- /dev/null +++ b/wgtpkg-files.c @@ -0,0 +1,312 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#define _GNU_SOURCE +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <syslog.h> +#include <dirent.h> +#include <stdio.h> + +#include "wgtpkg.h" + +struct fdb { + unsigned int count; + struct filedesc **files; +}; + +static struct fdb allfiles = { .count = 0, .files = NULL }; +static struct fdb allsignatures = { .count = 0, .files = NULL }; + +static const char author_file[] = "author-signature.xml"; +static const char distributor_file_prefix[] = "signature"; +static const char distributor_file_suffix[] = ".xml"; + +static unsigned int what_signature(const char *name) +{ + unsigned int len, id, nid; + + if (!strcmp(name, author_file)) + return UINT_MAX; + + len = sizeof(distributor_file_prefix)-1; + if (memcmp(name, distributor_file_prefix, len)) + return 0; + if (name[len] <= '0' || name[len] > '9') + return 0; + id = (unsigned int)(name[len++] - '0'); + while ('0' <= name[len] && name[len] <= '9') { + nid = 10 * id + (unsigned int)(name[len++] - '0'); + if (nid < id || nid == UINT_MAX) { + syslog(LOG_WARNING, "number too big for %s", name); + return 0; + } + id = nid; + } + if (strcmp(name+len, distributor_file_suffix)) + return 0; + + return id; +} + +static struct filedesc *get_filedesc(const char *name, int create) +{ + int cmp; + unsigned int low, up, mid, sig; + struct filedesc *result, **grow; + + /* search */ + low = 0; + up = allfiles.count; + while(low < up) { + mid = (low + up) >> 1; + result = allfiles.files[mid]; + cmp = strcmp(result->name, name); + if (!cmp) + return result; /* found */ + if (cmp > 0) + up = mid; + else + low = mid + 1; + } + + /* not found, can create ? */ + if (!create) + return NULL; + + sig = what_signature(name); + + /* allocations */ + grow = realloc(allfiles.files, (allfiles.count + 1) * sizeof(struct filedesc *)); + if (grow == NULL) { + syslog(LOG_ERR, "realloc failed in get_filedesc"); + return NULL; + } + allfiles.files = grow; + + if (sig) { + grow = realloc(allsignatures.files, (allsignatures.count + 1) * sizeof(struct filedesc *)); + if (grow == NULL) { + syslog(LOG_ERR, "second realloc failed in get_filedesc"); + return NULL; + } + allsignatures.files = grow; + } + + result = malloc(sizeof(struct filedesc) + strlen(name)); + if (!result) { + syslog(LOG_ERR, "calloc failed in get_filedesc"); + return NULL; + } + + /* initialisation */ + result->type = type_unset; + result->flags = sig == 0 ? 0 : sig == UINT_MAX ? flag_author_signature : flag_distributor_signature; + result->zindex = 0; + result->signum = sig; + strcpy(result->name, name); + + /* insertion */ + if (low < allfiles.count) + memmove(allfiles.files+low+1, allfiles.files+low, (allfiles.count - low) * sizeof(struct filedesc *)); + allfiles.files[low] = result; + allfiles.count++; + if (sig) { + for (low = 0 ; low < allsignatures.count && sig > allsignatures.files[low]->signum ; low++); + if (low < allsignatures.count) + memmove(allsignatures.files+low+1, allsignatures.files+low, (allsignatures.count - low) * sizeof(struct filedesc *)); + allsignatures.files[low] = result; + allsignatures.count++; + } + + return result; +} + + +static struct filedesc *file_add(const char *name, enum entrytype type) +{ + struct filedesc *desc; + + desc = get_filedesc(name, 1); + if (!desc) + errno = ENOMEM; + else if (desc->type == type_unset) + desc->type = type; + else { + syslog(LOG_ERR, "redeclaration of %s in file_add", name); + errno = EEXIST; + desc = NULL; + } + return desc; +} + +void file_reset() +{ + unsigned int i; + + allsignatures.count = 0; + for (i = 0 ; i < allfiles.count ; i++) + free(allfiles.files[i]); + allfiles.count = 0; +} + +unsigned int file_count() +{ + return allfiles.count; +} + +struct filedesc *file_of_index(unsigned int index) +{ + assert(index < allfiles.count); + return allfiles.files[index]; +} + +struct filedesc *file_of_name(const char *name) +{ + return get_filedesc(name, 0); +} + +struct filedesc *file_add_directory(const char *name) +{ + return file_add(name, type_directory); +} + +struct filedesc *file_add_file(const char *name) +{ + return file_add(name, type_file); +} + +unsigned int signature_count() +{ + return allsignatures.count; +} + +struct filedesc *signature_of_index(unsigned int index) +{ + assert(index < allsignatures.count); + return allsignatures.files[index]; +} + +struct filedesc *get_signature(unsigned int number) +{ + unsigned int idx; + + if (number == 0) + number = UINT_MAX; + for (idx = 0 ; idx < allsignatures.count ; idx++) + if (allsignatures.files[idx]->signum == number) + return allsignatures.files[idx]; + return NULL; +} + +struct filedesc *create_signature(unsigned int number) +{ + struct filedesc *result; + char *name; + int len; + + result = NULL; + if (number == 0 || number == UINT_MAX) + len = asprintf(&name, "%s", author_file); + else + len = asprintf(&name, "%s%u%s", distributor_file_prefix, number, distributor_file_suffix); + + if (len < 0) + syslog(LOG_ERR, "asprintf failed in create_signature"); + else { + assert(len > 0); + result = file_of_name(name); + if (result == NULL) + result = file_add_file(name); + free(name); + } + + return result; +} + +void file_clear_flags() +{ + unsigned int i; + for (i = 0 ; i < allfiles.count ; i++) + allfiles.files[i]->flags &= flag_signature; +} + +static int fill_files_rec(char name[PATH_MAX], int offset) +{ + int len, err; + DIR *dir; + struct dirent *ent; + + if (offset == 0) + dir = opendir("."); + else { + dir = opendir(name); + name[offset++] = '/'; + } + if (!dir) { + syslog(LOG_ERR, "opendir %.*s failed in zwr", offset, name); + return -1; + } + + ent = readdir(dir); + while (ent != NULL) { + len = strlen(ent->d_name); + if (ent->d_name[0] == '.' && (len == 1 || + (ent->d_name[1] == '.' && len == 2))) + ; + else if (offset + len >= PATH_MAX) { + closedir(dir); + syslog(LOG_ERR, "name too long in fill_files_rec"); + errno = ENAMETOOLONG; + return -1; + } else { + memcpy(name + offset, ent->d_name, 1+len); + switch (ent->d_type) { + case DT_DIR: + if (file_add_directory(name) == NULL) { + closedir(dir); + return -1; + } + err = fill_files_rec(name, offset + len); + if (err) { + closedir(dir); + return err; + } + break; + case DT_REG: + if (file_add_file(name) == NULL) { + closedir(dir); + return -1; + } + break; + default: + break; + } + } + ent = readdir(dir); + } + + closedir(dir); + return 0; +} + +int fill_files() +{ + char name[PATH_MAX]; + return fill_files_rec(name, 0); +} + diff --git a/wgtpkg-install.c b/wgtpkg-install.c new file mode 100644 index 0000000..7781c62 --- /dev/null +++ b/wgtpkg-install.c @@ -0,0 +1,82 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _BSD_SOURCE /* see readdir */ + +#include <stdlib.h> +#include <stdio.h> +#include <dirent.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <syslog.h> + +#include "wgtpkg.h" + +/* install the widget of the file */ +static void install(const char *wgtfile) +{ +printf("\n\nINSTALLING widget %s\n", wgtfile); + + if (enter_workdir(1)) + goto error; + + if (zread(wgtfile, 0)) + goto error; + + if (check_all_signatures()) + goto error; + + return; + +error: + return; + exit(1); +} + +/* install the widgets of the list */ +int main(int ac, char **av) +{ + int i, kwd; + + openlog("wgtpkg-install", LOG_PERROR, LOG_AUTH); + + xmlsec_init(); + + /* canonic names for files */ + for (i = 1 ; av[i] != NULL ; i++) + if ((av[i] = realpath(av[i], NULL)) == NULL) { + syslog(LOG_ERR, "error while getting realpath of %dth argument", i); + return 1; + } + + /* workdir */ + kwd = 1; + if (make_workdir(kwd)) { + syslog(LOG_ERR, "failed to create a working directory"); + return 1; + } + if (!kwd) + atexit(remove_workdir); + + /* install widgets */ + for (av++ ; *av ; av++) + install(*av); + + exit(0); + return 0; +} + diff --git a/wgtpkg-pack.c b/wgtpkg-pack.c new file mode 100644 index 0000000..9164447 --- /dev/null +++ b/wgtpkg-pack.c @@ -0,0 +1,156 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <syslog.h> +#include <getopt.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "wgtpkg.h" + +#if !defined(MAXCERT) +#define MAXCERT 20 +#endif +#if !defined(DEFAULT_KEY_FILE) +#define DEFAULT_KEY_FILE "key.pem" +#endif +#if !defined(DEFAULT_CERT_FILE) +#define DEFAULT_CERT_FILE "cert.pem" +#endif + +const char appname[] = "wgtpkg-pack"; + +static void usage() +{ + printf( + "usage: %s [-f] [-o wgtfile] directory\n" + "\n" + " -o wgtfile the output widget file\n" + " -f force overwriting\n" + "\n", + appname + ); +} + +static struct option options[] = { + { "output", required_argument, NULL, 'o' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } +}; + +/* install the widgets of the list */ +int main(int ac, char **av) +{ + int i, force; + char *wgtfile, *directory, *x; + struct stat s; + + openlog(appname, LOG_PERROR, LOG_USER); + + force = 0; + wgtfile = directory = NULL; + for (;;) { + i = getopt_long(ac, av, "hfo:", options, NULL); + if (i < 0) + break; + switch (i) { + case 'o': + wgtfile = optarg; + break; + case 'f': + force = 1; + break; + case 'h': + usage(); + return 0; + case ':': + syslog(LOG_ERR, "missing argument"); + return 1; + default: + syslog(LOG_ERR, "unrecognized option"); + return 1; + } + } + + /* remaining arguments and final checks */ + if (optind >= ac) { + syslog(LOG_ERR, "no directory set"); + return 1; + } + directory = av[optind++]; + if (optind < ac) { + syslog(LOG_ERR, "extra parameters found"); + return 1; + } + + /* set default values */ + if (wgtfile == NULL && 0 > asprintf(&wgtfile, "%s.wgt", directory)) { + syslog(LOG_ERR, "asprintf failed"); + return 1; + } + + /* check values */ + if (stat(directory, &s)) { + syslog(LOG_ERR, "can't find directory %s", directory); + return 1; + } + if (!S_ISDIR(s.st_mode)) { + syslog(LOG_ERR, "%s isn't a directory", directory); + return 1; + } + if (access(wgtfile, F_OK) == 0 && force == 0) { + syslog(LOG_ERR, "can't overwrite existing %s", wgtfile); + return 1; + } + +printf("\n\nPACKING widget %s from directory %s\n", wgtfile, directory); + + /* creates an existing widget (for realpath it must exist) */ + i = open(wgtfile, O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666); + if (i < 0) { + syslog(LOG_ERR, "can't write widget %s", wgtfile); + return 1; + } + close(i); + + /* compute absolutes paths */ + x = realpath(wgtfile, NULL); + if (x == NULL) { + syslog(LOG_ERR, "realpath failed for %s",wgtfile); + return 1; + } + wgtfile = x; + + /* set and enter the workdir */ + if (set_workdir(directory, 0) || enter_workdir(0)) + return 1; + + + if (fill_files()) + return 1; + + return !!zwrite(wgtfile); +} + + diff --git a/wgtpkg-sign.c b/wgtpkg-sign.c new file mode 100644 index 0000000..6a6a72a --- /dev/null +++ b/wgtpkg-sign.c @@ -0,0 +1,195 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <syslog.h> +#include <getopt.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "wgtpkg.h" + +#if !defined(MAXCERT) +#define MAXCERT 20 +#endif +#if !defined(DEFAULT_KEY_FILE) +#define DEFAULT_KEY_FILE "key.pem" +#endif +#if !defined(DEFAULT_CERT_FILE) +#define DEFAULT_CERT_FILE "cert.pem" +#endif + +const char appname[] = "wgtpkg-sign"; + +static unsigned int get_number(const char *value) +{ + char *end; + unsigned long int val; + + val = strtoul(value, &end, 10); + if (*end || 0 == val || val >= UINT_MAX || *value == '-') { + syslog(LOG_ERR, "bad number value %s", value); + exit(1); + } + return (unsigned int)val; +} + +static void usage() +{ + printf( + "usage: %s [-f] [-k keyfile] [-c certfile]... [-o wgtfile] [-d number | -a] directory\n" + "\n" + " -k keyfile the private key to use for author signing\n" + " -c certfile the certificate(s) to use for author signing\n" + " -d number the number of the distributor signature (zero for automatic)\n" + " -a the author signature\n" + " -f force overwriting\n" + "\n", + appname + ); +} + +static struct option options[] = { + { "key", required_argument, NULL, 'k' }, + { "certificate", required_argument, NULL, 'c' }, + { "distributor", required_argument, NULL, 'd' }, + { "author", no_argument, NULL, 'a' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } +}; + +/* install the widgets of the list */ +int main(int ac, char **av) +{ + int i, force, ncert, author; + unsigned int number; + char *keyfile, *certfiles[MAXCERT+1], *directory, **x; + struct stat s; + + openlog(appname, LOG_PERROR, LOG_USER); + + force = ncert = author = 0; + number = UINT_MAX; + keyfile = directory = NULL; + for (;;) { + i = getopt_long(ac, av, "hfak:c:d:", options, NULL); + if (i < 0) + break; + switch (i) { + case 'c': + if (ncert == MAXCERT) { + syslog(LOG_ERR, "maximum count of certificates reached"); + return 1; + } + certfiles[ncert++] = optarg; + continue; + case 'k': x = &keyfile; break; + case 'd': number = get_number(optarg); continue; + case 'f': force = 1; continue; + case 'a': author = 1; continue; + case 'h': usage(); return 0; + case ':': + syslog(LOG_ERR, "missing argument"); + return 1; + default: + syslog(LOG_ERR, "unrecognized option"); + return 1; + } + if (*x != NULL) { + syslog(LOG_ERR, "option set twice"); + return 1; + } + *x = optarg; + } + + /* remaining arguments and final checks */ + if (optind >= ac) { + syslog(LOG_ERR, "no directory set"); + return 1; + } + directory = av[optind++]; + if (optind < ac) { + syslog(LOG_ERR, "extra parameters found"); + return 1; + } + + /* set default values */ + if (keyfile == NULL) + keyfile = DEFAULT_KEY_FILE; + if (ncert == 0) + certfiles[ncert++] = DEFAULT_CERT_FILE; + + /* check values */ + if (stat(directory, &s)) { + syslog(LOG_ERR, "can't find directory %s", directory); + return 1; + } + if (!S_ISDIR(s.st_mode)) { + syslog(LOG_ERR, "%s isn't a directory", directory); + return 1; + } + if (access(keyfile, R_OK) != 0) { + syslog(LOG_ERR, "can't access private key %s", keyfile); + return 1; + } + for(i = 0 ; i < ncert ; i++) + if (access(certfiles[i], R_OK) != 0) { + syslog(LOG_ERR, "can't access certificate %s", certfiles[i]); + return 1; + } + + /* init xmlsec module */ + if (xmlsec_init()) + return 1; + + + /* compute absolutes paths */ +#define rp(x) do { char *p = realpath(x, NULL); if (p != NULL) x = p; else { syslog(LOG_ERR, "realpath failed for %s",x); return 1; } } while(0) + rp(keyfile); + for(i = 0 ; i < ncert ; i++) + rp(certfiles[i]); +#undef rp + + /* set and enter the workdir */ + if (set_workdir(directory, 0) || enter_workdir(0)) + return 1; + + if (fill_files()) + return 1; + + if (author) + number = 0; + else if (number == UINT_MAX) + for (number = 1; get_signature(number) != NULL ; number++); + + if (!force && get_signature(number) != NULL) { + syslog(LOG_ERR, "can't overwrite existing signature %s", get_signature(number)->name); + return 1; + } + +printf("\n\nSIGNING content of directory %s for number %u\n", directory, number); + + certfiles[ncert] = NULL; + return !!create_digsig(number, keyfile, (const char**)certfiles); +} + diff --git a/wgtpkg-workdir.c b/wgtpkg-workdir.c new file mode 100644 index 0000000..4b0b08c --- /dev/null +++ b/wgtpkg-workdir.c @@ -0,0 +1,166 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <syslog.h> +#include <errno.h> +#include <sys/stat.h> + +#include "wgtpkg.h" + +#ifndef PREDIR +#define PREDIR "/tmp/" +#endif + +static int mode = 0700; +static char workdir[PATH_MAX]; + +/* removes recursively the content of a directory */ +static int clean_dir() +{ + int cr; + DIR *dir; + struct dirent *ent; + struct { + struct dirent entry; + char spare[PATH_MAX]; + } entry; + + dir = opendir("."); + if (dir == NULL) { + syslog(LOG_ERR, "opendir failed in clean_dir"); + return -1; + } + + cr = -1; + for (;;) { + if (readdir_r(dir, &entry.entry, &ent) != 0) { + syslog(LOG_ERR, "readdir_r failed in clean_dir"); + goto error; + } + if (ent == NULL) + break; + if (ent->d_name[0] == '.' && (ent->d_name[1] == 0 + || (ent->d_name[1] == '.' && ent->d_name[2] == 0))) + continue; + cr = unlink(ent->d_name); + if (!cr) + continue; + if (errno != EISDIR) { + syslog(LOG_ERR, "unlink of %s failed in clean_dir", ent->d_name); + goto error; + } + if (chdir(ent->d_name)) { + syslog(LOG_ERR, "enter directory %s failed in clean_dir", ent->d_name); + goto error; + } + cr = clean_dir(); + if (cr) + goto error; + if (chdir("..")) + goto error; + cr = rmdir(ent->d_name); + if (cr) { + syslog(LOG_ERR, "rmdir of %s failed in clean_dir", ent->d_name); + goto error; + } + } + cr = 0; +error: + closedir(dir); + return cr; +} + +/* removes the content of the working directory */ +int enter_workdir(int clean) +{ + int rc = chdir(workdir); + if (rc) + syslog(LOG_ERR, "entring workdir %s failed", workdir); + else if (clean) + rc = clean_dir(); + return rc; +} + +/* removes the working directory */ +void remove_workdir() +{ + enter_workdir(1); + chdir(".."); + unlink(workdir); +} + +int set_workdir(const char *name, int create) +{ + int rc; + size_t length; + struct stat s; + + /* check the length */ + length = strlen(name); + if (length >= sizeof workdir) { + syslog(LOG_ERR, "workdir name too long"); + return -1; + } + + rc = stat(name, &s); + if (rc) { + if (!create) { + syslog(LOG_ERR, "no workdir %s", name); + return -1; + } + rc = mkdir(name, mode); + if (rc) { + syslog(LOG_ERR, "can't create workdir %s", name); + return -1; + } + + } else if (!S_ISDIR(s.st_mode)) { + syslog(LOG_ERR, "%s isn't a directory", name); + return -1; + } + memcpy(workdir, name, 1+length); + return 0; +} + +/* install the widgets of the list */ +int make_workdir(int reuse) +{ + int i; + + /* create a temporary directory */ + for (i = 0 ; ; i++) { + if (i == INT_MAX) { + syslog(LOG_ERR, "exhaustion of workdirs"); + return -1; + } + sprintf(workdir, PREDIR "PACK%d", i); + if (!mkdir(workdir, mode)) + break; + if (errno != EEXIST) { + syslog(LOG_ERR, "error in creation of workdir %s: %m", workdir); + return -1; + } + if (reuse) + break; + } + + return 0; +} + diff --git a/wgtpkg-xmlsec.c b/wgtpkg-xmlsec.c new file mode 100644 index 0000000..5c65217 --- /dev/null +++ b/wgtpkg-xmlsec.c @@ -0,0 +1,352 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <syslog.h> +#include <unistd.h> +#include <stdio.h> +#include <dirent.h> +#include <string.h> +#include <assert.h> + +#include <libxml/tree.h> +#include <xmlsec/xmlsec.h> +#include <xmlsec/xmltree.h> +#include <xmlsec/xmldsig.h> +#include <xmlsec/crypto.h> +#include <xmlsec/templates.h> +#include <xmlsec/errors.h> +#include <xmlsec/io.h> + + +#include "wgtpkg.h" + +static int initstatus; +static int initdone; +static xmlSecKeysMngrPtr keymgr; + +#ifndef CA_ROOT_DIRECTORY +#define CA_ROOT_DIRECTORY "./ca-certificates" +#endif + +static int file_match_cb(const char *uri) +{ + struct filedesc *fdesc = file_of_name(uri); + return fdesc != NULL && fdesc->type == type_file && (fdesc->flags & flag_distributor_signature) == 0; +} + +static void *file_open_cb(const char *file) +{ + struct filedesc *fdesc; + FILE *f; + + fdesc = file_of_name(file); + if (fdesc == NULL) { + syslog(LOG_ERR, "shouldn't open uri %s", file); + return NULL; + } + + f = fopen(file, "r"); + if (f == NULL) + syslog(LOG_ERR, "can't open file %s for reading", file); + else + fdesc->flags |= flag_opened; + + return f; +} + +static int file_read_cb(void *context, char *buffer, int len) +{ + size_t r = fread(buffer, 1, len, (FILE*)context); + return r ? (int)r : feof((FILE*)context) ? 0 : - 1; +} + +static int file_close_cb(void *context) +{ + return (int)fclose((FILE*)context); +} + +static void errors_cb(const char *file, int line, const char *func, const char *errorObject, const char *errorSubject, int reason, const char *msg) +{ + syslog(LOG_ERR, "xmlSec error %3d: %s (subject=\"%s\", object=\"%s\")", reason, msg, errorSubject ? errorSubject : "?", errorObject ? errorObject : "?"); +} + +static int fill_trusted_keys() +{ + int err; + DIR *dir; + struct dirent *ent; + char path[PATH_MAX], *e; + + e = stpcpy(path, CA_ROOT_DIRECTORY); + dir = opendir(path); + if (!dir) { + syslog(LOG_ERR, "opendir %s failed in fill_trusted_keys", path); + return -1; + } + + *e++ = '/'; + ent = readdir(dir); + while (ent != NULL) { + if (ent->d_type == DT_REG) { + strcpy(e, ent->d_name); + err = xmlSecCryptoAppKeysMngrCertLoad(keymgr, path, xmlSecKeyDataFormatPem, xmlSecKeyDataTypeTrusted); + if (err < 0) { + syslog(LOG_ERR, "xmlSecCryptoAppKeysMngrCertLoadMemory failed for %s", path); + closedir(dir); + return -1; + } + } + ent = readdir(dir); + } + + closedir(dir); + return 0; + +} + +int xmlsec_init() +{ + + if (initdone) + goto end; + + initdone = 1; + initstatus = -1; + + if(xmlSecInit() < 0) { + syslog(LOG_ERR, "xmlSecInit failed."); + goto end; + } + + if(xmlSecCryptoAppInit(NULL) < 0) { + syslog(LOG_ERR, "xmlSecCryptoAppInit failed."); + goto end; + } + + if(xmlSecCryptoInit() < 0) { + syslog(LOG_ERR, "xmlSecCryptoInit failed."); + goto end; + } + + xmlSecErrorsSetCallback(errors_cb); + + xmlSecIOCleanupCallbacks(); + if (xmlSecIORegisterCallbacks(file_match_cb, + file_open_cb, file_read_cb, file_close_cb)) { + syslog(LOG_ERR, "xmlSecIORegisterCallbacks failed."); + goto end; + } + + keymgr = xmlSecKeysMngrCreate(); + if (keymgr == NULL) { + syslog(LOG_ERR, "xmlSecKeysMngrCreate failed."); + goto end; + } + + if(xmlSecCryptoAppDefaultKeysMngrInit(keymgr) < 0) { + syslog(LOG_ERR, "xmlSecCryptoAppDefaultKeysMngrInit failed."); + goto end; + } + fill_trusted_keys(); + + initstatus = 0; +end: + return initstatus; +} + + +void xmlsec_shutdown() +{ + xmlSecKeysMngrDestroy(keymgr); + + xmlSecCryptoShutdown(); + + xmlSecCryptoAppShutdown(); + + xmlSecShutdown(); +} + +int xmlsec_verify(xmlNodePtr node) +{ + int rc; + xmlSecDSigCtxPtr dsigctx; + + assert(initdone && !initstatus); + + dsigctx = xmlSecDSigCtxCreate(keymgr); + if (dsigctx == NULL) { + syslog(LOG_ERR, "xmlSecDSigCtxCreate failed."); + rc = -1; + } else { + rc = xmlSecDSigCtxVerify(dsigctx, node); + if (rc) + syslog(LOG_ERR, "xmlSecDSigCtxVerify failed."); + else if (dsigctx->status != xmlSecDSigStatusSucceeded) { + syslog(LOG_ERR, "invalid signature."); + rc = -1; + } + xmlSecDSigCtxDestroy(dsigctx); + } + + return rc; +} + +static const struct { const char *id; const char *xml; } properties[2] = { + { + .id = "AuthorSignature", + .xml = + "<SignatureProperties xmlns:dsp=\"http://www.w3.org/2009/xmldsig-properties\">" + "<SignatureProperty Id=\"profile\" Target=\"#AuthorSignature\">" + "<dsp:Profile URI=\"http://www.w3.org/ns/widgets-digsig#profile\"></dsp:Profile>" + "</SignatureProperty>" + "<SignatureProperty Id=\"role\" Target=\"#AuthorSignature\">" + "<dsp:Role URI=\"http://www.w3.org/ns/widgets-digsig#role-author\"></dsp:Role>" + "</SignatureProperty>" + "<SignatureProperty Id=\"identifier\" Target=\"#AuthorSignature\">" + "<dsp:Identifier></dsp:Identifier>" + "</SignatureProperty>" + "</SignatureProperties>" + }, + { + .id = "DistributorSignature", + .xml = + "<SignatureProperties xmlns:dsp=\"http://www.w3.org/2009/xmldsig-properties\">" + "<SignatureProperty Id=\"profile\" Target=\"#DistributorSignature\">" + "<dsp:Profile URI=\"http://www.w3.org/ns/widgets-digsig#profile\"></dsp:Profile>" + "</SignatureProperty>" + "<SignatureProperty Id=\"role\" Target=\"#DistributorSignature\">" + "<dsp:Role URI=\"http://www.w3.org/ns/widgets-digsig#role-distributor\"></dsp:Role>" + "</SignatureProperty>" + "<SignatureProperty Id=\"identifier\" Target=\"#DistributorSignature\">" + "<dsp:Identifier></dsp:Identifier>" + "</SignatureProperty>" + "</SignatureProperties>" + } +}; + +xmlDocPtr xmlsec_create(int index, const char *key, const char **certs) +{ + unsigned int i, fc, mask; + struct filedesc *fdesc; + xmlNodePtr sign, obj, ref, kinfo, props; + xmlDocPtr doc; + int rc; + xmlSecDSigCtxPtr dsigctx; + + assert(initdone && !initstatus); + + /* create the document */ + doc = xmlNewDoc("1.0"); + if (doc == NULL) { + syslog(LOG_ERR, "xmlNewDoc failed"); + goto error; + } + + /* create the root signature node */ + sign = xmlSecTmplSignatureCreate(doc, xmlSecTransformInclC14N11Id, xmlSecTransformRsaSha256Id, properties[!!index].id); + if (sign == NULL) { + syslog(LOG_ERR, "xmlSecTmplSignatureCreate failed"); + goto error2; + } + xmlDocSetRootElement(doc, sign); + + /* create the object and its reference */ + obj = xmlSecTmplSignatureAddObject(sign, "prop", NULL, NULL); + if (obj == NULL) { + syslog(LOG_ERR, "xmlSecTmplSignatureAddObject failed"); + goto error2; + } + rc = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, properties[!!index].xml, &props); + if (rc) { + syslog(LOG_ERR, "xmlParseBalancedChunkMemory failed"); + goto error2; + } + if (NULL == xmlAddChild(obj, props)) { + syslog(LOG_ERR, "filling object node failed"); + xmlFreeNode(obj); + goto error2; + } + + /* create references to files */ + mask = index ? flag_distributor_signature : flag_signature; + fc = file_count(); + for (i = 0 ; i < fc ; i++) { + fdesc = file_of_index(i); + if (fdesc->type == type_file && (fdesc->flags & mask) == 0) { + ref = xmlSecTmplSignatureAddReference(sign, xmlSecTransformSha256Id, NULL, fdesc->name, NULL); + if (ref == NULL) { + syslog(LOG_ERR, "creation of reference to %s failed", fdesc->name); + goto error2; + } + } + } + + /* create reference to object having properties */ + ref = xmlSecTmplSignatureAddReference(sign, xmlSecTransformSha256Id, NULL, "#prop", NULL); + if (ref == NULL) { + syslog(LOG_ERR, "creation of reference to #prop failed"); + goto error2; + } + if (NULL == xmlSecTmplReferenceAddTransform(ref, xmlSecTransformInclC14N11Id)) { + syslog(LOG_ERR, "setting transform reference to #prop failed"); + goto error2; + } + + /* adds the X509 data */ + kinfo = xmlSecTmplSignatureEnsureKeyInfo(sign, NULL); + if (kinfo == NULL) { + syslog(LOG_ERR, "xmlSecTmplSignatureEnsureKeyInfo failed"); + goto error2; + } + if (NULL == xmlSecTmplKeyInfoAddX509Data(kinfo)) { + syslog(LOG_ERR, "xmlSecTmplKeyInfoAddX509Data failed"); + goto error2; + } + + /* sign now */ + dsigctx = xmlSecDSigCtxCreate(keymgr); + if (dsigctx == NULL) { + syslog(LOG_ERR, "xmlSecDSigCtxCreate failed."); + goto error3; + } + dsigctx->signKey = xmlSecCryptoAppKeyLoad(key, xmlSecKeyDataFormatPem, NULL, NULL, NULL); + if (dsigctx->signKey == NULL) { + syslog(LOG_ERR, "loading key %s failed.", key); + goto error3; + } + while (*certs) { + if(xmlSecCryptoAppKeyCertLoad(dsigctx->signKey, *certs, xmlSecKeyDataFormatPem) < 0) { + syslog(LOG_ERR, "loading certificate %s failed.", *certs); + goto error3; + } + certs++; + } + if(xmlSecDSigCtxSign(dsigctx, sign) < 0) { + syslog(LOG_ERR, "signing the document failed."); + goto error3; + } + xmlSecDSigCtxDestroy(dsigctx); + return doc; + +error3: + xmlSecDSigCtxDestroy(dsigctx); +error2: + xmlFreeDoc(doc); +error: + return NULL; +} + diff --git a/wgtpkg-zip.c b/wgtpkg-zip.c new file mode 100644 index 0000000..98501ff --- /dev/null +++ b/wgtpkg-zip.c @@ -0,0 +1,317 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#define _BSD_SOURCE /* see readdir */ + +#include <limits.h> +#include <zip.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> +#include <dirent.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "wgtpkg.h" + + +#if !defined(MODE_OF_FILE_CREATION) +#define MODE_OF_FILE_CREATION 0640 +#endif +#if !defined(MODE_OF_DIRECTORY_CREATION) +#define MODE_OF_DIRECTORY_CREATION 0750 +#endif + + +int create_directory(char *file, int mode) +{ + int rc; + char *last = strrchr(file, '/'); + if (last != NULL) + *last = 0; + rc = mkdir(file, mode); + if (rc) { + if (errno == EEXIST) + rc = 0; + else if (errno == ENOENT) { + rc = create_directory(file, mode); + if (!rc) + rc = mkdir(file, mode); + } + } + if (rc) + syslog(LOG_ERR, "can't create directory %s", file); + if (last != NULL) + *last = '/'; + return rc; +} + +int create_file(char *file, int fmode, int dmode) +{ + int fd = creat(file, fmode); + if (fd < 0 && errno == ENOENT) { + if (!create_directory(file, dmode)) + fd = creat(file, fmode); + } + if (fd < 0) + syslog(LOG_ERR, "can't create file %s", file); + return fd; +} + +/* read (extract) 'zipfile' in current directory */ +int zread(const char *zipfile, unsigned long long maxsize) +{ + struct filedesc *fdesc; + int err, fd, len; + struct zip *zip; + zip_int64_t z64; + unsigned int count, index; + struct zip_file *zfile; + struct zip_stat zstat; + char buffer[32768]; + ssize_t sizr, sizw; + size_t esize; + + /* open the zip file */ + zip = zip_open(zipfile, ZIP_CHECKCONS, &err); + if (!zip) { + syslog(LOG_ERR, "Can't connect to file %s", zipfile); + return -1; + } + + z64 = zip_get_num_entries(zip, 0); + if (z64 < 0 || z64 > UINT_MAX) { + syslog(LOG_ERR, "too many entries in %s", zipfile); + goto error; + } + count = (unsigned int)z64; + + /* records the files */ + file_reset(); + esize = 0; + for (index = 0 ; index < count ; index++) { + err = zip_stat_index(zip, index, ZIP_FL_ENC_GUESS, &zstat); + /* check the file name */ + if (zstat.name[0] == '/') { + syslog(LOG_ERR, "absolute entry %s found in %s", zstat.name, zipfile); + goto error; + } + len = strlen(zstat.name); + if (len == 0) { + syslog(LOG_ERR, "empty entry found in %s", zipfile); + goto error; + } + if (zstat.size == 0) { + /* directory name */ + if (zstat.name[len - 1] != '/') { + syslog(LOG_ERR, "bad directory name %s in %s", zstat.name, zipfile); + goto error; + } + /* record */ + fdesc = file_add_directory(zstat.name); + } else { + /* directory name */ + if (zstat.name[len - 1] == '/') { + syslog(LOG_ERR, "bad file name %s in %s", zstat.name, zipfile); + goto error; + } + /* get the size */ + esize += zstat.size; + /* record */ + fdesc = file_add_file(zstat.name); + } + if (!fdesc) + goto error; + fdesc->zindex = index; + } + + /* check the size */ + if (maxsize && esize > maxsize) { + syslog(LOG_ERR, "extracted size %zu greater than allowed size %llu", esize, maxsize); + goto error; + } + + /* unpack the recorded files */ + assert(count == file_count()); + for (index = 0 ; index < count ; index++) { + fdesc = file_of_index(index); + assert(fdesc != NULL); + err = zip_stat_index(zip, fdesc->zindex, ZIP_FL_ENC_GUESS, &zstat); + assert(zstat.name[0] != '/'); + if (zstat.size == 0) { + /* directory name */ + err = create_directory((char*)zstat.name, MODE_OF_DIRECTORY_CREATION); + if (err && errno != EEXIST) + goto error; + } else { + /* file name */ + zfile = zip_fopen_index(zip, fdesc->zindex, 0); + if (!zfile) { + syslog(LOG_ERR, "Can't open %s in %s", zstat.name, zipfile); + goto error; + } + fd = create_file((char*)zstat.name, MODE_OF_FILE_CREATION, MODE_OF_DIRECTORY_CREATION); + if (fd < 0) + goto errorz; + /* extract */ + z64 = zstat.size; + while (z64) { + sizr = zip_fread(zfile, buffer, sizeof buffer); + if (sizr < 0) { + syslog(LOG_ERR, "error while reading %s in %s", zstat.name, zipfile); + goto errorzf; + } + sizw = write(fd, buffer, sizr); + if (sizw < 0) { + syslog(LOG_ERR, "error while writing %s", zstat.name); + goto errorzf; + } + z64 -= sizw; + } + close(fd); + zip_fclose(zfile); + } + } + + zip_close(zip); + return 0; + +errorzf: + close(fd); +errorz: + zip_fclose(zfile); +error: + zip_close(zip); + return -1; +} + +struct zws { + struct zip *zip; + char name[PATH_MAX]; + char buffer[32768]; +}; + +static int zwr(struct zws *zws, int offset) +{ + int len, err; + DIR *dir; + struct dirent *ent; + zip_int64_t z64; + struct zip_source *zsrc; + + if (offset == 0) + dir = opendir("."); + else { + dir = opendir(zws->name); + zws->name[offset++] = '/'; + } + if (!dir) { + syslog(LOG_ERR, "opendir %.*s failed in zwr", offset, zws->name); + return -1; + } + + ent = readdir(dir); + while (ent != NULL) { + len = strlen(ent->d_name); + if (ent->d_name[0] == '.' && (len == 1 || + (ent->d_name[1] == '.' && len == 2))) + ; + else if (offset + len >= sizeof(zws->name)) { + closedir(dir); + syslog(LOG_ERR, "name too long in zwr"); + errno = ENAMETOOLONG; + return -1; + } else { + memcpy(zws->name + offset, ent->d_name, 1+len); + switch (ent->d_type) { + case DT_DIR: + z64 = zip_dir_add(zws->zip, zws->name, ZIP_FL_ENC_UTF_8); + if (z64 < 0) { + syslog(LOG_ERR, "zip_dir_add of %s failed", zws->name); + closedir(dir); + return -1; + } + err = zwr(zws, offset + len); + if (err) { + closedir(dir); + return -1; + } + break; + case DT_REG: + zsrc = zip_source_file(zws->zip, zws->name, 0, 0); + if (zsrc == NULL) { + syslog(LOG_ERR, "zip_source_file of %s failed", zws->name); + closedir(dir); + return -1; + } + z64 = zip_file_add(zws->zip, zws->name, zsrc, ZIP_FL_ENC_UTF_8); + if (z64 < 0) { + syslog(LOG_ERR, "zip_file_add of %s failed", zws->name); + zip_source_free(zsrc); + closedir(dir); + return -1; + } + break; + default: + break; + } + } + ent = readdir(dir); + } + + closedir(dir); + return 0; +} + +/* write (pack) content of the current directory in 'zipfile' */ +int zwrite(const char *zipfile) +{ + int err; + struct zws zws; + + zws.zip = zip_open(zipfile, ZIP_CREATE|ZIP_TRUNCATE, &err); + if (!zws.zip) { + syslog(LOG_ERR, "Can't open %s for write", zipfile); + return -1; + } + + err = zwr(&zws, 0); + zip_close(zws.zip); + return err; +} + + +#if defined(TEST_READ) +int main(int ac, char **av) +{ + for(av++ ; *av ; av++) + zread(*av); + return 0; +} +#endif + +#if defined(TEST_WRITE) +int main(int ac, char **av) +{ + for(av++ ; *av ; av++) + zwrite(*av); + return 0; +} +#endif + diff --git a/wgtpkg.h b/wgtpkg.h new file mode 100644 index 0000000..14ab662 --- /dev/null +++ b/wgtpkg.h @@ -0,0 +1,115 @@ +/* + Copyright 2015 IoT.bzh + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + +#include <libxml/tree.h> + +struct filedesc; + +/**************************************************************/ +/* from wgtpkg-base64 */ + +extern char *base64encw(const char *buffer, int length, int width); +extern char *base64enc(const char *buffer, int length); +extern int base64dec(const char *buffer, char **output); +extern int base64eq(const char *buf1, const char *buf2); + +/**************************************************************/ +/* from wgtpkg-certs */ + +extern void clear_certificates(); +extern int add_certificate_b64(const char *b64); + +/**************************************************************/ +/* from wgtpkg-digsig */ + +/* verify the digital signature in file */ +extern int verify_digsig(struct filedesc *fdesc); + +/* create a digital signature */ +extern int create_digsig(int index, const char *key, const char **certs); + +/* check the signatures of the current directory */ +extern int check_all_signatures(); + +/**************************************************************/ +/* from wgtpkg-files */ + +enum entrytype { + type_unset = 0, + type_file = 1, + type_directory = 2 +}; + +enum fileflag { + flag_referenced = 1, + flag_opened = 2, + flag_author_signature = 4, + flag_distributor_signature = 8, + flag_signature = 12 +}; + +struct filedesc { + enum entrytype type; + unsigned int flags; + unsigned int signum; + unsigned int zindex; + char name[1]; +}; + +extern void file_reset(); +extern void file_clear_flags(); +extern unsigned int file_count(); +extern struct filedesc *file_of_index(unsigned int index); +extern struct filedesc *file_of_name(const char *name); +extern struct filedesc *file_add_directory(const char *name); +extern struct filedesc *file_add_file(const char *name); +extern int fill_files(); + +extern unsigned int signature_count(); +extern struct filedesc *signature_of_index(unsigned int index); +extern struct filedesc *create_signature(unsigned int number); +extern struct filedesc *get_signature(unsigned int number); + +extern int file_set_prop(struct filedesc *file, const char *name, const char *value); +extern const char *file_get_prop(struct filedesc *file, const char *name); + +/**************************************************************/ +/* from wgtpkg-workdir */ + +extern int enter_workdir(int clean); +extern void remove_workdir(); +extern int make_workdir(int reuse); +extern int set_workdir(const char *name, int create); + +/**************************************************************/ +/* from wgtpkg-xmlsec */ + +extern int xmlsec_init(); +extern void xmlsec_shutdown(); +extern int xmlsec_verify(xmlNodePtr node); +extern xmlDocPtr xmlsec_create(int index, const char *key, const char **certs); + +/**************************************************************/ +/* from wgtpkg-zip */ + +/* read (extract) 'zipfile' in current directory */ +extern int zread(const char *zipfile, unsigned long long maxsize); + +/* write (pack) content of the current directory in 'zipfile' */ +extern int zwrite(const char *zipfile); + + |