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

mendersoftware / mender / 951586380

pending completion
951586380

push

gitlab-ci

kacf
chore: Use system reboot command when `Automatic` reboot is requested.

I won't add tests for this, because it is much better to test this
properly later when we get our acceptance tests running. It will be
untested till then...

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

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

4268 of 6026 relevant lines covered (70.83%)

147.81 hits per line

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

78.38
/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
expected::ExpectedString EncodeBase64(vector<uint8_t> to_encode) {
15✔
70
        // Predict the len of the decoded for later verification. From man page:
71
        // For every 3 bytes of input provided 4 bytes of output
72
        // data will be produced. If n is not divisible by 3 (...)
73
        // the output is padded such that it is always divisible by 4.
74
        const auto predicted_len = 4 * ((to_encode.size() + 2) / 3);
15✔
75

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

80
        const auto output_len =
81
                EVP_EncodeBlock(buffer.data(), to_encode.data(), static_cast<int>(to_encode.size()));
15✔
82

83
        if (predicted_len != static_cast<unsigned long>(output_len)) {
15✔
84
                return expected::unexpected(
×
85
                        MakeError(Base64Error, "The predicted and the actual length differ"));
×
86
        }
87

88
        return string(buffer.begin(), buffer.end() - 1); // Remove the last zero byte
30✔
89
}
90

91
expected::ExpectedBytes DecodeBase64(string to_decode) {
12✔
92
        // Predict the len of the decoded for later verification. From man page:
93
        // For every 4 input bytes exactly 3 output bytes will be
94
        // produced. The output will be padded with 0 bits if necessary
95
        // to ensure that the output is always 3 bytes.
96
        const auto predicted_len = 3 * ((to_decode.size() + 3) / 4);
12✔
97

98
        auto buffer {vector<unsigned char>(predicted_len)};
24✔
99

100
        const auto output_len = EVP_DecodeBlock(
12✔
101
                buffer.data(),
102
                common::ByteVectorFromString(to_decode).data(),
24✔
103
                static_cast<int>(to_decode.size()));
12✔
104

105
        if (predicted_len != static_cast<unsigned long>(output_len)) {
12✔
106
                return expected::unexpected(MakeError(
×
107
                        Base64Error,
108
                        "The predicted (" + std::to_string(predicted_len) + ") and the actual ("
×
109
                                + std::to_string(output_len) + ") length differ"));
×
110
        }
111

112
        // Subtract padding bytes. Inspired by internal OpenSSL code from:
113
        // https://github.com/openssl/openssl/blob/ff88545e02ab48a52952350c52013cf765455dd3/crypto/ct/ct_b64.c#L46
114
        for (auto it = to_decode.crbegin(); *it == '='; it++) {
16✔
115
                buffer.pop_back();
4✔
116
        }
117

118
        return buffer;
12✔
119
}
120

121
string GetOpenSSLErrorMessage() {
5✔
122
        const auto sysErrorCode = errno;
5✔
123
        auto sslErrorCode = ERR_get_error();
5✔
124

125
        std::string errorDescription;
5✔
126
        while (sslErrorCode != 0) {
16✔
127
                errorDescription += ERR_error_string(sslErrorCode, nullptr);
11✔
128
                sslErrorCode = ERR_get_error();
11✔
129
        }
130
        if (sysErrorCode != 0) {
5✔
131
                if (!errorDescription.empty()) {
5✔
132
                        errorDescription += '\n';
5✔
133
                }
134
                errorDescription += "System error, code=" + std::to_string(sysErrorCode);
5✔
135
                errorDescription += ", ";
5✔
136
                errorDescription += strerror(sysErrorCode);
5✔
137
        }
138
        return errorDescription;
5✔
139
}
140

141
expected::ExpectedString ExtractPublicKey(const string &private_key_path) {
14✔
142
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
143
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
28✔
144

145
        if (!private_bio_key.get()) {
14✔
146
                return expected::unexpected(MakeError(SetupError, "Failed to open the private key file"));
2✔
147
        }
148

149
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
150
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
26✔
151
        if (private_key == nullptr) {
13✔
152
                return expected::unexpected(MakeError(SetupError, "Failed to load the key"));
×
153
        }
154

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

157
        if (!bio_public_key.get()) {
13✔
158
                return expected::unexpected(MakeError(SetupError, "Failed to open the private key file"));
×
159
        }
160

161
        int ret = PEM_write_bio_PUBKEY(bio_public_key.get(), private_key.get());
13✔
162
        if (ret != OPENSSL_SUCCESS) {
13✔
163
                return expected::unexpected(MakeError(
×
164
                        SetupError,
165
                        "Failed to extract the public key. OpenSSL BIO write failed: "
166
                                + GetOpenSSLErrorMessage()));
×
167
        }
168

169
        int pending = BIO_ctrl_pending(bio_public_key.get());
13✔
170
        if (pending <= 0) {
13✔
171
                return expected::unexpected(
×
172
                        MakeError(SetupError, "Failed to extract the public key. Zero byte key unexpected"));
×
173
        }
174

175
        vector<uint8_t> key_vector(pending);
26✔
176

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

179
        if (read <= 0) {
13✔
180
                MakeError(SetupError, "Failed to extract the public key. Zero bytes read from BIO");
×
181
        }
182

183
        return string(key_vector.begin(), key_vector.end());
26✔
184
}
185

186
expected::ExpectedBytes SignData(const string private_key_path, const vector<uint8_t> digest) {
15✔
187
        auto bio_private_key = unique_ptr<BIO, void (*)(BIO *)>(
188
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
30✔
189
        if (bio_private_key == nullptr) {
15✔
190
                return expected::unexpected(MakeError(SetupError, "Failed to open the private key file"));
2✔
191
        }
192

193
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
194
                PEM_read_bio_PrivateKey(bio_private_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
28✔
195
        if (pkey == nullptr) {
14✔
196
                return expected::unexpected(MakeError(SetupError, "Failed to load the key"));
×
197
        }
198

199
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
200
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
28✔
201

202
        if (EVP_PKEY_sign_init(pkey_signer_ctx.get()) <= 0) {
14✔
203
                return expected::unexpected(
×
204
                        MakeError(SetupError, "Failed to initialize the OpenSSL signer"));
×
205
        }
206
        if (EVP_PKEY_CTX_set_rsa_padding(pkey_signer_ctx.get(), RSA_PKCS1_PADDING) <= 0) {
14✔
207
                return expected::unexpected(
×
208
                        MakeError(SetupError, "Failed to set the OpenSSL padding to RSA_PKCS1"));
×
209
        }
210
        if (EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256()) <= 0) {
14✔
211
                return expected::unexpected(
×
212
                        MakeError(SetupError, "Failed to set the OpenSSL signature to sha256"));
×
213
        }
214

215
        vector<uint8_t> signature {};
28✔
216

217
        // Set the needed signature buffer length
218
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
14✔
219
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
14✔
220
                <= 0) {
14✔
221
                return expected::unexpected(
×
222
                        MakeError(SetupError, "Failed to get the signature buffer length"));
×
223
        }
224
        signature.resize(siglength);
14✔
225

226
        if (EVP_PKEY_sign(
14✔
227
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
228
                <= 0) {
14✔
229
                return expected::unexpected(MakeError(SetupError, "Failed to sign the digest"));
×
230
        }
231

232
        return signature;
14✔
233
}
234

235
expected::ExpectedString Sign(const string &private_key_path, const mender::sha::SHA &shasum) {
15✔
236
        auto exp_signed_data = SignData(private_key_path, shasum);
45✔
237
        if (!exp_signed_data) {
15✔
238
                return expected::unexpected(exp_signed_data.error());
2✔
239
        }
240
        vector<uint8_t> signature = exp_signed_data.value();
14✔
241

242
        return EncodeBase64(signature);
14✔
243
}
244

245
expected::ExpectedString SignRawData(
14✔
246
        const string &private_key_path, const vector<uint8_t> &raw_data) {
247
        auto exp_shasum = mender::sha::Shasum(raw_data);
28✔
248

249
        if (!exp_shasum) {
14✔
250
                return expected::unexpected(exp_shasum.error());
×
251
        }
252
        auto shasum = exp_shasum.value();
28✔
253
        log::Debug("Shasum is: " + shasum.String());
14✔
254

255
        return Sign(private_key_path, shasum);
14✔
256
}
257

258
expected::ExpectedBool VerifySignData(
11✔
259
        const string &public_key_path,
260
        const mender::sha::SHA &shasum,
261
        const vector<uint8_t> &signature) {
262
        auto bio_key =
263
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new_file(public_key_path.c_str(), "r"), bio_free_func);
22✔
264
        if (bio_key == nullptr) {
11✔
265
                return expected::unexpected(MakeError(
3✔
266
                        SetupError, "Failed to open the public key file: " + GetOpenSSLErrorMessage()));
6✔
267
        }
268

269
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
270
                PEM_read_bio_PUBKEY(bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
16✔
271
        if (pkey == nullptr) {
8✔
272
                return expected::unexpected(
2✔
273
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
4✔
274
        }
275

276
        // prepare context
277
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
278
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
12✔
279

280
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
6✔
281
        if (ret <= 0) {
6✔
282
                return expected::unexpected(MakeError(
×
283
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
284
        }
285
        ret = EVP_PKEY_CTX_set_rsa_padding(pkey_signer_ctx.get(), RSA_PKCS1_PADDING);
6✔
286
        if (ret <= 0) {
6✔
287
                return expected::unexpected(MakeError(
×
288
                        SetupError,
289
                        "Failed to set the OpenSSL padding to RSA_PKCS1: " + GetOpenSSLErrorMessage()));
×
290
        }
291
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
6✔
292
        if (ret <= 0) {
6✔
293
                return expected::unexpected(MakeError(
×
294
                        SetupError,
295
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
296
        }
297

298
        // verify signature
299
        ret = EVP_PKEY_verify(
6✔
300
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
301
        if (ret < 0) {
6✔
302
                return expected::unexpected(MakeError(
×
303
                        VerificationError,
304
                        "Failed to verify signature. OpenSSL PKEY verify failed: " + GetOpenSSLErrorMessage()));
×
305
        }
306

307
        return ret == 1;
12✔
308
}
309

310
expected::ExpectedBool VerifySign(
11✔
311
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
312
        // signature: decode base64
313
        auto exp_decoded_signature = DecodeBase64(signature);
22✔
314
        if (!exp_decoded_signature) {
11✔
315
                return expected::unexpected(exp_decoded_signature.error());
×
316
        }
317
        auto decoded_signature = exp_decoded_signature.value();
22✔
318

319
        return VerifySignData(public_key_path, shasum, decoded_signature);
11✔
320
}
321

322
} // namespace crypto
323
} // namespace common
324
} // 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