• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 1480592373

03 Oct 2024 03:40PM UTC coverage: 76.387% (+0.04%) from 76.346%
1480592373

push

gitlab-ci

danielskinstad
feat: change generated key from RSA to ED25519

Ticket: MEN-7534
Changelog: Change the generated key from RSA to ED25519. This is
generated if there is no key provided in the configuration file, and if
and if there is no previously generated key. Existing keys won't be affected,
so this will only affect installation in new devices.
The motivation for this change is more efficient computation.

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>

6 of 6 new or added lines in 3 files covered. (100.0%)

73 existing lines in 3 files now uncovered.

7350 of 9622 relevant lines covered (76.39%)

11245.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.91
/src/common/crypto/platform/openssl/crypto.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <common/crypto.hpp>
16

17
#include <common/crypto/platform/openssl/openssl_config.h>
18

19
#include <cstdint>
20
#include <string>
21
#include <vector>
22
#include <memory>
23

24
#include <openssl/bn.h>
25
#include <openssl/ecdsa.h>
26
#include <openssl/err.h>
27
#include <openssl/engine.h>
28
#include <openssl/ui.h>
29
#ifndef MENDER_CRYPTO_OPENSSL_LEGACY
30
#include <openssl/provider.h>
31
#include <openssl/store.h>
32
#endif // MENDER_CRYPTO_OPENSSL_LEGACY
33

34
#include <openssl/evp.h>
35
#include <openssl/conf.h>
36
#include <openssl/pem.h>
37
#include <openssl/rsa.h>
38

39
#include <common/io.hpp>
40
#include <common/error.hpp>
41
#include <common/expected.hpp>
42
#include <common/common.hpp>
43

44
#include <artifact/sha/sha.hpp>
45

46

47
namespace mender {
48
namespace common {
49
namespace crypto {
50

51
const size_t MENDER_DIGEST_SHA256_LENGTH = 32;
52

53
const size_t OPENSSL_SUCCESS = 1;
54

55
using namespace std;
56

57
namespace error = mender::common::error;
58
namespace io = mender::common::io;
59

60
using EnginePtr = unique_ptr<ENGINE, void (*)(ENGINE *)>;
61
#ifndef MENDER_CRYPTO_OPENSSL_LEGACY
62
using ProviderPtr = unique_ptr<OSSL_PROVIDER, int (*)(OSSL_PROVIDER *)>;
63
#endif // MENDER_CRYPTO_OPENSSL_LEGACY
64

65
class OpenSSLResourceHandle {
×
66
public:
67
        EnginePtr engine;
68
};
69

70
auto resource_handle_free_func = [](OpenSSLResourceHandle *h) {
×
71
        if (h) {
×
72
                delete h;
×
73
        }
74
};
×
75

76
auto pkey_ctx_free_func = [](EVP_PKEY_CTX *ctx) {
26✔
77
        if (ctx) {
26✔
78
                EVP_PKEY_CTX_free(ctx);
26✔
79
        }
80
};
26✔
81
auto pkey_free_func = [](EVP_PKEY *key) {
53✔
82
        if (key) {
53✔
83
                EVP_PKEY_free(key);
53✔
84
        }
85
};
53✔
86
auto bio_free_func = [](BIO *bio) {
49✔
87
        if (bio) {
49✔
88
                BIO_free(bio);
49✔
89
        }
90
};
49✔
91
auto bio_free_all_func = [](BIO *bio) {
16✔
92
        if (bio) {
16✔
93
                BIO_free_all(bio);
16✔
94
        }
95
};
16✔
96
auto bn_free = [](BIGNUM *bn) {
×
97
        if (bn) {
×
98
                BN_free(bn);
×
99
        }
100
};
×
101
auto engine_free_func = [](ENGINE *e) {
×
102
        if (e) {
×
103
                ENGINE_free(e);
×
104
        }
105
};
×
106

107
auto password_callback = [](char *buf, int size, int rwflag, void *u) {
3✔
108
        // We'll only use this callback for reading passphrases, not for
109
        // writing them.
110
        assert(rwflag == 0);
111

112
        if (u == nullptr) {
3✔
113
                return 0;
114
        }
115

116
        // NB: buf is not expected to be null terminated.
117
        char *const pass = static_cast<char *>(u);
118
        strncpy(buf, pass, size);
3✔
119

120
        return static_cast<int>(strnlen(pass, size));
3✔
121
};
122

123

124
// NOTE: GetOpenSSLErrorMessage should be called upon all OpenSSL errors, as
125
// the errors are queued, and if not harvested, the FIFO structure of the
126
// queue will mean that if you just get one, you might actually get the wrong
127
// one.
128
string GetOpenSSLErrorMessage() {
40✔
129
        const auto sysErrorCode = errno;
40✔
130
        auto sslErrorCode = ERR_get_error();
40✔
131

132
        std::string errorDescription {};
133
        while (sslErrorCode != 0) {
169✔
134
                if (!errorDescription.empty()) {
129✔
135
                        errorDescription += '\n';
136
                }
137
                errorDescription += ERR_error_string(sslErrorCode, nullptr);
129✔
138
                sslErrorCode = ERR_get_error();
129✔
139
        }
140
        if (sysErrorCode != 0) {
40✔
141
                if (!errorDescription.empty()) {
38✔
142
                        errorDescription += '\n';
143
                }
144
                errorDescription += "System error, code=" + std::to_string(sysErrorCode);
76✔
145
                errorDescription += ", ";
38✔
146
                errorDescription += strerror(sysErrorCode);
38✔
147
        }
148
        return errorDescription;
40✔
149
}
150

151
ExpectedPrivateKey LoadFromHSMEngine(const Args &args) {
×
152
        log::Trace("Loading the private key from HSM");
×
153

154
        ENGINE_load_builtin_engines();
×
155
        auto engine = EnginePtr(ENGINE_by_id(args.ssl_engine.c_str()), engine_free_func);
×
156

157
        if (engine == nullptr) {
×
158
                return expected::unexpected(MakeError(
×
159
                        SetupError,
160
                        "Failed to get the " + args.ssl_engine
×
161
                                + " engine. No engine with the ID found: " + GetOpenSSLErrorMessage()));
×
162
        }
163
        log::Debug("Loaded the HSM engine successfully!");
×
164

165
        int res = ENGINE_init(engine.get());
×
166
        if (not res) {
×
167
                return expected::unexpected(MakeError(
×
168
                        SetupError,
169
                        "Failed to initialise the hardware security module (HSM): "
170
                                + GetOpenSSLErrorMessage()));
×
171
        }
172
        log::Debug("Successfully initialised the HSM engine");
×
173

174
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
175
                ENGINE_load_private_key(
176
                        engine.get(),
177
                        args.private_key_path.c_str(),
178
                        (UI_METHOD *) nullptr,
179
                        nullptr /*callback_data */),
180
                pkey_free_func);
×
181
        if (private_key == nullptr) {
×
182
                return expected::unexpected(MakeError(
×
183
                        SetupError,
184
                        "Failed to load the private key from the hardware security module: "
185
                                + GetOpenSSLErrorMessage()));
×
186
        }
187
        log::Debug("Successfully loaded the private key from the HSM Engine: " + args.ssl_engine);
×
188

189
        auto handle = unique_ptr<OpenSSLResourceHandle, void (*)(OpenSSLResourceHandle *)>(
190
                new OpenSSLResourceHandle {std::move(engine)}, resource_handle_free_func);
×
191
        return PrivateKey(std::move(private_key), std::move(handle));
×
192
}
193

194
#ifdef MENDER_CRYPTO_OPENSSL_LEGACY
195
ExpectedPrivateKey LoadFrom(const Args &args) {
37✔
196
        log::Trace("Loading private key from file: " + args.private_key_path);
74✔
197
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
198
                BIO_new_file(args.private_key_path.c_str(), "r"), bio_free_func);
74✔
199
        if (private_bio_key == nullptr) {
37✔
200
                return expected::unexpected(MakeError(
6✔
201
                        SetupError,
202
                        "Failed to load the private key file " + args.private_key_path + ": "
12✔
203
                                + GetOpenSSLErrorMessage()));
30✔
204
        }
205

206
        char *passphrase = const_cast<char *>(args.private_key_passphrase.c_str());
207

208
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
209
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, password_callback, passphrase),
210
                pkey_free_func);
62✔
211
        if (private_key == nullptr) {
31✔
212
                return expected::unexpected(MakeError(
4✔
213
                        SetupError,
214
                        "Failed to load the private key: " + args.private_key_path + " "
8✔
215
                                + GetOpenSSLErrorMessage()));
20✔
216
        }
217

218
        return PrivateKey(std::move(private_key));
27✔
219
}
220
#endif // MENDER_CRYPTO_OPENSSL_LEGACY
221

222
#ifndef MENDER_CRYPTO_OPENSSL_LEGACY
223
ExpectedPrivateKey LoadFrom(const Args &args) {
224
        char *passphrase = const_cast<char *>(args.private_key_passphrase.c_str());
225

226
        auto ui_method = unique_ptr<UI_METHOD, void (*)(UI_METHOD *)>(
227
                UI_UTIL_wrap_read_pem_callback(password_callback, 0 /* rw_flag */), UI_destroy_method);
228
        auto ctx = unique_ptr<OSSL_STORE_CTX, int (*)(OSSL_STORE_CTX *)>(
229
                OSSL_STORE_open(
230
                        args.private_key_path.c_str(),
231
                        ui_method.get(),
232
                        passphrase,
233
                        nullptr, /* OSSL_PARAM params[] */
234
                        nullptr),
235
                OSSL_STORE_close);
236

237
        if (ctx == nullptr) {
238
                return expected::unexpected(MakeError(
239
                        SetupError,
240
                        "Failed to load the private key from: " + args.private_key_path
241
                                + " error: " + GetOpenSSLErrorMessage()));
242
        }
243

244
        // Go through all objects in the context till we find the first private key
245
        while (not OSSL_STORE_eof(ctx.get())) {
246
                auto info = unique_ptr<OSSL_STORE_INFO, void (*)(OSSL_STORE_INFO *)>(
247
                        OSSL_STORE_load(ctx.get()), OSSL_STORE_INFO_free);
248

249
                if (info == nullptr) {
250
                        log::Error(
251
                                "Failed to load the the private key: " + args.private_key_path
252
                                + " trying the next object in the context: " + GetOpenSSLErrorMessage());
253
                        continue;
254
                }
255

256
                const int type_info {OSSL_STORE_INFO_get_type(info.get())};
257
                switch (type_info) {
258
                case OSSL_STORE_INFO_PKEY: {
259
                        // NOTE: get1 creates a duplicate of the pkey from the info, which can be
260
                        // used after the info ctx is destroyed
261
                        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
262
                                OSSL_STORE_INFO_get1_PKEY(info.get()), pkey_free_func);
263
                        if (private_key == nullptr) {
264
                                return expected::unexpected(MakeError(
265
                                        SetupError,
266
                                        "Failed to load the private key: " + args.private_key_path
267
                                                + " error: " + GetOpenSSLErrorMessage()));
268
                        }
269

270
                        return PrivateKey(std::move(private_key));
271
                }
272
                default:
273
                        const string info_type_string = OSSL_STORE_INFO_type_string(type_info);
274
                        log::Debug("Unhandled OpenSSL type: expected PrivateKey, got: " + info_type_string);
275
                        continue;
276
                }
277
        }
278

279
        return expected::unexpected(
280
                MakeError(SetupError, "Failed to load the private key: " + GetOpenSSLErrorMessage()));
281
}
282
#endif // ndef MENDER_CRYPTO_OPENSSL_LEGACY
283

284
ExpectedPrivateKey PrivateKey::Load(const Args &args) {
37✔
285
        // Load OpenSSL config
286
        if ((CONF_modules_load_file(nullptr, nullptr, 0) != OPENSSL_SUCCESS)) {
37✔
287
                log::Warning("Failed to load OpenSSL configuration file: " + GetOpenSSLErrorMessage());
44✔
288
        }
289

290
        log::Trace("Loading private key");
74✔
291
        if (args.ssl_engine != "") {
37✔
292
                return LoadFromHSMEngine(args);
×
293
        }
294
        return LoadFrom(args);
37✔
295
}
296

297
ExpectedPrivateKey PrivateKey::Generate() {
7✔
298
#ifdef MENDER_CRYPTO_OPENSSL_LEGACY
299
        auto pkey_gen_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
300
                EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr), pkey_ctx_free_func);
14✔
301
#else
302
        auto pkey_gen_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
303
                EVP_PKEY_CTX_new_from_name(nullptr, "ED25519", nullptr), pkey_ctx_free_func);
304
#endif // MENDER_CRYPTO_OPENSSL_LEGACY
305

306
        int ret = EVP_PKEY_keygen_init(pkey_gen_ctx.get());
7✔
307
        if (ret != OPENSSL_SUCCESS) {
7✔
UNCOV
308
                return expected::unexpected(MakeError(
×
309
                        SetupError,
310
                        "Failed to generate a private key. Initialization failed: "
UNCOV
311
                                + GetOpenSSLErrorMessage()));
×
312
        }
313
        EVP_PKEY *pkey = nullptr;
7✔
314
#ifdef MENDER_CRYPTO_OPENSSL_LEGACY
315
        ret = EVP_PKEY_keygen(pkey_gen_ctx.get(), &pkey);
7✔
316
#else
317
        ret = EVP_PKEY_generate(pkey_gen_ctx.get(), &pkey);
318
#endif // MENDER_CRYPTO_OPENSSL_LEGACY
319
        if (ret != OPENSSL_SUCCESS) {
7✔
UNCOV
320
                return expected::unexpected(MakeError(
×
321
                        SetupError,
UNCOV
322
                        "Failed to generate a private key. Generation failed: " + GetOpenSSLErrorMessage()));
×
323
        }
324

325
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(pkey, pkey_free_func);
7✔
326
        return PrivateKey(std::move(private_key));
7✔
327
}
328

329
expected::ExpectedString EncodeBase64(vector<uint8_t> to_encode) {
14✔
330
        // Predict the len of the decoded for later verification. From man page:
331
        // For every 3 bytes of input provided 4 bytes of output
332
        // data will be produced. If n is not divisible by 3 (...)
333
        // the output is padded such that it is always divisible by 4.
334
        const uint64_t predicted_len {4 * ((to_encode.size() + 2) / 3)};
14✔
335

336
        // Add space for a NUL terminator. From man page:
337
        // Additionally a NUL terminator character will be added
338
        auto buffer {vector<unsigned char>(predicted_len + 1)};
14✔
339

340
        const int64_t output_len {
341
                EVP_EncodeBlock(buffer.data(), to_encode.data(), static_cast<int>(to_encode.size()))};
14✔
342
        assert(output_len >= 0);
343

344
        if (predicted_len != static_cast<uint64_t>(output_len)) {
14✔
UNCOV
345
                return expected::unexpected(
×
UNCOV
346
                        MakeError(Base64Error, "The predicted and the actual length differ"));
×
347
        }
348

349
        return string(buffer.begin(), buffer.end() - 1); // Remove the last zero byte
28✔
350
}
351

352
expected::ExpectedBytes DecodeBase64(string to_decode) {
15✔
353
        // Predict the len of the decoded for later verification. From man page:
354
        // For every 4 input bytes exactly 3 output bytes will be
355
        // produced. The output will be padded with 0 bits if necessary
356
        // to ensure that the output is always 3 bytes.
357
        const uint64_t predicted_len {3 * ((to_decode.size() + 3) / 4)};
15✔
358

359
        auto buffer {vector<unsigned char>(predicted_len)};
15✔
360

361
        const int64_t output_len {EVP_DecodeBlock(
15✔
362
                buffer.data(),
363
                common::ByteVectorFromString(to_decode).data(),
15✔
364
                static_cast<int>(to_decode.size()))};
15✔
365
        assert(output_len >= 0);
366

367
        if (predicted_len != static_cast<uint64_t>(output_len)) {
15✔
UNCOV
368
                return expected::unexpected(MakeError(
×
369
                        Base64Error,
UNCOV
370
                        "The predicted (" + std::to_string(predicted_len) + ") and the actual ("
×
371
                                + std::to_string(output_len) + ") length differ"));
×
372
        }
373

374
        // Subtract padding bytes. Inspired by internal OpenSSL code from:
375
        // https://github.com/openssl/openssl/blob/ff88545e02ab48a52952350c52013cf765455dd3/crypto/ct/ct_b64.c#L46
376
        for (auto it = to_decode.crbegin(); *it == '='; it++) {
24✔
377
                buffer.pop_back();
378
        }
379

380
        return buffer;
15✔
381
}
382

383

384
expected::ExpectedString ExtractPublicKey(const Args &args) {
9✔
385
        auto exp_private_key = PrivateKey::Load(args);
9✔
386
        if (!exp_private_key) {
9✔
387
                return expected::unexpected(exp_private_key.error());
2✔
388
        }
389

390
        auto bio_public_key = unique_ptr<BIO, void (*)(BIO *)>(BIO_new(BIO_s_mem()), bio_free_all_func);
16✔
391

392
        if (!bio_public_key.get()) {
8✔
UNCOV
393
                return expected::unexpected(MakeError(
×
394
                        SetupError,
395
                        "Failed to extract the public key from the private key " + args.private_key_path
×
UNCOV
396
                                + "):" + GetOpenSSLErrorMessage()));
×
397
        }
398

399
        int ret = PEM_write_bio_PUBKEY(bio_public_key.get(), exp_private_key.value().Get());
8✔
400
        if (ret != OPENSSL_SUCCESS) {
8✔
UNCOV
401
                return expected::unexpected(MakeError(
×
402
                        SetupError,
UNCOV
403
                        "Failed to extract the public key from the private key (" + args.private_key_path
×
UNCOV
404
                                + "): OpenSSL BIO write failed: " + GetOpenSSLErrorMessage()));
×
405
        }
406

407
        // NOTE: At this point we already have a public key available for extraction.
408
        // However, when using some providers in OpenSSL3 the external provider might
409
        // write the key in the old PKCS#1 format. The format is not deprecated, but
410
        // our older backends only understand the format if it is in the PKCS#8
411
        // (SubjectPublicKey) format:
412
        //
413
        // For us who don't speak OpenSSL:
414
        //
415
        // -- BEGIN RSA PUBLIC KEY -- <- PKCS#1 (old format)
416
        // -- BEGIN PUBLIC KEY -- <- PKCS#8 (new format - can hold different key types)
417

418

419
        auto evp_public_key = PkeyPtr(
420
                PEM_read_bio_PUBKEY(bio_public_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
16✔
421

422
        if (evp_public_key == nullptr) {
8✔
UNCOV
423
                return expected::unexpected(MakeError(
×
424
                        SetupError,
UNCOV
425
                        "Failed to extract the public key from the private key " + args.private_key_path
×
426
                                + "):" + GetOpenSSLErrorMessage()));
×
427
        }
428

429
        auto bio_public_key_new =
430
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new(BIO_s_mem()), bio_free_all_func);
16✔
431

432
        if (bio_public_key_new == nullptr) {
8✔
433
                return expected::unexpected(MakeError(
×
434
                        SetupError,
UNCOV
435
                        "Failed to extract the public key from the public key " + args.private_key_path
×
436
                                + "):" + GetOpenSSLErrorMessage()));
×
437
        }
438

439
        ret = PEM_write_bio_PUBKEY(bio_public_key_new.get(), evp_public_key.get());
8✔
440
        if (ret != OPENSSL_SUCCESS) {
8✔
UNCOV
441
                return expected::unexpected(MakeError(
×
442
                        SetupError,
443
                        "Failed to extract the public key from the private key: (" + args.private_key_path
×
UNCOV
444
                                + "): OpenSSL BIO write failed: " + GetOpenSSLErrorMessage()));
×
445
        }
446

447
        int pending = BIO_ctrl_pending(bio_public_key_new.get());
8✔
448
        if (pending <= 0) {
8✔
UNCOV
449
                return expected::unexpected(MakeError(
×
450
                        SetupError,
UNCOV
451
                        "Failed to extract the public key from bio ctrl: (" + args.private_key_path
×
UNCOV
452
                                + "): Zero byte key unexpected: " + GetOpenSSLErrorMessage()));
×
453
        }
454

455
        vector<uint8_t> key_vector(pending);
8✔
456

457
        size_t read = BIO_read(bio_public_key_new.get(), key_vector.data(), pending);
8✔
458

459
        if (read == 0) {
8✔
UNCOV
460
                MakeError(
×
461
                        SetupError,
462
                        "Failed to extract the public key from (" + args.private_key_path
×
UNCOV
463
                                + "): Zero bytes read from BIO: " + GetOpenSSLErrorMessage());
×
464
        }
465

466
        return string(key_vector.begin(), key_vector.end());
16✔
467
}
468

469
static expected::ExpectedBytes SignED25519(EVP_PKEY *pkey, const vector<uint8_t> &raw_data) {
2✔
470
        size_t sig_len;
471

472
        auto md_ctx = unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX *)>(EVP_MD_CTX_new(), EVP_MD_CTX_free);
4✔
473
        if (md_ctx == nullptr) {
2✔
474
                return expected::unexpected(MakeError(
×
UNCOV
475
                        SetupError, "Failed to initialize the OpenSSL md_ctx: " + GetOpenSSLErrorMessage()));
×
476
        }
477

478
        int ret {EVP_DigestSignInit(md_ctx.get(), nullptr, nullptr, nullptr, pkey)};
2✔
479
        if (ret != OPENSSL_SUCCESS) {
2✔
UNCOV
480
                return expected::unexpected(MakeError(
×
481
                        SetupError, "Failed to initialize the OpenSSL signature: " + GetOpenSSLErrorMessage()));
×
482
        }
483

484
        /* Calculate the required size for the signature by passing a nullptr buffer */
485
        ret = EVP_DigestSign(md_ctx.get(), nullptr, &sig_len, raw_data.data(), raw_data.size());
2✔
486
        if (ret != OPENSSL_SUCCESS) {
2✔
UNCOV
487
                return expected::unexpected(MakeError(
×
488
                        SetupError,
489
                        "Failed to find the required size of the signature buffer: "
UNCOV
490
                                + GetOpenSSLErrorMessage()));
×
491
        }
492

493
        vector<uint8_t> sig(sig_len);
2✔
494
        ret = EVP_DigestSign(md_ctx.get(), sig.data(), &sig_len, raw_data.data(), raw_data.size());
2✔
495
        if (ret != OPENSSL_SUCCESS) {
2✔
UNCOV
496
                return expected::unexpected(
×
UNCOV
497
                        MakeError(SetupError, "Failed to sign the message: " + GetOpenSSLErrorMessage()));
×
498
        }
499

500
        // The signature may in some cases be shorter than the previously allocated
501
        // length (which is the max)
502
        sig.resize(sig_len);
2✔
503

504
        return sig;
2✔
505
}
506

507
expected::ExpectedBytes SignGeneric(PrivateKey &private_key, const vector<uint8_t> &digest) {
8✔
508
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
509
                EVP_PKEY_CTX_new(private_key.Get(), nullptr), pkey_ctx_free_func);
16✔
510

511
        if (EVP_PKEY_sign_init(pkey_signer_ctx.get()) <= 0) {
8✔
UNCOV
512
                return expected::unexpected(MakeError(
×
UNCOV
513
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
514
        }
515
        if (EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256()) <= 0) {
8✔
UNCOV
516
                return expected::unexpected(MakeError(
×
517
                        SetupError,
UNCOV
518
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
519
        }
520

521
        vector<uint8_t> signature {};
522

523
        // Set the needed signature buffer length
524
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
525
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
8✔
526
                <= 0) {
UNCOV
527
                return expected::unexpected(MakeError(
×
UNCOV
528
                        SetupError, "Failed to get the signature buffer length: " + GetOpenSSLErrorMessage()));
×
529
        }
530
        signature.resize(siglength);
8✔
531

532
        if (EVP_PKEY_sign(
8✔
533
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
534
                <= 0) {
UNCOV
535
                return expected::unexpected(
×
UNCOV
536
                        MakeError(SetupError, "Failed to sign the digest: " + GetOpenSSLErrorMessage()));
×
537
        }
538

539
        // The signature may in some cases be shorter than the previously allocated
540
        // length (which is the max)
541
        signature.resize(siglength);
8✔
542

543
        return signature;
8✔
544
}
545

546
expected::ExpectedBytes SignData(const Args &args, const vector<uint8_t> &raw_data) {
11✔
547
        auto exp_private_key = PrivateKey::Load(args);
11✔
548
        if (!exp_private_key) {
11✔
549
                return expected::unexpected(exp_private_key.error());
2✔
550
        }
551

552
        log::Info("Signing with: " + args.private_key_path);
10✔
553

554
        auto key_type = EVP_PKEY_base_id(exp_private_key.value().Get());
10✔
555

556
        // ED25519 signatures need to be handled independently, because of how the
557
        // signature scheme is designed.
558
        if (key_type == EVP_PKEY_ED25519) {
10✔
559
                return SignED25519(exp_private_key.value().Get(), raw_data);
2✔
560
        }
561

562
        auto exp_shasum = mender::sha::Shasum(raw_data);
8✔
563
        if (!exp_shasum) {
8✔
UNCOV
564
                return expected::unexpected(exp_shasum.error());
×
565
        }
566
        auto digest = exp_shasum.value(); /* The shasummed data = digest in crypto world */
8✔
567
        log::Debug("Shasum is: " + digest.String());
16✔
568

569
        return SignGeneric(exp_private_key.value(), digest);
16✔
570
}
571

572
expected::ExpectedString Sign(const Args &args, const vector<uint8_t> &raw_data) {
11✔
573
        auto exp_signed_data = SignData(args, raw_data);
11✔
574
        if (!exp_signed_data) {
11✔
575
                return expected::unexpected(exp_signed_data.error());
2✔
576
        }
577
        vector<uint8_t> signature = exp_signed_data.value();
10✔
578

579
        return EncodeBase64(signature);
20✔
580
}
581

582
const size_t mender_decode_buf_size = 256;
583
const size_t ecdsa256keySize = 32;
584

585
// Try and decode the keys from pure binary, assuming that the points on the
586
// curve (r,s), have been concatenated together (r || s), and simply dumped to
587
// binary. Which is what we did in the `mender-artifact` tool.
588
// (See MEN-1740) for some insight into previous issues, and the chosen fix.
589
static expected::ExpectedBytes TryASN1EncodeMenderCustomBinaryECFormat(
2✔
590
        const vector<uint8_t> &signature,
591
        const mender::sha::SHA &shasum,
592
        std::function<BIGNUM *(const unsigned char *signature, int length, BIGNUM *_unused)>
593
                BinaryDecoderFn) {
594
        // Verify that the marshalled keys match our expectation
595
        const size_t assumed_signature_size {2 * ecdsa256keySize};
596
        if (signature.size() > assumed_signature_size) {
2✔
UNCOV
597
                return expected::unexpected(MakeError(
×
598
                        SetupError,
UNCOV
599
                        "Unexpected size of the signature for ECDSA. Expected 2*" + to_string(ecdsa256keySize)
×
UNCOV
600
                                + " bytes. Got: " + to_string(signature.size())));
×
601
        }
602
        auto ecSig = unique_ptr<ECDSA_SIG, void (*)(ECDSA_SIG *)>(ECDSA_SIG_new(), ECDSA_SIG_free);
4✔
603
        if (ecSig == nullptr) {
2✔
UNCOV
604
                return expected::unexpected(MakeError(
×
605
                        SetupError,
606
                        "Failed to allocate the structure for the ECDSA signature: "
UNCOV
607
                                + GetOpenSSLErrorMessage()));
×
608
        }
609

610
        auto r = unique_ptr<BIGNUM, void (*)(BIGNUM *)>(
611
                BinaryDecoderFn(signature.data(), ecdsa256keySize, nullptr /* allocate new memory for r */),
612
                bn_free);
4✔
613
        if (r == nullptr) {
2✔
UNCOV
614
                return expected::unexpected(MakeError(
×
615
                        SetupError,
616
                        "Failed to extract the r(andom) part from the ECDSA signature in the binary representation: "
UNCOV
617
                                + GetOpenSSLErrorMessage()));
×
618
        }
619
        auto s = unique_ptr<BIGNUM, void (*)(BIGNUM *)>(
620
                BinaryDecoderFn(
621
                        signature.data() + ecdsa256keySize,
622
                        ecdsa256keySize,
623
                        nullptr /* allocate new memory for s */),
624
                bn_free);
4✔
625
        if (s == nullptr) {
2✔
626
                return expected::unexpected(MakeError(
×
627
                        SetupError,
628
                        "Failed to extract the s(ignature) part from the ECDSA signature in the binary representation: "
UNCOV
629
                                + GetOpenSSLErrorMessage()));
×
630
        }
631

632
        // Set the r&s values in the SIG struct
633
        // r & s now owned by ecSig
634
        int ret {ECDSA_SIG_set0(ecSig.get(), r.get(), s.get())};
2✔
635
        if (ret != OPENSSL_SUCCESS) {
2✔
UNCOV
636
                return expected::unexpected(MakeError(
×
637
                        SetupError,
638
                        "Failed to set the signature parts in the ECDSA structure: "
UNCOV
639
                                + GetOpenSSLErrorMessage()));
×
640
        }
641
        r.release();
642
        s.release();
643

644
        /* Allocate some array guaranteed to hold the DER-encoded structure */
645
        vector<uint8_t> der_encoded_byte_array(mender_decode_buf_size);
2✔
646
        unsigned char *arr_p = &der_encoded_byte_array[0];
2✔
647
        int len = i2d_ECDSA_SIG(ecSig.get(), &arr_p);
2✔
648
        if (len < 0) {
2✔
UNCOV
649
                return expected::unexpected(MakeError(
×
650
                        SetupError,
651
                        "Failed to set the signature parts in the ECDSA structure: "
UNCOV
652
                                + GetOpenSSLErrorMessage()));
×
653
        }
654
        /* Resize to the actual size of the DER-encoded signature */
655
        der_encoded_byte_array.resize(len);
2✔
656

657
        return der_encoded_byte_array;
2✔
658
}
659

660

661
expected::ExpectedBool VerifySignData(
662
        const string &public_key_path,
663
        const mender::sha::SHA &shasum,
664
        const vector<uint8_t> &signature);
665

666
static expected::ExpectedBool VerifyECDSASignData(
2✔
667
        const string &public_key_path,
668
        const mender::sha::SHA &shasum,
669
        const vector<uint8_t> &signature) {
670
        expected::ExpectedBytes exp_der_encoded_signature =
671
                TryASN1EncodeMenderCustomBinaryECFormat(signature, shasum, BN_bin2bn)
4✔
672
                        .or_else([&signature, &shasum](error::Error big_endian_error) {
×
673
                                log::Debug(
×
674
                                        "Failed to decode the signature binary blob from our custom binary format assuming the big-endian encoding, error: "
UNCOV
675
                                        + big_endian_error.String()
×
UNCOV
676
                                        + " falling back and trying anew assuming it is little-endian encoded: ");
×
677
                                return TryASN1EncodeMenderCustomBinaryECFormat(signature, shasum, BN_lebin2bn);
×
678
                        });
2✔
679
        if (!exp_der_encoded_signature) {
2✔
UNCOV
680
                return expected::unexpected(
×
UNCOV
681
                        MakeError(VerificationError, exp_der_encoded_signature.error().message));
×
682
        }
683

684
        vector<uint8_t> der_encoded_signature = exp_der_encoded_signature.value();
2✔
685

686
        return VerifySignData(public_key_path, shasum, der_encoded_signature);
2✔
687
}
688

689
static bool OpenSSLSignatureVerificationError(int a) {
690
        /*
691
         * The signature check errored. This is different from the signature being
692
         * wrong. We simply were not able to perform the check in this instance.
693
         * Therefore, we fall back to trying the custom marshalled binary ECDSA
694
         * signature, which we have been using in Mender.
695
         */
696
        return a < 0;
697
}
698

699
expected::ExpectedBool VerifySignData(
16✔
700
        const string &public_key_path,
701
        const mender::sha::SHA &shasum,
702
        const vector<uint8_t> &signature) {
703
        auto bio_key =
704
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new_file(public_key_path.c_str(), "r"), bio_free_func);
32✔
705
        if (bio_key == nullptr) {
16✔
706
                return expected::unexpected(MakeError(
3✔
707
                        SetupError,
708
                        "Failed to open the public key file from (" + public_key_path
6✔
709
                                + "):" + GetOpenSSLErrorMessage()));
15✔
710
        }
711

712
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
713
                PEM_read_bio_PUBKEY(bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
26✔
714
        if (pkey == nullptr) {
13✔
715
                return expected::unexpected(MakeError(
2✔
716
                        SetupError,
717
                        "Failed to load the public key from(" + public_key_path
4✔
718
                                + "): " + GetOpenSSLErrorMessage()));
10✔
719
        }
720

721
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
722
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
22✔
723

724
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
11✔
725
        if (ret <= 0) {
11✔
726
                return expected::unexpected(MakeError(
×
UNCOV
727
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
728
        }
729
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
11✔
730
        if (ret <= 0) {
11✔
UNCOV
731
                return expected::unexpected(MakeError(
×
732
                        SetupError,
UNCOV
733
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
734
        }
735

736
        // verify signature
737
        ret = EVP_PKEY_verify(
11✔
738
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
739
        if (OpenSSLSignatureVerificationError(ret)) {
11✔
740
                log::Debug(
2✔
741
                        "Failed to verify the signature with the supported OpenSSL binary formats. Falling back to the custom Mender encoded binary format for ECDSA signatures: "
742
                        + GetOpenSSLErrorMessage());
4✔
743
                return VerifyECDSASignData(public_key_path, shasum, signature);
2✔
744
        }
745
        if (ret == OPENSSL_SUCCESS) {
9✔
746
                return true;
747
        }
748
        /* This is the case where ret == 0. The signature is simply wrong */
749
        return false;
750
}
751

752
expected::ExpectedBool VerifySign(
14✔
753
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
754
        // signature: decode base64
755
        auto exp_decoded_signature = DecodeBase64(signature);
28✔
756
        if (!exp_decoded_signature) {
14✔
UNCOV
757
                return expected::unexpected(exp_decoded_signature.error());
×
758
        }
759
        auto decoded_signature = exp_decoded_signature.value();
14✔
760

761
        return VerifySignData(public_key_path, shasum, decoded_signature);
14✔
762
}
763

764
error::Error PrivateKey::SaveToPEM(const string &private_key_path) {
6✔
765
        auto bio_key = unique_ptr<BIO, void (*)(BIO *)>(
766
                BIO_new_file(private_key_path.c_str(), "w"), bio_free_func);
12✔
767
        if (bio_key == nullptr) {
6✔
768
                return MakeError(
769
                        SetupError,
770
                        "Failed to open the private key file (" + private_key_path
2✔
771
                                + "): " + GetOpenSSLErrorMessage());
4✔
772
        }
773

774
        auto ret =
775
                PEM_write_bio_PrivateKey(bio_key.get(), key.get(), nullptr, nullptr, 0, nullptr, nullptr);
5✔
776
        if (ret != OPENSSL_SUCCESS) {
5✔
777
                return MakeError(
778
                        SetupError,
UNCOV
779
                        "Failed to save the private key to file (" + private_key_path
×
UNCOV
780
                                + "): " + GetOpenSSLErrorMessage());
×
781
        }
782

783
        return error::NoError;
5✔
784
}
785

786
} // namespace crypto
787
} // namespace common
788
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc