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

mendersoftware / mender / 951814156

pending completion
951814156

push

gitlab-ci

oleorhagen
chore(crypto): harvest the OpenSSL error message for all failed calls

Since the OpenSSL errors are stored in a queue, and harvested in a FIFO like
fashion, it is important that we do harvest the messages upon every call which
fails, otherwise the extraction will generate the wrong error messages.

Therefore we loop over, and extract all errors from the queue on each call to
`GetOpenSSLErrorMessage`.

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

40 of 40 new or added lines in 1 file covered. (100.0%)

4203 of 5295 relevant lines covered (79.38%)

166.16 hits per line

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

76.43
/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 <cstdint>
18
#include <string>
19
#include <vector>
20
#include <memory>
21

22
#include <common/io.hpp>
23
#include <common/error.hpp>
24
#include <common/expected.hpp>
25
#include <common/common.hpp>
26

27
#include <artifact/sha/sha.hpp>
28

29
#include <openssl/evp.h>
30
#include <openssl/pem.h>
31
#include <openssl/err.h>
32
#include <openssl/rsa.h>
33

34

35
namespace mender {
36
namespace common {
37
namespace crypto {
38

39
const size_t MENDER_DIGEST_SHA256_LENGTH = 32;
40

41
const size_t OPENSSL_SUCCESS = 1;
42

43
using namespace std;
44

45
namespace error = mender::common::error;
46
namespace io = mender::common::io;
47

48
auto pkey_ctx_free_func = [](EVP_PKEY_CTX *ctx) {
20✔
49
        if (ctx) {
20✔
50
                EVP_PKEY_CTX_free(ctx);
20✔
51
        }
52
};
20✔
53
auto pkey_free_func = [](EVP_PKEY *key) {
33✔
54
        if (key) {
33✔
55
                EVP_PKEY_free(key);
33✔
56
        }
57
};
33✔
58
auto bio_free_func = [](BIO *bio) {
35✔
59
        if (bio) {
35✔
60
                BIO_free(bio);
35✔
61
        }
62
};
35✔
63
auto bio_free_all_func = [](BIO *bio) {
13✔
64
        if (bio) {
13✔
65
                BIO_free_all(bio);
13✔
66
        }
67
};
13✔
68

69
// NOTE: GetOpenSSLErrorMessage should be called upon all OpenSSL errors, as
70
// the errors are queued, and if not harvested, the FIFO structure of the
71
// queue will mean that if you just get one, you might actually get the wrong
72
// one.
73
string GetOpenSSLErrorMessage() {
7✔
74
        const auto sysErrorCode = errno;
7✔
75
        auto sslErrorCode = ERR_get_error();
7✔
76

77
        std::string errorDescription {};
7✔
78
        while (sslErrorCode != 0) {
22✔
79
                if (!errorDescription.empty()) {
15✔
80
                        errorDescription += '\n';
8✔
81
                }
82
                errorDescription += ERR_error_string(sslErrorCode, nullptr);
15✔
83
                sslErrorCode = ERR_get_error();
15✔
84
        }
85
        if (sysErrorCode != 0) {
7✔
86
                if (!errorDescription.empty()) {
7✔
87
                        errorDescription += '\n';
7✔
88
                }
89
                errorDescription += "System error, code=" + std::to_string(sysErrorCode);
7✔
90
                errorDescription += ", ";
7✔
91
                errorDescription += strerror(sysErrorCode);
7✔
92
        }
93
        return errorDescription;
7✔
94
}
95

96

97
expected::ExpectedString EncodeBase64(vector<uint8_t> to_encode) {
15✔
98
        // Predict the len of the decoded for later verification. From man page:
99
        // For every 3 bytes of input provided 4 bytes of output
100
        // data will be produced. If n is not divisible by 3 (...)
101
        // the output is padded such that it is always divisible by 4.
102
        const auto predicted_len = 4 * ((to_encode.size() + 2) / 3);
15✔
103

104
        // Add space for a NUL terminator. From man page:
105
        // Additionally a NUL terminator character will be added
106
        auto buffer {vector<unsigned char>(predicted_len + 1)};
30✔
107

108
        const auto output_len =
109
                EVP_EncodeBlock(buffer.data(), to_encode.data(), static_cast<int>(to_encode.size()));
15✔
110

111
        if (predicted_len != static_cast<unsigned long>(output_len)) {
15✔
112
                return expected::unexpected(
×
113
                        MakeError(Base64Error, "The predicted and the actual length differ"));
×
114
        }
115

116
        return string(buffer.begin(), buffer.end() - 1); // Remove the last zero byte
30✔
117
}
118

119
expected::ExpectedBytes DecodeBase64(string to_decode) {
12✔
120
        // Predict the len of the decoded for later verification. From man page:
121
        // For every 4 input bytes exactly 3 output bytes will be
122
        // produced. The output will be padded with 0 bits if necessary
123
        // to ensure that the output is always 3 bytes.
124
        const auto predicted_len = 3 * ((to_decode.size() + 3) / 4);
12✔
125

126
        auto buffer {vector<unsigned char>(predicted_len)};
24✔
127

128
        const auto output_len = EVP_DecodeBlock(
12✔
129
                buffer.data(),
130
                common::ByteVectorFromString(to_decode).data(),
24✔
131
                static_cast<int>(to_decode.size()));
12✔
132

133
        if (predicted_len != static_cast<unsigned long>(output_len)) {
12✔
134
                return expected::unexpected(MakeError(
×
135
                        Base64Error,
136
                        "The predicted (" + std::to_string(predicted_len) + ") and the actual ("
×
137
                                + std::to_string(output_len) + ") length differ"));
×
138
        }
139

140
        // Subtract padding bytes. Inspired by internal OpenSSL code from:
141
        // https://github.com/openssl/openssl/blob/ff88545e02ab48a52952350c52013cf765455dd3/crypto/ct/ct_b64.c#L46
142
        for (auto it = to_decode.crbegin(); *it == '='; it++) {
16✔
143
                buffer.pop_back();
4✔
144
        }
145

146
        return buffer;
12✔
147
}
148

149

150
expected::ExpectedString ExtractPublicKey(const string &private_key_path) {
14✔
151
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
152
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
28✔
153

154
        if (!private_bio_key.get()) {
14✔
155
                return expected::unexpected(MakeError(
1✔
156
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
157
        }
158

159
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
160
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
26✔
161
        if (private_key == nullptr) {
13✔
162
                return expected::unexpected(
×
163
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
164
        }
165

166
        auto bio_public_key = unique_ptr<BIO, void (*)(BIO *)>(BIO_new(BIO_s_mem()), bio_free_all_func);
26✔
167

168
        if (!bio_public_key.get()) {
13✔
169
                return expected::unexpected(MakeError(
×
170
                        SetupError,
171
                        "Failed to extract the public key from the private key: " + GetOpenSSLErrorMessage()));
×
172
        }
173

174
        int ret = PEM_write_bio_PUBKEY(bio_public_key.get(), private_key.get());
13✔
175
        if (ret != OPENSSL_SUCCESS) {
13✔
176
                return expected::unexpected(MakeError(
×
177
                        SetupError,
178
                        "Failed to extract the public key. OpenSSL BIO write failed: "
179
                                + GetOpenSSLErrorMessage()));
×
180
        }
181

182
        int pending = BIO_ctrl_pending(bio_public_key.get());
13✔
183
        if (pending <= 0) {
13✔
184
                return expected::unexpected(MakeError(
×
185
                        SetupError,
186
                        "Failed to extract the public key. Zero byte key unexpected: "
187
                                + GetOpenSSLErrorMessage()));
×
188
        }
189

190
        vector<uint8_t> key_vector(pending);
26✔
191

192
        size_t read = BIO_read(bio_public_key.get(), key_vector.data(), pending);
13✔
193

194
        if (read <= 0) {
13✔
195
                MakeError(
×
196
                        SetupError,
197
                        "Failed to extract the public key. Zero bytes read from BIO: "
198
                                + GetOpenSSLErrorMessage());
×
199
        }
200

201
        return string(key_vector.begin(), key_vector.end());
26✔
202
}
203

204
expected::ExpectedBytes SignData(const string private_key_path, const vector<uint8_t> digest) {
15✔
205
        auto bio_private_key = unique_ptr<BIO, void (*)(BIO *)>(
206
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
30✔
207
        if (bio_private_key == nullptr) {
15✔
208
                return expected::unexpected(MakeError(
1✔
209
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
210
        }
211

212
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
213
                PEM_read_bio_PrivateKey(bio_private_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
28✔
214
        if (pkey == nullptr) {
14✔
215
                return expected::unexpected(
×
216
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
217
        }
218

219
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
220
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
28✔
221

222
        if (EVP_PKEY_sign_init(pkey_signer_ctx.get()) <= 0) {
14✔
223
                return expected::unexpected(MakeError(
×
224
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
225
        }
226
        if (EVP_PKEY_CTX_set_rsa_padding(pkey_signer_ctx.get(), RSA_PKCS1_PADDING) <= 0) {
14✔
227
                return expected::unexpected(MakeError(
×
228
                        SetupError,
229
                        "Failed to set the OpenSSL padding to RSA_PKCS1: " + GetOpenSSLErrorMessage()));
×
230
        }
231
        if (EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256()) <= 0) {
14✔
232
                return expected::unexpected(MakeError(
×
233
                        SetupError,
234
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
235
        }
236

237
        vector<uint8_t> signature {};
28✔
238

239
        // Set the needed signature buffer length
240
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
14✔
241
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
14✔
242
                <= 0) {
14✔
243
                return expected::unexpected(MakeError(
×
244
                        SetupError, "Failed to get the signature buffer length: " + GetOpenSSLErrorMessage()));
×
245
        }
246
        signature.resize(siglength);
14✔
247

248
        if (EVP_PKEY_sign(
14✔
249
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
250
                <= 0) {
14✔
251
                return expected::unexpected(
×
252
                        MakeError(SetupError, "Failed to sign the digest: " + GetOpenSSLErrorMessage()));
×
253
        }
254

255
        return signature;
14✔
256
}
257

258
expected::ExpectedString Sign(const string &private_key_path, const mender::sha::SHA &shasum) {
15✔
259
        auto exp_signed_data = SignData(private_key_path, shasum);
45✔
260
        if (!exp_signed_data) {
15✔
261
                return expected::unexpected(exp_signed_data.error());
2✔
262
        }
263
        vector<uint8_t> signature = exp_signed_data.value();
14✔
264

265
        return EncodeBase64(signature);
14✔
266
}
267

268
expected::ExpectedString SignRawData(
14✔
269
        const string &private_key_path, const vector<uint8_t> &raw_data) {
270
        auto exp_shasum = mender::sha::Shasum(raw_data);
28✔
271

272
        if (!exp_shasum) {
14✔
273
                return expected::unexpected(exp_shasum.error());
×
274
        }
275
        auto shasum = exp_shasum.value();
28✔
276
        log::Debug("Shasum is: " + shasum.String());
14✔
277

278
        return Sign(private_key_path, shasum);
14✔
279
}
280

281
expected::ExpectedBool VerifySignData(
11✔
282
        const string &public_key_path,
283
        const mender::sha::SHA &shasum,
284
        const vector<uint8_t> &signature) {
285
        auto bio_key =
286
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new_file(public_key_path.c_str(), "r"), bio_free_func);
22✔
287
        if (bio_key == nullptr) {
11✔
288
                return expected::unexpected(MakeError(
3✔
289
                        SetupError, "Failed to open the public key file: " + GetOpenSSLErrorMessage()));
6✔
290
        }
291

292
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
293
                PEM_read_bio_PUBKEY(bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
16✔
294
        if (pkey == nullptr) {
8✔
295
                return expected::unexpected(
2✔
296
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
4✔
297
        }
298

299
        // prepare context
300
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
301
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
12✔
302

303
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
6✔
304
        if (ret <= 0) {
6✔
305
                return expected::unexpected(MakeError(
×
306
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
307
        }
308
        ret = EVP_PKEY_CTX_set_rsa_padding(pkey_signer_ctx.get(), RSA_PKCS1_PADDING);
6✔
309
        if (ret <= 0) {
6✔
310
                return expected::unexpected(MakeError(
×
311
                        SetupError,
312
                        "Failed to set the OpenSSL padding to RSA_PKCS1: " + GetOpenSSLErrorMessage()));
×
313
        }
314
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
6✔
315
        if (ret <= 0) {
6✔
316
                return expected::unexpected(MakeError(
×
317
                        SetupError,
318
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
319
        }
320

321
        // verify signature
322
        ret = EVP_PKEY_verify(
6✔
323
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
324
        if (ret < 0) {
6✔
325
                return expected::unexpected(MakeError(
×
326
                        VerificationError,
327
                        "Failed to verify signature. OpenSSL PKEY verify failed: " + GetOpenSSLErrorMessage()));
×
328
        }
329

330
        return ret == 1;
12✔
331
}
332

333
expected::ExpectedBool VerifySign(
11✔
334
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
335
        // signature: decode base64
336
        auto exp_decoded_signature = DecodeBase64(signature);
22✔
337
        if (!exp_decoded_signature) {
11✔
338
                return expected::unexpected(exp_decoded_signature.error());
×
339
        }
340
        auto decoded_signature = exp_decoded_signature.value();
22✔
341

342
        return VerifySignData(public_key_path, shasum, decoded_signature);
11✔
343
}
344

345
} // namespace crypto
346
} // namespace common
347
} // 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

© 2025 Coveralls, Inc