From 578691f45efed9c15bcb6e93001dc01858b56d44 Mon Sep 17 00:00:00 2001 From: Mirek Kratochvil Date: Sun, 10 Sep 2017 17:29:23 +0200 Subject: [PATCH] secret-locking on symmetric keys --- autogen.sh | 2 +- configure.ac | 17 ++++ man/ccr.1 | 50 ++++++++++- src/actions.cpp | 216 +++++++++++++++++------------------------------- src/actions.h | 27 ++++-- src/arcfour.h | 43 +++++++++- src/generator.h | 7 +- src/main.cpp | 85 ++++++++++++++----- src/pwrng.cpp | 99 ++++++++++++++++++++++ src/pwrng.h | 90 ++++++++++++++++++++ src/symkey.cpp | 122 +++++++++++++++++++++++++++ src/symkey.h | 7 +- 12 files changed, 587 insertions(+), 178 deletions(-) create mode 100644 src/pwrng.cpp create mode 100644 src/pwrng.h diff --git a/autogen.sh b/autogen.sh index 271d2c0..2f7d9d5 100755 --- a/autogen.sh +++ b/autogen.sh @@ -28,7 +28,7 @@ echo "${NAME}_CPPFLAGS = -I\$(srcdir)/$i/ ${COMMON_CPPFLAGS}" >>$OUT echo "${NAME}_CFLAGS = ${COMMON_CFLAGS}" >>$OUT echo "${NAME}_CXXFLAGS = ${COMMON_CXXFLAGS}" >>$OUT echo "${NAME}_LDFLAGS = ${COMMON_LDFLAGS} \$(CRYPTOPP_CFLAGS) " >>$OUT -echo "${NAME}_LDADD = -lgmp -lfftw3 -lm \$(CRYPTOPP_LIBS) ${COMMON_LDADD} " >>$OUT +echo "${NAME}_LDADD = -lgmp -lfftw3 -lm \$(CRYPTOPP_LIBS) ${COMMON_LDADD} \$(EXTRA_LIBS) " >>$OUT if [[ "$OSTYPE" == "darwin"* ]]; then glibtoolize --force && aclocal && autoconf && automake --add-missing diff --git a/configure.ac b/configure.ac index 4f3b083..cfea2d0 100644 --- a/configure.ac +++ b/configure.ac @@ -53,6 +53,22 @@ else AC_DEFINE([HAVE_CRYPTOPP], [0]) fi +#check for readpassphrase. If none is found, we use getpass (with a warning) +AC_CHECK_HEADER([readpassphrase.h], + [READPASSPHRASE=native], + AC_CHECK_HEADER([bsd/readpassphrase.h], + [READPASSPHRASE=bsd], )) + +if test "$READPASSPHRASE" = "native"; then + AC_DEFINE([HAVE_READPASSPHRASE], [1]) +fi +if test "$READPASSPHRASE" = "bsd"; then + AC_DEFINE([HAVE_BSDREADPASSPHRASE], [1]) + AC_CHECK_LIB([bsd], [readpassphrase], + [LIBS="-lbsd $LIBS"], #is there a better version of this? + AC_MSG_ERROR([library for bsd/readpassphrase.h not found])) +fi + #check for standard functions AC_CHECK_FUNCS([memset mkdir], , AC_MSG_ERROR([Required function missing])) @@ -63,6 +79,7 @@ AC_CHECK_HEADERS([fcntl.h inttypes.h stddef.h stdlib.h string.h sys/file.h unist AC_CHECK_HEADER_STDBOOL AC_C_INLINE AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINT8_T diff --git a/man/ccr.1 b/man/ccr.1 index e602022..e5983f0 100644 --- a/man/ccr.1 +++ b/man/ccr.1 @@ -1,4 +1,4 @@ -.TH CCR 1 2016-01-12 "ccr" "Codecrypt" +.TH CCR 1 2017-10-22 "ccr" "Codecrypt" .SH NAME .B ccr \- The post-quantum cryptography encryption and signing tool @@ -227,6 +227,12 @@ Rename matching public keys. Use "-N" to specify a new name. \fB\-M\fR, \fB\-\-rename\-secret\fR <\fIkeyspec\fR> Rename matching private keys. +.TP +\fB\-w\fR, \fB\-\-with-lock\fR <\fIfile\fR> +When loading the secret part of the keyring, decrypt the file using the +specified shared key. If that file looks encrypted and \fB-w\fR is not +specified, asking for the password interactively (i.e. "-w @") will be assumed. + .SH FILES Codecrypt stores user data in a directory specified by environment variable @@ -300,6 +306,40 @@ idealized case and very roughly) halves the bit security (although the attack remains exponential). Users who are aware of large quantum computers being built are advised to use 2^192 or 2^256 bit security keys. +.SH PASSWORD-DERIVED SYMMETRIC KEYS AND PASSWORD-PROTECTED SECRETS + +Symmetric keys can be specified using a filename, or expanded from a password +(which is convenient e.g. for protecting private keys): If the filename for +\fB-S\fR starts with "@", program will first check the rest of the filename to +find a symmetric cipher algorithm specification, as in \fB-g\fR. If nothing is +specified, it will check CCR_SYMMETRIC_ALGORITHM environment variable, and if +that is still unspecified, it will default to "SYM,SHORTBLOCK". The reason for +defaulting the short blocks is that the functionality focuses on tiny keys. + +After the symmetric algorithm is chosen, program will try to get the password +from environment variable CCR_SYMMETRIC_PASSWORD. If that variable is not set, +it will ask the user for the password interactively. + +The password will be expanded to internally form a symmetric key for the +specified algorithm, which will in turn be used for the requested action. + +Symmetric and private keys may be encrypted by a password or a symmetric key. +Parameter \fB-w\fR accepts the same arguments as \fB-S\fR, with the exception +that the resulting loaded or internally generated symmetric key will be used to +encrypt or decrypt symmetric and private keys when required: + +Actions \fB-L\fR and \fB-U\fR can be used to lock, resp. unlock private keys +(specific keys to be modified can be selected using \fB--filter\fR) or +symmetric keys (if used together with \fB-S\fR). Action \fB-g\fR can be +modified by \fB-L\fR in the same way. + +The environment variables used for automatically-specifying the password in +this case are separate from the previous ones: CCR_KEYRING_PASSWORD and +CCR_KEYRING_ALGORITHM for locking/unlocking private keys, respectively +CCR_SYMKEY_PASSWORD and CCR_SYMKEY_ALGORITHM for specifying symmetric key used +to unlock other symmetric keys (even the ones that are themselves used for +locking other keys). + .SH WARNINGS AND CAVEATS .SS General advice @@ -369,6 +409,14 @@ Codecrypt. If the seed source of your system can not be trusted, fix the system instead. +.SH Password-derived symmetric keys + +Passwords are weak and, if times did not change and humanoids are still +humanoids, you are prone to $5 wrench attacks. + +Combination of \fB-L\fR and \fB-S\fR options can be exploited to output a +password-expanded key to a file. Doing that for any real purpose is a bad idea. + .SH Troubleshooting/FAQ Q: I can't read/verify messages from versions 1.3.1 and older! diff --git a/src/actions.cpp b/src/actions.cpp index 49c015f..f05a95b 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -41,7 +41,6 @@ #define ENVELOPE_CLEARSIGN "clearsigned" #define ENVELOPE_DETACHSIGN "detachsign" #define ENVELOPE_HASHFILE "hashfile" -#define ENVELOPE_SYMKEY "symkey" #define MSG_CLEARTEXT "MESSAGE-IN-CLEARTEXT" #define MSG_DETACHED "MESSAGE-DETACHED" @@ -59,8 +58,10 @@ inline bool open_keyring (keyring&KR) #define PREPARE_KEYRING if(!open_keyring(KR)) return 1 -int action_gen_symkey (const std::string&algspec, - const std::string&symmetric, bool armor) +static int action_gen_symkey (const std::string&algspec, + const std::string&symmetric, + const std::string&withlock, + bool armor, bool force_lock) { symkey sk; ccr_rng r; @@ -71,42 +72,13 @@ int action_gen_symkey (const std::string&algspec, return 1; } - sencode*SK = sk.serialize(); - std::string data = SK->encode(); - sencode_destroy (SK); - - std::ofstream sk_out; - sk_out.open (symmetric == "-" ? "/dev/stdout" : symmetric.c_str(), - std::ios::out | std::ios::binary); - if (!sk_out) { - err ("error: can't open symkey file for writing"); - return 1; - } - - if (armor) { - std::vector parts; - parts.resize (1); - base64_encode (data, parts[0]); - data = envelope_format (ENVELOPE_SYMKEY, parts, r); - } - - sk_out << data; - if (!sk_out.good()) { - err ("error: can't write to symkey file"); - return 1; - } - - sk_out.close(); - if (!sk_out.good()) { - err ("error: couldn't close symkey file"); - return 1; - } + if (!sk.save (symmetric, "", armor, force_lock, r)) return 1; return 0; } typedef std::map algspectable_t; -algspectable_t& algspectable() +static algspectable_t& algspectable() { static algspectable_t table; static bool init = false; @@ -119,11 +91,10 @@ algspectable_t& algspectable() table["SIG-192"] = "FMTSEQ192C-CUBE384-CUBE192"; table["SIG-256"] = "FMTSEQ256C-CUBE512-CUBE256"; + table["SYM"] = "CHACHA20,CUBE512"; #if HAVE_CRYPTOPP==1 - table["SYM"] = "CHACHA20,SHA256"; table["SYM-COMBINED"] = "CHACHA20,XSYND,ARCFOUR,CUBE512,SHA512"; #else - table["SYM"] = "CHACHA20,CUBE512"; table["SYM-COMBINED"] = "CHACHA20,XSYND,ARCFOUR,CUBE512"; #endif @@ -134,7 +105,8 @@ algspectable_t& algspectable() } int action_gen_key (const std::string& p_algspec, const std::string&name, - const std::string&symmetric, bool armor, + const std::string&symmetric, const std::string&withlock, + bool armor, bool force_lock, keyring&KR, algorithm_suite&AS) { std::string algspec = to_unicase (p_algspec); @@ -186,7 +158,8 @@ int action_gen_key (const std::string& p_algspec, const std::string&name, //handle symmetric operation if (symmetric.length()) - return action_gen_symkey (algspec, symmetric, armor); + return action_gen_symkey (algspec, symmetric, withlock, + armor, force_lock); algorithm*alg = NULL; std::string algname; @@ -261,57 +234,11 @@ int action_gen_key (const std::string& p_algspec, const std::string&name, * signatures/encryptions */ -int action_sym_encrypt (const std::string&symmetric, bool armor) +static int action_sym_encrypt (const std::string&symmetric, + const std::string&withlock, bool armor) { - //read the symmetric key first - std::ifstream sk_in; - sk_in.open (symmetric == "-" ? "/dev/stdin" : symmetric.c_str(), - std::ios::in | std::ios::binary); - - if (!sk_in) { - err ("error: can't open symkey file"); - return 1; - } - - std::string sk_data; - if (!read_all_input (sk_data, sk_in)) { - err ("error: can't read symkey"); - return 1; - } - sk_in.close(); - - if (armor) { - std::vector parts; - std::string type; - if (!envelope_read (sk_data, 0, type, parts)) { - err ("error: no data envelope found"); - return 1; - } - - if (type != ENVELOPE_SYMKEY || parts.size() != 1) { - err ("error: wrong envelope format"); - return 1; - } - - if (!base64_decode (parts[0], sk_data)) { - err ("error: malformed data"); - return 1; - } - } - - sencode*SK = sencode_decode (sk_data); - if (!SK) { - err ("error: could not parse input sencode"); - return 1; - } - symkey sk; - if (!sk.unserialize (SK)) { - err ("error: could not parse input structure"); - return 1; - } - - sencode_destroy (SK); + if (!sk.load (symmetric, withlock, true, armor)) return 1; ccr_rng r; if (!r.seed (256)) SEED_FAILED; @@ -326,10 +253,11 @@ int action_sym_encrypt (const std::string&symmetric, bool armor) int action_encrypt (const std::string&recipient, bool armor, const std::string&symmetric, + const std::string&withlock, keyring&KR, algorithm_suite&AS) { if (symmetric.length()) - return action_sym_encrypt (symmetric, armor); + return action_sym_encrypt (symmetric, withlock, armor); //first, read plaintext std::string data; @@ -403,56 +331,11 @@ int action_encrypt (const std::string&recipient, bool armor, } -int action_sym_decrypt (const std::string&symmetric, bool armor) +static int action_sym_decrypt (const std::string&symmetric, + const std::string&withlock, bool armor) { - std::ifstream sk_in; - sk_in.open (symmetric == "-" ? "/dev/stdin" : symmetric.c_str(), - std::ios::in | std::ios::binary); - - if (!sk_in) { - err ("error: can't open symkey file"); - return 1; - } - - std::string sk_data; - if (!read_all_input (sk_data, sk_in)) { - err ("error: can't read symkey"); - return 1; - } - sk_in.close(); - - if (armor) { - std::vector parts; - std::string type; - if (!envelope_read (sk_data, 0, type, parts)) { - err ("error: no data envelope found"); - return 1; - } - - if (type != ENVELOPE_SYMKEY || parts.size() != 1) { - err ("error: wrong envelope format"); - return 1; - } - - if (!base64_decode (parts[0], sk_data)) { - err ("error: malformed data"); - return 1; - } - } - - sencode*SK = sencode_decode (sk_data); - if (!SK) { - err ("error: could not parse input sencode"); - return 1; - } - symkey sk; - if (!sk.unserialize (SK)) { - err ("error: could not parse input structure"); - return 1; - } - - sencode_destroy (SK); + if (!sk.load (symmetric, withlock, false, armor)) return 1; int ret = sk.decrypt (std::cin, std::cout); @@ -461,10 +344,11 @@ int action_sym_decrypt (const std::string&symmetric, bool armor) } int action_decrypt (bool armor, const std::string&symmetric, + const std::string&withlock, keyring&KR, algorithm_suite&AS) { if (symmetric.length()) - return action_sym_decrypt (symmetric, armor); + return action_sym_decrypt (symmetric, withlock, armor); std::string data; read_all_input (data); @@ -566,7 +450,7 @@ int action_decrypt (bool armor, const std::string&symmetric, return 0; } -int action_hash_sign (bool armor, const std::string&symmetric) +static int action_hash_sign (bool armor, const std::string&symmetric) { hashfile hf; if (!hf.create (std::cin)) { @@ -612,6 +496,7 @@ int action_hash_sign (bool armor, const std::string&symmetric) int action_sign (const std::string&user, bool armor, const std::string&detach, bool clearsign, const std::string&symmetric, + const std::string&withlock, keyring&KR, algorithm_suite&AS) { //symmetric processing has its own function @@ -748,7 +633,7 @@ int action_sign (const std::string&user, bool armor, const std::string&detach, return 0; } -int action_hash_verify (bool armor, const std::string&symmetric) +static int action_hash_verify (bool armor, const std::string&symmetric) { // first, input the hashfile std::ifstream hf_in; @@ -807,6 +692,7 @@ int action_hash_verify (bool armor, const std::string&symmetric) int action_verify (bool armor, const std::string&detach, bool clearsign, bool yes, const std::string&symmetric, + const std::string&withlock, keyring&KR, algorithm_suite&AS) { //symmetric processing has its own function @@ -1048,6 +934,7 @@ int action_verify (bool armor, const std::string&detach, */ int action_sign_encrypt (const std::string&user, const std::string&recipient, + const std::string&withlock, bool armor, keyring&KR, algorithm_suite&AS) { /* @@ -1161,6 +1048,7 @@ int action_sign_encrypt (const std::string&user, const std::string&recipient, int action_decrypt_verify (bool armor, bool yes, + const std::string&withlock, keyring&KR, algorithm_suite&AS) { std::string data; @@ -1872,3 +1760,53 @@ int action_rename_sec (bool yes, } return 0; } + +/* + * locking/unlocking + */ + +static int action_lock_symkey (const std::string&symmetric, + const std::string&withlock, + bool armor) +{ + symkey sk; + if (!sk.load (symmetric, "", true, armor)) return 1; + ccr_rng r; + if (!r.seed (256)) SEED_FAILED; + if (!sk.save (symmetric, withlock, armor, true, r)) return 1; + return 0; +} + +int action_lock_sec (const std::string&filter, + const std::string&symmetric, + const std::string&withlock, + bool armor, + keyring&) +{ + if (!symmetric.empty()) + return action_lock_symkey (symmetric, withlock, armor); + return 1; +} + +static int action_unlock_symkey (const std::string&symmetric, + const std::string&withlock, + bool armor) +{ + symkey sk; + if (!sk.load (symmetric, withlock, false, armor)) return 1; + ccr_rng r; + if (!r.seed (256)) SEED_FAILED; + if (!sk.save (symmetric, "", armor, false, r)) return 1; + return 0; +} + +int action_unlock_sec (const std::string&filter, + const std::string&symmetric, + const std::string&withlock, + bool armor, + keyring&) +{ + if (!symmetric.empty()) + return action_unlock_symkey (symmetric, withlock, armor); + return 1; +} diff --git a/src/actions.h b/src/actions.h index 76d8512..725722b 100644 --- a/src/actions.h +++ b/src/actions.h @@ -29,7 +29,8 @@ #include "algorithm.h" int action_gen_key (const std::string& algspec, const std::string&name, - const std::string&symmetric, bool armor, + const std::string&symmetric, const std::string&withlock, + bool armor, bool force_lock, keyring&, algorithm_suite&); /* @@ -37,24 +38,25 @@ int action_gen_key (const std::string& algspec, const std::string&name, */ int action_encrypt (const std::string&recipient, bool armor, - const std::string&symmetric, + const std::string&symmetric, const std::string&withlock, keyring&, algorithm_suite&); int action_decrypt (bool armor, const std::string&symmetric, - keyring&, algorithm_suite&); + const std::string&withlock, keyring&, algorithm_suite&); int action_sign (const std::string&user, bool armor, const std::string&detach, bool clearsign, const std::string&symmetric, - keyring&, algorithm_suite&); + const std::string&withlock, keyring&, algorithm_suite&); int action_verify (bool armor, const std::string&detach, bool clearsign, bool yes, const std::string&symmetric, - keyring&, algorithm_suite&); + const std::string&withlock, keyring&, algorithm_suite&); int action_sign_encrypt (const std::string&user, const std::string&recipient, - bool armor, keyring&, algorithm_suite&); + const std::string&withlock, bool armor, + keyring&, algorithm_suite&); -int action_decrypt_verify (bool armor, bool yes, +int action_decrypt_verify (bool armor, bool yes, const std::string&withlock, keyring&, algorithm_suite&); /* @@ -96,5 +98,16 @@ int action_rename_sec (bool yes, const std::string&filter, const std::string&name, keyring&); +int action_lock_sec (const std::string&filter, + const std::string&symmetric, + const std::string&withlock, + bool armor, + keyring&); + +int action_unlock_sec (const std::string&filter, + const std::string&symmetric, + const std::string&withlock, + bool armor, + keyring&); #endif diff --git a/src/arcfour.h b/src/arcfour.h index b17641e..5d42a4d 100644 --- a/src/arcfour.h +++ b/src/arcfour.h @@ -37,7 +37,7 @@ public: I = J = 0; S.resize (Ssize); mask = ~ (inttype) 0; - if ( (inttype) (1 << bits)) mask %= 1 << bits; + if ( (inttype) (1 << bits) != 0) mask %= 1 << bits; for (size_t i = 0; i < Ssize; ++i) S[i] = i; } @@ -45,7 +45,31 @@ public: init(); } - void load_key (const inttype*begin, const inttype*end) { + //ugly byte padding with zeroes for streamcipher compatibility + void load_key (const byte*begin, const byte*end) { + inttype j, t; + size_t i; + const byte *keypos; + + //eat whole key iteratively, even if longer than permutation + for (; begin < end; begin += mask + 1) { + j = 0; + for (i = 0, keypos = begin; + i <= mask; + ++i, ++keypos) { + if (keypos >= end) keypos = begin; //rotate + j = (j + S[i] + (*keypos)) & mask; + t = S[j]; + S[j] = S[i]; + S[i] = t; + } + } + + discard (disc_bytes); + } + + //this works on wide keys + void load_wkey (const inttype*begin, const inttype*end) { inttype j, t; size_t i; const inttype *keypos; @@ -67,7 +91,11 @@ public: discard (disc_bytes); } - inttype gen() { + inline byte gen() { + return genw(); + } + + inttype genw() { I = (I + 1) & mask; J = (J + S[I]) & mask; @@ -79,13 +107,20 @@ public: return S[ (S[I] + S[J]) & mask]; } - void gen (size_t n, inttype*out) { + void gen (size_t n, byte*out) { if (out) for (size_t i = 0; i < n; ++i) out[i] = gen(); else for (size_t i = 0; i < n; ++i) gen(); } + void genw (size_t n, inttype*out) { + if (out) + for (size_t i = 0; i < n; ++i) out[i] = genw(); + else + for (size_t i = 0; i < n; ++i) genw(); + } + void gen (size_t n, std::vector&out) { out.resize (n); gen (n, & (out[0])); diff --git a/src/generator.h b/src/generator.h index 27c7c33..d45c921 100644 --- a/src/generator.h +++ b/src/generator.h @@ -25,11 +25,12 @@ #include "prng.h" #include -#define randmax_type uint64_t class ccr_rng : public prng { public: + typedef uint64_t randmax_t; + chacha20 r; ccr_rng() { @@ -43,8 +44,8 @@ public: bool seed (uint bits, bool quick = true); uint random (uint n) { - randmax_type i; - r.gen (sizeof (randmax_type), (byte*) &i); + randmax_t i; + r.gen (sizeof (randmax_t), (byte*) &i); return i % n; } }; diff --git a/src/main.cpp b/src/main.cpp index dd41dfb..2e45537 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,8 +42,8 @@ void print_help (char*pname) out (" -T, --test perform (probably nonexistent) testing/debugging stuff"); outeol; out ("Global options:"); - out (" -R, --in input file, default is stdin"); - out (" -o, --out output file, default is stdout"); + out (" -R, --in set input file, default is stdin"); + out (" -o, --out set output file, default is stdout"); out (" -E, --err the same for stderr"); out (" -a, --armor use ascii-armored I/O"); out (" -y, --yes assume that answer is `yes' everytime"); @@ -76,12 +76,18 @@ void print_help (char*pname) out (" -X, --delete-secret"); out (" -m, --rename rename matching keys"); out (" -M, --rename-secret"); + out (" -L, --lock lock secrets"); + out (" -U, --unlock unlock secrets"); outeol; out ("Key management options:"); - out (" -n, --no-action on import, only show what would be imported"); - out (" -N, --name specify a new name for renaming or importing"); out (" -F, --filter only work with keys with matching names"); out (" -f, --fingerprint format full key IDs nicely for human eyes"); + out (" -N, --name specify a new name for renaming or importing"); + out (" -n, --no-action on import, only show what would be imported"); + out (" -w, --with-lock specify the symmetric key for (un)locking the secrets"); + outeol; + out (" With -S and -w, using `@' as the key file name will cause the program to"); + out (" interactively ask for a password and derive the symmetric key from it."); outeol; out ("Codecrypt eats data. Use it with caution."); outeol; @@ -126,6 +132,7 @@ int main (int argc, char**argv) std::string recipient, user, input, output, err_output, name, filter, + withlock, action_param, detach_sign, symmetric; @@ -163,6 +170,9 @@ int main (int argc, char**argv) {"delete-secret", 1, 0, 'X' }, {"rename-secret", 1, 0, 'M' }, + {"lock", 0, 0, 'L' }, + {"unlock", 0, 0, 'U' }, + {"gen-key", 1, 0, 'g' }, {"name", 1, 0, 'N' }, @@ -171,6 +181,8 @@ int main (int argc, char**argv) {"fingerprint", 0, 0, 'f' }, {"no-action", 0, 0, 'n' }, + {"with-lock", 1, 0, 'w' }, + //actions {"sign", 0, 0, 's' }, {"verify", 0, 0, 'v' }, @@ -188,7 +200,7 @@ int main (int argc, char**argv) option_index = -1; c = getopt_long (argc, argv, - "hVTayr:u:R:o:E:kipx:m:KIPX:M:g:N:F:fnsvedCb:S:", + "hVTayr:u:R:o:E:kipx:m:KIPX:M:LUg:N:F:fnw:svedCb:S:", long_opts, &option_index); if (c == -1) break; @@ -251,26 +263,32 @@ int main (int argc, char**argv) read_action ('X') read_action ('M') - read_action ('g') + read_action ('U') read_single_opt ('N', name, - "please specify single name") + "specify a single name") read_single_opt ('F', filter, - "please specify single filter string") + "specify a single filter string") read_flag ('f', opt_fingerprint) read_flag ('n', opt_import_no_action) + read_single_opt ('w', withlock, + "specify a single key lock") + /* * combinations of s+e and d+v are possible. result is - * 'E' = "big encrypt with sig" and 'D' "big decrypt - * with verify". + * 'E' = "big encrypt with sig", 'D' "big decrypt + * with verify" and 'G' = "generate and lock" */ read_action_comb ('s', 'e', 'E') - read_action_comb ('v', 'd', 'D') read_action_comb ('e', 's', 'E') + read_action_comb ('v', 'd', 'D') read_action_comb ('d', 'v', 'D') + read_action_comb ('g', 'L', 'G') + read_action_comb ('L', 'g', 'G') + read_flag ('C', opt_clearsign) read_single_opt ('b', detach_sign, "specify only one detach-sign file") @@ -352,14 +370,18 @@ int main (int argc, char**argv) } if (symmetric.length()) switch (action) { - case 'd': case 'e': - case 'g': + case 'd': case 's': case 'v': + case 'g': + case 'G': + case 'L': + case 'U': break; default: - progerr ("specified action doesn't support symmetric operation"); + progerr ("specified action doesn't support" + " symmetric operation"); exitval = 1; goto exit; } @@ -367,36 +389,45 @@ int main (int argc, char**argv) switch (action) { case 'g': exitval = action_gen_key (action_param, name, - symmetric, opt_armor, + symmetric, withlock, + opt_armor, false, + KR, AS); + break; + + case 'G': + exitval = action_gen_key (action_param, name, + symmetric, withlock, + opt_armor, true, KR, AS); break; case 'e': exitval = action_encrypt (recipient, opt_armor, symmetric, - KR, AS); + withlock, KR, AS); break; case 'd': - exitval = action_decrypt (opt_armor, symmetric, KR, AS); + exitval = action_decrypt (opt_armor, symmetric, withlock, + KR, AS); break; case 's': exitval = action_sign (user, opt_armor, detach_sign, - opt_clearsign, symmetric, KR, AS); + opt_clearsign, symmetric, withlock, KR, AS); break; case 'v': exitval = action_verify (opt_armor, detach_sign, opt_clearsign, - opt_yes, symmetric, KR, AS); + opt_yes, symmetric, withlock, KR, AS); break; case 'E': - exitval = action_sign_encrypt (user, recipient, opt_armor, - KR, AS); + exitval = action_sign_encrypt (user, recipient, withlock, + opt_armor, KR, AS); break; case 'D': - exitval = action_decrypt_verify (opt_armor, opt_yes, + exitval = action_decrypt_verify (opt_armor, opt_yes, withlock, KR, AS); break; @@ -445,6 +476,16 @@ int main (int argc, char**argv) exitval = action_rename_sec (opt_yes, action_param, name, KR); break; + case 'L': + exitval = action_lock_sec (filter, symmetric, withlock, + opt_armor, KR); + break; + + case 'U': + exitval = action_unlock_sec (filter, symmetric, withlock, + opt_armor, KR); + break; + default: progerr ("no action specified, use `--help'"); exitval = 1; diff --git a/src/pwrng.cpp b/src/pwrng.cpp new file mode 100644 index 0000000..e6ea49a --- /dev/null +++ b/src/pwrng.cpp @@ -0,0 +1,99 @@ + +/* + * This file is part of Codecrypt. + * + * Copyright (C) 2013-2017 Mirek Kratochvil + * + * Codecrypt is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Codecrypt is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Codecrypt. If not, see . + */ + +#include "pwrng.h" + +#include "iohelpers.h" +#include + +#if (HAVE_READPASSPHRASE == 1) +#include +#elif (HAVE_BSDREADPASSPHRASE == 1) +#include +#else +#warning "Falling back to getpass(3), which is marked obsolete!" +/* If you see this, you might as well want to take the readpassphrase() + * implementation from e.g. openssh's openbsd-compat and put it here. */ +#include +#endif + +#define MAX_PW_LEN 1024 //like if someone enjoyed typing that. + +static bool read_password (const std::string&prompt, std::string&pw) +{ +#if (HAVE_READPASSPHRASE == 1 || HAVE_BSDREADPASSPHRASE==1) + /* readpassphrase reads at most bufsiz-1 bytes and gets the terminating + * zero just right */ + std::vector pwbuf; + pwbuf.resize (MAX_PW_LEN, 0); + if (!readpassphrase (prompt.c_str(), pwbuf.data(), MAX_PW_LEN, + RPP_REQUIRE_TTY)) + return false; + + pw = pwbuf.data(); + return true; +#else + char* pass = getpass (prompt.c_str()); + if (!pass) return false; + pw = pass; + return true; +#endif +} + +bool pw_rng::seed_from_user_password (const std::string&reason, + const std::string&env_var, + bool verify) +{ + + std::string pw; + + const char*env = getenv (env_var.c_str()); + if (env) { + pw = env; + err ("Password for " + << reason + << " successfully read from environment " + << env_var); + } else { + if (!read_password + ("Enter password for " + reason + ": ", pw)) { + err ("pwrng: interactive password reading failed"); + return false; + } + + if (verify) { + std::string pw2; + if (!read_password + ("Same password again for verification: ", + pw2)) { + err ("pwrng: password verification failed"); + return false; + } + if (pw != pw2) { + err ("Passwords do not match!"); + return false; + } + } + } + + r.load_key ( (byte*) pw.data(), + (byte*) (pw.data() + pw.length())); + return true; +} diff --git a/src/pwrng.h b/src/pwrng.h new file mode 100644 index 0000000..e505957 --- /dev/null +++ b/src/pwrng.h @@ -0,0 +1,90 @@ + +/* + * This file is part of Codecrypt. + * + * Copyright (C) 2013-2016 Mirek Kratochvil + * + * Codecrypt is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Codecrypt is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Codecrypt. If not, see . + */ + +#ifndef _ccr_pwrng_h_ +#define _ccr_pwrng_h_ + +#include "arcfour.h" +#include "prng.h" + +#include + +class pw_rng : public prng +{ +public: + /* + * Using wide arcfour for this purpose might seem weird, but: + * + * - it has large memory requirements + * (1Mbit, with possible ~0.95Mbit of entropy) + * + * - it takes some (very easily parametrizable) amount of time to seed, + * touching the above memory more or less randomly in the process + * + * - "retry rate" is constrained by how many passwords the human user + * can enter per time unit, which (together with the fact that the + * output of this thing is not supposed to get broadcasted directly) + * mostly disables all the known statistical attacks on arcfour + * + * - it's a highly nonstandard variant of a well-understood concept + * (therefore a good candidate for codecrypt right?) + * + * - arcfour is fast, but notably immune to vectorization and similar + * speedups. + * + * The other variant would be scrypt, which we don't implement for two + * reasons: + * + * - there's currently an scrypt-based cryptocoin, which provides + * insane amount of available inversion power against scrypt, which, if + * slightly abused, would invert any password-based key in seconds + * + * - admit it, arcfour is nicer + * + * Discarding 1M of output is very probably good for most uses (it + * permutes well and takes just around 50ms to run on current + * mainstream hardware) but YMMV. + * + * Please report any reasonable cases against this parameter choice. + */ + + arcfour r; + + void init () { + r.init(); + } + + void clear() { + r.clear(); + } + + bool seed_from_user_password (const std::string& reason, + const std::string& env_var, + bool verify); + + typedef uint64_t randmax_t; + uint random (uint n) { + randmax_t i; + r.gen (sizeof (randmax_t), (byte*) &i); + return i % n; + } +}; + +#endif diff --git a/src/symkey.cpp b/src/symkey.cpp index e04c3dd..57f6922 100644 --- a/src/symkey.cpp +++ b/src/symkey.cpp @@ -84,6 +84,128 @@ bool symkey::create (const std::string&in, prng&rng) return true; } +/* + * loading/saving + */ + +#include "envelope.h" +#include "base64.h" +#include "seclock.h" + +#define ENVELOPE_SYMKEY "symkey" + +bool symkey::load (const std::string&fn, const std::string&withlock, + bool for_encryption, bool armor) +{ + if (fn.length() && fn[0] == '@') { + //shared-secret password is requested + return load_lock_secret (*this, fn, "expanding shared secret", + "SYMMETRIC", for_encryption); + } + + std::ifstream sk_in; + sk_in.open (fn == "-" ? "/dev/stdin" : fn.c_str(), + std::ios::in | std::ios::binary); + + if (!sk_in) { + err ("error: can't open symkey file"); + return false; + } + + std::string sk_data; + if (!read_all_input (sk_data, sk_in)) { + err ("error: can't read symkey"); + return false; + } + sk_in.close(); + + if (armor) { + std::vector parts; + std::string type; + if (!envelope_read (sk_data, 0, type, parts)) { + err ("error: no data envelope found"); + return false; + } + + if (type != ENVELOPE_SYMKEY || parts.size() != 1) { + err ("error: wrong envelope format"); + return false; + } + + if (!base64_decode (parts[0], sk_data)) { + err ("error: malformed data"); + return false; + } + } + + if (looks_like_locked_secret (sk_data)) { + std::string tmp; + if (!unlock_secret (sk_data, tmp, + withlock, fn, "SYMKEY")) return false; + sk_data = tmp; + } + + sencode*SK = sencode_decode (sk_data); + if (!SK) { + err ("error: could not parse input sencode"); + return false; + } + + if (!unserialize (SK)) { + err ("error: could not parse input structure"); + sencode_destroy (SK); + return false; + } + + sencode_destroy (SK); + + return true; +} + +bool symkey::save (const std::string&fn, const std::string&withlock, + bool armor, bool force_lock, prng&r) +{ + sencode*SK = serialize(); + std::string data = SK->encode(); + sencode_destroy (SK); + + if (force_lock) { + std::string tmp; + if (!lock_secret (data, tmp, withlock, fn, "SYMKEY", r)) + return false; + data = tmp; + } + + std::ofstream sk_out; + sk_out.open (fn == "-" ? "/dev/stdout" : fn.c_str(), + std::ios::out | std::ios::binary); + if (!sk_out) { + err ("error: can't open symkey file for writing"); + return false; + } + + if (armor) { + std::vector parts; + parts.resize (1); + base64_encode (data, parts[0]); + data = envelope_format (ENVELOPE_SYMKEY, parts, r); + } + + sk_out << data; + if (!sk_out.good()) { + err ("error: can't write to symkey file"); + return false; + } + + sk_out.close(); + if (!sk_out.good()) { + err ("error: couldn't close symkey file"); + return false; + } + + return true; +} + typedef std::list > scs_t; typedef std::list > hashes_t; diff --git a/src/symkey.h b/src/symkey.h index 22718e9..6c9c41e 100644 --- a/src/symkey.h +++ b/src/symkey.h @@ -28,7 +28,7 @@ #include #include "types.h" -#include "generator.h" +#include "prng.h" #include "sencode.h" class symkey @@ -48,6 +48,11 @@ public: bool is_valid(); bool create (const std::string&, prng&); + + bool load (const std::string&fn, const std::string&withlock, + bool for_encryption, bool armor); + bool save (const std::string&fn, const std::string&withlock, + bool armor, bool force_lock, prng&r); }; #endif