/*
* Copyright (c) 2021 Clementine Computing LLC.
*
* This file is part of PopuFare.
*
* PopuFare is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PopuFare 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with PopuFare. If not, see .
*
*/
#include "fareqr.h"
// AES CBC, sha256 message digest, with salt.
// Functions modified from https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
//
// openssl example commands to encrypt/decrypt:
// msg=";123456789012345?"
// key="oovevobie8woid1Ou3iu6aboochei2AeKoceeCh1iePheuRae4Yai1dahtheegi7"
// openssl enc -aes-256-cbc -in <( echo -n "$msg" ) -out /dev/stdout -pass pass:"$key" -e -base64 -md sha256
//
// ---
//
// to descypt with openssl
//
// openssl aes-256-cbc -d -a -in <( echo "U2FsdGVkX19gpKzk+Y5SGgnQr1uwpUYUpea6nu77wJfG6bD4GZpRneSaxaallz0n" ) -out /dev/stdout -pass pass:"$key" -md sha256
//
static int aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *iv, unsigned char *ciphertext) {
EVP_CIPHER_CTX *ctx;
int len;
int ciphertext_len;
// Create and initialise the context
if(!(ctx = EVP_CIPHER_CTX_new())) { return -1; }
// Initialise the encryption operation. IMPORTANT - ensure you use a key
// and IV size appropriate for your cipher
// In this example we are using 256 bit AES (i.e. a 256 bit key). The
// IV size for *most* modes is the same as the block size. For AES this
// is 128 bits
//
if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) { return -2; }
// Provide the message to be encrypted, and obtain the encrypted output.
// EVP_EncryptUpdate can be called multiple times if necessary
//
if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) { return -3; }
ciphertext_len = len;
// Finalise the encryption. Further ciphertext bytes may be written at
// this stage.
//
if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) { return -4; }
ciphertext_len += len;
// Clean up
//
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
static int aes_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx;
int plaintext_len, len;
// Create and initialise the context
//
if(!(ctx = EVP_CIPHER_CTX_new())) { return -1; }
// Initialise the decryption operation. IMPORTANT - ensure you use a key
// and IV size appropriate for your cipher
// In this example we are using 256 bit AES (i.e. a 256 bit key). The
// IV size for *most* modes is the same as the block size. For AES this
// is 128 bits
//
if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) { return -2; }
// Provide the message to be decrypted, and obtain the plaintext output.
// EVP_DecryptUpdate can be called multiple times if necessary.
//
if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { return -3; }
plaintext_len = len;
// Finalise the decryption. Further plaintext bytes may be written at
// this stage.
//
if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) { return -4; }
plaintext_len += len;
// Clean up
//
EVP_CIPHER_CTX_free(ctx);
return plaintext_len;
}
// Helper function to take secret key/pass (pass_key) and the encoded
// base64 string and decrypt it with AES CBC 256, sha256 message digest.
//
// return the number of bytes decoded on success
// return non-positive number on error
//
static int decode_b64(unsigned char *decrypt_text,
unsigned char *pass_key,
unsigned char *enc_b64) {
int i=0, _ret = 0, r=0;
unsigned char *enc_b = NULL, salt[8];
unsigned char *key = NULL, *iv = NULL, *ciphertext_b = NULL;
size_t enc_len=0, enc_size=0, ciphertext_len;
if ((!decrypt_text) || (!pass_key) || (!enc_b64)) { return -9; }
enc_size = Base64decode_len((char *)enc_b64);
if (enc_size < 1) {
_ret=-1;
goto _decode_b64_cleanup;
}
enc_b = (unsigned char *)calloc(enc_size, sizeof(char));
if (!enc_b) {
_ret=-2;
goto _decode_b64_cleanup;
}
Base64decode((char *)enc_b, (char *)enc_b64);
enc_len = enc_size-1;
// Check exmpanded string is well formed (has "Salted__" prefix, etc.)
// and extract the salt
//
if (enc_len < 16) {
_ret = -3;
goto _decode_b64_cleanup;
}
if (strncmp((char *)enc_b, "Salted__", 8)!=0) {
_ret = -4;
goto _decode_b64_cleanup;
}
for (i=0; i<8; i++) { salt[i] = enc_b[i+8]; }
// Move past the salt prefix 8 bytes) and 8 bytes of salt
//
ciphertext_b = enc_b + 16;
ciphertext_len = enc_len - 16;
// Allocate iv, key and extract them from the salt and pass phrase
//
iv = (unsigned char *)calloc(256, sizeof(char));
key = (unsigned char *)calloc(256, sizeof(char));
r = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, pass_key, strlen((const char *)pass_key), 1, key, iv);
if (!r) {
_ret = -5;
goto _decode_b64_cleanup;
}
r = aes_decrypt(ciphertext_b, ciphertext_len, key, iv, decrypt_text);
if (r<=0) {
_ret = -6;
goto _decode_b64_cleanup;
}
decrypt_text[r] = '\0';
_ret = r;
_decode_b64_cleanup:
if (enc_b) { free(enc_b); }
if (key) { free(key); }
if (iv) { free(iv); }
return _ret;
}
static int encode_b64(unsigned char *enc_b64,
unsigned char *pass_key,
unsigned char *msg) {
int i=0, _ret = 0, r=0;
unsigned char *enc_b = NULL;
unsigned char *key = NULL, *iv = NULL;
size_t b64_sz=0, enc_len=0;
unsigned char salt[8];
char _pfx[] = "Salted__";
if ((!enc_b64) || (!pass_key) || (!msg)) { return -9; }
for (i=0; i<8; i++) { salt[i] = (unsigned char)(rand() % 256); }
iv = (unsigned char *)calloc(256, sizeof(char));
key = (unsigned char *)calloc(256, sizeof(char));
r = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, pass_key, strlen((char *)pass_key), 1, key, iv);
if (!r) {
_ret = -5;
goto _encode_b64_cleanup;
}
enc_b = (unsigned char *)calloc(2*LINE_BUFFER_SIZE, sizeof(char));
if (!enc_b) {
_ret = -4;
goto _encode_b64_cleanup;
}
for (i=0; i<8; i++) {
enc_b[i] = _pfx[i];
enc_b[i+8] = salt[i];
}
enc_len = aes_encrypt(msg, strlen((char *)msg), key, iv, enc_b+16);
if (enc_len<=0) {
_ret = -6;
goto _encode_b64_cleanup;
}
enc_len += 16;
b64_sz = Base64encode_len(enc_len);
Base64encode((char *)enc_b64, (char *)enc_b, enc_len);
_ret = b64_sz;
_encode_b64_cleanup:
if (enc_b) { free(enc_b); }
if (key) { free(key); }
if (iv) { free(iv); }
return _ret;
}
// `fqreqr_lookup_decode` uses the public key portion of `fareqr_s`
// to lookup the private pass/key in the file `seedfn`.
// If found, it then proceeds to decrypt and store the encrypted
// credential in `dst_cred`
//
// returns 0 on success
// returns non zero on error
//
int fareqr_lookup_decode(char *seedfn, char *fareqr_s, char *dst_cred) {
int i, n, r, _ret = 0;;
char *p=NULL, *stop_tok=NULL;
char *enc_str = NULL, *dec_str = NULL, *plain_str=NULL,
*pub_key = NULL, *priv_key = NULL;
if ((!seedfn) || (!fareqr_s) || (!dst_cred)) { return -1; }
if (fareqr_s[0] != '@') { return -2; }
stop_tok = strchr(fareqr_s, '%');
if (!stop_tok) { return -3; }
pub_key = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
priv_key = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
enc_str = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
dec_str = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
plain_str = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
pub_key[0] = priv_key[0] = enc_str[0] = dec_str[0] = plain_str[0] = '\0';
for (i=0, p = (fareqr_s+1); (i<(LINE_BUFFER_SIZE-1)) && (p= check_pub_len) { return -3; }
if ( (*p) != check_pub[pub_tok_read_len] ) { return -4; }
pub_tok_read_len++;
}
}
enc_str = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
for (n=0, p = (stop_tok+1); (*p) && ((*p) != '$'); p++, n++) {
enc_str[n] = *p;
}
enc_str[n] = '\0';
r = decode_b64((unsigned char *)dst_cred, (unsigned char *)tok_secret, (unsigned char *)enc_str);
if (r < 0) {
_ret = -1;
goto _fareqr_decode_cleanup;
}
_fareqr_decode_cleanup:
if (enc_str) { free(enc_str); }
return _ret;
}
// return negative on error or not found
// 0 on success (found)
//
int fareqr_lookup_seed_secret(char *seedfn, char *pub, char *priv) {
int i;
FILE *fp;
char buf[LINE_BUFFER_SIZE] = {0};
int pos = 0, ch=0, line_no=0;
char *tok0_ptr=NULL,
*tok1_ptr=NULL;
fp = fopen(seedfn, "r");
if (!fp) { perror(seedfn); return -2;}
while (!feof(fp)) {
ch = fgetc(fp);
// process line if we reach a newline or eof
//
if (feof(fp) || (ch == '\n')) {
// if the line is empty or a comment, skip
//
if ((pos==0) || (buf[0] == '#')) {
}
// get tokens out of line, using ' ' as the
// delimeter
//
else {
tok0_ptr = buf;
tok1_ptr = strchr(buf, ' ');
if (tok1_ptr) {
// tie off the tok0_ptr (public key) for use later
//
*tok1_ptr = '\0';
// start our private key ptr at the start of the
// private key, skipping over whitespace
//
do {
tok1_ptr++;
} while ( (*tok1_ptr) && ((*tok1_ptr) == ' ') );
// Check it against our supplied 'pulbic' key
//
if (strcmp(pub, tok0_ptr)==0) {
// If we've found it, copy it over to the `priv`
// above and return
//
for (i=0; tok1_ptr[i] && (tok1_ptr[i] != ' '); i++) {
priv[i] = tok1_ptr[i];
}
priv[i]='\0';
return 0;
}
}
line_no++;
}
pos=0;
buf[0]='\0';
continue;
}
buf[pos] = ch;
pos++;
buf[pos] = '\0';
}
fclose(fp);
return -1;
}
// to compile:
// gcc -D__FAREQR_MAIN__ fareqr.c b64.c -o fareqr
//
#ifdef __FAREQR_MAIN__
#define FAREQR_VERSION "0.1.0"
void show_version(FILE *ofp) {
fprintf(ofp, "fareqr version %s\n", FAREQR_VERSION);
}
void show_help(FILE *ofp) {
show_version(ofp);
fprintf(ofp, "\nusage:\n\n");
fprintf(ofp, " fareqr encode \n");
fprintf(ofp, " fareqr decode \n");
fprintf(ofp, " fareqr dbdecode \n");
fprintf(ofp, " fareqr version\n");
fprintf(ofp, " fareqr help\n");
fprintf(ofp, "\n");
fprintf(ofp, "fareqr is a program to help with encoding and decoding 'fareqr' strings.\n");
fprintf(ofp, "\n");
fprintf(ofp, "A fareqr string is of the form:\n");
fprintf(ofp, "\n");
fprintf(ofp, " @%%$\n");
fprintf(ofp, "\n");
fprintf(ofp, "Where `` is the base64 encoded AES encrypted string\n");
fprintf(ofp, "of the credential, encrypted with the privkey.\n");
fprintf(ofp, "\n");
fprintf(ofp, "The is a text file of pairs.\n");
fprintf(ofp, "\n");
fprintf(ofp, "Here is some example usage:\n");
fprintf(ofp, "\n");
fprintf(ofp, " $ fareqr encode 'wu9XouSh' 'ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' ';123456789060535?'\n");
fprintf(ofp, " @wu9XouSh%%U2FsdGVkX19nxmlzUf9K7GAgplhbmU4tcFYYa/Xz6oq0XrxSYTbBOA2yffAi7A0z$\n");
fprintf(ofp, " $ fareqr decode 'ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' '@wu9XouSh%%U2FsdGVkX19nxmlzUf9K7GAgplhbmU4tcFYYa/Xz6oq0XrxSYTbBOA2yffAi7A0z$'\n");
fprintf(ofp, " ;123456789060535?\n");
fprintf(ofp, " $ echo 'wu9XouSh ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' > ./qr.seed\n");
fprintf(ofp, " $ fareqr dbdecode ./qr.seed '@wu9XouSh%%U2FsdGVkX19nxmlzUf9K7GAgplhbmU4tcFYYa/Xz6oq0XrxSYTbBOA2yffAi7A0z$'\n");
fprintf(ofp, " ;123456789060535?\n");
fprintf(ofp, "\n");
fprintf(ofp, "Where 'wu9XouSh' is the public key, 'ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' is the private key and\n");
fprintf(ofp, "';123456789060535?' is the credential information to be encoded.\n");
fprintf(ofp, "\n");
fprintf(ofp, "\n");
fflush(ofp);
}
/*
*
* quick test/start:
*
* $ fareqr encode 'wu9XouSh' 'ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' ';123456789060535?'
* @wu9XouSh%VFl8VF1PV19TXEBeUVxdBl4=
*
* $ fareqr decode 'ohNgizahkephain3aosoh2AeH1aethoo4cie6oiSaezimaighai2eiVaefahfien' '@wu9XouSh%VFl8VF1PV19TXEBeUVxdBl4=$'
* ;123456789060535?
*
* $ fareqr dbdecode '@wu9XouSh%VFl8VF1PV19TXEBeUVxdBl4=$'
* ;123456789060535?
*
*/
int main(int argc, char **argv) {
int i, r;
char *tok_public = NULL,
*tok_secret = NULL,
*tok_cred = NULL,
*fareqr_str = NULL;
uint8_t x,y,z;
uint8_t *src_data = NULL, *dst_data = NULL;
int src_data_n = 0, dst_data_n = 0;
char *fn = NULL;
if (argc <= 1) {
show_help(stderr);
exit(1);
}
if (strcmp(argv[1], "encode")==0) {
if (argc>2) {
tok_public = strdup(argv[2]);
if (argc>3) {
tok_secret = strdup(argv[3]);
if (argc>4) {
tok_cred = strdup(argv[4]);
}
}
}
if ((!tok_public) || (!tok_secret) || (!tok_cred)) {
show_help(stderr);
if (tok_public) { free(tok_public); }
if (tok_secret) { free(tok_secret); }
if (tok_cred) { free(tok_cred); }
exit(2);
}
fareqr_str = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
r = fareqr_encode(tok_public, tok_secret, tok_cred, fareqr_str);
if (r==0) {
printf("%s\n", fareqr_str);
}
}
else if (strcmp(argv[1], "decode")==0) {
if (argc>2) {
tok_secret = strdup(argv[2]);
if (argc>3) {
fareqr_str = strdup(argv[3]);
}
}
tok_cred = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
tok_cred[0] = '0';
r = fareqr_decode(fareqr_str, tok_public, tok_secret, tok_cred);
if (r<0) {
fprintf(stderr, "error, failed to decode qr fare string (%i)\n", r);
}
else {
printf("%s\n", tok_cred);
}
}
else if (strcmp(argv[1], "dbdecode")==0) {
if (argc>2) {
fn = strdup(argv[2]);
if (argc>3) {
fareqr_str = strdup(argv[3]);
}
}
tok_cred = (char *)malloc(sizeof(char)*LINE_BUFFER_SIZE);
tok_cred[0] = '0';
r = fareqr_lookup_decode(fn, fareqr_str, tok_cred);
if (r<0) {
fprintf(stderr, "could not decode '%s' with db file '%s', exiting (got %i)\n",
fareqr_str, fn, r);
}
else {
printf("%s\n", tok_cred);
}
}
// test out our AES encryption
//
else if (strcmp(argv[1], "aes-decode")==0) {
if (argc>2) {
tok_secret = strdup(argv[2]);
if (argc>3) {
fareqr_str = strdup(argv[3]);
}
}
tok_cred = (unsigned char *)calloc(LINE_BUFFER_SIZE, sizeof(char));
r = decode_b64(tok_cred, tok_secret, fareqr_str);
if (r<0) {
fprintf(stderr, "could not decode aes base64 string (AES CBC 256, sha256 md) (got %i)\n", r);
}
else {
printf("%s\n", tok_cred);
}
}
// test out our AES encryption
//
else if (strcmp(argv[1], "aes-encode")==0) {
if (argc>2) {
tok_secret = strdup(argv[2]);
if (argc>3) {
tok_cred = strdup(argv[3]);
}
}
fareqr_str = (unsigned char *)calloc(2*LINE_BUFFER_SIZE, sizeof(char));
r = encode_b64(fareqr_str, tok_secret, tok_cred);
if (r<0) {
fprintf(stderr, "could not encode aes base64 string (AES CBC 256, sha256 md) (got %i)\n", r);
}
else {
printf("%s\n", fareqr_str);
}
}
else if (strcmp(argv[1], "help")==0) {
show_help(stdout);
exit(0);
}
else if (strcmp(argv[1], "version")==0) {
show_version(stdout);
exit(0);
}
else {
fprintf(stderr, "unknown operation '%s'\n", argv[1]);
show_help(stderr);
exit(3);
}
if (tok_public) { free(tok_public); }
if (tok_secret) { free(tok_secret); }
if (tok_cred) { free(tok_cred); }
if (fareqr_str) { free(fareqr_str); }
if (src_data) { free(src_data); }
if (dst_data) { free(dst_data); }
if (fn) { free(fn); }
}
#endif