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