/* [ʞ] soda - libsodium front end * ~ lexi hale * © AGPLv3 * @ vim: ft=c */ #define _POSIX_C_SOURCE 200809L #include #include #include /* boo hiss */ #include #include #include #include #include #include #define sz(x) (sizeof(x)/sizeof((x)[0])) #define _IAIA_FN_ITOA str_of_int #define _IAIA_FN_ATOI int_of_str #include "clib/iaia.c" #ifdef _WIN32 # define open _open # define close _close # define read _read # define write _write #endif #define _self_name "soda" typedef unsigned long long word; typedef enum atom { /* commands */ atom_none, atom_key, atom_gen, atom_pub, atom_psk, atom_pair, atom_cert, atom_sign, atom_verify, atom_enc, atom_dec, atom_wrap, atom_unwrap, atom_help, _atom_cmd_n, /* flags */ atom_verbose, atom_quiet, atom_noise, atom_ascii_armor, atom_color_on, atom_color_off, atom_out, atom_outfd, atom_in, atom_infd, atom_pw, atom_pwfd, atom_pwtty, atom_sig, atom_sigfd, atom_symmetric, atom_raw, atom_fd, atom_fmtv, _atom_flag_n, /* debug values */ atom_debug, atom_info, atom_warn, atom_fatal, atom_silent, _atom_n, atom_capture = _atom_n } atom; enum noise { noise_debug, noise_info, noise_warn, noise_fatal, noise_silent, _noise_n }; struct { const char* label; unsigned char color; } noise_level_text[] = { [noise_debug] = {"(debug",5}, [noise_info ] = {" (info", 4}, [noise_warn ] = {" (warn", 3}, [noise_fatal] = {"(fatal",1}, }; struct cmd_spec { const char* desc; const char** params; struct cmd_spec* sub; } cmd_specs[_atom_cmd_n] = { {NULL,NULL,NULL}, [atom_key] = { "create and manipulate keypairs for public-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ [atom_gen] = { "generate new keypair", NULL, NULL }, [atom_pub] = { "extract the public key portion from a keypair", NULL, NULL}, [atom_wrap] = { "convert a binary key to its ascii-armor representation", NULL, NULL}, }}, [atom_psk] = { "create and manipulate keys for secret-key crypto", NULL, (struct cmd_spec[_atom_cmd_n]){ [atom_gen] = { "generate new PSK", NULL, NULL }, }}, [atom_help] = { "display usage listings for commands", (const char*[]){"commands…",NULL}, NULL }, [atom_sign] = { "sign data using public-key crypto", (const char*[]){"keyfile",NULL}, NULL }, [atom_verify] = { "verify signature on data", (const char*[]){"pubfile",NULL}, NULL }, [atom_enc] = { "encrypt data", NULL, (struct cmd_spec[_atom_cmd_n]){ [atom_pub] = { "encrypt against public key", (const char*[]){ "pubfile", NULL }, NULL}, [atom_psk] = { "encrypt against symmetric, pre-shared key", (const char*[]){ "pskfile", NULL }, NULL}, }}, [atom_wrap] = {"wrap binary input with ascii armor so it can survive transmission mechanisms that are not 8-bit clean", NULL,NULL}, [atom_unwrap] = {"translate ascii-armored input back to the original binary", NULL,NULL}, }; enum say { say_stop, say_string, say_string_list, say_char, say_uint, say_sint, say_hex }; struct sayp { enum say kind; union { const char* str; const char** strlist; char ch; long long unsigned int uint; long long signed int sint; }; }; enum feature { feature_off = 0, feature_on = 1, feature_auto = 2, }; struct ctx { size_t argc; const char** argv; size_t cmdc; atom* cmdv; const char* bin; struct { enum noise noise; enum feature ascii_armor, color; bool fd; bool pw_force_tty; const char* infile,* outfile,* pwfile,* sigfile; word infd, outfd, pwfd, sigfd; bool symcrypt; word fmtv; } flags; }; bool isws(const char c) { /* TODO: maybe ignore all characters outside the ascii armory? */ return (c == ' ' || c == '\t' || c == '\n'); } enum { _buffer_max = 512 }; struct buffer { int fd; /* where to flush */ size_t sz; char bytes[_buffer_max]; }; void buffer_flush(struct buffer* b) { write(b->fd, b->bytes, b->sz); b->sz = 0; } #define _buffer_write(s,b) buffer_write(s, b, sizeof(b)) void buffer_write(struct buffer* b, const char* str, size_t sz) { if (sz == 0) { size_t i; for (i = 0; str[i] != 0; ++i) { const size_t ofs = (b->sz + i) % _buffer_max; b->bytes[ofs] = str[i]; if (ofs == 0 && i != 0) buffer_flush(b); } b -> sz = (b->sz + i) % _buffer_max; } else if (sz > _buffer_max) { buffer_flush(b); write(b->fd, str, sz); b->sz = sz; } else if (_buffer_max - b->sz < sz) { buffer_flush(b); memcpy(b->bytes, str, sz); b->sz = sz; } else { memcpy(b->bytes + b->sz, str, sz); b->sz += sz; } } void buffer_push(struct buffer* b, const char c) { buffer_write(b, &c, 1); } void say(struct ctx c, enum noise kind, struct sayp* s) { struct buffer o = {2,0}; if (c.flags.noise > kind) return; if (noise_level_text -> label != NULL) if (c.flags.color) { buffer_write(&o,"\x1b[1;3",0); buffer_push(&o, 0x30 + noise_level_text[kind].color); _buffer_write(&o,"m"); buffer_write(&o, noise_level_text[kind].label, 0); _buffer_write(&o,")\x1b[m"); } else { buffer_write(&o, noise_level_text[kind].label, 0); buffer_push(&o,')'); } bool bright = false; while(s -> kind != say_stop) { if (c.flags.color) { if (bright) _buffer_write(&o," \x1b[1m"); else _buffer_write(&o," \x1b[m"); } else { buffer_push(&o,' '); } char intbuf[128]; char* const intbuf_end = intbuf + sizeof intbuf; char* start; explain: switch(s -> kind) { case say_string: buffer_write(&o, s -> str, 0); break; case say_char: buffer_push(&o, s -> ch); break; case say_uint: str_of_int(10, s -> uint, intbuf, intbuf_end - 1, &start, true); buffer_write(&o, start, intbuf_end - start); break; case say_sint: /* TODO oops! iaia doesn't have signed int support yet 🙃 */ /* str_of_int(10, s -> uint, intbuf, intbuf + sizeof intbuf, &end, true) */ /* buffer_write(&o, intbuf, end - intbuf); */ break; case say_hex: str_of_int(16, s -> uint, intbuf, intbuf_end - 1, &start, true); _buffer_write(&o, "0x"); buffer_write(&o, start, intbuf_end - start); break; case say_string_list: buffer_push(&o,'['); for(size_t i = 0; s -> strlist[i] != NULL; ++i) { if (i != 0) dprintf(2,", "); _buffer_write(&o,"“"); buffer_write(&o,s -> strlist[i],0); _buffer_write(&o,"”"); } buffer_push(&o,']'); break; } ++s; bright = !bright; } if (c.flags.color) _buffer_write(&o, "\x1b[m\n"); else buffer_push(&o, '\n'); buffer_flush(&o); } enum { _atom_max_str_len = 12 }; struct { atom val; const char* str;} /* first name listed for an atom is treated as the canonical name * by reconstitute() and displayed in usage listings */ atom_strings[] = { /* commands */ {atom_key, "key"}, {atom_key, "privkey"}, {atom_key, "priv"}, {atom_pub, "pub"}, {atom_pub, "pubkey"}, {atom_psk, "psk"}, {atom_cert, "cert"}, {atom_cert, "certificate"}, {atom_sign, "sign"}, {atom_verify, "verify"}, {atom_verify, "vfy"}, {atom_verify, "v"}, {atom_enc, "enc"}, {atom_enc, "encipher"}, {atom_enc, "encrypt"}, {atom_dec, "dec"}, {atom_dec, "decipher"}, {atom_dec, "decrypt"}, {atom_wrap, "wrap"}, {atom_wrap, "armor"}, {atom_unwrap, "unwrap"}, {atom_unwrap, "dearmor"}, {atom_unwrap, "disarmor"}, {atom_gen, "gen"}, {atom_gen, "generate"}, {atom_gen, "create"}, {atom_gen, "new"}, {atom_help, "help"}, /* flags */ {atom_quiet, "quiet"}, {atom_ascii_armor, "ascii-armor"}, {atom_raw, "raw"}, {atom_sig, "sig"}, {atom_sig, "sigfile"}, {atom_sigfd, "sig-fd"}, {atom_sig, "sigfd"}, {atom_verbose, "verbose"}, {atom_noise, "noise"}, {atom_color_on, "color"}, {atom_color_off, "no-color"}, {atom_out, "out"}, {atom_out, "output"}, {atom_out, "outfile"}, {atom_outfd, "out-fd"}, {atom_outfd, "outfd"}, {atom_in, "in"}, {atom_out, "input"}, {atom_out, "infile"}, {atom_infd, "in-fd"}, {atom_infd, "infd"}, {atom_pw, "pw"}, {atom_pw, "passphrase"}, {atom_pw, "password"}, {atom_pwfd, "pw-fd"}, {atom_pwfd, "pwfd"}, {atom_pwtty, "pw-tty"}, {atom_pwtty, "pwtty"}, {atom_fd, "fd"}, {atom_symmetric, "symmetric"}, {atom_symmetric, "sym"}, {atom_symmetric, "symcrypt"}, {atom_fmtv, "format"}, {atom_fmtv, "fmt"}, {atom_fmtv, "format-version"}, /* debug levels */ {atom_silent, "silent"}, {atom_debug, "debug"}, {atom_debug, "dbg"}, {atom_info, "info"}, {atom_warn, "warn"}, {atom_warn, "warnings"}, {atom_fatal, "fatal"}, {atom_fatal, "error"}, }; atom atomize(const char* str) { for(size_t i = 0; i < sz(atom_strings); ++i) { if (strncmp(atom_strings[i].str, str, _atom_max_str_len) == 0) { return atom_strings[i].val; } } return atom_none; } const char* reconstitute(atom a) { for(size_t i = 0; i < sz(atom_strings); ++i) { if (atom_strings[i].val == a) return atom_strings[i].str; } return NULL; } struct flag { enum flag_kind { flag_none, _flag_noparam, flag_on, flag_off, flag_switch, flag_inc, flag_dec, flag_sdec, _flag_param, flag_atom, flag_str, flag_uint, flag_sint, flag_raw_atom, flag_value } kind; char shortname; const char* desc; struct { atom first; atom last; } opts; size_t offset; } #define _declflag_raw(a,k,s,o,rs,re,d) [atom_##a] = { \ .kind = flag_##k, \ .shortname = s, \ .desc = d, \ .opts = { rs,re }, \ .offset = (size_t)&(((struct ctx*)0) -> flags.o) \ }, #define _declflag(a,k,s,o,d) _declflag_raw(a,k,s,o,0,0,d) #define _declflaga(a,rs,re,s,o,d) _declflag_raw(a,atom,s,o,rs,re,d) flags[_atom_n] = { {flag_none}, _declflag(ascii_armor, value + feature_on, 'a', ascii_armor, "emit ascii-encoded data instead of raw binary " "data (default if stdout is a TTY)") _declflag(raw, value + feature_off, 'r', ascii_armor, "emit binary data instead of raw ascii-armored " "data (default if stdout is not a TTY)") _declflag(in, str, 'i', infile, "read data from a file instead of stdin") _declflag(infd, uint, 'I', infd, "read data from a file instead of stdin") _declflag(out, str, 'o', outfile, "write data to a file instead of stdout") _declflag(outfd, uint, 'O', outfd, "write data to a file instead of stdout") _declflag(pw, str, 'p', pwfile, "load passphrase from file instead of stdin") _declflag(pwfd, uint, 'P', pwfd, "load passphrase from file instead of stdin") _declflag(pwtty, switch, 'W', pw_force_tty, "always read password from the input TTY unless a file is specified instead") _declflag(sig, str, 'g', sigfile, "read signature from specified file instead of extracting it from input data") _declflag(sigfd, uint, 'G', sigfd, "read signature from specified file descriptor instead of extracting it from input data") _declflag(symmetric, switch, 's', symcrypt, "secure sensitive key material with symmetric cryptography") _declflag(fd, switch, 'f', fd, "interpret filename arguments to commands as file descriptor numbers") _declflag(color_on, value + feature_on, 'c', color, "enable colorized output (default if stderr is a TTY)") _declflag(color_off, value + feature_off, 'C', color, "disable colorized output") _declflag(verbose, dec, 'v', noise, "increase noise level") _declflag(quiet, value + noise_silent, 'q', noise, "emit nothing to stderr") _declflaga(noise, atom_debug, atom_silent, 'n', noise, "set noise level") _declflag(fmtv, uint, 'F', fmtv, "use a specific data file format version instead of the native version (0)") }; const char** flag_enum_desc[_atom_n] = { [atom_noise] = (const char*[_noise_n]){ [noise_silent] = "run in complete silence", [noise_debug] = "emit all notices, including those useful only for debugging", [noise_info] = "report on the progress of the operation", [noise_warn] = "emit non-fatal warnings", [noise_fatal] = "only emit fatal errors", }, }; #undef _declflag #undef _declflaga #undef _declflag_raw /* the casts here are a bit wacky. this is unfortunately necessary as C * insists on evaluating each branch of the generic expression regardless * of which type is picked. why anyone thought this was a good idea is * beyond me. */ #define _t(x) (_Generic((x), \ const char**: (struct sayp){say_string_list, .strlist = (const char**)(size_t)(x)}, \ char: (struct sayp){say_char, .ch = (char)(size_t)(x)}, \ char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ const char*: (struct sayp){say_string, .str = (const char*)(size_t)(x)}, \ int: (struct sayp){say_sint, .sint = (long long)(x)}, \ short: (struct sayp){say_sint, .sint = (long long)(x)}, \ long: (struct sayp){say_sint, .sint = (long long)(x)}, \ long long: (struct sayp){say_sint, .sint = (long long)(x)}, \ unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ unsigned short: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ long long unsigned: (struct sayp){say_uint, .uint = (long long unsigned)(x)}, \ default: (struct sayp){say_hex, .uint = (size_t)(x)})), #define _say(k,...) say(c, noise_##k, ((struct sayp[]){__VA_ARGS__ {say_stop}})) #define _report(k,s) _say(k, _t(s)) void usage(struct ctx c, const struct cmd_spec* tree) { const char* bin = c.bin; const char* fmt_flag,* fmt_usage,* fmt_enum,* head_start,* head_fin; if (c.flags.color) { fmt_usage = "\x1b[1musage:\x1b[m %s \x1b[32m[flags]\x1b[m%s \x1b[33mcommand\x1b[m\n"; fmt_flag = " \x1b[1;36m%c%c \x1b[;95m--%s\x1b[m: %s\n"; fmt_enum = " \x1b[94m%s\x1b[m: %s\n"; head_start = "\x1b[1m"; head_fin = "\x1b[m"; } else { fmt_usage = "usage: %s [flags]%s \n"; fmt_flag = " %c%c --%s: %s\n"; fmt_enum = " %s: %s\n"; head_start = head_fin = ""; } char ccmd[32]; char *cptr = ccmd; *cptr = 0; if (tree == NULL) tree = cmd_specs; else if(c.cmdc > 1) for (size_t i = 1; i", tree[i].params[j]); } } dprintf(2,"%s: %s\n", tree[i].sub != NULL ? " …" : "", tree[i].desc); } dprintf(2,"%sflags:%s\n",head_start,head_fin); for (size_t i = _atom_cmd_n + 1; i<_atom_flag_n; ++i) { /* if (flags[i].kind == flag_none) continue; */ dprintf(2,fmt_flag, flags[i].shortname != 0 ? '-' : ' ', flags[i].shortname != 0 ? flags[i].shortname : ' ', reconstitute(i), flags[i].desc); if (flags[i].kind == flag_atom || flags[i].kind == flag_raw_atom) { for (atom a = flags[i].opts.first; a < flags[i].opts.last + 1; ++a) { dprintf(2,fmt_enum,reconstitute(a),flag_enum_desc[i][a - flags[i].opts.first]); } } } } bool atom_match(struct ctx c, size_t* match) { /* primitive pattern-matching function */ for (size_t i = 0; i < c.cmdc; ++i) { if (match[i] == 0) return false; if (match[i] < atom_capture) { if (match[i] != c.cmdv[i]) return false; } else if (c.cmdv[i] >= _atom_n) { const char** dest = (const char**)(match[i] - atom_capture); *dest = c.argv[c.cmdv[i] - _atom_n]; } else { return false; } } return match[c.cmdc] == 0; } /* the ascii armory is intentionally limited to characters that are * not only printable but that are unlikely to have special meaning * such that they would require escaping for any form of input. * while this is a more limited set than base64, it's significantly * stronger armor -- a user can, for instance, simply paste it into * most shells without quoting. */ const char ascii_armory[] = "0123456789/abcdefghijklmopqrstuvwxyz." "-ABCDEFGHIJKLMOPQRSTUVWXYZ_"; const char ascii_value[] = "\x25\x24\x0a\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\00\00\00" "\00\00\00\00\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" "\x31\x32\00\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e" "\00\00\00\00\x3f\00\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13" "\x14\x15\x16\x17\00\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21" "\x22"; /* generated by a script. do not touch. */ enum { armor_split_every = 48 }; char* armor(const unsigned char* src, char* dest, size_t sz, size_t brstart, bool format) { char* bufptr = dest; unsigned char carry = 0; for(size_t i = 0; i> 2)), b2 = ascii_value[s[1] - '-'] + (64 * ( carry & 3)); *(dest++)=b1; *(dest++)=b2; } return dest; } struct pstr { size_t len; const char* str; }; #define _p(x) {sizeof(x)-1,(x)} struct encoding { struct pstr bin; struct pstr asc; const char* desc;}; enum doc { doc_none, doc_header, doc_footer, _doc_versions, doc_version_1, doc_version_2, doc_version_3, _doc_types, doc_type_priv /* private key */, doc_type_pub /* public key */, doc_type_psk /* shared secret key */, doc_type_asc /* arbitrary ascii-armored data */, doc_type_sig /* separate signature */, doc_type_signed /* document prefixed with signature */, doc_type_master /* master key used to derive keys deterministically */, _doc_n }; const struct encoding docbytes[_doc_n] = { {0,NULL}, [doc_header] = {_p("\x26\xe9\x11\x1a"), _p("[" _self_name)}, [doc_footer] = {_p(""), _p("]")}, [doc_version_1] = {_p("\01"), _p("1-")}, [doc_version_2] = {_p("\02"), _p("2-")}, [doc_version_3] = {_p("\03"), _p("3-")}, [doc_type_priv] = {_p("\01"), _p("priv-"),"private key material"}, [doc_type_pub] = {_p("\02"), _p("pub-"), "public key material"}, [doc_type_psk] = {_p("\03"), _p("psk-"), "shared key material"}, [doc_type_asc] = {_p("\04"), _p("asc-"), "ascii-armored binary data"}, [doc_type_sig] = {_p("\05"), _p("sig-"), "separate signature"}, [doc_type_signed] = {_p("\06"), _p("signed-"), "data with signature"}, [doc_type_master] = {_p("\07"), _p("master-"), "master key"}, }; char* compare_wsi(const char* s, char* d, size_t sz) { for (size_t i=0; iraw + len, run - len)) { if (e == -1) { switch(errno) { case EBADF: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("does not reference an open, readable file")); break; case EFAULT: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("references a file outside your accessible address space")); break; case EIO: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be read due to an I/O error")); break; case EISDIR: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("is a directory, not a readable file")); break; default: _say(fatal, _t("file descriptor") _t((unsigned)fd) _t("could not be opened due to an unknown error - consult errno table for code") _t((unsigned)errno)); break; } goto err; } len += e; if (len == run) { run *= 2; buf = realloc(buf, run + sizeof(struct file)); } else if (len < run) continue; } buf -> rawlen = len; buf -> type = doc_none; return buf; err: free(buf); return NULL; } struct file* read_file(struct ctx c, const int fd) { struct file* buf = load_file(c, fd); bool binary; size_t len = buf -> rawlen; char* cur = buf -> raw; if (file_cmp(doc_header,&cur,true)) { _report(debug, "parsing binary data"); binary = true; } else if (file_cmp(doc_header,&cur,false)) { _report(debug, "parsing ascii-armored data"); binary = false; } else { _say(fatal, _t("cannot parse data from fd") _t((unsigned)fd)); goto err; } /* write(2, cur, run - (cur - buf->raw)); */ for (enum doc i = _doc_versions + 1; i < _doc_types; ++ i) { if (file_cmp(i, &cur, binary)) { _say(debug, _t("file is format version") _t((unsigned) i - _doc_versions)); buf -> version = i; goto found_version; } } /* else */ { _say(fatal, _t("unrecognized file format version; are you using the most recent release of " _self_name "?")); goto err; } found_version:; for (enum doc i = _doc_types + 1; i < _doc_n; ++ i) { if (file_cmp(i, &cur, binary)) { _say(debug, _t("file contains") _t(docbytes[i].desc)); buf -> type = i; goto found_type; } } /* else */ { _say(fatal, _t("unrecognized file type; are you using the most recent release of " _self_name "?")); goto err; } found_type: if (!binary) { size_t s = (len - (cur - buf->raw)) - docbytes[doc_footer].asc.len; _say(debug, _t("disarmoring") _t(s) _t("bytes of encoded text")); char* end = disarmor(cur, buf -> raw, s); buf -> datlen = end - cur; } else { buf -> datlen = (len - (cur - buf->raw)) - docbytes[doc_footer].bin.len; } buf -> datastart = cur; return buf; err: free(buf); return NULL; } void write_armored_v1(struct ctx c, const unsigned char* data, size_t sz) { struct buffer o = {c.flags.outfd,0}; # define _wra_fld(f) buffer_write(&o, docbytes[doc_##f].asc.str,\ docbytes[doc_##f].asc.len); _wra_fld(header); _wra_fld(version_1); _wra_fld(type_asc); char buf[sz*2]; char* end = armor(data,buf,sz,13, isatty(c.flags.outfd)); buffer_write(&o, buf, end - buf); _wra_fld(footer); # undef _wra_fld if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); buffer_flush(&o); } void write_privkey_v1(struct ctx c, const unsigned char* material) { struct buffer o = {c.flags.outfd,0}; # define _wrp_fld(f) buffer_write(&o, \ (c.flags.ascii_armor ? docbytes[doc_##f].asc.str \ : docbytes[doc_##f].bin.str), \ (c.flags.ascii_armor ? docbytes[doc_##f].asc.len \ : docbytes[doc_##f].bin.len)) _wrp_fld(header); _wrp_fld(version_1); _wrp_fld(type_priv); if (c.flags.ascii_armor) { char buf[crypto_sign_SECRETKEYBYTES * 2]; char* end = armor(material,buf,crypto_sign_SECRETKEYBYTES,13, isatty(c.flags.outfd)); buffer_write(&o, buf, end - buf); } else { buffer_write(&o, material, crypto_sign_SECRETKEYBYTES); } _wrp_fld(footer); if (isatty(c.flags.outfd)) buffer_push(&o, '\n'); buffer_flush(&o); } typedef void (*write_privkey_t)(struct ctx, const unsigned char*); typedef void (*write_armored_t)(struct ctx, const unsigned char*, size_t); write_privkey_t write_privkey[] = { [0] = write_privkey_v1, /* current version */ [1] = write_privkey_v1, }; write_armored_t write_armored[] = { [0] = write_armored_v1, /* current version */ [1] = write_armored_v1, }; int route(struct ctx c) { if (sodium_init() < 0) { _report(fatal, "could not initialize libsodium"); return 2; } switch (c.cmdv[0]) { case atom_key: { switch (c.cmdv[1]) { case atom_gen: { if (c.cmdc != 2) { usage(c,NULL); return 1; } _report(info, "generating new private key"); if (c.flags.symcrypt == false) { _say(warn, _t("not using symmetric encryption; key will be emitted in") _t("plain text")); } else { /* read passphrase from stdin/file */ } /* libsodium is a bit weird. it uses different key types for * encrypting and signing. we emit a signing (ed25519) key * because it can be converted to an encryption key, but not * vice-versa. libsodium also does not have any means to * derive a public key from a private key, even though this * is trivial, but instead stores the public key in the second * half of the private key (despite emitting it AGAIN into the * second parameter). so we generate a signing keypair, discard * the returned public key, and save the "private key" (including * its copy of the public key). this is extremely awkward and * hopefully a better solution will be found in the fullness of * time. */ unsigned char skey[crypto_sign_SECRETKEYBYTES]; { unsigned char _[crypto_sign_PUBLICKEYBYTES]; crypto_sign_keypair(_,skey); } (*write_privkey[c.flags.fmtv])(c, skey); } break; case atom_pub: { if (c.cmdc != 2) { usage(c,NULL); return 1; } struct file* skey = read_file(c, c.flags.infd); if (skey == NULL) return 6; free(skey); } break; case atom_wrap: { if (c.cmdc != 2) { usage(c,NULL); return 1; } struct file* key = read_file(c, c.flags.infd); c.flags.ascii_armor = feature_on; if (key -> type == doc_type_priv) { (*write_privkey[c.flags.fmtv])(c, key -> datastart); } else if (key -> type == doc_type_pub) { /* (*write_pubkey[c.flags.fmtv])(c, key); */ } else { _report(fatal, "file is not a " _self_name " key"); free(key); return 8; } free(key); return 0; } break; default: goto no_cmd; } } break; case atom_wrap: { if (c.cmdc != 1) { usage(c,NULL); return 1; } struct file* data = load_file(c, c.flags.infd); if (data == NULL) return 6; (*write_armored[c.flags.fmtv])(c, data->raw, data->rawlen); free(data); return 0; } break; case atom_unwrap: { if (c.cmdc != 1) { usage(c,NULL); return 1; } struct file* data = read_file(c, c.flags.infd); if (data == NULL) return 6; if (isatty(c.flags.outfd)) _say(warn, _t("printing binary values to tty")); write(c.flags.outfd, data->datastart, data->datlen); free(data); return 0; } break; case atom_help: { if (c.cmdc == 1) usage(c,NULL); else { const struct cmd_spec* tree = cmd_specs; for (size_t i = 1; i < c.cmdc; ++i) { if (c.cmdv[i] < _atom_n) { if (tree[c.cmdv[i]].sub != NULL) { tree = tree[c.cmdv[i]].sub; } else { _say(fatal, _t("no help available for subcommand") _t(reconstitute(c.cmdv[i]))); return 3; } } else { _say(fatal, _t("no help available;") _t(c.argv[c.cmdv[i] - _atom_n]) _t("is not a recognized command")); return 4; } } usage(c,tree); } return 0; } break; default: goto no_cmd; } return 0; no_cmd: _say(fatal, _t("no such command")); usage(c,NULL); return 1; } int open_file(struct ctx c, const char* path, int flags, word* retfd) { int fd = open(path,flags,0600); if (fd == -1) { switch (errno) { case EACCES: _say(fatal, _t("file") _t(path) _t("could not be opened because you do not have permission to read it")); break; case EFAULT: _say(fatal, _t("file") _t(path) _t("could not be opened because the path is outside your accessible address space")); break; case EISDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because it is a directory, not a regular file")); break; case ELOOP: _say(fatal, _t("file") _t(path) _t("could not be opened because too many symbolic links were encountered")); break; case EMFILE: _say(fatal, _t("file") _t(path) _t("could not be opened because the per-process file limit is too low")); break; case ENOENT: _say(fatal, _t("file") _t(path) _t("could not be opened because it does not exist")); break; case ENOMEM: _say(fatal, _t("file") _t(path) _t("could not be opened, either because not enough memory remains to make use of a FIFO or because the kernel is running out of memory (!)")); break; case ENOTDIR: _say(fatal, _t("file") _t(path) _t("could not be opened because a file was specified in place of a directory in the path")); break; case ENXIO: _say(fatal, _t("file") _t(path) _t("could not be opened because it is not a regular file or FIFO")); break; case EPERM: _say(fatal, _t("file") _t(path) _t("could not be opened because it is sealed against access")); break; case EROFS: _say(fatal, _t("file") _t(path) _t("could not be opened for writing because it is located on a read-only filesystem")); break; default: _say(fatal, _t("file") _t(path) _t("could not be opened for unclear reasons - consult errno entry for code") _t((unsigned)errno)); break; } return errno; } *retfd = fd; return 0; } int main(unsigned int argc, const char** argv) { atom cmdlist[argc]; const char* paramlist[argc]; size_t paramcount = 0; struct ctx c = { .argv = paramlist, .cmdc = 0, .cmdv = cmdlist, .bin = (argc > 0 ? argv[0] : NULL), .flags = { .ascii_armor = feature_auto, .color = feature_auto, .noise = noise_fatal, .infile = NULL, .infd = 0, .outfile = NULL, .outfd = 1, .pwfile = NULL, .pwfd = 0, .pw_force_tty = false, .sigfile = NULL, .sigfd = 0, /* if sigfd == infd, extract sig from input stream */ .fd = false, .symcrypt = false, .fmtv = 0, /* 0 = native version */ }, }; if (argc == 0) { _report(fatal, "invoked incorrectly or your environment is not sane; the 0th value of the argument array should be the name of or path to the program you are attempting to execute"); return -1; } struct flag* paramstack[argc]; size_t stackptr = 0; enum { reading_params, reading_commands, reading_flags } reading_mode = reading_flags; _say(debug, _t("parsing command line") _t(argv)); for (size_t i = 1; i < argc; ++i) { const char* const p = argv[i]; if (reading_mode >= reading_flags && p[0] == '-') { /* flag */ bool longflag; if (p[1] == '-') { if (p[2] == 0) { /* "--" has been passed; all further arguments must be * interpreted as parameters or commands, not flags */ reading_mode = reading_commands; continue; } else if (p[2] == '-' && p[3] == 0) { /* "---" has been passed; all further arguments must be * interpreted as parameters, not flags or commands */ reading_mode = reading_params; continue; } else longflag = true; } else longflag = false; _say(debug, _t("parsing flag") _t(p)); for (size_t o = 1; p[o] != 0; ++o) { atom option = atom_none; if (longflag) option = atomize(p+2); else { /* short flag */ _say(debug, _t("parsing short flag") _t(p[o])); for (size_t j = 0; j < sz(flags); ++ j) if (flags[j].kind != flag_none && flags[j].shortname == p[o]) { option = (atom)j; break; } } if (option == atom_none) { if (!longflag && p[o] == '-') { if (p[o+1] == '-' && p[o+2] == 0) { /* shorthand for --- */ reading_mode = reading_params; continue; } else if (p[o+1] == 0) { /* shorthand for -- */ reading_mode = reading_commands; continue; } } char ftext[3] = {'-',p[o],0}; _say(fatal, _t("flag") _t(longflag ? p+2 : ftext) _t("not meaningful")); goto parse_fail; } handle_flag:; struct flag* f = flags + option; void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); if (f -> kind > _flag_param) { if (f->kind>=flag_value) { *((unsigned*)fld) = f->kind - flag_value; } else paramstack[stackptr++] = f; } else if (f -> kind > _flag_noparam) { switch(f -> kind) { case flag_on: *((bool*)fld) = true; break; case flag_off: *((bool*)fld) = false; break; case flag_switch: *((bool*)fld) = !*((bool*)fld); break; /* in order to avoid creepy behavior in pathological cases, * we enforce saturation arithmetic for the increment/decrement * flag types */ case flag_inc: { unsigned* v = fld; if (*v < UINT_MAX) ++*v; } break; case flag_dec: { unsigned* v = fld; if (*v > 0) --*v; } break; case flag_sdec: { signed* v = fld; if (*v > INT_MIN) --*v; } break; } } if (p[1] == '-') break; } continue; } if (stackptr > 0) { struct flag* f = paramstack[--stackptr]; _say(debug, _t("handling argument for flag") _t(reconstitute(f - flags))); void* fld = ((unsigned char*)&c) + (unsigned char)(f -> offset); switch(f -> kind) { case flag_str: *((const char**)fld) = p; break; case flag_uint: { iaia_error_type e = int_of_str(10, p, (unsigned long long*)fld); switch (e) { case iaia_e_ok: continue; case iaia_e_domain: _say(fatal, _t("argument") _t(p) _t("outside domain for base")); } return 5; } break; case flag_atom: case flag_raw_atom: { atom a = atomize(p); if (a == atom_none || a < f -> opts.first || a > f -> opts.last) { _say(fatal, _t("invalid argument") _t(p) _t("for flag") _t(reconstitute(f - flags))); goto parse_fail; } *((atom*)fld) = f->kind == flag_raw_atom ? a : a - f -> opts.first; } break; } continue; } if (reading_mode >= reading_commands) { if (p[0] == '=') { /* parameter even if atomizable */ paramlist[paramcount] = p + 1; cmdlist[c.cmdc] = _atom_n + paramcount; ++c.cmdc; ++ paramcount; continue; } else { atom a = atomize(p); if (a != atom_none) { cmdlist[c.cmdc] = a; ++ c.cmdc; continue; } } } /* only parameters remain to be read */ paramlist[paramcount] = p; cmdlist[c.cmdc] = _atom_n + paramcount; ++ c.cmdc; ++ paramcount; } parse_done: _say(debug, _t("stored") _t(c.cmdc) _t("arguments and") _t(paramcount) _t("parameters")); if (c.flags.noise <= noise_debug) for (size_t i = 0; i= sz(write_privkey)) { _say(fatal, _t("data format version") _t(c.flags.fmtv) _t("is not known. the highest available version is") _t(sz(write_privkey) - 1) _t("- are you sure you're using the latest release of " _self_name "?")); return -2; } int err; if (c.flags.pwfile != NULL) { err = open_file(c,c.flags.pwfile, O_RDONLY, &c.flags.pwfd); if(err) return err; } if (c.flags.infile != NULL) { err = open_file(c,c.flags.infile, O_RDONLY, &c.flags.infd); if(err) return err; } if (c.flags.outfile != NULL) { err = open_file(c,c.flags.outfile, O_WRONLY | O_TRUNC | O_CREAT, &c.flags.outfd); if(err) return err; } if (c.flags.sigfile != NULL) { err = open_file(c,c.flags.sigfile, O_RDONLY, &c.flags.sigfd); if(err) return err; } if (c.flags.color == feature_auto) c.flags.color = isatty(2); if (c.flags.ascii_armor == feature_auto) c.flags.ascii_armor = isatty(c.flags.outfd); /* this is hateful but i can't figure out a better way */ if (c.flags.pw_force_tty && !isatty(c.flags.pwfd)) { _say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as it is not a tty and -W was passed;") _t("/dev/tty") _t("will be used instead")); } else if (c.flags.pwfd == c.flags.infd) { _say(debug, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as input data is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead")); } else if (c.flags.pwfd == c.flags.sigfd) { _say(warn, _t("passphrases cannot be read from fd") _t(c.flags.pwfd) _t("as the signature is already being read from that descriptor;") _t("/dev/tty") _t("will be used instead")); } else goto skip_pwfd_change; pwfd_change: { err = open_file(c,"/dev/tty", O_WRONLY, &c.flags.pwfd); if(err) return err; } skip_pwfd_change: _say(debug, _t("input fd =") _t(c.flags.infd)); _say(debug, _t("output fd =") _t(c.flags.outfd)); _say(debug, _t("pw fd =") _t(c.flags.pwfd)); if (c.cmdc > 0) return route(c); parse_fail: if (c.flags.noise < noise_silent) usage(c,NULL); return 1; }