Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "tlsvalidator.h"
19 :
20 : #ifdef HAVE_CONFIG_H
21 : #include "config.h"
22 : #endif
23 :
24 : #include <opendht/infohash.h> // for toHex
25 : #include <dhtnet/certstore.h>
26 :
27 : #include "fileutils.h"
28 : #include "logger.h"
29 : #include "security_const.h"
30 :
31 : #include <sstream>
32 : #include <iomanip>
33 :
34 : #include <cstdio>
35 : #include <cerrno>
36 : #include <cassert>
37 : #include <ctime>
38 :
39 : #include <sys/types.h>
40 : #include <sys/stat.h>
41 :
42 : #ifndef _MSC_VER
43 : #include <libgen.h>
44 : #endif
45 :
46 : #ifndef _WIN32
47 : #include <sys/socket.h>
48 : #include <netinet/tcp.h>
49 : #include <netinet/in.h>
50 : #include <netdb.h>
51 : #else
52 : #ifndef _MSC_VER
53 : #define close(x) closesocket(x)
54 : #endif
55 : #endif
56 : #include <unistd.h>
57 : #include <fcntl.h>
58 :
59 : #ifdef _MSC_VER
60 : #include "windirent.h"
61 : #endif
62 :
63 : #include <filesystem>
64 :
65 : namespace jami {
66 : namespace tls {
67 :
68 : // Map the internal ring Enum class of the exported names
69 :
70 : const EnumClassNames<TlsValidator::CheckValues> TlsValidator::CheckValuesNames = {{
71 : /* CheckValues Name */
72 : /* PASSED */ libjami::Certificate::CheckValuesNames::PASSED,
73 : /* FAILED */ libjami::Certificate::CheckValuesNames::FAILED,
74 : /* UNSUPPORTED */ libjami::Certificate::CheckValuesNames::UNSUPPORTED,
75 : /* ISO_DATE */ libjami::Certificate::CheckValuesNames::ISO_DATE,
76 : /* CUSTOM */ libjami::Certificate::CheckValuesNames::CUSTOM,
77 : /* CUSTOM */ libjami::Certificate::CheckValuesNames::DATE,
78 : }};
79 :
80 : const CallbackMatrix1D<TlsValidator::CertificateCheck, TlsValidator, TlsValidator::CheckResult>
81 : TlsValidator::checkCallback = {{
82 : /* CertificateCheck Callback */
83 : /*HAS_PRIVATE_KEY */ &TlsValidator::hasPrivateKey,
84 : /*EXPIRED */ &TlsValidator::notExpired,
85 : /*STRONG_SIGNING */ &TlsValidator::strongSigning,
86 : /*NOT_SELF_SIGNED */ &TlsValidator::notSelfSigned,
87 : /*KEY_MATCH */ &TlsValidator::keyMatch,
88 : /*PRIVATE_KEY_STORAGE_PERMISSION */ &TlsValidator::privateKeyStoragePermissions,
89 : /*PUBLIC_KEY_STORAGE_PERMISSION */ &TlsValidator::publicKeyStoragePermissions,
90 : /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ &TlsValidator::privateKeyDirectoryPermissions,
91 : /*PUBLICKEY_DIRECTORY_PERMISSIONS */ &TlsValidator::publicKeyDirectoryPermissions,
92 : /*PRIVATE_KEY_STORAGE_LOCATION */ &TlsValidator::privateKeyStorageLocation,
93 : /*PUBLIC_KEY_STORAGE_LOCATION */ &TlsValidator::publicKeyStorageLocation,
94 : /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ &TlsValidator::privateKeySelinuxAttributes,
95 : /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ &TlsValidator::publicKeySelinuxAttributes,
96 : /*EXIST */ &TlsValidator::exist,
97 : /*VALID */ &TlsValidator::valid,
98 : /*VALID_AUTHORITY */ &TlsValidator::validAuthority,
99 : /*KNOWN_AUTHORITY */ &TlsValidator::knownAuthority,
100 : /*NOT_REVOKED */ &TlsValidator::notRevoked,
101 : /*AUTHORITY_MISMATCH */ &TlsValidator::authorityMatch,
102 : /*UNEXPECTED_OWNER */ &TlsValidator::expectedOwner,
103 : /*NOT_ACTIVATED */ &TlsValidator::activated,
104 : }};
105 :
106 : const CallbackMatrix1D<TlsValidator::CertificateDetails, TlsValidator, TlsValidator::CheckResult>
107 : TlsValidator::getterCallback = {{
108 : /* EXPIRATION_DATE */ &TlsValidator::getExpirationDate,
109 : /* ACTIVATION_DATE */ &TlsValidator::getActivationDate,
110 : /* REQUIRE_PRIVATE_KEY_PASSWORD */ &TlsValidator::requirePrivateKeyPassword,
111 : /* PUBLIC_SIGNATURE */ &TlsValidator::getPublicSignature,
112 : /* VERSION_NUMBER */ &TlsValidator::getVersionNumber,
113 : /* SERIAL_NUMBER */ &TlsValidator::getSerialNumber,
114 : /* ISSUER */ &TlsValidator::getIssuer,
115 : /* SUBJECT_KEY_ALGORITHM */ &TlsValidator::getSubjectKeyAlgorithm,
116 : /* SUBJECT_KEY */ &TlsValidator::getSubjectKey,
117 : /* CN */ &TlsValidator::getCN,
118 : /* UID */ &TlsValidator::getUID,
119 : /* N */ &TlsValidator::getN,
120 : /* O */ &TlsValidator::getO,
121 : /* SIGNATURE_ALGORITHM */ &TlsValidator::getSignatureAlgorithm,
122 : /* MD5_FINGERPRINT */ &TlsValidator::getMd5Fingerprint,
123 : /* SHA1_FINGERPRINT */ &TlsValidator::getSha1Fingerprint,
124 : /* PUBLIC_KEY_ID */ &TlsValidator::getPublicKeyId,
125 : /* ISSUER_DN */ &TlsValidator::getIssuerDN,
126 : /* ISSUER_CN */ &TlsValidator::getIssuerCN,
127 : /* ISSUER_UID */ &TlsValidator::getIssuerUID,
128 : /* ISSUER_N */ &TlsValidator::getIssuerN,
129 : /* ISSUER_O */ &TlsValidator::getIssuerO,
130 : /* NEXT_EXPECTED_UPDATE_DATE */ &TlsValidator::getIssuerDN, // TODO
131 : /* OUTGOING_SERVER */ &TlsValidator::outgoingServer,
132 : /* IS_CA */ &TlsValidator::isCA,
133 : }};
134 :
135 : const Matrix1D<TlsValidator::CertificateCheck, TlsValidator::CheckValuesType> TlsValidator::enforcedCheckType = {{
136 : /* CertificateCheck Callback */
137 : /*HAS_PRIVATE_KEY */ CheckValuesType::BOOLEAN,
138 : /*EXPIRED */ CheckValuesType::BOOLEAN,
139 : /*STRONG_SIGNING */ CheckValuesType::BOOLEAN,
140 : /*NOT_SELF_SIGNED */ CheckValuesType::BOOLEAN,
141 : /*KEY_MATCH */ CheckValuesType::BOOLEAN,
142 : /*PRIVATE_KEY_STORAGE_PERMISSION */ CheckValuesType::BOOLEAN,
143 : /*PUBLIC_KEY_STORAGE_PERMISSION */ CheckValuesType::BOOLEAN,
144 : /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ CheckValuesType::BOOLEAN,
145 : /*PUBLICKEY_DIRECTORY_PERMISSIONS */ CheckValuesType::BOOLEAN,
146 : /*PRIVATE_KEY_STORAGE_LOCATION */ CheckValuesType::BOOLEAN,
147 : /*PUBLIC_KEY_STORAGE_LOCATION */ CheckValuesType::BOOLEAN,
148 : /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ CheckValuesType::BOOLEAN,
149 : /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ CheckValuesType::BOOLEAN,
150 : /*EXIST */ CheckValuesType::BOOLEAN,
151 : /*VALID */ CheckValuesType::BOOLEAN,
152 : /*VALID_AUTHORITY */ CheckValuesType::BOOLEAN,
153 : /*KNOWN_AUTHORITY */ CheckValuesType::BOOLEAN,
154 : /*NOT_REVOKED */ CheckValuesType::BOOLEAN,
155 : /*AUTHORITY_MISMATCH */ CheckValuesType::BOOLEAN,
156 : /*UNEXPECTED_OWNER */ CheckValuesType::BOOLEAN,
157 : /*NOT_ACTIVATED */ CheckValuesType::BOOLEAN,
158 : }};
159 :
160 : const EnumClassNames<TlsValidator::CertificateCheck> TlsValidator::CertificateCheckNames = {{
161 : /* CertificateCheck Name */
162 : /*HAS_PRIVATE_KEY */ libjami::Certificate::ChecksNames::HAS_PRIVATE_KEY,
163 : /*EXPIRED */ libjami::Certificate::ChecksNames::EXPIRED,
164 : /*STRONG_SIGNING */ libjami::Certificate::ChecksNames::STRONG_SIGNING,
165 : /*NOT_SELF_SIGNED */ libjami::Certificate::ChecksNames::NOT_SELF_SIGNED,
166 : /*KEY_MATCH */ libjami::Certificate::ChecksNames::KEY_MATCH,
167 : /*PRIVATE_KEY_STORAGE_PERMISSION */ libjami::Certificate::ChecksNames::PRIVATE_KEY_STORAGE_PERMISSION,
168 : /*PUBLIC_KEY_STORAGE_PERMISSION */ libjami::Certificate::ChecksNames::PUBLIC_KEY_STORAGE_PERMISSION,
169 : /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ libjami::Certificate::ChecksNames::PRIVATE_KEY_DIRECTORY_PERMISSIONS,
170 : /*PUBLICKEY_DIRECTORY_PERMISSIONS */ libjami::Certificate::ChecksNames::PUBLIC_KEY_DIRECTORY_PERMISSIONS,
171 : /*PRIVATE_KEY_STORAGE_LOCATION */ libjami::Certificate::ChecksNames::PRIVATE_KEY_STORAGE_LOCATION,
172 : /*PUBLIC_KEY_STORAGE_LOCATION */ libjami::Certificate::ChecksNames::PUBLIC_KEY_STORAGE_LOCATION,
173 : /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ libjami::Certificate::ChecksNames::PRIVATE_KEY_SELINUX_ATTRIBUTES,
174 : /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ libjami::Certificate::ChecksNames::PUBLIC_KEY_SELINUX_ATTRIBUTES,
175 : /*EXIST */ libjami::Certificate::ChecksNames::EXIST,
176 : /*VALID */ libjami::Certificate::ChecksNames::VALID,
177 : /*VALID_AUTHORITY */ libjami::Certificate::ChecksNames::VALID_AUTHORITY,
178 : /*KNOWN_AUTHORITY */ libjami::Certificate::ChecksNames::KNOWN_AUTHORITY,
179 : /*NOT_REVOKED */ libjami::Certificate::ChecksNames::NOT_REVOKED,
180 : /*AUTHORITY_MISMATCH */ libjami::Certificate::ChecksNames::AUTHORITY_MISMATCH,
181 : /*UNEXPECTED_OWNER */ libjami::Certificate::ChecksNames::UNEXPECTED_OWNER,
182 : /*NOT_ACTIVATED */ libjami::Certificate::ChecksNames::NOT_ACTIVATED,
183 : }};
184 :
185 : const EnumClassNames<TlsValidator::CertificateDetails> TlsValidator::CertificateDetailsNames = {{
186 : /* EXPIRATION_DATE */ libjami::Certificate::DetailsNames::EXPIRATION_DATE,
187 : /* ACTIVATION_DATE */ libjami::Certificate::DetailsNames::ACTIVATION_DATE,
188 : /* REQUIRE_PRIVATE_KEY_PASSWORD */ libjami::Certificate::DetailsNames::REQUIRE_PRIVATE_KEY_PASSWORD,
189 : /* PUBLIC_SIGNATURE */ libjami::Certificate::DetailsNames::PUBLIC_SIGNATURE,
190 : /* VERSION_NUMBER */ libjami::Certificate::DetailsNames::VERSION_NUMBER,
191 : /* SERIAL_NUMBER */ libjami::Certificate::DetailsNames::SERIAL_NUMBER,
192 : /* ISSUER */ libjami::Certificate::DetailsNames::ISSUER,
193 : /* SUBJECT_KEY_ALGORITHM */ libjami::Certificate::DetailsNames::SUBJECT_KEY_ALGORITHM,
194 : /* SUBJECT_KEY */ libjami::Certificate::DetailsNames::SUBJECT_KEY,
195 : /* CN */ libjami::Certificate::DetailsNames::CN,
196 : /* UID */ libjami::Certificate::DetailsNames::UID,
197 : /* N */ libjami::Certificate::DetailsNames::N,
198 : /* O */ libjami::Certificate::DetailsNames::O,
199 : /* SIGNATURE_ALGORITHM */ libjami::Certificate::DetailsNames::SIGNATURE_ALGORITHM,
200 : /* MD5_FINGERPRINT */ libjami::Certificate::DetailsNames::MD5_FINGERPRINT,
201 : /* SHA1_FINGERPRINT */ libjami::Certificate::DetailsNames::SHA1_FINGERPRINT,
202 : /* PUBLIC_KEY_ID */ libjami::Certificate::DetailsNames::PUBLIC_KEY_ID,
203 : /* ISSUER_DN */ libjami::Certificate::DetailsNames::ISSUER_DN,
204 : /* ISSUER_CN */ libjami::Certificate::DetailsNames::ISSUER_CN,
205 : /* ISSUER_UID */ libjami::Certificate::DetailsNames::ISSUER_UID,
206 : /* ISSUER_N */ libjami::Certificate::DetailsNames::ISSUER_N,
207 : /* ISSUER_O */ libjami::Certificate::DetailsNames::ISSUER_O,
208 : /* NEXT_EXPECTED_UPDATE_DATE */ libjami::Certificate::DetailsNames::NEXT_EXPECTED_UPDATE_DATE,
209 : /* OUTGOING_SERVER */ libjami::Certificate::DetailsNames::OUTGOING_SERVER,
210 : /* IS_CA */ libjami::Certificate::DetailsNames::IS_CA,
211 :
212 : }};
213 :
214 : const EnumClassNames<const TlsValidator::CheckValuesType> TlsValidator::CheckValuesTypeNames = {{
215 : /* Type Name */
216 : /* BOOLEAN */ libjami::Certificate::ChecksValuesTypesNames::BOOLEAN,
217 : /* ISO_DATE */ libjami::Certificate::ChecksValuesTypesNames::ISO_DATE,
218 : /* CUSTOM */ libjami::Certificate::ChecksValuesTypesNames::CUSTOM,
219 : /* NUMBER */ libjami::Certificate::ChecksValuesTypesNames::NUMBER,
220 : }};
221 :
222 : const Matrix2D<TlsValidator::CheckValuesType, TlsValidator::CheckValues, bool> TlsValidator::acceptedCheckValuesResult = {
223 : {
224 : /* Type PASSED FAILED UNSUPPORTED ISO_DATE CUSTOM NUMBER */
225 : /* BOOLEAN */ {{true, true, true, false, false, false}},
226 : /* ISO_DATE */ {{false, false, true, true, false, false}},
227 : /* CUSTOM */ {{false, false, true, false, true, false}},
228 : /* NUMBER */ {{false, false, true, false, false, true}},
229 : }};
230 :
231 0 : TlsValidator::TlsValidator(const dhtnet::tls::CertificateStore& certStore,
232 0 : const std::vector<std::vector<uint8_t>>& crtChain)
233 0 : : TlsValidator(certStore, std::make_shared<dht::crypto::Certificate>(crtChain.begin(), crtChain.end()))
234 0 : {}
235 :
236 0 : TlsValidator::TlsValidator(const dhtnet::tls::CertificateStore& certStore,
237 : const std::string& certificate,
238 : const std::string& privatekey,
239 : const std::string& privatekeyPasswd,
240 0 : const std::string& caList)
241 0 : : certStore_(certStore)
242 0 : , certificatePath_(certificate)
243 0 : , privateKeyPath_(privatekey)
244 0 : , caListPath_(caList)
245 0 : , certificateFound_(false)
246 : {
247 0 : std::vector<uint8_t> certificate_raw;
248 : try {
249 0 : certificate_raw = fileutils::loadFile(certificatePath_);
250 0 : certificateFileFound_ = true;
251 0 : } catch (const std::exception& e) {
252 0 : }
253 :
254 0 : if (not certificate_raw.empty()) {
255 : try {
256 0 : x509crt_ = std::make_shared<dht::crypto::Certificate>(certificate_raw);
257 0 : certificateContent_ = x509crt_->getPacked();
258 0 : certificateFound_ = true;
259 0 : } catch (const std::exception& e) {
260 0 : }
261 : }
262 :
263 : try {
264 0 : auto privateKeyContent = fileutils::loadFile(privateKeyPath_);
265 0 : dht::crypto::PrivateKey key_tmp(privateKeyContent, privatekeyPasswd);
266 0 : privateKeyFound_ = true;
267 0 : privateKeyPassword_ = not privatekeyPasswd.empty();
268 0 : privateKeyMatch_ = key_tmp.getPublicKey().getId() == x509crt_->getId();
269 0 : } catch (const dht::crypto::DecryptError& d) {
270 : // If we encounter a DecryptError, it means the private key exists and is encrypted,
271 : // otherwise we would get some other exception.
272 0 : JAMI_WARN("decryption error: %s", d.what());
273 0 : privateKeyFound_ = true;
274 0 : privateKeyPassword_ = true;
275 0 : } catch (const std::exception& e) {
276 0 : JAMI_WARN("creation failed: %s", e.what());
277 0 : }
278 0 : }
279 :
280 0 : TlsValidator::TlsValidator(const dhtnet::tls::CertificateStore& certStore, const std::vector<uint8_t>& certificate_raw)
281 0 : : certStore_(certStore)
282 : {
283 : try {
284 0 : x509crt_ = std::make_shared<dht::crypto::Certificate>(certificate_raw);
285 0 : certificateContent_ = x509crt_->getPacked();
286 0 : certificateFound_ = true;
287 0 : } catch (const std::exception& e) {
288 0 : throw TlsValidatorException("Unable to load certificate");
289 0 : }
290 0 : }
291 :
292 0 : TlsValidator::TlsValidator(const dhtnet::tls::CertificateStore& certStore,
293 0 : const std::shared_ptr<dht::crypto::Certificate>& crt)
294 0 : : certStore_(certStore)
295 0 : , certificateFound_(true)
296 : {
297 : try {
298 0 : if (not crt)
299 0 : throw std::invalid_argument("Certificate must be set");
300 0 : x509crt_ = crt;
301 0 : certificateContent_ = x509crt_->getPacked();
302 0 : } catch (const std::exception& e) {
303 0 : throw TlsValidatorException("Unable to load certificate");
304 0 : }
305 0 : }
306 :
307 0 : TlsValidator::~TlsValidator() {}
308 :
309 : /**
310 : * This method convert results into validated strings
311 : *
312 : * @todo The date should be validated, this is currently not an issue
313 : */
314 : std::string
315 0 : TlsValidator::getStringValue([[maybe_unused]] const TlsValidator::CertificateCheck check,
316 : const TlsValidator::CheckResult result)
317 : {
318 0 : assert(acceptedCheckValuesResult[enforcedCheckType[check]][result.first]);
319 :
320 0 : switch (result.first) {
321 0 : case CheckValues::PASSED:
322 : case CheckValues::FAILED:
323 : case CheckValues::UNSUPPORTED:
324 0 : return std::string(CheckValuesNames[result.first]);
325 0 : case CheckValues::ISO_DATE:
326 : // TODO validate date
327 : // return CheckValues::FAILED;
328 0 : return result.second;
329 0 : case CheckValues::NUMBER:
330 : // TODO Validate numbers
331 : case CheckValues::CUSTOM:
332 0 : return result.second;
333 0 : default:
334 : // Consider any other case (such as forced int->CheckValues casting) as failed
335 0 : return std::string(CheckValuesNames[CheckValues::FAILED]);
336 : };
337 : }
338 :
339 : /**
340 : * Check if all boolean check passed
341 : * return true if there was no ::FAILED checks
342 : *
343 : * Checks functions are not "const", so this function isn't
344 : */
345 : bool
346 0 : TlsValidator::isValid(bool verbose)
347 : {
348 0 : for (const CertificateCheck check : Matrix0D<CertificateCheck>()) {
349 0 : if (enforcedCheckType[check] == CheckValuesType::BOOLEAN) {
350 0 : if (((this->*(checkCallback[check]))()).first == CheckValues::FAILED) {
351 0 : if (verbose)
352 0 : JAMI_WARNING("Check failed: {}", CertificateCheckNames[check]);
353 0 : return false;
354 : }
355 : }
356 : }
357 0 : return true;
358 : }
359 :
360 : /**
361 : * Convert all checks results into a string map
362 : */
363 : std::map<std::string, std::string>
364 0 : TlsValidator::getSerializedChecks()
365 : {
366 0 : std::map<std::string, std::string> ret;
367 0 : if (not certificateFound_) {
368 : // Instead of checking `certificateFound` everywhere, handle it once
369 0 : ret[std::string(CertificateCheckNames[CertificateCheck::EXIST])] = getStringValue(CertificateCheck::EXIST,
370 0 : exist());
371 : } else {
372 0 : for (const CertificateCheck check : Matrix0D<CertificateCheck>())
373 0 : ret[std::string(CertificateCheckNames[check])] = getStringValue(check, (this->*(checkCallback[check]))());
374 : }
375 :
376 0 : return ret;
377 0 : }
378 :
379 : /**
380 : * Get a map with all common certificate details
381 : */
382 : std::map<std::string, std::string>
383 0 : TlsValidator::getSerializedDetails()
384 : {
385 0 : std::map<std::string, std::string> ret;
386 0 : if (certificateFound_) {
387 0 : for (const CertificateDetails& det : Matrix0D<CertificateDetails>()) {
388 0 : const CheckResult r = (this->*(getterCallback[det]))();
389 0 : std::string val;
390 : // TODO move this to a fuction
391 0 : switch (r.first) {
392 0 : case CheckValues::PASSED:
393 : case CheckValues::FAILED:
394 : case CheckValues::UNSUPPORTED:
395 0 : val = CheckValuesNames[r.first];
396 0 : break;
397 0 : case CheckValues::ISO_DATE:
398 : // TODO validate date
399 : case CheckValues::NUMBER:
400 : // TODO Validate numbers
401 : case CheckValues::CUSTOM:
402 : default:
403 0 : val = r.second;
404 0 : break;
405 : }
406 0 : ret[std::string(CertificateDetailsNames[det])] = val;
407 0 : }
408 : }
409 0 : return ret;
410 0 : }
411 :
412 : /**
413 : * Helper method to return UNSUPPORTED when an error is detected
414 : */
415 : static TlsValidator::CheckResult
416 0 : checkError(int err, char* copy_buffer, size_t size)
417 : {
418 0 : return TlsValidator::TlsValidator::CheckResult(err == GNUTLS_E_SUCCESS ? TlsValidator::CheckValues::CUSTOM
419 : : TlsValidator::CheckValues::UNSUPPORTED,
420 0 : err == GNUTLS_E_SUCCESS ? std::string(copy_buffer, size) : "");
421 : }
422 :
423 : /**
424 : * Convert a time_t to an ISO date string
425 : */
426 : static TlsValidator::CheckResult
427 0 : formatDate(const time_t time)
428 : {
429 : char buffer[12];
430 0 : struct tm* timeinfo = localtime(&time);
431 0 : strftime(buffer, sizeof(buffer), "%F", timeinfo);
432 0 : return TlsValidator::CheckResult(TlsValidator::CheckValues::ISO_DATE, buffer);
433 : }
434 :
435 : /**
436 : * Helper method to return UNSUPPORTED when an error is detected
437 : *
438 : * This method also convert the output to binary
439 : */
440 : static TlsValidator::CheckResult
441 0 : checkBinaryError(int err, char* copy_buffer, size_t resultSize)
442 : {
443 0 : if (err == GNUTLS_E_SUCCESS)
444 0 : return TlsValidator::CheckResult(TlsValidator::CheckValues::CUSTOM,
445 0 : dht::toHex(reinterpret_cast<uint8_t*>(copy_buffer), resultSize));
446 : else
447 0 : return TlsValidator::CheckResult(TlsValidator::CheckValues::UNSUPPORTED, "");
448 : }
449 :
450 : /**
451 : * Check if a certificate has been signed with the authority
452 : */
453 : unsigned int
454 0 : TlsValidator::compareToCa()
455 : {
456 : // Don't check unless the certificate changed
457 0 : if (caChecked_)
458 0 : return caValidationOutput_;
459 :
460 : // build the CA trusted list
461 : gnutls_x509_trust_list_t trust;
462 0 : gnutls_x509_trust_list_init(&trust, 0);
463 :
464 0 : auto root_cas = certStore_.getTrustedCertificates();
465 0 : auto err = gnutls_x509_trust_list_add_cas(trust, root_cas.data(), root_cas.size(), 0);
466 0 : if (err)
467 0 : JAMI_WARN("gnutls_x509_trust_list_add_cas failed: %s", gnutls_strerror(err));
468 :
469 0 : if (not caListPath_.empty()) {
470 0 : if (std::filesystem::is_directory(caListPath_))
471 0 : gnutls_x509_trust_list_add_trust_dir(trust, caListPath_.c_str(), nullptr, GNUTLS_X509_FMT_PEM, 0, 0);
472 : else
473 0 : gnutls_x509_trust_list_add_trust_file(trust, caListPath_.c_str(), nullptr, GNUTLS_X509_FMT_PEM, 0, 0);
474 : }
475 :
476 : // build the certificate chain
477 0 : auto crts = x509crt_->getChain();
478 0 : err = gnutls_x509_trust_list_verify_crt2(trust,
479 : crts.data(),
480 0 : crts.size(),
481 : nullptr,
482 : 0,
483 : GNUTLS_PROFILE_TO_VFLAGS(GNUTLS_PROFILE_MEDIUM),
484 : &caValidationOutput_,
485 : nullptr);
486 :
487 0 : gnutls_x509_trust_list_deinit(trust, true);
488 :
489 0 : if (err) {
490 0 : JAMI_WARN("gnutls_x509_trust_list_verify_crt2 failed: %s", gnutls_strerror(err));
491 0 : return GNUTLS_CERT_SIGNER_NOT_FOUND;
492 : }
493 :
494 0 : caChecked_ = true;
495 0 : return caValidationOutput_;
496 0 : }
497 :
498 : #if 0 // disabled, see .h for reason
499 : /**
500 : * Verify if a hostname is valid
501 : *
502 : * @warning This function is blocking
503 : *
504 : * Mainly based on Fedora Defensive Coding tutorial
505 : * https://docs.fedoraproject.org/en-US/Fedora_Security_Team/1/html/Defensive_Coding/sect-Defensive_Coding-TLS-Client-GNUTLS.html
506 : */
507 : int TlsValidator::verifyHostnameCertificate(const std::string& host, const uint16_t port)
508 : {
509 : int err, arg, res = -1;
510 : unsigned int status = (unsigned) -1;
511 : const char *errptr = nullptr;
512 : gnutls_session_t session = nullptr;
513 : gnutls_certificate_credentials_t cred = nullptr;
514 : unsigned int certslen = 0;
515 : const gnutls_datum_t *certs = nullptr;
516 : gnutls_x509_crt_t cert = nullptr;
517 :
518 : char buf[4096];
519 : int sockfd;
520 : struct sockaddr_in name;
521 : struct hostent *hostinfo;
522 : const int one = 1;
523 : fd_set fdset;
524 : struct timeval tv;
525 :
526 : if (!host.size() || !port) {
527 : JAMI_ERR("Wrong parameters used - host %s, port %d.", host.c_str(), port);
528 : return res;
529 : }
530 :
531 : /* Create the socket. */
532 : sockfd = socket (PF_INET, SOCK_STREAM, 0);
533 : if (sockfd < 0) {
534 : JAMI_ERR("Unable to create socket.");
535 : return res;
536 : }
537 : /* Set non-blocking so we can dected timeouts. */
538 : arg = fcntl(sockfd, F_GETFL, nullptr);
539 : if (arg < 0)
540 : goto out;
541 : arg |= O_NONBLOCK;
542 : if (fcntl(sockfd, F_SETFL, arg) < 0)
543 : goto out;
544 :
545 : /* Give the socket a name. */
546 : memset(&name, 0, sizeof(name));
547 : name.sin_family = AF_INET;
548 : name.sin_port = htons(port);
549 : hostinfo = gethostbyname(host.c_str());
550 : if (hostinfo == nullptr) {
551 : JAMI_ERR("Unknown host %s.", host.c_str());
552 : goto out;
553 : }
554 : name.sin_addr = *(struct in_addr *)hostinfo->h_addr;
555 : /* Connect to the address specified in name struct. */
556 : err = connect(sockfd, (struct sockaddr *)&name, sizeof(name));
557 : if (err < 0) {
558 : /* Connection in progress, use select to see if timeout is reached. */
559 : if (errno == EINPROGRESS) {
560 : do {
561 : FD_ZERO(&fdset);
562 : FD_SET(sockfd, &fdset);
563 : tv.tv_sec = 10; // 10 second timeout
564 : tv.tv_usec = 0;
565 : err = select(sockfd + 1, nullptr, &fdset, nullptr, &tv);
566 : if (err < 0 && errno != EINTR) {
567 : JAMI_ERR("Unable to connect to hostname %s at port %d",
568 : host.c_str(), port);
569 : goto out;
570 : } else if (err > 0) {
571 : /* Select returned, if so_error is clean we are ready. */
572 : int so_error;
573 : socklen_t len = sizeof(so_error);
574 : getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);
575 :
576 : if (so_error) {
577 : JAMI_ERR("Connection delayed.");
578 : goto out;
579 : }
580 : break; // exit do-while loop
581 : } else {
582 : JAMI_ERR("Connection timeout.");
583 : goto out;
584 : }
585 : } while(1);
586 : } else {
587 : JAMI_ERR("Unable to connect to hostname %s at port %d", host.c_str(), port);
588 : goto out;
589 : }
590 : }
591 : /* Set the socked blocking again. */
592 : arg = fcntl(sockfd, F_GETFL, nullptr);
593 : if (arg < 0)
594 : goto out;
595 : arg &= ~O_NONBLOCK;
596 : if (fcntl(sockfd, F_SETFL, arg) < 0)
597 : goto out;
598 :
599 : /* Disable Nagle algorithm that slows down the SSL handshake. */
600 : err = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
601 : if (err < 0) {
602 : JAMI_ERR("Unable to set TCP_NODELAY.");
603 : goto out;
604 : }
605 :
606 :
607 : /* Load the trusted CA certificates. */
608 : err = gnutls_certificate_allocate_credentials(&cred);
609 : if (err != GNUTLS_E_SUCCESS) {
610 : JAMI_ERR("Unable to allocate credentials - %s", gnutls_strerror(err));
611 : goto out;
612 : }
613 : err = gnutls_certificate_set_x509_system_trust(cred);
614 : if (err != GNUTLS_E_SUCCESS) {
615 : JAMI_ERR("Unable to load credentials.");
616 : goto out;
617 : }
618 :
619 : /* Create the session object. */
620 : err = gnutls_init(&session, GNUTLS_CLIENT);
621 : if (err != GNUTLS_E_SUCCESS) {
622 : JAMI_ERR("Unable to init session -%s\n", gnutls_strerror(err));
623 : goto out;
624 : }
625 :
626 : /* Configure the cipher preferences. The default set should be good enough. */
627 : err = gnutls_priority_set_direct(session, "NORMAL", &errptr);
628 : if (err != GNUTLS_E_SUCCESS) {
629 : JAMI_ERR("Unable to set up ciphers - %s (%s)", gnutls_strerror(err), errptr);
630 : goto out;
631 : }
632 :
633 : /* Install the trusted certificates. */
634 : err = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
635 : if (err != GNUTLS_E_SUCCESS) {
636 : JAMI_ERR("Unable to set up credentials - %s", gnutls_strerror(err));
637 : goto out;
638 : }
639 :
640 : /* Associate the socket with the session object and set the server name. */
641 : gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) (uintptr_t) sockfd);
642 : err = gnutls_server_name_set(session, GNUTLS_NAME_DNS, host.c_str(), host.size());
643 : if (err != GNUTLS_E_SUCCESS) {
644 : JAMI_ERR("Unable to set server name - %s", gnutls_strerror(err));
645 : goto out;
646 : }
647 :
648 : /* Establish the connection. */
649 : err = gnutls_handshake(session);
650 : if (err != GNUTLS_E_SUCCESS) {
651 : JAMI_ERR("Handshake failed - %s", gnutls_strerror(err));
652 : goto out;
653 : }
654 : /* Obtain the server certificate chain. The server certificate
655 : * itself is stored in the first element of the array. */
656 : certs = gnutls_certificate_get_peers(session, &certslen);
657 : if (certs == nullptr || certslen == 0) {
658 : JAMI_ERR("Unable to obtain peer certificate - %s", gnutls_strerror(err));
659 : goto out;
660 : }
661 :
662 : /* Validate the certificate chain. */
663 : err = gnutls_certificate_verify_peers2(session, &status);
664 : if (err != GNUTLS_E_SUCCESS) {
665 : JAMI_ERR("Unable to verify the certificate chain - %s", gnutls_strerror(err));
666 : goto out;
667 : }
668 : if (status != 0) {
669 : gnutls_datum_t msg;
670 : #if GNUTLS_VERSION_AT_LEAST_3_1_4
671 : int type = gnutls_certificate_type_get(session);
672 : err = gnutls_certificate_verification_status_print(status, type, &out, 0);
673 : #else
674 : err = -1;
675 : #endif
676 : if (err == 0) {
677 : JAMI_ERR("Certificate validation failed - %s\n", msg.data);
678 : gnutls_free(msg.data);
679 : goto out;
680 : } else {
681 : JAMI_ERR("Certificate validation failed with code 0x%x.", status);
682 : goto out;
683 : }
684 : }
685 :
686 : /* Match the peer certificate against the hostname.
687 : * We can only obtain a set of DER-encoded certificates from the
688 : * session object, so we have to re-parse the peer certificate into
689 : * a certificate object. */
690 :
691 : err = gnutls_x509_crt_init(&cert);
692 : if (err != GNUTLS_E_SUCCESS) {
693 : JAMI_ERR("Unable to init certificate - %s", gnutls_strerror(err));
694 : goto out;
695 : }
696 :
697 : /* The peer certificate is the first certificate in the list. */
698 : err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM);
699 : if (err != GNUTLS_E_SUCCESS)
700 : err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER);
701 : if (err != GNUTLS_E_SUCCESS) {
702 : JAMI_ERR("Unable to read peer certificate - %s", gnutls_strerror(err));
703 : goto out;
704 : }
705 : /* Finally check if the hostnames match. */
706 : err = gnutls_x509_crt_check_hostname(cert, host.c_str());
707 : if (err == 0) {
708 : JAMI_ERR("Hostname %s does not match certificate.", host.c_str());
709 : goto out;
710 : }
711 :
712 : /* Try sending and receiving some data through. */
713 : snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host.c_str());
714 : err = gnutls_record_send(session, buf, strlen(buf));
715 : if (err < 0) {
716 : JAMI_ERR("Send failed - %s", gnutls_strerror(err));
717 : goto out;
718 : }
719 : err = gnutls_record_recv(session, buf, sizeof(buf));
720 : if (err < 0) {
721 : JAMI_ERR("Recv failed - %s", gnutls_strerror(err));
722 : goto out;
723 : }
724 :
725 : JAMI_DBG("Hostname %s seems to point to a valid server.", host.c_str());
726 : res = 0;
727 : out:
728 : if (session) {
729 : gnutls_bye(session, GNUTLS_SHUT_RDWR);
730 : gnutls_deinit(session);
731 : }
732 : if (cert)
733 : gnutls_x509_crt_deinit(cert);
734 : if (cred)
735 : gnutls_certificate_free_credentials(cred);
736 : close(sockfd);
737 : return res;
738 : }
739 : #endif
740 :
741 : /**
742 : * Check if the Validator have access to a private key
743 : */
744 : TlsValidator::CheckResult
745 0 : TlsValidator::hasPrivateKey()
746 : {
747 0 : if (privateKeyFound_)
748 0 : return TlsValidator::CheckResult(CheckValues::PASSED, "");
749 :
750 : try {
751 0 : dht::crypto::PrivateKey key_tmp(certificateContent_);
752 0 : } catch (const std::exception& e) {
753 0 : return CheckResult(CheckValues::FAILED, e.what());
754 0 : }
755 :
756 0 : JAMI_DBG("Key from %s seems valid.", certificatePath_.c_str());
757 0 : return CheckResult(CheckValues::PASSED, "");
758 : }
759 :
760 : /**
761 : * Check if the certificate is not expired
762 : *
763 : * The double negative is used because all boolean checks need to have
764 : * a consistent return value semantic
765 : *
766 : * @fixme Handle both "with ca" and "without ca" case
767 : */
768 : TlsValidator::CheckResult
769 0 : TlsValidator::notExpired()
770 : {
771 0 : if (exist().first == CheckValues::FAILED)
772 0 : TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
773 :
774 : // time_t expirationTime = gnutls_x509_crt_get_expiration_time(cert);
775 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_EXPIRED ? CheckValues::FAILED : CheckValues::PASSED,
776 0 : "");
777 : }
778 :
779 : /**
780 : * If the activation value is in the past
781 : *
782 : * @fixme Handle both "with ca" and "without ca" case
783 : */
784 : TlsValidator::CheckResult
785 0 : TlsValidator::activated()
786 : {
787 0 : if (exist().first == CheckValues::FAILED)
788 0 : TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
789 :
790 : // time_t activationTime = gnutls_x509_crt_get_activation_time(cert);
791 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_NOT_ACTIVATED ? CheckValues::FAILED
792 : : CheckValues::PASSED,
793 0 : "");
794 : }
795 :
796 : /**
797 : * If the algorithm used to sign the certificate is considered weak by modern
798 : * standard
799 : */
800 : TlsValidator::CheckResult
801 0 : TlsValidator::strongSigning()
802 : {
803 0 : if (exist().first == CheckValues::FAILED)
804 0 : TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
805 :
806 : // Doesn't seem to have the same value as
807 : // certtool --infile /home/etudiant/Téléchargements/mynsauser.pem --key-inf
808 : // TODO figure out why
809 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_INSECURE_ALGORITHM ? CheckValues::FAILED
810 : : CheckValues::PASSED,
811 0 : "");
812 : }
813 :
814 : /**
815 : * The certificate is not self signed
816 : */
817 : TlsValidator::CheckResult
818 0 : TlsValidator::notSelfSigned()
819 : {
820 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
821 : }
822 :
823 : /**
824 : * The provided key can be used along with the certificate
825 : */
826 : TlsValidator::CheckResult
827 0 : TlsValidator::keyMatch()
828 : {
829 0 : if (exist().first == CheckValues::FAILED)
830 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
831 :
832 0 : if (not privateKeyFound_)
833 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
834 0 : return TlsValidator::CheckResult(privateKeyMatch_ ? CheckValues::PASSED : CheckValues::FAILED, "");
835 : }
836 :
837 : TlsValidator::CheckResult
838 0 : TlsValidator::privateKeyStoragePermissions()
839 : {
840 : struct stat statbuf;
841 0 : int err = stat(privateKeyPath_.c_str(), &statbuf);
842 0 : if (err)
843 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
844 :
845 : // clang-format off
846 : return TlsValidator::CheckResult(
847 0 : (statbuf.st_mode & S_IFREG) && /* Regular file only */
848 : /* READ WRITE EXECUTE */
849 0 : /* Owner */ ( (statbuf.st_mode & S_IRUSR) /* write is not relevant */ && !(statbuf.st_mode & S_IXUSR))
850 0 : /* Group */ && (!(statbuf.st_mode & S_IRGRP) && !(statbuf.st_mode & S_IWGRP) && !(statbuf.st_mode & S_IXGRP))
851 0 : /* Other */ && (!(statbuf.st_mode & S_IROTH) && !(statbuf.st_mode & S_IWOTH) && !(statbuf.st_mode & S_IXOTH))
852 0 : ? CheckValues::PASSED:CheckValues::FAILED, "");
853 : // clang-format on
854 : }
855 :
856 : TlsValidator::CheckResult
857 0 : TlsValidator::publicKeyStoragePermissions()
858 : {
859 : struct stat statbuf;
860 0 : int err = stat(certificatePath_.c_str(), &statbuf);
861 0 : if (err)
862 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
863 :
864 : // clang-format off
865 : return TlsValidator::CheckResult(
866 0 : (statbuf.st_mode & S_IFREG) && /* Regular file only */
867 : /* READ WRITE EXECUTE */
868 0 : /* Owner */ ( (statbuf.st_mode & S_IRUSR) /* write is not relevant */ && !(statbuf.st_mode & S_IXUSR))
869 0 : /* Group */ && ( /* read is not relevant */ !(statbuf.st_mode & S_IWGRP) && !(statbuf.st_mode & S_IXGRP))
870 0 : /* Other */ && ( /* read is not relevant */ !(statbuf.st_mode & S_IWOTH) && !(statbuf.st_mode & S_IXOTH))
871 0 : ? CheckValues::PASSED:CheckValues::FAILED, "");
872 : // clang-format on
873 : }
874 :
875 : TlsValidator::CheckResult
876 0 : TlsValidator::privateKeyDirectoryPermissions()
877 : {
878 : namespace fs = std::filesystem;
879 :
880 0 : if (privateKeyPath_.empty())
881 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
882 :
883 0 : fs::path dir = fs::path(privateKeyPath_).parent_path();
884 0 : if (dir.empty())
885 0 : dir = fs::path("."); // mimic dirname() behavior when no separator
886 :
887 0 : std::error_code ec;
888 0 : auto st = fs::status(dir, ec);
889 0 : if (ec)
890 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
891 :
892 0 : if (!fs::is_directory(st))
893 0 : return TlsValidator::CheckResult(CheckValues::FAILED, "");
894 :
895 0 : auto perm = st.permissions();
896 :
897 0 : bool ownerRead = (perm & fs::perms::owner_read) != fs::perms::none;
898 0 : bool ownerExec = (perm & fs::perms::owner_exec) != fs::perms::none;
899 0 : bool groupAny = (perm & (fs::perms::group_read | fs::perms::group_write | fs::perms::group_exec))
900 0 : != fs::perms::none;
901 0 : bool othersAny = (perm & (fs::perms::others_read | fs::perms::others_write | fs::perms::others_exec))
902 0 : != fs::perms::none;
903 :
904 0 : bool ok = ownerRead && ownerExec && !groupAny && !othersAny;
905 0 : return TlsValidator::CheckResult(ok ? CheckValues::PASSED : CheckValues::FAILED, "");
906 0 : }
907 :
908 : TlsValidator::CheckResult
909 0 : TlsValidator::publicKeyDirectoryPermissions()
910 : {
911 : namespace fs = std::filesystem;
912 :
913 0 : if (certificatePath_.empty())
914 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
915 :
916 0 : fs::path dir = fs::path(certificatePath_).parent_path();
917 0 : if (dir.empty())
918 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
919 :
920 0 : std::error_code ec;
921 0 : auto st = fs::status(dir, ec);
922 0 : if (ec)
923 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
924 :
925 0 : if (!fs::is_directory(st))
926 0 : return TlsValidator::CheckResult(CheckValues::FAILED, "");
927 :
928 0 : auto perm = st.permissions();
929 :
930 0 : bool ownerRead = (perm & fs::perms::owner_read) != fs::perms::none;
931 0 : bool ownerExec = (perm & fs::perms::owner_exec) != fs::perms::none;
932 0 : bool groupAny = (perm & (fs::perms::group_read | fs::perms::group_write | fs::perms::group_exec))
933 0 : != fs::perms::none;
934 0 : bool othersAny = (perm & (fs::perms::others_read | fs::perms::others_write | fs::perms::others_exec))
935 0 : != fs::perms::none;
936 :
937 0 : bool ok = ownerRead && ownerExec && !groupAny && !othersAny;
938 0 : return TlsValidator::CheckResult(ok ? CheckValues::PASSED : CheckValues::FAILED, "");
939 0 : }
940 :
941 : /**
942 : * Certificate should be located in specific path on some operating systems
943 : */
944 : TlsValidator::CheckResult
945 0 : TlsValidator::privateKeyStorageLocation()
946 : {
947 : // TODO
948 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
949 : }
950 :
951 : /**
952 : * Certificate should be located in specific path on some operating systems
953 : */
954 : TlsValidator::CheckResult
955 0 : TlsValidator::publicKeyStorageLocation()
956 : {
957 : // TODO
958 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
959 : }
960 :
961 : /**
962 : * SELinux provide additional key protection mechanism
963 : */
964 : TlsValidator::CheckResult
965 0 : TlsValidator::privateKeySelinuxAttributes()
966 : {
967 : // TODO
968 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
969 : }
970 :
971 : /**
972 : * SELinux provide additional key protection mechanism
973 : */
974 : TlsValidator::CheckResult
975 0 : TlsValidator::publicKeySelinuxAttributes()
976 : {
977 : // TODO
978 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
979 : }
980 :
981 : /**
982 : * If the key need decryption
983 : *
984 : * Double factor authentication is recommended
985 : */
986 : TlsValidator::CheckResult
987 0 : TlsValidator::requirePrivateKeyPassword()
988 : {
989 0 : return TlsValidator::CheckResult(privateKeyPassword_ ? CheckValues::PASSED : CheckValues::FAILED, "");
990 : }
991 : /**
992 : * The CA and certificate provide conflicting ownership information
993 : */
994 : TlsValidator::CheckResult
995 0 : TlsValidator::expectedOwner()
996 : {
997 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_UNEXPECTED_OWNER ? CheckValues::FAILED
998 : : CheckValues::PASSED,
999 0 : "");
1000 : }
1001 :
1002 : /**
1003 : * The file has been found
1004 : */
1005 : TlsValidator::CheckResult
1006 0 : TlsValidator::exist()
1007 : {
1008 0 : return TlsValidator::CheckResult((certificateFound_ or certificateFileFound_) ? CheckValues::PASSED
1009 : : CheckValues::FAILED,
1010 0 : "");
1011 : }
1012 :
1013 : /**
1014 : * The certificate is invalid compared to the authority
1015 : *
1016 : * @todo Handle case when there is facultative authority, such as DHT
1017 : */
1018 : TlsValidator::CheckResult
1019 0 : TlsValidator::valid()
1020 : {
1021 0 : return TlsValidator::CheckResult(certificateFound_ ? CheckValues::PASSED : CheckValues::FAILED, "");
1022 : }
1023 :
1024 : /**
1025 : * The provided authority is invalid
1026 : */
1027 : TlsValidator::CheckResult
1028 0 : TlsValidator::validAuthority()
1029 : {
1030 : // TODO Merge with either above or below
1031 0 : return TlsValidator::CheckResult((compareToCa() & GNUTLS_CERT_SIGNER_NOT_FOUND) ? CheckValues::FAILED
1032 : : CheckValues::PASSED,
1033 0 : "");
1034 : }
1035 :
1036 : /**
1037 : * Check if the authority match the certificate
1038 : */
1039 : TlsValidator::CheckResult
1040 0 : TlsValidator::authorityMatch()
1041 : {
1042 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_SIGNER_NOT_CA ? CheckValues::FAILED
1043 : : CheckValues::PASSED,
1044 0 : "");
1045 : }
1046 :
1047 : /**
1048 : * When an account require an authority known by the system (like /usr/share/ssl/certs)
1049 : * then the whole chain of trust need be to checked
1050 : *
1051 : * @fixme port crypto_cert_load_trusted
1052 : * @fixme add account settings
1053 : * @todo implement the check
1054 : */
1055 : TlsValidator::CheckResult
1056 0 : TlsValidator::knownAuthority()
1057 : {
1058 : // TODO need a new boolean account setting "require trusted authority" or something defaulting
1059 : // to true using GNUTLS_CERT_SIGNER_NOT_FOUND is a temporary placeholder as it is close enough
1060 0 : return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_SIGNER_NOT_FOUND ? CheckValues::FAILED
1061 : : CheckValues::PASSED,
1062 0 : "");
1063 : }
1064 :
1065 : /**
1066 : * Check if the certificate has been revoked
1067 : */
1068 : TlsValidator::CheckResult
1069 0 : TlsValidator::notRevoked()
1070 : {
1071 0 : return TlsValidator::CheckResult((compareToCa() & GNUTLS_CERT_REVOKED)
1072 0 : || (compareToCa() & GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE)
1073 0 : ? CheckValues::FAILED
1074 : : CheckValues::PASSED,
1075 0 : "");
1076 : }
1077 :
1078 : /**
1079 : * A certificate authority has been provided
1080 : */
1081 : bool
1082 0 : TlsValidator::hasCa() const
1083 : {
1084 0 : return (x509crt_ and x509crt_->issuer); /* or
1085 : (caCert_ != nullptr and caCert_->certificateFound_);*/
1086 : }
1087 :
1088 : //
1089 : // Certificate details
1090 : //
1091 :
1092 : // TODO gnutls_x509_crl_get_this_update
1093 :
1094 : /**
1095 : * An hexadecimal representation of the signature
1096 : */
1097 : TlsValidator::CheckResult
1098 0 : TlsValidator::getPublicSignature()
1099 : {
1100 0 : size_t resultSize = sizeof(copy_buffer);
1101 0 : int err = gnutls_x509_crt_get_signature(x509crt_->cert, copy_buffer, &resultSize);
1102 0 : return checkBinaryError(err, copy_buffer, resultSize);
1103 : }
1104 :
1105 : /**
1106 : * Return the certificate version
1107 : */
1108 : TlsValidator::CheckResult
1109 0 : TlsValidator::getVersionNumber()
1110 : {
1111 0 : int version = gnutls_x509_crt_get_version(x509crt_->cert);
1112 0 : if (version < 0)
1113 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1114 :
1115 0 : std::ostringstream convert;
1116 0 : convert << version;
1117 :
1118 0 : return TlsValidator::CheckResult(CheckValues::NUMBER, convert.str());
1119 0 : }
1120 :
1121 : /**
1122 : * Return the certificate serial number
1123 : */
1124 : TlsValidator::CheckResult
1125 0 : TlsValidator::getSerialNumber()
1126 : {
1127 : // gnutls_x509_crl_iter_crt_serial
1128 : // gnutls_x509_crt_get_authority_key_gn_serial
1129 0 : size_t resultSize = sizeof(copy_buffer);
1130 0 : int err = gnutls_x509_crt_get_serial(x509crt_->cert, copy_buffer, &resultSize);
1131 0 : return checkBinaryError(err, copy_buffer, resultSize);
1132 : }
1133 :
1134 : /**
1135 : * If the certificate is not self signed, return the issuer
1136 : */
1137 : TlsValidator::CheckResult
1138 0 : TlsValidator::getIssuer()
1139 : {
1140 0 : if (not x509crt_->issuer) {
1141 0 : auto icrt = certStore_.findIssuer(x509crt_);
1142 0 : if (icrt)
1143 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, icrt->getId().toString());
1144 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1145 0 : }
1146 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, x509crt_->issuer->getId().toString());
1147 : }
1148 :
1149 : /**
1150 : * The algorithm used to sign the certificate details (rather than the certificate itself)
1151 : */
1152 : TlsValidator::CheckResult
1153 0 : TlsValidator::getSubjectKeyAlgorithm()
1154 : {
1155 0 : unsigned key_length = 0;
1156 0 : gnutls_pk_algorithm_t algo = (gnutls_pk_algorithm_t) gnutls_x509_crt_get_pk_algorithm(x509crt_->cert, &key_length);
1157 :
1158 0 : if (algo < 0)
1159 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1160 :
1161 0 : const char* name = gnutls_pk_get_name(algo);
1162 :
1163 0 : if (!name)
1164 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1165 :
1166 0 : if (key_length)
1167 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, fmt::format("{} ({} bits)", name, key_length));
1168 : else
1169 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, name);
1170 : }
1171 :
1172 : /**
1173 : * The subject public key
1174 : */
1175 : TlsValidator::CheckResult
1176 0 : TlsValidator::getSubjectKey()
1177 : {
1178 : try {
1179 0 : std::vector<uint8_t> data;
1180 0 : x509crt_->getPublicKey().pack(data);
1181 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, dht::toHex(data));
1182 0 : } catch (const dht::crypto::CryptoException& e) {
1183 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, e.what());
1184 0 : }
1185 : }
1186 :
1187 : /**
1188 : * The 'CN' section of a DN (RFC4514)
1189 : */
1190 : TlsValidator::CheckResult
1191 0 : TlsValidator::getCN()
1192 : {
1193 : // TODO split, cache
1194 0 : size_t resultSize = sizeof(copy_buffer);
1195 0 : int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, copy_buffer, &resultSize);
1196 0 : return checkError(err, copy_buffer, resultSize);
1197 : }
1198 :
1199 : /**
1200 : * The 'UID' section of a DN (RFC4514)
1201 : */
1202 : TlsValidator::CheckResult
1203 0 : TlsValidator::getUID()
1204 : {
1205 0 : size_t resultSize = sizeof(copy_buffer);
1206 0 : int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_LDAP_UID, 0, 0, copy_buffer, &resultSize);
1207 0 : return checkError(err, copy_buffer, resultSize);
1208 : }
1209 :
1210 : /**
1211 : * The 'N' section of a DN (RFC4514)
1212 : */
1213 : TlsValidator::CheckResult
1214 0 : TlsValidator::getN()
1215 : {
1216 : // TODO split, cache
1217 0 : size_t resultSize = sizeof(copy_buffer);
1218 0 : int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_NAME, 0, 0, copy_buffer, &resultSize);
1219 0 : return checkError(err, copy_buffer, resultSize);
1220 : }
1221 :
1222 : /**
1223 : * The 'O' section of a DN (RFC4514)
1224 : */
1225 : TlsValidator::CheckResult
1226 0 : TlsValidator::getO()
1227 : {
1228 : // TODO split, cache
1229 0 : size_t resultSize = sizeof(copy_buffer);
1230 0 : int err = gnutls_x509_crt_get_dn_by_oid(x509crt_->cert,
1231 : GNUTLS_OID_X520_ORGANIZATION_NAME,
1232 : 0,
1233 : 0,
1234 0 : copy_buffer,
1235 : &resultSize);
1236 0 : return checkError(err, copy_buffer, resultSize);
1237 : }
1238 :
1239 : /**
1240 : * Return the algorithm used to sign the Key
1241 : *
1242 : * For example: RSA
1243 : */
1244 : TlsValidator::CheckResult
1245 0 : TlsValidator::getSignatureAlgorithm()
1246 : {
1247 0 : gnutls_sign_algorithm_t algo = (gnutls_sign_algorithm_t) gnutls_x509_crt_get_signature_algorithm(x509crt_->cert);
1248 :
1249 0 : if (algo < 0)
1250 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1251 :
1252 0 : const char* algoName = gnutls_sign_get_name(algo);
1253 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, algoName);
1254 : }
1255 :
1256 : /**
1257 : *Compute the key fingerprint
1258 : *
1259 : * This need to be used along with getSha1Fingerprint() to avoid collisions
1260 : */
1261 : TlsValidator::CheckResult
1262 0 : TlsValidator::getMd5Fingerprint()
1263 : {
1264 0 : size_t resultSize = sizeof(copy_buffer);
1265 0 : int err = gnutls_x509_crt_get_fingerprint(x509crt_->cert, GNUTLS_DIG_MD5, copy_buffer, &resultSize);
1266 0 : return checkBinaryError(err, copy_buffer, resultSize);
1267 : }
1268 :
1269 : /**
1270 : * Compute the key fingerprint
1271 : *
1272 : * This need to be used along with getMd5Fingerprint() to avoid collisions
1273 : */
1274 : TlsValidator::CheckResult
1275 0 : TlsValidator::getSha1Fingerprint()
1276 : {
1277 0 : size_t resultSize = sizeof(copy_buffer);
1278 0 : int err = gnutls_x509_crt_get_fingerprint(x509crt_->cert, GNUTLS_DIG_SHA1, copy_buffer, &resultSize);
1279 0 : return checkBinaryError(err, copy_buffer, resultSize);
1280 : }
1281 :
1282 : /**
1283 : * Return an hexadecimal identifier
1284 : */
1285 : TlsValidator::CheckResult
1286 0 : TlsValidator::getPublicKeyId()
1287 : {
1288 : static unsigned char unsigned_copy_buffer[4096];
1289 0 : size_t resultSize = sizeof(unsigned_copy_buffer);
1290 0 : int err = gnutls_x509_crt_get_key_id(x509crt_->cert, 0, unsigned_copy_buffer, &resultSize);
1291 :
1292 : // TODO check for GNUTLS_E_SHORT_MEMORY_BUFFER and increase the buffer size
1293 : // TODO get rid of the cast, display a HEX or something, need research
1294 :
1295 0 : return checkBinaryError(err, (char*) unsigned_copy_buffer, resultSize);
1296 : }
1297 : // gnutls_x509_crt_get_authority_key_id
1298 :
1299 : /**
1300 : * If the certificate is not self signed, return the issuer DN (RFC4514)
1301 : */
1302 : TlsValidator::CheckResult
1303 0 : TlsValidator::getIssuerDN()
1304 : {
1305 0 : size_t resultSize = sizeof(copy_buffer);
1306 0 : int err = gnutls_x509_crt_get_issuer_dn(x509crt_->cert, copy_buffer, &resultSize);
1307 0 : return checkError(err, copy_buffer, resultSize);
1308 : }
1309 :
1310 : /**
1311 : * If the certificate is not self signed, return the issuer CN
1312 : */
1313 : TlsValidator::CheckResult
1314 0 : TlsValidator::getIssuerCN()
1315 : {
1316 0 : size_t resultSize = sizeof(copy_buffer);
1317 0 : int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
1318 : GNUTLS_OID_X520_COMMON_NAME,
1319 : 0,
1320 : 0,
1321 0 : copy_buffer,
1322 : &resultSize);
1323 0 : return checkError(err, copy_buffer, resultSize);
1324 : }
1325 :
1326 : /**
1327 : * If the certificate is not self signed, return the issuer UID
1328 : */
1329 : TlsValidator::CheckResult
1330 0 : TlsValidator::getIssuerUID()
1331 : {
1332 0 : size_t resultSize = sizeof(copy_buffer);
1333 0 : int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert, GNUTLS_OID_LDAP_UID, 0, 0, copy_buffer, &resultSize);
1334 0 : return checkError(err, copy_buffer, resultSize);
1335 : }
1336 :
1337 : /**
1338 : * If the certificate is not self signed, return the issuer N
1339 : */
1340 : TlsValidator::CheckResult
1341 0 : TlsValidator::getIssuerN()
1342 : {
1343 0 : size_t resultSize = sizeof(copy_buffer);
1344 0 : int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert, GNUTLS_OID_X520_NAME, 0, 0, copy_buffer, &resultSize);
1345 0 : return checkError(err, copy_buffer, resultSize);
1346 : }
1347 :
1348 : /**
1349 : * If the certificate is not self signed, return the issuer O
1350 : */
1351 : TlsValidator::CheckResult
1352 0 : TlsValidator::getIssuerO()
1353 : {
1354 0 : size_t resultSize = sizeof(copy_buffer);
1355 0 : int err = gnutls_x509_crt_get_issuer_dn_by_oid(x509crt_->cert,
1356 : GNUTLS_OID_X520_ORGANIZATION_NAME,
1357 : 0,
1358 : 0,
1359 0 : copy_buffer,
1360 : &resultSize);
1361 0 : return checkError(err, copy_buffer, resultSize);
1362 : }
1363 :
1364 : /**
1365 : * Get the expiration date
1366 : *
1367 : * @todo Move to "certificateDetails()" method once completed
1368 : */
1369 : TlsValidator::CheckResult
1370 0 : TlsValidator::getExpirationDate()
1371 : {
1372 0 : if (not certificateFound_)
1373 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1374 :
1375 0 : time_t expiration = gnutls_x509_crt_get_expiration_time(x509crt_->cert);
1376 :
1377 0 : return formatDate(expiration);
1378 : }
1379 :
1380 : /**
1381 : * Get the activation date
1382 : *
1383 : * @todo Move to "certificateDetails()" method once completed
1384 : */
1385 : TlsValidator::CheckResult
1386 0 : TlsValidator::getActivationDate()
1387 : {
1388 0 : if (not certificateFound_)
1389 0 : return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, "");
1390 :
1391 0 : time_t expiration = gnutls_x509_crt_get_activation_time(x509crt_->cert);
1392 :
1393 0 : return formatDate(expiration);
1394 : }
1395 :
1396 : /**
1397 : * The expected outgoing server domain
1398 : *
1399 : * @todo Move to "certificateDetails()" method once completed
1400 : * @todo extract information for the certificate
1401 : */
1402 : TlsValidator::CheckResult
1403 0 : TlsValidator::outgoingServer()
1404 : {
1405 : // TODO
1406 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, "");
1407 : }
1408 :
1409 : /**
1410 : * If the certificate is not self signed, return the issuer
1411 : */
1412 : TlsValidator::CheckResult
1413 0 : TlsValidator::isCA()
1414 : {
1415 0 : return TlsValidator::CheckResult(CheckValues::CUSTOM, x509crt_->isCA() ? TRUE_STR : FALSE_STR);
1416 : }
1417 :
1418 : } // namespace tls
1419 : } // namespace jami
|