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

mendersoftware / mender / 993125853

05 Sep 2023 11:26AM UTC coverage: 79.004% (-0.5%) from 79.541%
993125853

push

gitlab-ci

lluiscampos
test: Add tests for `mender::auth::cli`

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

7 of 7 new or added lines in 2 files covered. (100.0%)

5825 of 7373 relevant lines covered (79.0%)

283.63 hits per line

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

78.3
/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
#include <common/crypto/platform/openssl/config.h>
27

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

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

36

37
namespace mender {
38
namespace common {
39
namespace crypto {
40

41
const size_t MENDER_DIGEST_SHA256_LENGTH = 32;
42

43
const size_t OPENSSL_SUCCESS = 1;
44

45
const int MENDER_DEFAULT_RSA_EXPONENT = 0x10001;
46

47
using namespace std;
48

49
namespace error = mender::common::error;
50
namespace io = mender::common::io;
51

52
auto pkey_ctx_free_func = [](EVP_PKEY_CTX *ctx) {
29✔
53
        if (ctx) {
29✔
54
                EVP_PKEY_CTX_free(ctx);
29✔
55
        }
56
};
29✔
57
auto pkey_free_func = [](EVP_PKEY *key) {
50✔
58
        if (key) {
50✔
59
                EVP_PKEY_free(key);
50✔
60
        }
61
};
50✔
62
auto bio_free_func = [](BIO *bio) {
54✔
63
        if (bio) {
54✔
64
                BIO_free(bio);
54✔
65
        }
66
};
54✔
67
auto bio_free_all_func = [](BIO *bio) {
14✔
68
        if (bio) {
14✔
69
                BIO_free_all(bio);
14✔
70
        }
71
};
14✔
72

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

81
        std::string errorDescription {};
14✔
82
        while (sslErrorCode != 0) {
45✔
83
                if (!errorDescription.empty()) {
31✔
84
                        errorDescription += '\n';
17✔
85
                }
86
                errorDescription += ERR_error_string(sslErrorCode, nullptr);
31✔
87
                sslErrorCode = ERR_get_error();
31✔
88
        }
89
        if (sysErrorCode != 0) {
14✔
90
                if (!errorDescription.empty()) {
14✔
91
                        errorDescription += '\n';
14✔
92
                }
93
                errorDescription += "System error, code=" + std::to_string(sysErrorCode);
14✔
94
                errorDescription += ", ";
14✔
95
                errorDescription += strerror(sysErrorCode);
14✔
96
        }
97
        return errorDescription;
14✔
98
}
99

100
ExpectedPrivateKey PrivateKey::LoadFromPEM(
13✔
101
        const string &private_key_path, const string &passphrase) {
102
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
103
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
26✔
104
        if (private_bio_key == nullptr) {
13✔
105
                return expected::unexpected(MakeError(
2✔
106
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
4✔
107
        }
108

109
        vector<char> chars(passphrase.begin(), passphrase.end());
22✔
110
        chars.push_back('\0');
11✔
111
        char *c_str = chars.data();
11✔
112

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

120
                if (u == nullptr) {
3✔
121
                        return 0;
×
122
                }
123

124
                // NB: buf is not expected to be null terminated.
125
                char *const pass = static_cast<char *>(u);
3✔
126
                strncpy(buf, pass, size);
3✔
127

128
                const int len = static_cast<int>(strlen(pass));
3✔
129
                return (len < size) ? len : size;
3✔
130
        };
131

132
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
133
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, callback, c_str), pkey_free_func);
22✔
134
        if (private_key == nullptr) {
11✔
135
                return expected::unexpected(
4✔
136
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
8✔
137
        }
138

139
        return unique_ptr<PrivateKey>(new PrivateKey(std::move(private_key)));
14✔
140
}
141

142
ExpectedPrivateKey PrivateKey::LoadFromPEM(const string &private_key_path) {
6✔
143
        return PrivateKey::LoadFromPEM(private_key_path, "");
12✔
144
}
145

146
ExpectedPrivateKey PrivateKey::Generate(const unsigned int bits, const unsigned int exponent) {
6✔
147
#ifdef MENDER_CRYPTO_OPENSSL_LEGACY
148
        auto pkey_gen_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
149
                EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr), pkey_ctx_free_func);
12✔
150

151
        int ret = EVP_PKEY_keygen_init(pkey_gen_ctx.get());
6✔
152
        if (ret != OPENSSL_SUCCESS) {
6✔
153
                return expected::unexpected(MakeError(
×
154
                        SetupError,
155
                        "Failed to generate a private key. Initialization failed: "
156
                                + GetOpenSSLErrorMessage()));
×
157
        }
158

159
        ret = EVP_PKEY_CTX_set_rsa_keygen_bits(pkey_gen_ctx.get(), bits);
6✔
160
        if (ret != OPENSSL_SUCCESS) {
6✔
161
                return expected::unexpected(MakeError(
×
162
                        SetupError,
163
                        "Failed to generate a private key. Parameters setting failed: "
164
                                + GetOpenSSLErrorMessage()));
×
165
        }
166

167
        auto exponent_bn = unique_ptr<BIGNUM, void (*)(BIGNUM *)>(BN_new(), [](BIGNUM *bn) {
6✔
168
                // no-op; the ownership is passed to EVP_PKEY_CTX_set_rsa_keygen_pubexp
169
        });
12✔
170
        ret = BN_set_word(exponent_bn.get(), exponent);
6✔
171
        if (ret != OPENSSL_SUCCESS) {
6✔
172
                return expected::unexpected(MakeError(
×
173
                        SetupError,
174
                        "Failed to generate a private key. Parameters setting failed: "
175
                                + GetOpenSSLErrorMessage()));
×
176
        }
177

178
        ret = EVP_PKEY_CTX_set_rsa_keygen_pubexp(pkey_gen_ctx.get(), exponent_bn.get());
6✔
179
        if (ret != OPENSSL_SUCCESS) {
6✔
180
                return expected::unexpected(MakeError(
×
181
                        SetupError,
182
                        "Failed to generate a private key. Parameters setting failed: "
183
                                + GetOpenSSLErrorMessage()));
×
184
        }
185

186
        EVP_PKEY *pkey = nullptr;
6✔
187
        ret = EVP_PKEY_keygen(pkey_gen_ctx.get(), &pkey);
6✔
188
        if (ret != OPENSSL_SUCCESS) {
6✔
189
                return expected::unexpected(MakeError(
×
190
                        SetupError,
191
                        "Failed to generate a private key. Generation failed: " + GetOpenSSLErrorMessage()));
×
192
        }
193
#else
194
        auto pkey_gen_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
195
                EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), pkey_ctx_free_func);
196

197
        int ret = EVP_PKEY_keygen_init(pkey_gen_ctx.get());
198
        if (ret != OPENSSL_SUCCESS) {
199
                return expected::unexpected(MakeError(
200
                        SetupError,
201
                        "Failed to generate a private key. Initialization failed: "
202
                                + GetOpenSSLErrorMessage()));
203
        }
204

205
        OSSL_PARAM params[3];
206
        auto bits_buffer = bits;
207
        auto exponent_buffer = exponent;
208
        params[0] = OSSL_PARAM_construct_uint("bits", &bits_buffer);
209
        params[1] = OSSL_PARAM_construct_uint("e", &exponent_buffer);
210
        params[2] = OSSL_PARAM_construct_end();
211

212
        ret = EVP_PKEY_CTX_set_params(pkey_gen_ctx.get(), params);
213
        if (ret != OPENSSL_SUCCESS) {
214
                return expected::unexpected(MakeError(
215
                        SetupError,
216
                        "Failed to generate a private key. Parameters setting failed: "
217
                                + GetOpenSSLErrorMessage()));
218
        }
219

220
        EVP_PKEY *pkey = nullptr;
221
        ret = EVP_PKEY_generate(pkey_gen_ctx.get(), &pkey);
222
        if (ret != OPENSSL_SUCCESS) {
223
                return expected::unexpected(MakeError(
224
                        SetupError,
225
                        "Failed to generate a private key. Generation failed: " + GetOpenSSLErrorMessage()));
226
        }
227
#endif
228

229
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(pkey, pkey_free_func);
6✔
230
        return unique_ptr<PrivateKey>(new PrivateKey(std::move(private_key)));
12✔
231
}
232

233
ExpectedPrivateKey PrivateKey::Generate(const unsigned int bits) {
5✔
234
        return PrivateKey::Generate(bits, MENDER_DEFAULT_RSA_EXPONENT);
5✔
235
}
236

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

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

248
        const auto output_len =
249
                EVP_EncodeBlock(buffer.data(), to_encode.data(), static_cast<int>(to_encode.size()));
17✔
250

251
        if (predicted_len != static_cast<unsigned long>(output_len)) {
17✔
252
                return expected::unexpected(
×
253
                        MakeError(Base64Error, "The predicted and the actual length differ"));
×
254
        }
255

256
        return string(buffer.begin(), buffer.end() - 1); // Remove the last zero byte
34✔
257
}
258

259
expected::ExpectedBytes DecodeBase64(string to_decode) {
13✔
260
        // Predict the len of the decoded for later verification. From man page:
261
        // For every 4 input bytes exactly 3 output bytes will be
262
        // produced. The output will be padded with 0 bits if necessary
263
        // to ensure that the output is always 3 bytes.
264
        const auto predicted_len = 3 * ((to_decode.size() + 3) / 4);
13✔
265

266
        auto buffer {vector<unsigned char>(predicted_len)};
26✔
267

268
        const auto output_len = EVP_DecodeBlock(
13✔
269
                buffer.data(),
270
                common::ByteVectorFromString(to_decode).data(),
26✔
271
                static_cast<int>(to_decode.size()));
13✔
272

273
        if (predicted_len != static_cast<unsigned long>(output_len)) {
13✔
274
                return expected::unexpected(MakeError(
×
275
                        Base64Error,
276
                        "The predicted (" + std::to_string(predicted_len) + ") and the actual ("
×
277
                                + std::to_string(output_len) + ") length differ"));
×
278
        }
279

280
        // Subtract padding bytes. Inspired by internal OpenSSL code from:
281
        // https://github.com/openssl/openssl/blob/ff88545e02ab48a52952350c52013cf765455dd3/crypto/ct/ct_b64.c#L46
282
        for (auto it = to_decode.crbegin(); *it == '='; it++) {
19✔
283
                buffer.pop_back();
6✔
284
        }
285

286
        return buffer;
13✔
287
}
288

289
expected::ExpectedString ExtractPublicKey(const string &private_key_path) {
16✔
290
        auto private_bio_key = unique_ptr<BIO, void (*)(BIO *)>(
291
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
32✔
292

293
        if (!private_bio_key.get()) {
16✔
294
                return expected::unexpected(MakeError(
2✔
295
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
4✔
296
        }
297

298
        auto private_key = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
299
                PEM_read_bio_PrivateKey(private_bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
28✔
300
        if (private_key == nullptr) {
14✔
301
                return expected::unexpected(
×
302
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
303
        }
304

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

307
        if (!bio_public_key.get()) {
14✔
308
                return expected::unexpected(MakeError(
×
309
                        SetupError,
310
                        "Failed to extract the public key from the private key: " + GetOpenSSLErrorMessage()));
×
311
        }
312

313
        int ret = PEM_write_bio_PUBKEY(bio_public_key.get(), private_key.get());
14✔
314
        if (ret != OPENSSL_SUCCESS) {
14✔
315
                return expected::unexpected(MakeError(
×
316
                        SetupError,
317
                        "Failed to extract the public key. OpenSSL BIO write failed: "
318
                                + GetOpenSSLErrorMessage()));
×
319
        }
320

321
        int pending = BIO_ctrl_pending(bio_public_key.get());
14✔
322
        if (pending <= 0) {
14✔
323
                return expected::unexpected(MakeError(
×
324
                        SetupError,
325
                        "Failed to extract the public key. Zero byte key unexpected: "
326
                                + GetOpenSSLErrorMessage()));
×
327
        }
328

329
        vector<uint8_t> key_vector(pending);
28✔
330

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

333
        if (read <= 0) {
14✔
334
                MakeError(
×
335
                        SetupError,
336
                        "Failed to extract the public key. Zero bytes read from BIO: "
337
                                + GetOpenSSLErrorMessage());
×
338
        }
339

340
        return string(key_vector.begin(), key_vector.end());
28✔
341
}
342

343
expected::ExpectedBytes SignData(const string private_key_path, const vector<uint8_t> digest) {
17✔
344
        auto bio_private_key = unique_ptr<BIO, void (*)(BIO *)>(
345
                BIO_new_file(private_key_path.c_str(), "r"), bio_free_func);
34✔
346
        if (bio_private_key == nullptr) {
17✔
347
                return expected::unexpected(MakeError(
1✔
348
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage()));
2✔
349
        }
350

351
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
352
                PEM_read_bio_PrivateKey(bio_private_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
32✔
353
        if (pkey == nullptr) {
16✔
354
                return expected::unexpected(
×
355
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
×
356
        }
357

358
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
359
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
32✔
360

361
        if (EVP_PKEY_sign_init(pkey_signer_ctx.get()) <= 0) {
16✔
362
                return expected::unexpected(MakeError(
×
363
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
364
        }
365
        if (EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256()) <= 0) {
16✔
366
                return expected::unexpected(MakeError(
×
367
                        SetupError,
368
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
369
        }
370

371
        vector<uint8_t> signature {};
32✔
372

373
        // Set the needed signature buffer length
374
        size_t digestlength = MENDER_DIGEST_SHA256_LENGTH, siglength;
16✔
375
        if (EVP_PKEY_sign(pkey_signer_ctx.get(), nullptr, &siglength, digest.data(), digestlength)
16✔
376
                <= 0) {
16✔
377
                return expected::unexpected(MakeError(
×
378
                        SetupError, "Failed to get the signature buffer length: " + GetOpenSSLErrorMessage()));
×
379
        }
380
        signature.resize(siglength);
16✔
381

382
        if (EVP_PKEY_sign(
16✔
383
                        pkey_signer_ctx.get(), signature.data(), &siglength, digest.data(), digestlength)
384
                <= 0) {
16✔
385
                return expected::unexpected(
×
386
                        MakeError(SetupError, "Failed to sign the digest: " + GetOpenSSLErrorMessage()));
×
387
        }
388

389
        // The signature may in some cases be shorter than the previously allocated
390
        // length (which is the max)
391
        signature.resize(siglength);
16✔
392

393
        return signature;
16✔
394
}
395

396
expected::ExpectedString Sign(const string &private_key_path, const mender::sha::SHA &shasum) {
17✔
397
        auto exp_signed_data = SignData(private_key_path, shasum);
51✔
398
        if (!exp_signed_data) {
17✔
399
                return expected::unexpected(exp_signed_data.error());
2✔
400
        }
401
        vector<uint8_t> signature = exp_signed_data.value();
16✔
402

403
        return EncodeBase64(signature);
16✔
404
}
405

406
expected::ExpectedString SignRawData(
16✔
407
        const string &private_key_path, const vector<uint8_t> &raw_data) {
408
        auto exp_shasum = mender::sha::Shasum(raw_data);
32✔
409

410
        if (!exp_shasum) {
16✔
411
                return expected::unexpected(exp_shasum.error());
×
412
        }
413
        auto shasum = exp_shasum.value();
32✔
414
        log::Debug("Shasum is: " + shasum.String());
16✔
415

416
        return Sign(private_key_path, shasum);
16✔
417
}
418

419
expected::ExpectedBool VerifySignData(
12✔
420
        const string &public_key_path,
421
        const mender::sha::SHA &shasum,
422
        const vector<uint8_t> &signature) {
423
        auto bio_key =
424
                unique_ptr<BIO, void (*)(BIO *)>(BIO_new_file(public_key_path.c_str(), "r"), bio_free_func);
24✔
425
        if (bio_key == nullptr) {
12✔
426
                return expected::unexpected(MakeError(
3✔
427
                        SetupError, "Failed to open the public key file: " + GetOpenSSLErrorMessage()));
6✔
428
        }
429

430
        auto pkey = unique_ptr<EVP_PKEY, void (*)(EVP_PKEY *)>(
431
                PEM_read_bio_PUBKEY(bio_key.get(), nullptr, nullptr, nullptr), pkey_free_func);
18✔
432
        if (pkey == nullptr) {
9✔
433
                return expected::unexpected(
2✔
434
                        MakeError(SetupError, "Failed to load the key: " + GetOpenSSLErrorMessage()));
4✔
435
        }
436

437
        // prepare context
438
        auto pkey_signer_ctx = unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX *)>(
439
                EVP_PKEY_CTX_new(pkey.get(), nullptr), pkey_ctx_free_func);
14✔
440

441
        auto ret = EVP_PKEY_verify_init(pkey_signer_ctx.get());
7✔
442
        if (ret <= 0) {
7✔
443
                return expected::unexpected(MakeError(
×
444
                        SetupError, "Failed to initialize the OpenSSL signer: " + GetOpenSSLErrorMessage()));
×
445
        }
446
        ret = EVP_PKEY_CTX_set_signature_md(pkey_signer_ctx.get(), EVP_sha256());
7✔
447
        if (ret <= 0) {
7✔
448
                return expected::unexpected(MakeError(
×
449
                        SetupError,
450
                        "Failed to set the OpenSSL signature to sha256: " + GetOpenSSLErrorMessage()));
×
451
        }
452

453
        // verify signature
454
        ret = EVP_PKEY_verify(
7✔
455
                pkey_signer_ctx.get(), signature.data(), signature.size(), shasum.data(), shasum.size());
456
        if (ret < 0) {
7✔
457
                return expected::unexpected(MakeError(
×
458
                        VerificationError,
459
                        "Failed to verify signature. OpenSSL PKEY verify failed: " + GetOpenSSLErrorMessage()));
×
460
        }
461

462
        return ret == 1;
14✔
463
}
464

465
expected::ExpectedBool VerifySign(
12✔
466
        const string &public_key_path, const mender::sha::SHA &shasum, const string &signature) {
467
        // signature: decode base64
468
        auto exp_decoded_signature = DecodeBase64(signature);
24✔
469
        if (!exp_decoded_signature) {
12✔
470
                return expected::unexpected(exp_decoded_signature.error());
×
471
        }
472
        auto decoded_signature = exp_decoded_signature.value();
24✔
473

474
        return VerifySignData(public_key_path, shasum, decoded_signature);
12✔
475
}
476

477
error::Error PrivateKey::SaveToPEM(const string &private_key_path) {
4✔
478
        auto bio_key = unique_ptr<BIO, void (*)(BIO *)>(
479
                BIO_new_file(private_key_path.c_str(), "w"), bio_free_func);
8✔
480
        if (bio_key == nullptr) {
4✔
481
                return MakeError(
482
                        SetupError, "Failed to open the private key file: " + GetOpenSSLErrorMessage());
×
483
        }
484

485
        // PEM_write_bio_PrivateKey_traditional will use the key-specific PKCS1
486
        // format if one is available for that key type, otherwise it will encode
487
        // to a PKCS8 key.
488
        auto ret = PEM_write_bio_PrivateKey_traditional(
4✔
489
                bio_key.get(), (EVP_PKEY *) key.get(), nullptr, nullptr, 0, nullptr, nullptr);
4✔
490
        if (ret != 1) {
4✔
491
                return MakeError(SetupError, "Failed dumping the key to BIO");
×
492
        }
493

494
        return error::NoError;
4✔
495
}
496

497
} // namespace crypto
498
} // namespace common
499
} // 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