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

mendersoftware / mender / 968408678

pending completion
968408678

push

gitlab-ci

oleorhagen
feat(artifact/scripts): Add support for executing state scripts

Ticket: MEN-6636
Changelog: None

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

126 of 126 new or added lines in 4 files covered. (100.0%)

5405 of 6857 relevant lines covered (78.82%)

195.75 hits per line

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

78.15
/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) {
23✔
49
        if (ctx) {
23✔
50
                EVP_PKEY_CTX_free(ctx);
23✔
51
        }
52
};
23✔
53
auto pkey_free_func = [](EVP_PKEY *key) {
37✔
54
        if (key) {
37✔
55
                EVP_PKEY_free(key);
37✔
56
        }
57
};
37✔
58
auto bio_free_func = [](BIO *bio) {
39✔
59
        if (bio) {
39✔
60
                BIO_free(bio);
39✔
61
        }
62
};
39✔
63
auto bio_free_all_func = [](BIO *bio) {
14✔
64
        if (bio) {
14✔
65
                BIO_free_all(bio);
14✔
66
        }
67
};
14✔
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) {
17✔
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);
17✔
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)};
34✔
107

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

111
        if (predicted_len != static_cast<unsigned long>(output_len)) {
17✔
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
34✔
117
}
118

119
expected::ExpectedBytes DecodeBase64(string to_decode) {
13✔
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);
13✔
125

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

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

133
        if (predicted_len != static_cast<unsigned long>(output_len)) {
13✔
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++) {
17✔
143
                buffer.pop_back();
4✔
144
        }
145

146
        return buffer;
13✔
147
}
148

149

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

154
        if (!private_bio_key.get()) {
15✔
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);
28✔
161
        if (private_key == nullptr) {
14✔
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);
28✔
167

168
        if (!bio_public_key.get()) {
14✔
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());
14✔
175
        if (ret != OPENSSL_SUCCESS) {
14✔
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());
14✔
183
        if (pending <= 0) {
14✔
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);
28✔
191

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

194
        if (read <= 0) {
14✔
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());
28✔
202
}
203

204
expected::ExpectedBytes SignData(const string private_key_path, const vector<uint8_t> digest) {
17✔
205
        auto bio_private_key = unique_ptr<BIO, void (*)(BIO *)>(
206
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
34✔
207
        if (bio_private_key == nullptr) {
17✔
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);
32✔
214
        if (pkey == nullptr) {
16✔
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);
32✔
221

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

232
        vector<uint8_t> signature {};
32✔
233

234
        // Set the needed signature buffer length
235
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
16✔
236
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
16✔
237
                <= 0) {
16✔
238
                return expected::unexpected(MakeError(
×
239
                        SetupError, "Failed to get the signature buffer length: " + GetOpenSSLErrorMessage()));
×
240
        }
241
        signature.resize(siglength);
16✔
242

243
        if (EVP_PKEY_sign(
16✔
244
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
245
                <= 0) {
16✔
246
                return expected::unexpected(
×
247
                        MakeError(SetupError, "Failed to sign the digest: " + GetOpenSSLErrorMessage()));
×
248
        }
249

250
        // The signature may in some cases be shorter than the previously allocated
251
        // length (which is the max)
252
        signature.resize(siglength);
16✔
253

254
        return signature;
16✔
255
}
256

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

264
        return EncodeBase64(signature);
16✔
265
}
266

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

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

277
        return Sign(private_key_path, shasum);
16✔
278
}
279

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

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

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

302
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
7✔
303
        if (ret <= 0) {
7✔
304
                return expected::unexpected(MakeError(
×
305
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
306
        }
307
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
7✔
308
        if (ret <= 0) {
7✔
309
                return expected::unexpected(MakeError(
×
310
                        SetupError,
311
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
312
        }
313

314
        // verify signature
315
        ret = EVP_PKEY_verify(
7✔
316
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
317
        if (ret < 0) {
7✔
318
                return expected::unexpected(MakeError(
×
319
                        VerificationError,
320
                        "Failed to verify signature. OpenSSL PKEY verify failed: " + GetOpenSSLErrorMessage()));
×
321
        }
322

323
        return ret == 1;
14✔
324
}
325

326
expected::ExpectedBool VerifySign(
12✔
327
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
328
        // signature: decode base64
329
        auto exp_decoded_signature = DecodeBase64(signature);
24✔
330
        if (!exp_decoded_signature) {
12✔
331
                return expected::unexpected(exp_decoded_signature.error());
×
332
        }
333
        auto decoded_signature = exp_decoded_signature.value();
24✔
334

335
        return VerifySignData(public_key_path, shasum, decoded_signature);
12✔
336
}
337

338
} // namespace crypto
339
} // namespace common
340
} // 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