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

mendersoftware / mender / 969241802

16 Aug 2023 08:29AM UTC coverage: 79.019% (+0.2%) from 78.825%
969241802

push

gitlab-ci

oleorhagen
chore(crypto): Make the PrivateKey constructor public

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

5314 of 6725 relevant lines covered (79.02%)

200.29 hits per line

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

80.46
/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) {
40✔
54
        if (key) {
40✔
55
                EVP_PKEY_free(key);
40✔
56
        }
57
};
40✔
58
auto bio_free_func = [](BIO *bio) {
45✔
59
        if (bio) {
45✔
60
                BIO_free(bio);
45✔
61
        }
62
};
45✔
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() {
11✔
74
        const auto sysErrorCode = errno;
11✔
75
        auto sslErrorCode = ERR_get_error();
11✔
76

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

96
ExpectedPrivateKey PrivateKey::LoadFromPEM(
7✔
97
        const string &private_key_path, const string &passphrase) {
98
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
99
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
14✔
100
        if (private_bio_key == nullptr) {
7✔
101
                return expected::unexpected(MakeError(
1✔
102
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
103
        }
104

105
        vector<char> chars(passphrase.begin(), passphrase.end());
12✔
106
        chars.push_back('\0');
6✔
107
        char *c_str = chars.data();
6✔
108

109
        // We need our own custom callback routine, as the default one will prompt
110
        // for a passphrase.
111
        auto callback = [](char *buf, int size, int rwflag, void *u) {
3✔
112
                // We'll only use this callback for reading passphrases, not for
113
                // writing them.
114
                assert(rwflag == 0);
3✔
115

116
                if (u == nullptr) {
3✔
117
                        return 0;
×
118
                }
119

120
                // NB: buf is not expected to be null terminated.
121
                char *const pass = static_cast<char *>(u);
3✔
122
                strncpy(buf, pass, size);
3✔
123

124
                const int len = static_cast<int>(strlen(pass));
3✔
125
                return (len < size) ? len : size;
3✔
126
        };
127

128
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
129
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, callback, c_str), pkey_free_func);
12✔
130
        if (private_key == nullptr) {
6✔
131
                return expected::unexpected(
3✔
132
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
6✔
133
        }
134

135
        return unique_ptr<PrivateKey>(new PrivateKey(std::move(private_key)));
6✔
136
}
137

138
ExpectedPrivateKey PrivateKey::LoadFromPEM(const string &private_key_path) {
5✔
139
        return PrivateKey::LoadFromPEM(private_key_path, "");
10✔
140
}
141

142
expected::ExpectedString EncodeBase64(vector<uint8_t> to_encode) {
17✔
143
        // Predict the len of the decoded for later verification. From man page:
144
        // For every 3 bytes of input provided 4 bytes of output
145
        // data will be produced. If n is not divisible by 3 (...)
146
        // the output is padded such that it is always divisible by 4.
147
        const auto predicted_len = 4 * ((to_encode.size() + 2) / 3);
17✔
148

149
        // Add space for a NUL terminator. From man page:
150
        // Additionally a NUL terminator character will be added
151
        auto buffer {vector<unsigned char>(predicted_len + 1)};
34✔
152

153
        const auto output_len =
154
                EVP_EncodeBlock(buffer.data(), to_encode.data(), static_cast<int>(to_encode.size()));
17✔
155

156
        if (predicted_len != static_cast<unsigned long>(output_len)) {
17✔
157
                return expected::unexpected(
×
158
                        MakeError(Base64Error, "The predicted and the actual length differ"));
×
159
        }
160

161
        return string(buffer.begin(), buffer.end() - 1); // Remove the last zero byte
34✔
162
}
163

164
expected::ExpectedBytes DecodeBase64(string to_decode) {
13✔
165
        // Predict the len of the decoded for later verification. From man page:
166
        // For every 4 input bytes exactly 3 output bytes will be
167
        // produced. The output will be padded with 0 bits if necessary
168
        // to ensure that the output is always 3 bytes.
169
        const auto predicted_len = 3 * ((to_decode.size() + 3) / 4);
13✔
170

171
        auto buffer {vector<unsigned char>(predicted_len)};
26✔
172

173
        const auto output_len = EVP_DecodeBlock(
13✔
174
                buffer.data(),
175
                common::ByteVectorFromString(to_decode).data(),
26✔
176
                static_cast<int>(to_decode.size()));
13✔
177

178
        if (predicted_len != static_cast<unsigned long>(output_len)) {
13✔
179
                return expected::unexpected(MakeError(
×
180
                        Base64Error,
181
                        "The predicted (" + std::to_string(predicted_len) + ") and the actual ("
×
182
                                + std::to_string(output_len) + ") length differ"));
×
183
        }
184

185
        // Subtract padding bytes. Inspired by internal OpenSSL code from:
186
        // https://github.com/openssl/openssl/blob/ff88545e02ab48a52952350c52013cf765455dd3/crypto/ct/ct_b64.c#L46
187
        for (auto it = to_decode.crbegin(); *it == '='; it++) {
17✔
188
                buffer.pop_back();
4✔
189
        }
190

191
        return buffer;
13✔
192
}
193

194

195
expected::ExpectedString ExtractPublicKey(const string &private_key_path) {
15✔
196
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
197
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
30✔
198

199
        if (!private_bio_key.get()) {
15✔
200
                return expected::unexpected(MakeError(
1✔
201
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
202
        }
203

204
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
205
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
28✔
206
        if (private_key == nullptr) {
14✔
207
                return expected::unexpected(
×
208
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
209
        }
210

211
        auto bio_public_key = unique_ptr<BIO, void (*)(BIO *)>(BIO_new(BIO_s_mem()), bio_free_all_func);
28✔
212

213
        if (!bio_public_key.get()) {
14✔
214
                return expected::unexpected(MakeError(
×
215
                        SetupError,
216
                        "Failed to extract the public key from the private key: " + GetOpenSSLErrorMessage()));
×
217
        }
218

219
        int ret = PEM_write_bio_PUBKEY(bio_public_key.get(), private_key.get());
14✔
220
        if (ret != OPENSSL_SUCCESS) {
14✔
221
                return expected::unexpected(MakeError(
×
222
                        SetupError,
223
                        "Failed to extract the public key. OpenSSL BIO write failed: "
224
                                + GetOpenSSLErrorMessage()));
×
225
        }
226

227
        int pending = BIO_ctrl_pending(bio_public_key.get());
14✔
228
        if (pending <= 0) {
14✔
229
                return expected::unexpected(MakeError(
×
230
                        SetupError,
231
                        "Failed to extract the public key. Zero byte key unexpected: "
232
                                + GetOpenSSLErrorMessage()));
×
233
        }
234

235
        vector<uint8_t> key_vector(pending);
28✔
236

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

239
        if (read <= 0) {
14✔
240
                MakeError(
×
241
                        SetupError,
242
                        "Failed to extract the public key. Zero bytes read from BIO: "
243
                                + GetOpenSSLErrorMessage());
×
244
        }
245

246
        return string(key_vector.begin(), key_vector.end());
28✔
247
}
248

249
expected::ExpectedBytes SignData(const string private_key_path, const vector<uint8_t> digest) {
17✔
250
        auto bio_private_key = unique_ptr<BIO, void (*)(BIO *)>(
251
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
34✔
252
        if (bio_private_key == nullptr) {
17✔
253
                return expected::unexpected(MakeError(
1✔
254
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
255
        }
256

257
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
258
                PEM_read_bio_PrivateKey(bio_private_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
32✔
259
        if (pkey == nullptr) {
16✔
260
                return expected::unexpected(
×
261
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
262
        }
263

264
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
265
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
32✔
266

267
        if (EVP_PKEY_sign_init(pkey_signer_ctx.get()) <= 0) {
16✔
268
                return expected::unexpected(MakeError(
×
269
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
270
        }
271
        if (EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256()) <= 0) {
16✔
272
                return expected::unexpected(MakeError(
×
273
                        SetupError,
274
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
275
        }
276

277
        vector<uint8_t> signature {};
32✔
278

279
        // Set the needed signature buffer length
280
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
16✔
281
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
16✔
282
                <= 0) {
16✔
283
                return expected::unexpected(MakeError(
×
284
                        SetupError, "Failed to get the signature buffer length: " + GetOpenSSLErrorMessage()));
×
285
        }
286
        signature.resize(siglength);
16✔
287

288
        if (EVP_PKEY_sign(
16✔
289
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
290
                <= 0) {
16✔
291
                return expected::unexpected(
×
292
                        MakeError(SetupError, "Failed to sign the digest: " + GetOpenSSLErrorMessage()));
×
293
        }
294

295
        // The signature may in some cases be shorter than the previously allocated
296
        // length (which is the max)
297
        signature.resize(siglength);
16✔
298

299
        return signature;
16✔
300
}
301

302
expected::ExpectedString Sign(const string &private_key_path, const mender::sha::SHA &shasum) {
17✔
303
        auto exp_signed_data = SignData(private_key_path, shasum);
51✔
304
        if (!exp_signed_data) {
17✔
305
                return expected::unexpected(exp_signed_data.error());
2✔
306
        }
307
        vector<uint8_t> signature = exp_signed_data.value();
16✔
308

309
        return EncodeBase64(signature);
16✔
310
}
311

312
expected::ExpectedString SignRawData(
16✔
313
        const string &private_key_path, const vector<uint8_t> &raw_data) {
314
        auto exp_shasum = mender::sha::Shasum(raw_data);
32✔
315

316
        if (!exp_shasum) {
16✔
317
                return expected::unexpected(exp_shasum.error());
×
318
        }
319
        auto shasum = exp_shasum.value();
32✔
320
        log::Debug("Shasum is: " + shasum.String());
16✔
321

322
        return Sign(private_key_path, shasum);
16✔
323
}
324

325
expected::ExpectedBool VerifySignData(
12✔
326
        const string &public_key_path,
327
        const mender::sha::SHA &shasum,
328
        const vector<uint8_t> &signature) {
329
        auto bio_key =
330
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new_file(public_key_path.c_str(), "r"), bio_free_func);
24✔
331
        if (bio_key == nullptr) {
12✔
332
                return expected::unexpected(MakeError(
3✔
333
                        SetupError, "Failed to open the public key file: " + GetOpenSSLErrorMessage()));
6✔
334
        }
335

336
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
337
                PEM_read_bio_PUBKEY(bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
18✔
338
        if (pkey == nullptr) {
9✔
339
                return expected::unexpected(
2✔
340
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
4✔
341
        }
342

343
        // prepare context
344
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
345
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
14✔
346

347
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
7✔
348
        if (ret <= 0) {
7✔
349
                return expected::unexpected(MakeError(
×
350
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
351
        }
352
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
7✔
353
        if (ret <= 0) {
7✔
354
                return expected::unexpected(MakeError(
×
355
                        SetupError,
356
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
357
        }
358

359
        // verify signature
360
        ret = EVP_PKEY_verify(
7✔
361
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
362
        if (ret < 0) {
7✔
363
                return expected::unexpected(MakeError(
×
364
                        VerificationError,
365
                        "Failed to verify signature. OpenSSL PKEY verify failed: " + GetOpenSSLErrorMessage()));
×
366
        }
367

368
        return ret == 1;
14✔
369
}
370

371
expected::ExpectedBool VerifySign(
12✔
372
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
373
        // signature: decode base64
374
        auto exp_decoded_signature = DecodeBase64(signature);
24✔
375
        if (!exp_decoded_signature) {
12✔
376
                return expected::unexpected(exp_decoded_signature.error());
×
377
        }
378
        auto decoded_signature = exp_decoded_signature.value();
24✔
379

380
        return VerifySignData(public_key_path, shasum, decoded_signature);
12✔
381
}
382

383
} // namespace crypto
384
} // namespace common
385
} // 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