/* 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" #ifndef OPENSSL_VERSION_MAJOR static int keysd_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; } #else static int keysd_generate(string &private_key, string &public_key, PARAM_STRING passphrase) { int ret = -1; private_key.clear(); public_key.clear(); ENGINE *e = nullptr; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA,e); if (ctx == nullptr){ tlmp_error ("ctx=%p\n",ctx); }else{ // Generate key int rc = EVP_PKEY_keygen_init (ctx); if (rc != 1){ tlmp_error ("keygen_init rc=%d\n",rc); }else{ rc = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048); if (rc != 1){ tlmp_error ("keygen_bits rc=%d\n",rc); }else{ EVP_PKEY *retkey=nullptr; rc = EVP_PKEY_generate(ctx,&retkey); if (rc != 1){ tlmp_error ("generate rc=%d\n",rc); }else{ { BIO *bio = BIO_new(BIO_s_mem()); PEM_write_bio_PrivateKey(bio, retkey,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, retkey); char line[1000]; while(BIO_gets(bio,line,sizeof(line))>0){ //printf ("line=%s\n",line); public_key += line; } BIO_free_all(bio); } //printf ("retkey.size=%d bits=%d\n",EVP_PKEY_get_size(retkey),EVP_PKEY_get_bits(retkey)); //printf ("private_key=%s\npublic_key=%s\n",private_key.c_str(),public_key.c_str()); ret = 0; } EVP_PKEY_free(retkey); } } EVP_PKEY_CTX_free(ctx); } return ret; } #endif 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 (keysd_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: %zu",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; }