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

mendersoftware / mender / 952995648

pending completion
952995648

push

gitlab-ci

lluiscampos
fix: Support parsing device_type with no endl at the end

As currently formatted by `mender setup` from the golang client

Changelog: None
Ticket: None

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>

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

4287 of 5396 relevant lines covered (79.45%)

159.13 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