QNGWKBROYBNQFPIS4AD4GSIWVGPVEMWISKVHTBGPJ3EF2X4HBKWAC 6TAON2MZTHHS2RYFHA3K7ATXQSHNC2DPGFOAUOU6LREJYSJTWXSAC CQNAPS3DTQM3GT2BWSGKG4RG5W3IVCMRVY5XDIKT3B5LPHG7VOWAC UW27LKXM2BJ77FQLTY4WPKDSSWI2RFNFRJ7CB4U3TS7KYVIV72LQC A2JAXDJWT2FAKADYOY6QOQ7LQRMTTCDIOYT7STSESVHLZQEQJBMAC 2UOWJD64KBUHIWGZTPGUWWJXH5ZIAMOEJRDNZQOW5QWBJDEACURQC JDZASPALXSFZOL3MXCKBPX74CUD3W743ZJ6W2422FIJ7NJOD67ZAC TFWMUQZSR25B6CLXFNFN56JFH3PJRHDFW7DYTGDOFCVKW4KC43NAC DG4L5ELJFZS5JATC47CHOCESJM5H3XSL4WDBXNV4ADTUTMMKWZDAC 5XO7IKBGCVXGVWMDJDE5MELS4FWRITKAU6NNV36NQ4TOZRR7UQ7QC NEUQJ7FRWFVZ7HPXGLLBU55FH6OSB374SQSRGIAAA233KFGEKTDAC 6WM2DD32XSC2HFLOXBDPFCU6BI2BD5ZYFGBIEIVEQOKWRCNTXROQC 7SNXCC5KSDXU3MBJT2FBEPAISWPY62DHPC2RLEYXC2WVTWX5TKKQC VSQGRPJ7PDH3MOC7GFVX5YONUZTLFRXU2O6CFT5MRGBGOO7PO6GAC FITNBSMMJCQIFJGUMVSZYHJM4OSBXEZO5YWYEJ4CXGMFPBSIT5WAC MPN7OJSZD5CS5N7WWS3ZSOYE7ZRCABIBHZDMHVS6IT25EO2INK7AC OUNA3ZMTTISFWX3HHIJ46WTYQJBX4OFSNU4SU3GOJMBLLHQDFT7AC I3MID22EURRK26C4JZ2U5FBEZ57GZVMBT44KFKTU4BBYXKLILWRAC TN5RCIH3OI5AW756GICACN24TBABGLNDQ7LZJTUYHXYVW3ARGBBQC KBD2A5MUJHKGV4JD6MLJXTKNAYZ5D44LX5WRB2ZXX5YCWBODWR3AC JNVQOJXCBGPCN3XT2GAAGZ2ZOIUVGRB6WCRWOPI6RPJDDWL2KGKAC 2CXQ53RHKGIT5KR7VHOVRVHCD5MK4V2J3AJDQ3CNSJLHJXXA4GXQC ICGJDX2EHUCWPATWZRMDCSSTRXGSPW5M4NGU5MAFG6R7QDUC5KPQC 5OVGZFP3HMFSJ7EETA6SPCIVV4PENITMC2ZK3EMPBFCZGZYWF7XQC else if(t == "'") a[ia[1]++] = _SYM_NUMBERS[c]else if(t == "(") _list_to_flat_awk_array_of_any(c, a, ia)
else if(t == "S") {if(redactp) {a[ia[1]++] = S_REDACTED} else {secretp[1] = 1a[ia[1]++] = _SECRET_STRING[c]}} else if(t == "e") {if(redactp) {a[ia[1]++] = S_ENCRYPTED} else {secretp[1] = 1a[ia[1]++] = _SECRET_STRING[_decrypt(_ENCRYPTED_STRING[c])]}} else if(t == "'") a[ia[1]++] = _SYM_NUMBERS[c]else if(t == "(") _list_to_flat_awk_array_of_any(c, a, redactp, secretp, ia)
* v0.9: secrets** Special forms addedencrypt decrypt with-secret-output-to unsafe-reveal reveal-encryptedrsprintf rprintf** Secretsglotawk will keep your secrets. Somewhat. Here's how it works: secretsare encrypted symmetrically with AES-256-CTR, using the~openssl-enc(1)~ utility. The key used lives in a file which glotawkcan read. By default this is ~/usr/local/etc/lacrum/keyfile~. You canset it to a different filename using a ~-v~ switch to ~awk~: ~awk -vKEY_FILE=/other/dir/myfile -f glotawk~.The reason for this feature is to hide the value of secrets that needto be written in code that configures the system, so that the code canbe more safely version-controlled. For example, CARP passphrases needto be set. But it wouldn't do to write the actual passphrases in apublicly shared repository, would it?But in this example, the world can still tell how often the CARPpassphrases are changed; and anyone can conduct offline brute-forceattacks. There's no authentication of the ciphertext, so changes toit, inadvertent or intentional, can change the plaintext or causedecryption errors. Assume this will happen, and change both thesecrets, and the encryption key, periodically. As presentlyimplemented, glotawk will not help you do this in the slightest.Speaking of implementation, here's what exactly is implemented. Thereare now three string types: strings (as before), secret strings, andencrypted strings. Syntactically, strings are, as before, stuffbetween double-quotes. Secret strings have a capital S just before thebeginning quote: ~S"shh, don't tell"~ . Encrypted strings have an ejust before the beginning quote: ~e"rfyNlBsckgA"~ . They containbase64-encoded bytes, so they won't make much sense. So this is howyou write the two new kinds of string, in glotawk programs.A bit of effort is expended to avoid showing secret strings.- When you ~print~ a secret string, or take its ~repr~, it shows as~S"(redacted)"~. This includes REPL results and stack traces printedwhen crashing.- When you expect to send secrets to an output file, use~with-secret-output-to~ rather than ~with-output-to~; if you send asecret string using ~printf~ to a non-secret output, it will throwan error.- When you use ~rprintf~ instead, any secret-string arguments will bereplaced with "(redacted)," and no error will be thrown regarding anon-secret output.- When you interpolate a secret string into another string using~sprintf~, the resulting string will be a secret string.- Same as with ~rprintf~ above, you can use ~rsprintf~ to redact, andthe result will be a plain string.- When you ~dump~ to an image file, all secret strings arefirst transmogrified into encrypted strings.Because of this latter automatic conversion, when you evaluate anencrypted string, it will be transmogrified into a secret string, sothat code using the value will work the same before and after thedump. If for some reason you need to manually convert betweenencrypted and secret strings, the ~encrypt~ and ~decrypt~ functions dothe job.When you are going to write an encrypted string literal in yoursource, you'll need to know what it is, in its encrypted form. That'swhat ~reveal-encrypted~ is for.And finally, if you need an escape valve, ~unsafe-reveal~ turns asecret or encrypted string into a plain string containing theplaintext.Because this functionality is implemented by writing ciphertextlinewise to temp files, and reading plaintext out of pipes linewise,newlines are not well preserved by secret and encrypted strings. Tryto keep things to one line.
_cdddr(form), env, d+1, hdlrs, st, errp) # to be evprogged
0, _cdddr(form), env, d+1, hdlrs, st, errp) # to be evproggedelse if(car == _symbol("with-secret-output-to"))return _with_output_to(_eval3(_cadr(form), env, env, d+1, hdlrs, st, errp),_eval3(_caddr(form), env, env, d+1, hdlrs, st, errp),1, _cdddr(form), env, d+1, hdlrs, st, errp) # to be evprogged
_list_to_flat_awk_array_of_any(evald, a)
_list_to_flat_awk_array_of_any(evald, a, redactp, secretp)if(secretp[1]) # the thing to be output is secretif(!_OUTPUT_SECRET)return _error(_cons(_string("printf: secret sent to non-secret output"),_nil()),env, d+1, hdlrs, st, errp)
function _nerf_encrypted_string(s) {# encrypted strings should only contain base64 that openssl can# grok. just in case it doesn't (encrypted string from the outside# with maliciously incorrect content), let's nerf it.gsub(/[^a-zA-Z0-9\/+=]/, "_", s)return s}function _repr_encrypted_string(n, es) {es = _nerf_encrypted_string(_ENCRYPTED_STRING[n])return "e\"" es "\""}
if(substr(this, 1, 1) == "\"") {
if(substr(this, 1, 2) == "e\"") { # encrypted string# no backslash-escapes; in fact, nerf any weird charactersans = _nerf_encrypted_string(substr(this, 3, length(this)-3))ans = _encrypted_string(ans)} else if(substr(this, 1, 2) == "S\"") { # secret stringans = _secret_string(substr(this, 3, length(this)-3))} else if(substr(this, 1, 1) == "\"") {
echo passphrase > keyfileEXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(unsafe-reveal "foo")' '"foo"' 'unsafe-reveal a mere string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be 'S"foo"' 'S"(redacted)"' 'secret strings not shown'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(sprintf "normal %s normal" S"foo")' 'S"(redacted)"' 'secretness propagates through sprintf'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(rsprintf "normal %s normal" S"foo")' '"normal (redacted) normal"' 'secretness stops at rsprintf'
EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(unsafe-reveal (sprintf "normal %s normal" S"foo"))' '"normal foo normal"' 'unsafe-reveal a secret string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(decrypt (encrypt "foo"))' 'S"(redacted)"' 'decrypt returns a secret string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(unsafe-reveal (encrypt "foo"))' '"foo"' 'unsafe-reveal an encrypted string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(printf "%s\n" S"foo")' '' 'printf will not print secrets to non-secret output' 87EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(with-secret-output-to ">>" "/dev/stdout" (printf "%s\n" S"foo"))' 'foo()' 'printf will print secrets to secret outputs'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(rprintf "normal %s normal\n" S"foo")' 'normal (redacted) normal()' 'rprintf will redact secrets and print to non-secret output'# this U2FsdGVkX1 seems to reliably come out of openssl enc# -aes-256-ctr. the rest, of course, will and must be different every# time, because CTR uses a nonce.EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(not (null (match (reveal-encrypted "foo") "^U2FsdGVkX1")))' 'true' 'reveal-encrypted, normal string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(not (null (match (reveal-encrypted (encrypt "foo")) "^U2FsdGVkX1")))' 'true' 'reveal-encrypted, encrypted string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(not (null (match (reveal-encrypted (decrypt (encrypt "foo"))) "^U2FsdGVkX1")))' 'true' 'reveal-encrypted, secret string'EXTRA_AWK_ARGS="-v KEY_FILE=keyfile" lisp_eval_should_be '(setq x S"woyfutnwyu") (dump "test-dump-file-2")' 'S"(redacted)"true' 'dumping with a secret string'if grep woyfutnwyu test-dump-file-2; thentap_fail "secret string encrypted in dump"elsetap_pass "secret string encrypted in dump"firm -f keyfilerm -f test-dump-file-2
# SPDX-License-Identifier: BSD-2-ClauseBEGIN {if(!KEY_FILE) {KEY_FILE = "/usr/local/etc/lacrum/key"}# ciphertext must be base64-encoded (as well as, you know,# encrypted)ENCRYPT_COMMAND = ("openssl enc -aes-256-ctr -pbkdf2 -a -kfile " KEY_FILE)DECRYPT_COMMAND = ("openssl enc -d -aes-256-ctr -pbkdf2 -a -kfile " KEY_FILE)S_REDACTED = "(redacted)"S_ENCRYPTED = "(encrypted)"}# eval cases for encrypt, decrypt, unsafe-reveal are in string.awk.# eval.awk also has a case for when an encrypted string is evaluated.function _obscure_string_in_array(arr, n) {gsub(/./, "x", arr[n])}function _decrypt_encrypted_string(n, env, d, hdlrs, st, errp, line, out, status, dcmd, tfn, oors) {# attempts to write via pipe and read via pipe from the same# subprocess have failed. so we use a temporary file. note that we# put the ciphertext, not the plaintext, in the temporary file.if( (status = ("mktemp" | getline tfn)) < 0) {return _error(_cons(_string("could not mktemp"),_nil()))}close("mktemp")if(!tfn)return _error(_cons(_string("no filename resulted from mktemp"),_nil()), env, d, hdlrs, st, errp)# ok now we have a temp file nameprint _ENCRYPTED_STRING[n] > tfnclose(tfn)dcmd = DECRYPT_COMMAND " < " tfnwhile( (status = (dcmd | getline line)) > 0 ) {out = out line}close(dcmd)if(status < 0) {return _error(_cons(_string("decrypting an encrypted string failed"),_nil()), env, d, hdlrs, st, errp)}_SECRET_STRING[n] = out_TYPE[n] = "S"_obscure_string_in_array(_ENCRYPTED_STRING, n)delete _ENCRYPTED_STRING[n]system("rm -f " tfn)return n}function _encrypt_string_base(typename, arr, n, env, d, hdlrs, st, errp, line, out, status, ecmd, tfn, oors) {# attempts to write via pipe and read via pipe from the same# subprocess have failed. so we use a temporary file. note that we# put the ciphertext, not the plaintext, in the temporary file.if( (status = ("mktemp" | getline tfn)) < 0) {return _error(_cons(_string("could not mktemp"),_nil()))}close("mktemp")if(!tfn)return _error(_cons(_string("no filename resulted from mktemp"),_nil()), env, d, hdlrs, st, errp)# ok now we have a temp file nameecmd = ENCRYPT_COMMAND " > " tfnoors = ORSORS = ""print arr[n] | ecmdORS = oorsclose(ecmd)while( (status = (getline line < tfn)) > 0 ) {out = out line}close(tfn)if(!out) {return _error(_cons(_string("encrypting a " typename " failed"),_nil()))}_ENCRYPTED_STRING[n] = out_TYPE[n] = "e"_obscure_string_in_array(arr, n)delete arr[n]system("rm -f " tfn)return n}function _encrypt_secret_string(n, env, d, hdlrs, st, errp) {return _encrypt_string_base("secret string", _SECRET_STRING, n,env, d, hdlrs, st, errp)}function _encrypt_string(n, env, d, hdlrs, st, errp) {return _encrypt_string_base("string", _STRING, n,env, d, hdlrs, st, errp)}function _encrypt(n, env, d, hdlrs, st, errp, t) {if(_atom_awk(n)) {if(!_is_literal(n)) {t = _TYPE[n]if(t == "S")return _encrypt_secret_string(n)else if(t == "s")return _encrypt_string(n)else if(t == "e")return _error(_cons(_string("cannot encrypt an encrypted string"),_nil()), env, d, hdlrs, st, errp)}}return _error(_cons(_string("cannot encrypt %s: " \"need a string or secret string"),_cons(_string(_repr(n)),_nil())),env, d, hdlrs, st, errp)}function _decrypt(n, env, d, hdlrs, st, errp, t) {if(_atom_awk(n)) {if(!_is_literal(n)) {t = _TYPE[n]if(t == "e")return _decrypt_encrypted_string(n, env, d, hdlrs, st, errp)}}return _error(_cons(_string("cannot decrypt %s: " \"need an encrypted string"),_cons(_string(_repr(n)),_nil())),env, d, hdlrs, st, errp)}function _unsafe_reveal(n, env, d, hdlrs, st, errp, t) {if(_atom_awk(n)) {if(!_is_literal(n)) {t = _TYPE[n]if(t == "S") {_STRING[n] = _SECRET_STRING[n]_TYPE[n] = "s"_obscure_string_in_array(_SECRET_STRING, n)delete _SECRET_STRING[n]return n} else if(t == "s") {return n} else if(t == "e") {return _unsafe_reveal(_decrypt(n), env, d, hdlrs, st, errp)}}}return _error(_cons(_string("cannot reveal %s: " \"need a secret string"),_cons(_string(_repr(n)),_nil())),env, d, hdlrs, st, errp)}function _reveal_encrypted(n, env, d, hdlrs, st, errp, t) {if(_atom_awk(n)) {if(!_is_literal(n)) {t = _TYPE[n]if(t == "s") {n = _encrypt_string(n, env, d, hdlrs, st, errp)if(_is_null(n)) return n # an error happenedt = _TYPE[n]} else if(t == "S") {n = _encrypt_secret_string(n, env, d, hdlrs, st, errp)if(_is_null(n)) return n # an error happenedt = _TYPE[n]}# not else if. after the above, or because it was already,# t should now be "e".if(t == "e") {return _string(_ENCRYPTED_STRING[n])}}}# if we have not returned, n was or is a weird typereturn _error(_cons(_string("cannot reveal-encrypted %s: must be a string or secret string"),_cons(n, _nil())),env, d, hdlrs, st, errp)}
if(car == _symbol("encrypt")) # see secrets.awkreturn _encrypt(_eval3(_cadr(form), env, env, d+1, hdlrs, st, errp),env, d+1, hdlrs, st, errp)else if(car == _symbol("decrypt"))return _decrypt(_eval3(_cadr(form), env, env, d+1, hdlrs, st, errp),env, d+1, hdlrs, st, errp)else if(car == _symbol("unsafe-reveal"))return _unsafe_reveal(_eval3(_cadr(form), env, env, d+1, hdlrs, st, errp),env, d+1, hdlrs, st, errp)else if(car == _symbol("reveal-encrypted"))return _reveal_encrypted(_eval3(_cadr(form), env, env, d+1, hdlrs, st, errp),env, d+1, hdlrs, st, errp)