/*
This file is part of Bolixo.
Bolixo is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Bolixo 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Bolixo. If not, see .
*/
/*
Manage public/private key creation.
Since this is a slow process, this is done here by this program.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "bolixo.h"
using BN_ptr = std::unique_ptr;
using EVP_KEY_ptr = std::unique_ptr;
using namespace std;
static DEBUG_KEY D_PROTO ("proto","Protocol information");
enum CONNECT_TYPE { TYPE_NONE, TYPE_CONTROL, TYPE_WORKER};
struct HANDLE_INFO: public ARRAY_OBJ{
CONNECT_TYPE type;
REQUEST_INFO req;
HANDLE_INFO(){
type = TYPE_NONE;
}
};
#include "proto/bo-keysd_control.protoh"
static int generate(string &private_key, string &public_key, PARAM_STRING passphrase)
{
int ret = -1;
private_key.clear();
public_key.clear();
RSA *rsa = RSA_new();
if (rsa != NULL){
BN_ptr bn(BN_new(), ::BN_free);
if (BN_set_word(bn.get(), RSA_F4)==1){
if (RSA_generate_key_ex(rsa, 2048, bn.get(), NULL) == 1){
// Convert RSA to PKEY
EVP_KEY_ptr pkey(EVP_PKEY_new(), ::EVP_PKEY_free);
if (EVP_PKEY_set1_RSA(pkey.get(), rsa) == 1){
{
BIO *bio = BIO_new(BIO_s_mem());
PEM_write_bio_PrivateKey(bio, pkey.get(),EVP_aes_192_cbc(), (unsigned char*)passphrase.ptr,strlen(passphrase.ptr), 0, 0);
char line[1000];
while(BIO_gets(bio,line,sizeof(line))>0){
private_key += line;
}
BIO_free_all(bio);
}
{
BIO *bio = BIO_new(BIO_s_mem());
PEM_write_bio_PUBKEY(bio, pkey.get());
char line[1000];
while(BIO_gets(bio,line,sizeof(line))>0){
//printf ("line=%s\n",line);
public_key += line;
}
BIO_free_all(bio);
}
ret = 0;
}
}
}
RSA_free (rsa);
}
return ret;
}
static void bo_keysd_doone (
_F_TCPSERVER_V1 &c,
pid_t &pid,
deque &accounts,
PARAM_STRING passphrase,
NSQL &usq, // Access to the users database
NSQL &fsq) // Access to the files database
{
glocal NSQL *usq = &usq;
glocal NSQL *fsq = &fsq;
glocal const char *passphrase = passphrase.ptr;
if (accounts.size() > 0){
int tb[2];
if (pipe(tb)==-1){
tlmp_error ("Can't setup pipe (%s)\n",strerror(errno));
}else{
glocal string todo = accounts.front();
accounts.pop_front();
pid = fork();
if (pid == (pid_t)0){
// Do the job
close (tb[0]);
(usq,"select userid,priv_key from users where userid_str='%s'",glocal.todo.c_str());
if (row[1] != NULL){
tlmp_error ("Private key generation: Key already generated for user %s\n",glocal.todo.c_str());
}else{
int userid=atoi(row[0]);
string privkey,pubkey;
if (generate (privkey,pubkey,glocal.passphrase) != -1){
if (sql_action(*glocal.usq,"update users set priv_key='%s',pub_key='%s' where userid=%d"
,privkey.c_str(),pubkey.c_str(),userid)==-1){
tlmp_error ("Update users failed\n");
}else if (sql_action(*glocal.fsq,"update id2name set pub_key='%s' where userid=%d",pubkey.c_str(),userid)==-1){
tlmp_error ("update id2name failed\n");
}
}
}
tlmp_error ("Private key generation: Can't find userid %s\n",glocal.todo.c_str());
_exit (0);
}else if (pid == (pid_t)-1){
tlmp_error ("Can't fork (%s)\n",strerror(errno));
}else{
HANDLE_INFO *n = new HANDLE_INFO;
n->type = TYPE_WORKER;
c.inject (tb[0],n);
close (tb[1]);
}
}
}
}
static EVP_PKEY *bo_keysd_load_private (PARAM_STRING p, PARAM_STRING passphrase)
{
BIO *bio = BIO_new_mem_buf((void*)p.ptr, strlen(p.ptr));
EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio, NULL,NULL,(void*)passphrase.ptr);
BIO_free_all (bio);
return pkey;
}
namespace {
struct BIOFreeAll { void operator()(BIO* p) { BIO_free_all(p); } };
}
std::string Base64Encode(unsigned char *sig, size_t sig_len)
{
std::unique_ptr b64(BIO_new(BIO_f_base64()));
BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
BIO* sink = BIO_new(BIO_s_mem());
BIO_push(b64.get(), sink);
BIO_write(b64.get(), sig,sig_len);
BIO_flush(b64.get());
const char* encoded;
const long len = BIO_get_mem_data(sink, &encoded);
return std::string(encoded, len);
}
static int bo_keysd_sign(
const BOB_TYPE &content,
EVP_PKEY *key,
string &result) // Signature base64
{
int ret = -1;
result.clear();
/* Create the Message Digest Context */
EVP_MD_CTX *mdctx = EVP_MD_CTX_create();
if (mdctx != NULL){
/* Initialise the DigestSign operation - SHA-256 has been selected as the message digest function in this example */
if(EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, key) == 1){
/* Call update with the message */
if(EVP_DigestSignUpdate(mdctx, content.getbuffer(),content.getsize()) == 1){
// Finalise the DigestSign operation
// First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the
// signature. Length is returned in slen */
size_t sig_len;
if(EVP_DigestSignFinal(mdctx, NULL, &sig_len) == 1){
//printf ("sig_len=%lu\n",sig_len);
// Allocate memory for the signature based on size in slen
unsigned char sig[sig_len];
// Obtain the signature
if(EVP_DigestSignFinal(mdctx, sig, &sig_len) == 1){
/* Success */
#if 0
for (unsigned i=0; i tmp.size()) len = tmp.size() - off;
result += tmp.substr(off,len) + "\n";
}
ret = 1;
}else{
// error
}
}
}
}
EVP_MD_CTX_destroy(mdctx);
}
return ret;
}
int main (int argc, char *argv[])
{
glocal int ret = -1;
glocal const char *data_dbserv = "localhost";
glocal const char *data_dbname = "files";
glocal const char *data_dbuser = NULL;
glocal const char *users_dbserv = "localhost";
glocal const char *users_dbname = "users";
glocal const char *users_dbuser = NULL;
glocal const char *control = "/var/run/bo-keysd.sock";
glocal const char *user = "bolixo";
glocal bool daemon = false;
glocal const char *pidfile = "/var/run/bo-keysd.pid";
glocal.ret = (argc,argv,"tlmpsql");
setproginfo ("bo-keysd",VERSION,"Generate public/private keys for user accounts");
setgrouparg ("Networking");
setarg ('c',"control","Unix socket for bo-keysd",glocal.control,false);
setgrouparg ("Database");
setarg (' ',"data_dbserv","Database server",glocal.data_dbserv,false);
setarg (' ',"data_dbname","Database name",glocal.data_dbname,false);
setarg (' ',"data_dbuser","Database user",glocal.data_dbuser,true);
setarg (' ',"users_dbserv","Database server for users",glocal.users_dbserv,false);
setarg (' ',"users_dbname","Database name for users",glocal.users_dbname,false);
setarg (' ',"users_dbuser","Database user for users",glocal.users_dbuser,true);
setgrouparg ("Misc.");
setarg (' ',"user","Run the program as this user",glocal.user,false);
setarg (' ',"daemon","Run in background",glocal.daemon,false);
setarg (' ',"pidfile","File holding the PID of the process",glocal.pidfile,false);
if (glocal.daemon){
syslog (LOG_ERR,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
if (glocal.daemon){
syslog (LOG_WARNING,"%s",msg);
}else{
fprintf (stderr,"%s",msg);
}
int ret = -1;
glocal string controlport = string_f("unix:%s",glocal.control);
glocal string passphrase;
glocal deque accounts;
glocal pid_t pid = (pid_t)-1;
glocal NSQL *usq; // SQL handle for the users database
glocal NSQL *worker_usq; // SQL handle for the doone process
glocal NSQL *worker_fsq; // SQL handle for the doone process
glocal const char *passwd = getenv("BO_WRITED_PWD");
if (glocal.passwd == NULL){
tlmp_error ("Can't get database password from environment, aborting\n");
exit (-1);
}
query_setdefaultdb (glocal.data_dbserv,glocal.data_dbname,glocal.data_dbuser,glocal.passwd);
query_getdefaultdb()->showerrormode(true);
NSQL nsql_sq (glocal.users_dbserv,glocal.users_dbname,glocal.users_dbuser,glocal.passwd);
nsql_sq.showerrormode (true);
nsql_sq.setunixpath ("/var/lib/mysql/mysql-users.sock");
NSQL worker_nsql_usq (glocal.users_dbserv,glocal.users_dbname,glocal.users_dbuser,glocal.passwd);
worker_nsql_usq.showerrormode (true);
worker_nsql_usq.setunixpath ("/var/lib/mysql/mysql-users.sock");
NSQL worker_nsql_fsq (glocal.data_dbserv,glocal.data_dbname,glocal.data_dbuser,glocal.passwd);
worker_nsql_fsq.showerrormode (true);
glocal.usq = &nsql_sq;
glocal.worker_usq = &worker_nsql_usq;
glocal.worker_fsq = &worker_nsql_fsq;
signal (SIGCHLD,SIG_IGN);
();
HANDLE_INFO *n = new HANDLE_INFO;
info.data = n;
if (string_cmp(info.port,glocal.controlport)==0){
n->type = TYPE_CONTROL;
}
HANDLE_INFO *n = (HANDLE_INFO*)info.data;
if (n->type == TYPE_WORKER){
glocal.pid = (pid_t)-1;
bo_keysd_doone(*this,glocal.pid,glocal.accounts,glocal.passphrase
,*glocal.worker_usq,*glocal.worker_fsq);
}
debug_printf (D_PROTO,"receive line: %s\n",line);
HANDLE_INFO *c = (HANDLE_INFO*)info.data;
static const char *tbtype[]={"none","control request","worker request"};
ERROR_PREFIX prefix ("%s:",tbtype[c->type]);
if (c->type == TYPE_CONTROL){
(this,c->req,line, info.linelen,endserver, endclient, no,c);
vector tb;
tb.push_back(string_f ("Version %s",VERSION));
tb.push_back(string_f("passphrase %s",glocal.passphrase.size()==0 ? "not set" : "set"));
tb.push_back(string_f("accounts size: %lu",glocal.accounts.size()));
tb.push_back(string_f("sub-process: %s",glocal.pid == (pid_t)-1 ? "not running" : "running"));
rep_status(tb);
rep_runstatus (glocal.accounts.size(),glocal.pid != (pid_t)-1);
endserver = true;
glocal ERR_CODE status = ERR_CODE_FAIL;
glocal string msg;
glocal.passphrase = passphrase;
// Check if we can decrypt one private key
(*glocal.usq,"select priv_key from users where userid=1");
if (row[0] == NULL){
glocal.status = ERR_CODE_CANTVERIFY;
glocal.msg = "Userid 1 has no private key, can't test passphrase";
}else{
EVP_PKEY *p = bo_keysd_load_private (row[0],glocal.passphrase);
if (p != NULL){
EVP_PKEY_free (p);
glocal.status = ERR_CODE_NONE;
}else{
glocal.status = ERR_CODE_INVALID;
glocal.msg = "Can't decipher private key of userid 1, invalid passphrase";
}
}
glocal.status = ERR_CODE_CANTVERIFY;
glocal.msg = "No userid 1 in database, can't test passphrase";
rep_setpassphrase (glocal.status,glocal.msg);
ERR_CODE status = ERR_CODE_FAIL;
string msg;
if (glocal.passphrase.size()==0){
msg = "pass phrase not set";
}else if (glocal.passphrase != passphrase){
msg = "pass phrase does not match";
}else{
status = ERR_CODE_NONE;
}
rep_checkpassphrase(status,msg);
if (glocal.passphrase.size()==0){
rep_genkey(ERR_CODE_NOPASSPHRASE,"Pass phrase not set");
}else{
glocal.accounts.push_back(account);
rep_genkey(ERR_CODE_NONE,"");
if (glocal.pid == (pid_t)-1){
bo_keysd_doone(glocal.TCPSERVER,glocal.pid,glocal.accounts,glocal.passphrase
,*glocal.worker_usq,*glocal.worker_fsq);
}
}
if (glocal.passphrase.size()==0){
rep_sign(ERR_CODE_NOPASSPHRASE,"Pass phrase not set","");
}else{
glocal int userid = userid;
glocal const BOB_TYPE *content = &content;
glocal ERR_CODE status = ERR_CODE_FAIL;
glocal string msg;
glocal string result;
(*glocal.usq,"select priv_key,name from users where userid=%d",userid);
if (row[0] == NULL){
glocal.status = ERR_CODE_CANTSIGN;
glocal.msg = string_f("No private key for account %s, can't sign",row[1]);
}else{
EVP_PKEY *p = bo_keysd_load_private (row[0],glocal.passphrase);
if (p != NULL){
if (bo_keysd_sign(*glocal.content,p,glocal.result) != -1){
glocal.status = ERR_CODE_NONE;
}else{
glocal.status = ERR_CODE_FAIL;
glocal.msg = "Internal error, Can't sign";
}
EVP_PKEY_free (p);
}else{
glocal.status = ERR_CODE_INVALID;
glocal.msg = string_f("Can't decipher private key of userid %d, invalid passphrase"
,glocal.userid);
}
}
glocal.status = ERR_CODE_IVLDACCOUNT;
glocal.msg = "Account does not exist";
rep_sign(glocal.status,glocal.msg,glocal.result);
}
if (on){
debug_seton();
}else{
debug_setoff();
}
debug_setfdebug (filename);
tlmp_error ("Control: Invalid command: %s\n",line);
endclient = true;
}
bool some_errors = false;
if (fdpass_setcontrol(s,glocal.control,glocal.user)==-1){
some_errors = true;
}
if (!some_errors && s.is_ok()){
s.setrawmode(true);
if (glocal.daemon){
daemon_init(glocal.pidfile,glocal.user);
}
s.loop();
ret = 0;
}
return ret;
return glocal.ret;
}