ZMQClient.cpp 9.14 KB
Newer Older
kladko's avatar
kladko committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
    Copyright (C) 2018-2019 SKALE Labs

    This file is part of skale-consensus.

    skale-consensus 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.

    skale-consensus 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 skale-consensus.  If not, see <https://www.gnu.org/licenses/>.

    @file ZMQClient.cpp
    @author Stan Kladko
    @date 2020
*/
kladko's avatar
kladko committed
23 24

#include "sys/random.h"
kladko's avatar
kladko committed
25 26 27
#include <sys/types.h>
#include <sys/syscall.h>

kladko's avatar
kladko committed
28

kladko's avatar
kladko committed
29 30
#include <fstream>
#include <streambuf>
kladko's avatar
kladko committed
31
#include <regex>
kladko's avatar
kladko committed
32

kladko's avatar
kladko committed
33
#include "sgxwallet_common.h"
kladko's avatar
kladko committed
34
#include "common.h"
kladko's avatar
kladko committed
35
#include "BLSCrypto.h"
kladko's avatar
kladko committed
36 37 38 39
#include "BLSSignReqMessage.h"
#include "BLSSignRspMessage.h"
#include "ECDSASignReqMessage.h"
#include "ECDSASignRspMessage.h"
Stan Kladko's avatar
Stan Kladko committed
40
#include "ZMQClient.h"
kladko's avatar
kladko committed
41 42 43 44 45


shared_ptr <ZMQMessage> ZMQClient::doRequestReply(Json::Value &_req) {

    Json::FastWriter fastWriter;
kladko's avatar
kladko committed
46

kladko's avatar
kladko committed
47 48
    if (sign) {
        CHECK_STATE(!certificate.empty());
kladko's avatar
kladko committed
49 50
        CHECK_STATE(!key.empty());

kladko's avatar
kladko committed
51
        _req["cert"] = certificate;
kladko's avatar
kladko committed
52 53 54

        string msgToSign = fastWriter.write(_req);

kladko's avatar
kladko committed
55
        _req["msgSig"] = signString(pkey, msgToSign);
kladko's avatar
kladko committed
56 57 58 59 60
    }

    string reqStr = fastWriter.write(_req);


kladko's avatar
kladko committed
61 62 63
    reqStr = reqStr.substr(0, reqStr.size() - 1);
    CHECK_STATE(reqStr.front() == '{');
    CHECK_STATE(reqStr.at(reqStr.size() - 1) == '}');
kladko's avatar
kladko committed
64

kladko's avatar
kladko committed
65

kladko's avatar
kladko committed
66 67
    auto resultStr = doZmqRequestReply(reqStr);

kladko's avatar
kladko committed
68
    try {
kladko's avatar
kladko committed
69

kladko's avatar
kladko committed
70 71 72
        CHECK_STATE(resultStr.size() > 5)
        CHECK_STATE(resultStr.front() == '{')
        CHECK_STATE(resultStr.back() == '}')
kladko's avatar
kladko committed
73 74


kladko's avatar
kladko committed
75
        return ZMQMessage::parse(resultStr.c_str(), resultStr.size(), false, false);
kladko's avatar
kladko committed
76 77
    } catch (std::exception &e) {
        spdlog::error(string("Error in doRequestReply:") + e.what());
kladko's avatar
kladko committed
78 79
        throw;
    } catch (...) {
kladko's avatar
kladko committed
80
        spdlog::error("Error in doRequestReply");
kladko's avatar
kladko committed
81
        throw;
kladko's avatar
kladko committed
82
    }
kladko's avatar
kladko committed
83

kladko's avatar
kladko committed
84

kladko's avatar
kladko committed
85 86
}

kladko's avatar
kladko committed
87

kladko's avatar
kladko committed
88 89 90
string ZMQClient::doZmqRequestReply(string &_req) {

    stringstream request;
kladko's avatar
kladko committed
91

kladko's avatar
kladko committed
92
    shared_ptr <zmq::socket_t> clientSocket = nullptr;
kladko's avatar
kladko committed
93 94 95 96 97 98 99 100

    {
        lock_guard <recursive_mutex> m(mutex);
        if (!clientSockets.count(getProcessID()))
            reconnect();
        clientSocket = clientSockets.at(getProcessID());
        CHECK_STATE(clientSocket);
    }
kladko's avatar
kladko committed
101 102
    CHECK_STATE(clientSocket);

kladko's avatar
kladko committed
103
    spdlog::debug("ZMQ client sending: \n {}", _req);
kladko's avatar
kladko committed
104

kladko's avatar
kladko committed
105 106 107 108 109 110 111 112 113 114
    s_send(*clientSocket, _req);

    while (true) {
        //  Poll socket for a reply, with timeout
        zmq::pollitem_t items[] = {
                {static_cast<void *>(*clientSocket), 0, ZMQ_POLLIN, 0}};
        zmq::poll(&items[0], 1, REQUEST_TIMEOUT);
        //  If we got a reply, process it
        if (items[0].revents & ZMQ_POLLIN) {
            string reply = s_recv(*clientSocket);
kladko's avatar
kladko committed
115 116 117

            CHECK_STATE(reply.size() > 5);
            reply = reply.substr(0, reply.size() - 1);
kladko's avatar
kladko committed
118
            spdlog::debug("ZMQ client received reply:{}", reply);
kladko's avatar
kladko committed
119 120 121
            CHECK_STATE(reply.front() == '{');
            CHECK_STATE(reply.back() == '}');

kladko's avatar
kladko committed
122 123 124 125 126 127 128 129 130 131 132
            return reply;
        } else {
            spdlog::error("W: no response from server, retrying...");
            reconnect();
            //  Send request again, on new socket
            s_send(*clientSocket, _req);
        }
    }
}


kladko's avatar
kladko committed
133
string ZMQClient::readFileIntoString(const string &_fileName) {
kladko's avatar
kladko committed
134 135 136 137 138
    ifstream t(_fileName);
    string str((istreambuf_iterator<char>(t)), istreambuf_iterator<char>());
    return str;
}

kladko's avatar
kladko committed
139

kladko's avatar
kladko committed
140

kladko's avatar
kladko committed
141 142 143 144
void ZMQClient::verifySig(EVP_PKEY* _pubkey, const string& _str, const string& _sig) {

    CHECK_STATE(_pubkey);
    CHECK_STATE(!_str.empty());
kladko's avatar
kladko committed
145

kladko's avatar
kladko committed
146 147
    static std::regex r("\\s+");
    auto msgToSign = std::regex_replace(_str, r, "");
kladko's avatar
kladko committed
148 149 150 151 152

    vector<uint8_t> binSig(256,0);

    uint64_t binLen = 0;

kladko's avatar
kladko committed
153 154
    CHECK_STATE2(hex2carray(_sig.c_str(), &binLen, binSig.data(), binSig.size()),
                 ZMQ_COULD_NOT_PARSE);
kladko's avatar
kladko committed
155 156 157 158 159 160 161 162 163 164 165 166

    CHECK_STATE(binLen > 0);

    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;

    size_t slen = 0;

    CHECK_STATE(mdctx = EVP_MD_CTX_create());

    CHECK_STATE((EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, _pubkey) == 1));

kladko's avatar
kladko committed
167
    CHECK_STATE(EVP_DigestVerifyUpdate(mdctx, msgToSign.c_str(), msgToSign.size()) == 1);
kladko's avatar
kladko committed
168 169 170 171 172 173

/* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the
 * signature. Length is returned in slen */



kladko's avatar
kladko committed
174 175
    CHECK_STATE2(EVP_DigestVerifyFinal(mdctx, binSig.data(), binLen) == 1,
                 ZMQ_COULD_NOT_VERIFY_SIG);
kladko's avatar
kladko committed
176 177 178 179 180 181 182 183 184 185

    if (mdctx) EVP_MD_CTX_destroy(mdctx);

    return;
}


string ZMQClient::signString(EVP_PKEY* _pkey, const string& _str) {

    CHECK_STATE(_pkey);
kladko's avatar
kladko committed
186 187 188 189 190 191
    CHECK_STATE(!_str.empty());

    static std::regex r("\\s+");
    auto msgToSign = std::regex_replace(_str, r, "");


kladko's avatar
kladko committed
192 193 194 195 196 197 198 199 200 201

    EVP_MD_CTX *mdctx = NULL;
    int ret = 0;
    unsigned char *signature = NULL;
    auto sig = &signature;
    size_t slen = 0;

    CHECK_STATE(mdctx = EVP_MD_CTX_create());


kladko's avatar
kladko committed
202
    CHECK_STATE((EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, _pkey) == 1));
kladko's avatar
kladko committed
203 204


kladko's avatar
kladko committed
205
    CHECK_STATE(EVP_DigestSignUpdate(mdctx, msgToSign.c_str(), msgToSign.size()) == 1);
kladko's avatar
kladko committed
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225

/* First call EVP_DigestSignFinal with a NULL sig parameter to obtain the length of the
 * signature. Length is returned in slen */

    CHECK_STATE(EVP_DigestSignFinal(mdctx, NULL, &slen) == 1);
    signature = (unsigned char *) OPENSSL_malloc(sizeof(unsigned char) * slen);
    CHECK_STATE(signature);
    CHECK_STATE(EVP_DigestSignFinal(mdctx, signature, &slen) == 1);

    auto hexSig = carray2Hex(signature, slen);

    string hexStringSig(hexSig.begin(), hexSig.end());

    /* Clean up */
    if (signature) OPENSSL_free(signature);
    if (mdctx) EVP_MD_CTX_destroy(mdctx);

    return hexStringSig;
}

kladko's avatar
kladko committed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
pair<EVP_PKEY*, X509*> ZMQClient::readPublicKeyFromCertStr(const string& _certStr) {

    CHECK_STATE(!_certStr.empty())

    BIO *bo = BIO_new(BIO_s_mem());
    CHECK_STATE(bo);
    BIO_write(bo, _certStr.c_str(), _certStr.size());

    X509* cert = nullptr;
    PEM_read_bio_X509(bo, &cert, 0, 0);
    CHECK_STATE(cert);
    auto key = X509_get_pubkey(cert);
    BIO_free(bo);
    CHECK_STATE(key);
    return {key, cert};
};
kladko's avatar
kladko committed
242

kladko's avatar
kladko committed
243
ZMQClient::ZMQClient(const string &ip, uint16_t port, bool _sign, const string &_certFileName,
kladko's avatar
kladko committed
244
                     const string &_certKeyName) : ctx(1), sign(_sign),
kladko's avatar
kladko committed
245
                                                   certKeyName(_certKeyName), certFileName(_certFileName) {
kladko's avatar
kladko committed
246

kladko's avatar
kladko committed
247 248 249
    spdlog::info("Initing ZMQClient. Sign:{} ", _sign);

    if (sign) {
kladko's avatar
kladko committed
250 251
        CHECK_STATE(!_certFileName.empty());
        CHECK_STATE(!_certKeyName.empty());
kladko's avatar
kladko committed
252

kladko's avatar
kladko committed
253
        certificate = readFileIntoString(_certFileName);
kladko's avatar
kladko committed
254 255
        CHECK_STATE(!certificate.empty());

kladko's avatar
kladko committed
256 257 258
        key = readFileIntoString(_certKeyName);
        CHECK_STATE(!key.empty());

kladko's avatar
kladko committed
259 260 261 262 263 264 265 266
        BIO *bo = BIO_new(BIO_s_mem());
        CHECK_STATE(bo);
        BIO_write(bo, key.c_str(), key.size());

        PEM_read_bio_PrivateKey(bo, &pkey, 0, 0);
        CHECK_STATE(pkey);
        BIO_free(bo);

kladko's avatar
kladko committed
267 268 269
        auto pubKeyStr = readFileIntoString(_certFileName);
        CHECK_STATE(!pubKeyStr.empty());

kladko's avatar
kladko committed
270
        tie(pubkey, x509Cert) = readPublicKeyFromCertStr(pubKeyStr);
kladko's avatar
kladko committed
271

kladko's avatar
kladko committed
272 273 274
        auto sig = signString(pkey, "sample");
        verifySig(pubkey, "sample", sig);

kladko's avatar
kladko committed
275 276 277 278 279 280 281 282 283
    } else {
        CHECK_STATE(_certFileName.empty());
        CHECK_STATE(_certKeyName.empty());
    }

    certFileName = _certFileName;
    certKeyName = _certKeyName;


kladko's avatar
kladko committed
284 285 286 287
    url = "tcp://" + ip + ":" + to_string(port);
}

void ZMQClient::reconnect() {
kladko's avatar
kladko committed
288

kladko's avatar
kladko committed
289
    lock_guard <recursive_mutex> lock(mutex);
kladko's avatar
kladko committed
290

kladko's avatar
kladko committed
291 292 293 294 295
    auto pid = getProcessID();

    if (clientSockets.count(pid) > 0) {
        clientSockets.erase(pid);
    }
kladko's avatar
kladko committed
296 297


kladko's avatar
kladko committed
298 299 300
    char identity[10];
    getrandom(identity, 10, 0);
    auto clientSocket = make_shared<zmq::socket_t>(ctx, ZMQ_DEALER);
kladko's avatar
kladko committed
301
    clientSocket->setsockopt(ZMQ_IDENTITY, identity, 10);
kladko's avatar
kladko committed
302 303 304
    //  Configure socket to not wait at close time
    int linger = 0;
    clientSocket->setsockopt(ZMQ_LINGER, &linger, sizeof(linger));
kladko's avatar
kladko committed
305
    clientSocket->connect(url);
kladko's avatar
kladko committed
306
    clientSockets.insert({pid, clientSocket});
kladko's avatar
kladko committed
307 308 309 310 311 312 313 314 315 316 317 318
}


string ZMQClient::blsSignMessageHash(const std::string &keyShareName, const std::string &messageHash, int t, int n) {
    Json::Value p;
    p["type"] = ZMQMessage::BLS_SIGN_REQ;
    p["keyShareName"] = keyShareName;
    p["messageHash"] = messageHash;
    p["n"] = n;
    p["t"] = t;
    auto result = dynamic_pointer_cast<BLSSignRspMessage>(doRequestReply(p));
    CHECK_STATE(result);
kladko's avatar
kladko committed
319 320
    CHECK_STATE(result->getStatus() == 0);

kladko's avatar
kladko committed
321 322 323 324 325 326 327 328 329 330 331
    return result->getSigShare();
}

string ZMQClient::ecdsaSignMessageHash(int base, const std::string &keyName, const std::string &messageHash) {
    Json::Value p;
    p["type"] = ZMQMessage::ECDSA_SIGN_REQ;
    p["base"] = base;
    p["keyName"] = keyName;
    p["messageHash"] = messageHash;
    auto result = dynamic_pointer_cast<ECDSASignRspMessage>(doRequestReply(p));
    CHECK_STATE(result);
kladko's avatar
kladko committed
332
    CHECK_STATE(result->getStatus() == 0);
kladko's avatar
kladko committed
333
    return result->getSignature();
kladko's avatar
kladko committed
334 335 336 337
}


uint64_t ZMQClient::getProcessID() {
kladko's avatar
kladko committed
338
    return syscall(__NR_gettid);
kladko's avatar
kladko committed
339
}