X-Git-Url: https://git.librecmc.org/?p=oweals%2Fucert.git;a=blobdiff_plain;f=ucert.c;h=208d5f67e10d11238736ae290b222c5f9c4f765a;hp=7be2f8f0691ad809f0f8851e3251c6c1a289ef3c;hb=38dcb1a6f12115e156aa4f36997bd4760347e821;hpb=b62d1266c14a64b39ba53cf54ebee812c3d6804d diff --git a/ucert.c b/ucert.c index 7be2f8f..208d5f6 100644 --- a/ucert.c +++ b/ucert.c @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -47,7 +48,23 @@ static enum { } cmd = CMD_NONE; static bool quiet; +#ifndef UCERT_STRIP_MESSAGES +#define DPRINTF(format, ...) \ + do { \ + if (!quiet) \ + fprintf(stderr, "%s(%d): " format, __func__, __LINE__, ## __VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(format, ...) do { } while (0) +#endif +/* + * ucert structure + * | BLOB | + * | SIGNATURE | PAYLOAD | + * | |[ BLOBMSG CONTAINER ]| + * | |[[T,i,v,e,f,pubkey ]]| + */ enum cert_attr { CERT_ATTR_SIGNATURE, CERT_ATTR_PAYLOAD, @@ -93,86 +110,117 @@ static const struct blobmsg_policy cert_payload_policy[CERT_PL_ATTR_MAX] = { [CERT_PL_ATTR_KEY_FINGERPRINT] = { .name = "fingerprint", .type = BLOBMSG_TYPE_STRING }, }; +/* list to store certificate chain at runtime */ struct cert_object { struct list_head list; struct blob_attr *cert[CERT_ATTR_MAX]; }; -static int write_file(const char *filename, void *buf, size_t len, bool append) { +/* write buffer to file */ +static bool write_file(const char *filename, void *buf, size_t len, bool append) { FILE *f; size_t outlen; f = fopen(filename, append?"a":"w"); if (!f) - return 1; + return false; outlen = fwrite(buf, 1, len, f); fclose(f); return (outlen == len); } -static int cert_load(const char *certfile, struct list_head *chain) { +/* reads a whole file to a buffer - returns -1 on errors and sets errno */ +static ssize_t read_file(const char *filename, void *buf, size_t len, size_t minlen) { FILE *f; + ssize_t ret; + + f = fopen(filename, "r"); + if (!f) + return -1; + + ret = fread(buf, 1, len, f); + + /* Ensure that feof() yields the correct result when the file is exactly + * len bytes long */ + fgetc(f); + + if (ferror(f)) { + ret = -1; + } else if (!feof(f)) { + errno = EOVERFLOW; + ret = -1; + } else if ((size_t)ret < minlen) { + errno = EINVAL; + ret = -1; + } + + fclose(f); + return ret; +} + +/* load certfile into list */ +static int cert_load(const char *certfile, struct list_head *chain) { struct blob_attr *certtb[CERT_ATTR_MAX]; struct blob_attr *bufpt; struct cert_object *cobj; char filebuf[CERT_BUF_LEN]; int ret = 0, pret = 0; - int len, pos = 0; + size_t pos = 0; + ssize_t len; - f = fopen(certfile, "r"); - if (!f) - return 1; - - len = fread(&filebuf, 1, CERT_BUF_LEN - 1, f); - if (len < 64) - return 1; - - ret = ferror(f) || !feof(f); - fclose(f); - if (ret) + len = read_file(certfile, filebuf, sizeof(filebuf) - 1, 0); + if (len < 0) { + if (!quiet) + perror("Unable to load certificate file"); return 1; + } bufpt = (struct blob_attr *)filebuf; do { - pret = blob_parse(bufpt, certtb, cert_policy, CERT_ATTR_MAX); + pret = blob_parse_untrusted(bufpt, len, certtb, cert_policy, CERT_ATTR_MAX); if (pret <= 0) /* no attributes found */ break; - if (pos + blob_pad_len(bufpt) > len) + if (pos + blob_pad_len(bufpt) > (size_t) len) /* blob exceeds filebuffer */ break; else pos += blob_pad_len(bufpt); + if (!certtb[CERT_ATTR_SIGNATURE]) + /* no signature -> drop */ + break; + cobj = calloc(1, sizeof(*cobj)); - memcpy(cobj->cert, &certtb, sizeof(certtb)); + cobj->cert[CERT_ATTR_SIGNATURE] = blob_memdup(certtb[CERT_ATTR_SIGNATURE]); + if (certtb[CERT_ATTR_PAYLOAD]) + cobj->cert[CERT_ATTR_PAYLOAD] = blob_memdup(certtb[CERT_ATTR_PAYLOAD]); + list_add_tail(&cobj->list, chain); ret += pret; - bufpt = blob_next(bufpt); /* repeat parsing while there is still enough remaining data in buffer */ - } while(len > pos + sizeof(struct blob_attr)); + } while((size_t) len > pos + sizeof(struct blob_attr) && (bufpt = blob_next(bufpt))); return (ret <= 0); } +#ifdef UCERT_FULL +/* append signature to certfile */ static int cert_append(const char *certfile, const char *sigfile) { - FILE *fs; char filebuf[CERT_BUF_LEN]; - struct blob_buf sigbuf; - int len; + struct blob_buf sigbuf = {0}; + ssize_t len; int ret; - fs = fopen(sigfile, "r"); - if (!fs) - return 1; + len = read_file(sigfile, filebuf, sizeof(filebuf) - 1, 64); + if (len < 0) { + if (!quiet) + perror("Unable to load signature file"); - len = fread(&filebuf, 1, CERT_BUF_LEN - 1, fs); - ret = ferror(fs) || !feof(fs) || (len < 64); - fclose(fs); - if (ret) return 1; + } blob_buf_init(&sigbuf, 0); blob_put(&sigbuf, CERT_ATTR_SIGNATURE, filebuf, len); @@ -180,7 +228,9 @@ static int cert_append(const char *certfile, const char *sigfile) { blob_buf_free(&sigbuf); return ret; } +#endif +/* verify the signature of a single chain element */ static int cert_verify_blob(struct blob_attr *cert[CERT_ATTR_MAX], const char *pubkeyfile, const char *pubkeydir) { int i; @@ -219,6 +269,7 @@ static int cert_verify_blob(struct blob_attr *cert[CERT_ATTR_MAX], return ret; } +/* verify cert chain (and message) */ static int chain_verify(const char *msgfile, const char *pubkeyfile, const char *pubkeydir, struct list_head *chain) { struct cert_object *cobj; @@ -226,9 +277,11 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, struct blob_attr *payloadtb[CERT_PL_ATTR_MAX]; char tmpdir[] = "/tmp/ucert-XXXXXX"; char chainedpubkey[256] = {0}; + char chainedfp[17] = {0}; char extsigfile[256] = {0}; int ret = 1; int checkmsg = 0; + struct timeval tv; if (mkdtemp(tmpdir) == NULL) return errno; @@ -236,12 +289,13 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, if (msgfile) checkmsg = -1; + gettimeofday(&tv, NULL); + list_for_each_entry(cobj, chain, list) { /* blob has payload, verify that using signature */ if (cobj->cert[CERT_ATTR_PAYLOAD]) { - struct timeval tv; - uint64_t validfrom; - uint64_t expiresat; + time_t validfrom; + time_t expiresat; uint32_t certtype; ret = cert_verify_blob(cobj->cert, chainedpubkey[0]?chainedpubkey:pubkeyfile, pubkeydir); @@ -255,7 +309,7 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, blob_len(cobj->cert[CERT_ATTR_PAYLOAD])); if (!containertb[CERT_CT_ATTR_PAYLOAD]) { ret = 1; - fprintf(stderr, "no ucert in signed payload\n"); + DPRINTF("no ucert in signed payload\n"); goto clean_and_return; } blobmsg_parse(cert_payload_policy, @@ -269,7 +323,7 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, !payloadtb[CERT_PL_ATTR_EXPIRETIME] || !payloadtb[CERT_PL_ATTR_PUBKEY]) { ret = 1; - fprintf(stderr, "missing mandatory ucert attributes\n"); + DPRINTF("missing mandatory ucert attributes\n"); goto clean_and_return; } certtype = blobmsg_get_u32(payloadtb[CERT_PL_ATTR_CERTTYPE]); @@ -278,15 +332,14 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, if (certtype != CERTTYPE_AUTH) { ret = 2; - fprintf(stderr, "wrong certificate type\n"); + DPRINTF("wrong certificate type\n"); goto clean_and_return; } - gettimeofday(&tv, NULL); if (tv.tv_sec < validfrom || tv.tv_sec >= expiresat) { ret = 3; - fprintf(stderr, "certificate expired\n"); + DPRINTF("certificate expired\n"); goto clean_and_return; } @@ -295,6 +348,17 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, blobmsg_data(payloadtb[CERT_PL_ATTR_PUBKEY]), blobmsg_data_len(payloadtb[CERT_PL_ATTR_PUBKEY]), false); + + if (usign_f_pubkey(chainedfp, chainedpubkey, quiet)) { + DPRINTF("cannot get fingerprint for chained key\n"); + ret = 2; + goto clean_and_return; + } + if (pubkeydir && _usign_key_is_revoked(chainedfp, pubkeydir)) { + DPRINTF("key %s has been revoked!\n", chainedfp); + ret = 4; + goto clean_and_return; + } } else { /* blob doesn't have payload, verify message using signature */ if (msgfile) { @@ -308,14 +372,14 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, pubkeydir, extsigfile, quiet); unlink(extsigfile); } else { - fprintf(stderr, "stray trailing signature without anything to verify!\n"); + DPRINTF("stray trailing signature without anything to verify!\n"); ret = 1; }; } } if (checkmsg == -1) - fprintf(stderr, "missing signature to verify message!\n"); + DPRINTF("missing signature to verify message!\n"); clean_and_return: if (chainedpubkey[0]) @@ -324,8 +388,11 @@ clean_and_return: return ret | checkmsg; } +#ifdef UCERT_FULL +/* dump single chain element to console */ static void cert_dump_blob(struct blob_attr *cert[CERT_ATTR_MAX]) { int i; + char *json = NULL; for (i = 0; i < CERT_ATTR_MAX; i++) { struct blob_attr *v = cert[i]; @@ -335,67 +402,65 @@ static void cert_dump_blob(struct blob_attr *cert[CERT_ATTR_MAX]) { switch(cert_policy[i].type) { case BLOB_ATTR_BINARY: - fprintf(stdout, "signature:\n---\n%s---\n", (char *) blob_data(v)); + printf("signature:\n---\n%s---\n", (char *) blob_data(v)); break; case BLOB_ATTR_NESTED: - fprintf(stdout, "payload:\n---\n%s\n---\n", blobmsg_format_json_indent(blob_data(v), false, 0)); + json = blobmsg_format_json_indent(blob_data(v), false, 0); + if (!json) { + DPRINTF("cannot parse payload\n"); + continue; + } + printf("payload:\n---\n%s\n---\n", json); + free(json); break; } } } +/* dump certfile to console */ static int cert_dump(const char *certfile) { struct cert_object *cobj; static LIST_HEAD(certchain); unsigned int count = 0; if (cert_load(certfile, &certchain)) { - fprintf(stderr, "cannot parse cert\n"); + DPRINTF("cannot parse cert\n"); return 1; } list_for_each_entry(cobj, &certchain, list) { - fprintf(stderr, "=== CHAIN ELEMENT %02u ===\n", ++count); + printf("=== CHAIN ELEMENT %02u ===\n", ++count); cert_dump_blob(cobj->cert); - fprintf(stderr, "========================\n"); } return 0; } +/* issue an auth certificate for pubkey */ static int cert_issue(const char *certfile, const char *pubkeyfile, const char *seckeyfile) { - struct blob_buf certbuf; - struct blob_buf payloadbuf; + struct blob_buf payloadbuf = {0}; + struct blob_buf certbuf = {0}; struct timeval tv; - struct stat st; - int pklen, siglen; + ssize_t pklen, siglen; int revoker = 1; void *c; - FILE *pkf, *sigf; char pkb[512]; - char sigb[512]; + char sigb[1024]; char fname[256], sfname[256]; char pkfp[17]; char tmpdir[] = "/tmp/ucert-XXXXXX"; - if (stat(certfile, &st) == 0) { - fprintf(stderr, "certfile %s exists, won't overwrite.\n", certfile); - return -1; - } + pklen = read_file(pubkeyfile, pkb, sizeof(pkb) - 1, 32); + if (pklen < 0) { + if (!quiet) + perror("Unable to load public key file"); - pkf = fopen(pubkeyfile, "r"); - if (!pkf) return -1; + } - pklen = fread(pkb, 1, 512, pkf); pkb[pklen] = '\0'; - if (pklen < 32) - return -1; - - fclose(pkf); - - if (usign_f_pubkey(pkfp, pubkeyfile)) + if (usign_f_pubkey(pkfp, pubkeyfile, quiet)) return -1; gettimeofday(&tv, NULL); @@ -424,16 +489,15 @@ static int cert_issue(const char *certfile, const char *pubkeyfile, const char * if (usign_s(fname, seckeyfile, sfname, quiet)) return 1; - sigf = fopen(sfname, "r"); - if (!sigf) - return 1; + siglen = read_file(sfname, sigb, sizeof(sigb) - 1, 1); + if (siglen < 0) { + if (!quiet) + perror("Unable to load signature file"); - siglen = fread(sigb, 1, 1024, sigf); - if (siglen < 1) return 1; + } sigb[siglen] = '\0'; - fclose(sigf); unlink(fname); unlink(sfname); @@ -442,7 +506,7 @@ static int cert_issue(const char *certfile, const char *pubkeyfile, const char * blob_put(&certbuf, CERT_ATTR_SIGNATURE, sigb, siglen); blob_put(&certbuf, CERT_ATTR_PAYLOAD, blob_data(payloadbuf.head), blob_len(payloadbuf.head)); snprintf(fname, sizeof(fname) - 1, "%s%s", certfile, revoker?".revoke":""); - write_file(fname, certbuf.head, blob_raw_len(certbuf.head), false); + write_file(fname, certbuf.head, blob_raw_len(certbuf.head), true); blob_buf_free(&certbuf); blob_buf_free(&payloadbuf); @@ -453,100 +517,231 @@ static int cert_issue(const char *certfile, const char *pubkeyfile, const char * return 0; } +#endif -static int cert_process_revoker(const char *certfile) { - fprintf(stderr, "not implemented\n"); - return 1; +/* process revoker certificate */ +static int cert_process_revoker(const char *certfile, const char *pubkeydir) { + static LIST_HEAD(certchain); + struct cert_object *cobj; + struct blob_attr *containertb[CERT_CT_ATTR_MAX]; + struct blob_attr *payloadtb[CERT_PL_ATTR_MAX]; + struct stat st; + struct timeval tv; + time_t validfrom; + enum certtype_id certtype; + char *fingerprint; + char rfname[512]; + + int ret = -1; + + if (cert_load(certfile, &certchain)) { + DPRINTF("cannot parse cert\n"); + return 1; + } + + gettimeofday(&tv, NULL); + + list_for_each_entry(cobj, &certchain, list) { + if (!cobj->cert[CERT_ATTR_PAYLOAD]) + return 2; + + /* blob has payload, verify that using signature */ + ret = cert_verify_blob(cobj->cert, NULL, pubkeydir); + if (ret) + return ret; + + blobmsg_parse(cert_cont_policy, + ARRAY_SIZE(cert_cont_policy), + containertb, + blob_data(cobj->cert[CERT_ATTR_PAYLOAD]), + blob_len(cobj->cert[CERT_ATTR_PAYLOAD])); + if (!containertb[CERT_CT_ATTR_PAYLOAD]) { + DPRINTF("no ucert in signed payload\n"); + return 2; + } + + blobmsg_parse(cert_payload_policy, + ARRAY_SIZE(cert_payload_policy), + payloadtb, + blobmsg_data(containertb[CERT_CT_ATTR_PAYLOAD]), + blobmsg_data_len(containertb[CERT_CT_ATTR_PAYLOAD])); + + if (!payloadtb[CERT_PL_ATTR_CERTTYPE] || + !payloadtb[CERT_PL_ATTR_VALIDFROMTIME] || + !payloadtb[CERT_PL_ATTR_KEY_FINGERPRINT]) { + DPRINTF("missing mandatory ucert attributes\n"); + return 2; + } + + certtype = blobmsg_get_u32(payloadtb[CERT_PL_ATTR_CERTTYPE]); + validfrom = blobmsg_get_u64(payloadtb[CERT_PL_ATTR_VALIDFROMTIME]); + fingerprint = blobmsg_get_string(payloadtb[CERT_PL_ATTR_KEY_FINGERPRINT]); + + if (certtype != CERTTYPE_REVOKE) { + DPRINTF("wrong certificate type\n"); + return 2; + } + + if (tv.tv_sec < validfrom) { + return 3; + } + + snprintf(rfname, sizeof(rfname)-1, "%s/%s", pubkeydir, fingerprint); + /* check if entry in pubkeydir exists */ + if (stat(rfname, &st) == 0) { + if (_usign_key_is_revoked(fingerprint, pubkeydir)) { + DPRINTF("existing revoker deadlink for key %s\n", fingerprint); + continue; + }; + + /* remove any other entry */ + if (unlink(rfname)) + return -1; + } + + ret = symlink(".revoked.", rfname); + if (ret) + return ret; + + DPRINTF("created revoker deadlink for key %s\n", fingerprint); + }; + + return ret; } +/* load and verify certfile (and message) */ static int cert_verify(const char *certfile, const char *pubkeyfile, const char *pubkeydir, const char *msgfile) { static LIST_HEAD(certchain); if (cert_load(certfile, &certchain)) { - fprintf(stderr, "cannot parse cert\n"); + DPRINTF("cannot parse cert\n"); return 1; } return chain_verify(msgfile, pubkeyfile, pubkeydir, &certchain); } +/* output help */ static int usage(const char *cmd) { +#ifndef UCERT_STRIP_MESSAGES fprintf(stderr, "Usage: %s \n" "Commands:\n" +#ifdef UCERT_FULL " -A: append signature (needs -c and -x)\n" - " -D: dump\n" + " -D: dump (needs -c)\n" " -I: issue cert and revoker (needs -c and -p and -s)\n" - " -R: process revoker certificate (needs -c)\n" - " -V: verify (needs -c and -p|-P)\n" +#endif /* UCERT_FULL */ + " -R: process revoker certificate (needs -c and -P)\n" + " -V: verify (needs -c and -p|-P, may have -m)\n" "Options:\n" " -c : certificate file\n" " -m : message file (verify only)\n" " -p : public key file\n" " -P : public key directory (verify only)\n" " -q: quiet (do not print verification result, use return code only)\n" +#ifdef UCERT_FULL " -s : secret key file (issue only)\n" - " -x : signature file\n" + " -x : signature file (append only)\n" +#endif /* UCERT_FULL */ "\n", cmd); +#endif /* UCERT_STRIP_MESSAGES */ return 1; } +/* parse command line options and call functions */ int main(int argc, char *argv[]) { int ch; const char *msgfile = NULL; - const char *sigfile = NULL; const char *pubkeyfile = NULL; const char *pubkeydir = NULL; const char *certfile = NULL; +#ifdef UCERT_FULL + const char *sigfile = NULL; const char *seckeyfile = NULL; +#endif quiet = false; - while ((ch = getopt(argc, argv, "ADIRVc:m:p:P:qs:x:")) != -1) { + while ((ch = getopt(argc, argv, + "RVc:m:p:P:q" +#ifdef UCERT_FULL + "ADIs:x:" +#endif + )) != -1) { switch (ch) { +#ifdef UCERT_FULL case 'A': + if (cmd != CMD_NONE) + return usage(argv[0]); cmd = CMD_APPEND; break; case 'D': + if (cmd != CMD_NONE) + return usage(argv[0]); cmd = CMD_DUMP; break; case 'I': + if (cmd != CMD_NONE) + return usage(argv[0]); cmd = CMD_ISSUE; break; +#endif case 'R': + if (cmd != CMD_NONE) + return usage(argv[0]); cmd = CMD_REVOKE; break; case 'V': + if (cmd != CMD_NONE) + return usage(argv[0]); cmd = CMD_VERIFY; break; case 'c': + if (certfile || cmd == CMD_NONE) + return usage(argv[0]); certfile = optarg; break; case 'm': + if (msgfile || cmd != CMD_VERIFY) + return usage(argv[0]); msgfile = optarg; break; case 'p': + if (pubkeyfile || (cmd != CMD_VERIFY && cmd != CMD_ISSUE) || cmd == CMD_NONE) + return usage(argv[0]); pubkeyfile = optarg; break; case 'P': + if (pubkeydir || (cmd != CMD_VERIFY && cmd != CMD_REVOKE) || cmd == CMD_NONE) + return usage(argv[0]); pubkeydir = optarg; break; case 'q': + if (quiet) + return usage(argv[0]); quiet = true; break; +#ifdef UCERT_FULL case 's': + if (seckeyfile || cmd != CMD_ISSUE || cmd == CMD_NONE) + return usage(argv[0]); seckeyfile = optarg; break; case 'x': + if (sigfile || cmd != CMD_APPEND || cmd == CMD_NONE) + return usage(argv[0]); sigfile = optarg; break; +#endif default: return usage(argv[0]); } } switch (cmd) { +#ifdef UCERT_FULL case CMD_APPEND: if (certfile && sigfile) return cert_append(certfile, sigfile); @@ -562,9 +757,10 @@ int main(int argc, char *argv[]) { return cert_issue(certfile, pubkeyfile, seckeyfile); else return usage(argv[0]); +#endif case CMD_REVOKE: - if (certfile) - return cert_process_revoker(certfile); + if (certfile && pubkeydir) + return cert_process_revoker(certfile, pubkeydir); else return usage(argv[0]); case CMD_VERIFY: @@ -572,7 +768,7 @@ int main(int argc, char *argv[]) { return cert_verify(certfile, pubkeyfile, pubkeydir, msgfile); else return usage(argv[0]); - case CMD_NONE: + default: return usage(argv[0]); }