Coverage Report

Created: 2026-10-16 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/phosphor-certificate-manager/x509_utils.cpp
Line
Count
Source
1
#include "config.h"
2
3
#include "x509_utils.hpp"
4
5
#include <openssl/asn1.h>
6
#include <openssl/bio.h>
7
#include <openssl/err.h>
8
#include <openssl/evp.h>
9
#include <openssl/pem.h>
10
#include <openssl/ssl3.h>
11
#include <openssl/x509_vfy.h>
12
13
#include <phosphor-logging/elog-errors.hpp>
14
#include <phosphor-logging/elog.hpp>
15
#include <phosphor-logging/lg2.hpp>
16
#include <xyz/openbmc_project/Certs/error.hpp>
17
#include <xyz/openbmc_project/Common/error.hpp>
18
19
#include <cstdio>
20
#include <ctime>
21
#include <exception>
22
#include <memory>
23
24
namespace phosphor::certs
25
{
26
27
namespace
28
{
29
30
using ::phosphor::logging::elog;
31
using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
32
using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
33
using Reason = ::phosphor::logging::xyz::openbmc_project::Certs::
34
    InvalidCertificate::REASON;
35
36
// RAII support for openSSL functions.
37
using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
38
using X509StoreCtxPtr =
39
    std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
40
using X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
41
using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
42
using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
43
using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>;
44
45
// Trust chain related errors.`
46
constexpr bool isTrustChainError(int error)
47
23
{
48
23
    return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
49
22
           error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ||
50
22
           error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
51
0
           error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ||
52
0
           error == X509_V_ERR_CERT_UNTRUSTED ||
53
0
           error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
54
23
}
55
} // namespace
56
57
X509StorePtr getX509Store(const std::string& certSrcPath)
58
0
{
59
    // Create an empty X509_STORE structure for certificate validation.
60
0
    X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
61
0
    if (!x509Store)
62
0
    {
63
0
        lg2::error("Error occurred during X509_STORE_new call");
64
0
        elog<InternalFailure>();
65
0
    }
66
67
0
    OpenSSL_add_all_algorithms();
68
69
    // ADD Certificate Lookup method.
70
    // lookup will be cleaned up automatically when the holding Store goes away.
71
0
    auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
72
73
0
    if (!lookup)
74
0
    {
75
0
        lg2::error("Error occurred during X509_STORE_add_lookup call");
76
0
        elog<InternalFailure>();
77
0
    }
78
    // Load the Certificate file into X509 Store.
79
0
    if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(),
80
0
                                            X509_FILETYPE_PEM);
81
0
        errCode != 1)
82
0
    {
83
0
        lg2::error(
84
0
            "Error occurred during X509_LOOKUP_load_file call, FILE:{FILE}",
85
0
            "FILE", certSrcPath);
86
0
        elog<InvalidCertificate>(Reason("Invalid certificate file format"));
87
0
    }
88
0
    return x509Store;
89
0
}
90
91
X509Ptr loadCert(const std::string& filePath)
92
0
{
93
    // Read Certificate file
94
0
    X509Ptr cert(X509_new(), ::X509_free);
95
0
    if (!cert)
96
0
    {
97
0
        lg2::error(
98
0
            "Error occurred during X509_new call, FILE:{FILE}, ERRCODE:{ERRCODE}",
99
0
            "FILE", filePath, "ERRCODE", ERR_get_error());
100
0
        elog<InternalFailure>();
101
0
    }
102
103
0
    BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
104
0
    if (!bioCert)
105
0
    {
106
0
        lg2::error("Error occurred during BIO_new_file call, FILE:{FILE}",
107
0
                   "FILE", filePath);
108
0
        elog<InternalFailure>();
109
0
    }
110
111
0
    X509* x509 = cert.get();
112
0
    if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
113
0
    {
114
0
        lg2::error("Error occurred during PEM_read_bio_X509 call, FILE:{FILE}",
115
0
                   "FILE", filePath);
116
0
        elog<InternalFailure>();
117
0
    }
118
0
    return cert;
119
0
}
120
121
// Checks that notBefore is not earlier than the unix epoch given that
122
// the corresponding DBus interface is uint64_t.
123
void validateCertificateStartDate(X509& cert)
124
125
{
125
125
    int days = 0;
126
125
    int secs = 0;
127
128
125
    ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
129
    // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
130
125
    ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
131
132
125
    const ASN1_TIME* notBefore = X509_get0_notBefore(&cert);
133
125
    ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
134
135
125
    if (days < 0 || secs < 0)
136
59
    {
137
59
        lg2::error("Certificate valid date starts before the Unix Epoch");
138
59
        elog<InvalidCertificate>(
139
59
            Reason("NotBefore should after 19700101000000Z"));
140
59
    }
141
125
}
142
143
void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
144
23
{
145
23
    int errCode = X509_V_OK;
146
23
    X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
147
23
    if (!storeCtx)
148
0
    {
149
0
        lg2::error("Error occurred during X509_STORE_CTX_new call");
150
0
        elog<InternalFailure>();
151
0
    }
152
153
23
    errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
154
23
    if (errCode != 1)
155
0
    {
156
0
        lg2::error("Error occurred during X509_STORE_CTX_init call");
157
0
        elog<InternalFailure>();
158
0
    }
159
160
    // Set time to current time.
161
23
    auto locTime = time(nullptr);
162
163
23
    X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
164
23
                            locTime);
165
166
23
    errCode = X509_verify_cert(storeCtx.get());
167
23
    if (errCode == 1)
168
0
    {
169
0
        errCode = X509_V_OK;
170
0
    }
171
23
    else if (errCode == 0)
172
23
    {
173
23
        errCode = X509_STORE_CTX_get_error(storeCtx.get());
174
23
        lg2::info(
175
23
            "Error occurred during X509_verify_cert call, checking for known "
176
23
            "error, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
177
23
            "ERRCODE", errCode, "ERROR_STR",
178
23
            X509_verify_cert_error_string(errCode));
179
23
    }
180
0
    else
181
0
    {
182
0
        lg2::error("Error occurred during X509_verify_cert call");
183
0
        elog<InternalFailure>();
184
0
    }
185
186
    // Allow certificate upload, for "certificate is not yet valid" and
187
    // trust chain related errors.
188
    // If ALLOW_EXPIRED is defined, allow expired certificate so that it
189
    // could be replaced
190
23
    bool isOK = (errCode == X509_V_OK) ||
191
23
                (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
192
23
                isTrustChainError(errCode) ||
193
0
                (allowExpired && errCode == X509_V_ERR_CERT_HAS_EXPIRED);
194
195
23
    if (!isOK)
196
0
    {
197
0
        if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
198
0
        {
199
0
            lg2::error("Expired certificate ");
200
0
            elog<InvalidCertificate>(Reason("Expired Certificate"));
201
0
        }
202
        // Logging general error here.
203
0
        lg2::error(
204
0
            "Certificate validation failed, ERRCODE:{ERRCODE}, ERROR_STR:{ERROR_STR}",
205
0
            "ERRCODE", errCode, "ERROR_STR",
206
0
            X509_verify_cert_error_string(errCode));
207
0
        elog<InvalidCertificate>(Reason("Certificate validation failed"));
208
0
    }
209
23
}
210
211
void validateCertificateInSSLContext(X509& cert)
212
66
{
213
66
    const SSL_METHOD* method = TLS_method();
214
66
    SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
215
66
    if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
216
43
    {
217
43
        lg2::error("Certificate is not usable, ERRCODE:{ERRCODE}", "ERRCODE",
218
43
                   ERR_get_error());
219
43
        elog<InvalidCertificate>(Reason("Certificate is not usable"));
220
43
    }
221
66
}
222
223
std::string generateCertId(X509& cert)
224
0
{
225
0
    unsigned long subjectNameHash = X509_subject_name_hash(&cert);
226
0
    unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
227
0
    static constexpr auto certIdLength = 17;
228
0
    char idBuff[certIdLength];
229
230
0
    snprintf(idBuff, certIdLength, "%08lx%08lx", subjectNameHash,
231
0
             issuerSerialHash);
232
233
0
    return {idBuff};
234
0
}
235
236
std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
237
1.27k
{
238
1.27k
    if (pem.size() > INT_MAX)
239
0
    {
240
0
        lg2::error("Error occurred during parseCert: PEM is too long");
241
0
        elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
242
0
    }
243
1.27k
    X509Ptr cert(X509_new(), ::X509_free);
244
1.27k
    if (!cert)
245
0
    {
246
0
        lg2::error("Error occurred during X509_new call, ERRCODE:{ERRCODE}",
247
0
                   "ERRCODE", ERR_get_error());
248
0
        elog<InternalFailure>();
249
0
    }
250
251
1.27k
    BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
252
1.27k
                      ::BIO_free);
253
1.27k
    X509* x509 = cert.get();
254
1.27k
    if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
255
1.15k
    {
256
1.15k
        lg2::error("Error occurred during PEM_read_bio_X509 call, PEM:{PEM}",
257
1.15k
                   "PEM", pem);
258
1.15k
        elog<InternalFailure>();
259
1.15k
    }
260
1.27k
    return cert;
261
1.27k
}
262
} // namespace phosphor::certs