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