X-Git-Url: https://git.librecmc.org/?p=oweals%2Fucert.git;a=blobdiff_plain;f=ucert.c;h=7de4c12711e861bbf1ca0e5c26c082c3e04c832a;hp=51b421ed89ddddbd7be8ddbf83e2b6ebcf4d2042;hb=afc86f352bf7915f81e9d38949e91306542aadee;hpb=a7169f497ffa539d08ddb04d18aeb9b893203c3a diff --git a/ucert.c b/ucert.c index 51b421e..7de4c12 100644 --- a/ucert.c +++ b/ucert.c @@ -47,7 +47,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,31 +109,35 @@ 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; + 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); } +/* load certfile into list */ static int cert_load(const char *certfile, struct list_head *chain) { FILE *f; struct blob_attr *certtb[CERT_ATTR_MAX]; + struct blob_attr *bufpt; struct cert_object *cobj; char filebuf[CERT_BUF_LEN]; - int ret = 0; - int len; + int ret = 0, pret = 0; + size_t len, pos = 0; f = fopen(certfile, "r"); if (!f) @@ -132,20 +152,64 @@ static int cert_load(const char *certfile, struct list_head *chain) { if (ret) return 1; - /* TODO: read more than one blob from file */ - ret = blob_parse(filebuf, certtb, cert_policy, CERT_ATTR_MAX); - cobj = calloc(1, sizeof(*cobj)); - cobj->cert = &certtb; - list_add_tail(&cobj->list, chain); + bufpt = (struct blob_attr *)filebuf; + do { + 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) + /* blob exceeds filebuffer */ + break; + else + pos += blob_pad_len(bufpt); + + if (!certtb[CERT_ATTR_SIGNATURE]) + /* no signature -> drop */ + break; + + cobj = calloc(1, sizeof(*cobj)); + 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; + /* repeat parsing while there is still enough remaining data in buffer */ + } while(len > pos + sizeof(struct blob_attr) && (bufpt = blob_next(bufpt))); return (ret <= 0); } -static int cert_append(const char *certfile, const char *pubkeyfile, const char *sigfile) { - fprintf(stderr, "not implemented\n"); - return 1; +#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 = {0}; + int len; + int ret; + + fs = fopen(sigfile, "r"); + if (!fs) + return 1; + + 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); + ret = write_file(certfile, sigbuf.head, blob_raw_len(sigbuf.head), true); + 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; @@ -184,6 +248,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; @@ -191,9 +256,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; @@ -201,17 +268,19 @@ static int chain_verify(const char *msgfile, const char *pubkeyfile, if (msgfile) checkmsg = -1; - list_for_each_entry(cobj, chain, list) { - ret = cert_verify_blob(cobj->cert, chainedpubkey[0]?chainedpubkey:pubkeyfile, pubkeydir); - if (ret) - goto clean_and_return; + 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); + if (ret) + goto clean_and_return; + blobmsg_parse(cert_cont_policy, ARRAY_SIZE(cert_cont_policy), containertb, @@ -219,7 +288,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, @@ -233,7 +302,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]); @@ -242,15 +311,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; } @@ -259,7 +327,19 @@ 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)) { + 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) { snprintf(extsigfile, sizeof(extsigfile) - 1, "%s/%s", tmpdir, "ext-sig"); write_file(extsigfile, @@ -271,14 +351,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]) @@ -287,8 +367,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]; @@ -298,50 +381,55 @@ 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---\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) + list_for_each_entry(cobj, &certchain, list) { + printf("=== CHAIN ELEMENT %02u ===\n", ++count); cert_dump_blob(cobj->cert); + } 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; 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; - } - pkf = fopen(pubkeyfile, "r"); if (!pkf) return -1; @@ -391,7 +479,7 @@ static int cert_issue(const char *certfile, const char *pubkeyfile, const char * if (siglen < 1) return 1; - sigb[siglen-1] = '\0'; + sigb[siglen] = '\0'; fclose(sigf); unlink(fname); @@ -401,7 +489,10 @@ 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); + revoker--; } @@ -409,103 +500,234 @@ 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" - " -A: append (needs -c and -p and/or -x)\n" - " -D: dump\n" +#ifdef UCERT_FULL + " -A: append signature (needs -c and -x)\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 's': - seckeyfile = 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 && (pubkeyfile || sigfile)) - return cert_append(certfile, pubkeyfile, sigfile); + if (certfile && sigfile) + return cert_append(certfile, sigfile); else return usage(argv[0]); case CMD_DUMP: @@ -518,9 +740,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: @@ -528,7 +751,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]); }