/* vim: set tabstop=8 shiftwidth=4 softtabstop=4 expandtab smarttab colorcolumn=80: */ /* * Copyright 2016 Red Hat, Inc. * * 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 "jwe.h" #include "pwd.h" #include #include #define SUMMARY "Encrypts plaintext using one or more JWKs and outputs a JWE" typedef struct { jcmd_opt_io_t io; json_t *keys; json_t *rcps; } jcmd_opt_t; static const char *prefix = "jose jwe enc [-i JWE] -I PT [-r RCP] -k JWK [-p] [-o JWE] [-O CT] [-c]\n\n" SUMMARY; static json_t * prompt(void) { const char *c = NULL; char *p = NULL; while (!p || !c || strcmp(p, c) != 0) { free(p); p = strdup(jwe_getpass("Please enter an encryption password: ")); if (!p) continue; if (strlen(p) < 8) { fprintf(stderr, "Password too short!\n"); continue; } c = jwe_getpass("Please re-enter the previous password: "); } free(p); return json_string(c); } static bool opt_set_password(const jcmd_cfg_t *cfg, void *vopt, const char *arg) { json_t **keys = vopt; if (!*keys) *keys = json_array(); return json_array_append_new(*keys, json_null()) == 0; } static const jcmd_doc_t doc_recipient[] = { { .arg = "FILE", .doc="Read JWE recipient template from FILE" }, { .arg = "-", .doc="Read JWE recipient template from standard input" }, {} }; static const jcmd_doc_t doc_password[] = { { .doc="Prompt for an encryption password" }, {} }; static const jcmd_cfg_t cfgs[] = { { .opt = { "input", required_argument, .val = 'i' }, .off = offsetof(jcmd_opt_t, io), .set = jcmd_opt_io_set_input, .doc = jcmd_jwe_doc_input, .def = "{}", }, { .opt = { "detached", required_argument, .val = 'I' }, .off = offsetof(jcmd_opt_t, io.detached), .set = jcmd_opt_set_ifile, .doc = jcmd_jwe_doc_detached, }, { .opt = { "recipient", required_argument, .val = 'r' }, .off = offsetof(jcmd_opt_t, rcps), .set = jcmd_opt_set_jsons, .doc = doc_recipient, .def = "{}", }, { .opt = { "key", required_argument, .val = 'k' }, .off = offsetof(jcmd_opt_t, keys), .set = jcmd_opt_set_jwks, .doc = jcmd_doc_key, }, { .opt = { "password", no_argument, .val = 'p' }, .off = offsetof(jcmd_opt_t, keys), .set = opt_set_password, .doc = doc_password, }, { .opt = { "output", required_argument, .val = 'o' }, .off = offsetof(jcmd_opt_t, io.output), .set = jcmd_opt_set_ofile, .doc = jcmd_jwe_doc_output, .def = "-", }, { .opt = { "detach", required_argument, .val = 'O' }, .off = offsetof(jcmd_opt_t, io.detach), .set = jcmd_opt_set_ofile, .doc = jcmd_jwe_doc_detach, }, { .opt = { "compact", no_argument, .val = 'c' }, .off = offsetof(jcmd_opt_t, io.compact), .set = jcmd_opt_set_flag, .doc = jcmd_jwe_doc_compact, }, {} }; static void jcmd_opt_cleanup(jcmd_opt_t *opt) { jcmd_opt_io_cleanup(&opt->io); json_decrefp(&opt->keys); json_decrefp(&opt->rcps); } static bool opt_validate(jcmd_opt_t *opt) { size_t nkeys = json_array_size(opt->keys); if (nkeys == 0) { fprintf(stderr, "Must specify a JWK or password!\n"); return false; } else if (nkeys > 1 && opt->io.compact) { fprintf(stderr, "Requested compact format with >1 recipient!\n"); return false; } if (!opt->io.detached) { fprintf(stderr, "Must specify detached input!\n"); return false; } if (json_array_remove(opt->rcps, 0) < 0) return false; if (json_array_size(opt->keys) < json_array_size(opt->rcps)) { fprintf(stderr, "Specified more recipients than keys!\n"); return false; } while (json_array_size(opt->rcps) < json_array_size(opt->keys)) { if (json_array_append_new(opt->rcps, json_object()) < 0) return false; } return true; } static json_t * wrap(jcmd_opt_t *opt) { json_auto_t *cek = json_object(); for (size_t i = 0; i < json_array_size(opt->keys); i++) { json_auto_t *jwk = json_incref(json_array_get(opt->keys, i)); json_t *rcp = json_array_get(opt->rcps, i); if (json_is_null(jwk)) { json_decref(jwk); jwk = prompt(); } if (!jose_jwe_enc_jwk(NULL, opt->io.obj, rcp, jwk, cek)) { fprintf(stderr, "Wrapping failed!\n"); return NULL; } } if (opt->io.compact) { json_t *jh = NULL; jh = jose_jwe_hdr(opt->io.obj, opt->io.obj); if (!jh) return NULL; if (json_object_set_new(opt->io.obj, "protected", jh) < 0) return NULL; if (json_object_get(opt->io.obj, "unprotected") && json_object_del(opt->io.obj, "unprotected") < 0) return NULL; if (json_object_get(opt->io.obj, "header") && json_object_del(opt->io.obj, "header") < 0) return NULL; } return json_incref(cek); } static int jcmd_jwe_enc(int argc, char *argv[]) { jcmd_opt_auto_t opt = { .io.fields = jcmd_jwe_fields }; jose_io_auto_t *out = NULL; jose_io_auto_t *enc = NULL; json_auto_t *cek = NULL; if (!jcmd_opt_parse(argc, argv, cfgs, &opt, prefix)) return EXIT_FAILURE; if (!opt_validate(&opt)) return EXIT_FAILURE; cek = wrap(&opt); if (!cek) return EXIT_FAILURE; out = jose_io_file(NULL, opt.io.detach ? opt.io.detach : opt.io.output); if (!out) return EXIT_FAILURE; if (!opt.io.detach) { jose_io_auto_t *b64 = NULL; b64 = jose_b64_enc_io(out); if (!b64) return EXIT_FAILURE; jose_io_auto(&out); out = jose_io_incref(b64); } enc = jose_jwe_enc_cek_io(NULL, opt.io.obj, cek, out); if (!enc) return EXIT_FAILURE; if (!opt.io.detached) { jose_io_auto_t *b64 = NULL; b64 = jose_b64_dec_io(enc); if (!b64) return EXIT_FAILURE; jose_io_auto(&enc); enc = jose_io_incref(b64); } if (opt.io.compact) { for (size_t i = 0; i < 3; i++) { const char *k = jcmd_jwe_fields[i].name; const char *v = NULL; if (json_unpack(opt.io.obj, "{s?s}", k, &v) < 0) return EXIT_FAILURE; fprintf(opt.io.output, "%s.", v ? v : ""); } } else { fprintf(opt.io.output, "{"); if (!opt.io.detach) fprintf(opt.io.output, "\"ciphertext\":\""); } if (opt.io.detached || opt.io.input) { FILE *f = opt.io.detached ? opt.io.detached : opt.io.input; for (int c = fgetc(f); c != EOF; c = fgetc(f)) { uint8_t b = c; if (!opt.io.detached && b == '.') break; if (!enc->feed(enc, &b, sizeof(b))) return EXIT_FAILURE; } for (int c = 0; opt.io.detached && opt.io.input && c != EOF && c != '.'; ) c = fgetc(opt.io.input); } else { const char *ct = NULL; size_t ctl = 0; if (json_unpack(opt.io.obj, "{s:s%}", "ciphertext", &ct, &ctl) < 0) return EXIT_FAILURE; if (!enc->feed(enc, ct, ctl)) return EXIT_FAILURE; } if (opt.io.input) { if (json_object_set_new(opt.io.obj, "tag", jcmd_compact_field(opt.io.input)) < 0) { fprintf(stderr, "Error reading last compact field!\n"); return EXIT_FAILURE; } } if (!enc->done(enc)) return EXIT_FAILURE; if (opt.io.compact) { const char *v = NULL; if (json_unpack(opt.io.obj, "{s:s}", "tag", &v) < 0) { fprintf(stderr, "Missing tag parameter!\n"); return false; } fprintf(opt.io.output, ".%s", v); } else { if (!opt.io.detach) fprintf(opt.io.output, "\","); json_dumpf(opt.io.obj, opt.io.output, JSON_EMBED | JSON_COMPACT | JSON_SORT_KEYS); fprintf(opt.io.output, "}"); } if (isatty(fileno(opt.io.output))) fprintf(opt.io.output, "\n"); return EXIT_SUCCESS; } JCMD_REGISTER(SUMMARY, jcmd_jwe_enc, "jwe", "enc")