1# -*- coding: utf-8 -*- 2"""QGIS Unit tests for bindings to core authentication system classes 3 4From build dir: LC_ALL=en_US.UTF-8 ctest -R PyQgsAuthenticationSystem -V 5 6.. note:: This program is free software; you can redistribute it and/or modify 7it under the terms of the GNU General Public License as published by 8the Free Software Foundation; either version 2 of the License, or 9(at your option) any later version. 10""" 11__author__ = 'Larry Shaffer' 12__date__ = '2014/11/05' 13__copyright__ = 'Copyright 2014, Boundless Spatial, Inc.' 14 15import os 16import tempfile 17 18from qgis.core import QgsAuthCertUtils, QgsPkiBundle, QgsAuthMethodConfig, QgsAuthMethod, QgsAuthConfigSslServer, QgsApplication 19from qgis.gui import QgsAuthEditorWidgets 20from qgis.PyQt.QtCore import QFileInfo, qDebug 21from qgis.PyQt.QtNetwork import QSsl, QSslError, QSslCertificate, QSslSocket 22from qgis.PyQt.QtTest import QTest 23from qgis.PyQt.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout 24from qgis.testing import start_app, unittest 25 26from utilities import unitTestDataPath 27 28AUTHDBDIR = tempfile.mkdtemp() 29os.environ['QGIS_AUTH_DB_DIR_PATH'] = AUTHDBDIR 30start_app() 31 32TESTDATA = os.path.join(unitTestDataPath(), 'auth_system') 33PKIDATA = os.path.join(TESTDATA, 'certs_keys') 34 35 36class TestQgsAuthManager(unittest.TestCase): 37 38 @classmethod 39 def setUpClass(cls): 40 cls.authm = QgsApplication.authManager() 41 assert not cls.authm.isDisabled(), cls.authm.disabledMessage() 42 43 cls.mpass = 'pass' # master password 44 45 db1 = QFileInfo(cls.authm.authenticationDatabasePath()).canonicalFilePath() 46 db2 = QFileInfo(AUTHDBDIR + '/qgis-auth.db').canonicalFilePath() 47 msg = 'Auth db temp path does not match db path of manager' 48 assert db1 == db2, msg 49 50 def setUp(self): 51 testid = self.id().split('.') 52 testheader = '\n#####_____ {0}.{1} _____#####\n'. \ 53 format(testid[1], testid[2]) 54 qDebug(testheader) 55 56 if (not self.authm.masterPasswordIsSet() or 57 not self.authm.masterPasswordHashInDatabase()): 58 self.set_master_password() 59 60 def widget_dialog(self, widget): 61 dlg = QDialog() 62 widget.setParent(dlg) 63 layout = QVBoxLayout() 64 layout.addWidget(widget) 65 layout.setMargin(6) 66 button_box = QDialogButtonBox(QDialogButtonBox.Close) 67 button_box.rejected.connect(dlg.close) 68 layout.addWidget(button_box) 69 dlg.setLayout(layout) 70 return dlg 71 72 def mkPEMBundle(self, client_cert, client_key, password, chain): 73 return QgsPkiBundle.fromPemPaths(PKIDATA + '/' + client_cert, 74 PKIDATA + '/' + client_key, 75 password, 76 QgsAuthCertUtils.certsFromFile( 77 PKIDATA + '/' + chain 78 )) 79 80 def show_editors_widget(self): 81 editors = QgsAuthEditorWidgets() 82 dlg = self.widget_dialog(editors) 83 dlg.exec_() 84 85 def set_master_password(self): 86 msg = 'Failed to store and verify master password in auth db' 87 assert self.authm.setMasterPassword(self.mpass, True), msg 88 89 def test_010_master_password(self): 90 msg = 'Master password is not set' 91 self.assertTrue(self.authm.masterPasswordIsSet(), msg) 92 93 msg = 'Master password hash is not in database' 94 self.assertTrue(self.authm.masterPasswordHashInDatabase(), msg) 95 96 msg = 'Master password not verified against hash in database' 97 self.assertTrue(self.authm.verifyMasterPassword(), msg) 98 99 msg = 'Master password comparison dissimilar' 100 self.assertTrue(self.authm.masterPasswordSame(self.mpass), msg) 101 102 msg = 'Master password not unset' 103 self.authm.clearMasterPassword() 104 self.assertFalse(self.authm.masterPasswordIsSet(), msg) 105 106 msg = 'Master password not reset and validated' 107 self.assertTrue(self.authm.setMasterPassword(self.mpass, True), msg) 108 109 # NOTE: reset of master password is in auth db test unit 110 111 def test_020_cert_utilities(self): 112 pass 113 114 def test_030_auth_settings(self): 115 pass 116 117 def test_040_authorities(self): 118 119 def rebuild_caches(): 120 m = 'Authorities cache could not be rebuilt' 121 self.assertTrue(self.authm.rebuildCaCertsCache(), m) 122 123 m = 'Authorities trust policy cache could not be rebuilt' 124 self.assertTrue(self.authm.rebuildTrustedCaCertsCache(), m) 125 126 def trusted_ca_certs(): 127 tr_certs = self.authm.trustedCaCerts() 128 m = 'Trusted authorities cache is empty' 129 self.assertIsNotNone(tr_certs, m) 130 return tr_certs 131 132 msg = 'No system root CAs' 133 self.assertIsNotNone(self.authm.systemRootCAs()) 134 135 # TODO: add more tests 136 full_chain = 'chains_subissuer-issuer-root_issuer2-root2.pem' 137 full_chain_path = os.path.join(PKIDATA, full_chain) 138 139 # load CA file authorities for later comparison 140 # noinspection PyTypeChecker 141 # ca_certs = QSslCertificate.fromPath(full_chain_path) 142 ca_certs = QgsAuthCertUtils.certsFromFile(full_chain_path) 143 msg = 'Authorities file could not be parsed' 144 self.assertIsNotNone(ca_certs, msg) 145 146 msg = 'Authorities file parsed count is incorrect' 147 self.assertEqual(len(ca_certs), 5, msg) 148 149 # first test CA file can be set and loaded 150 msg = 'Authority file path setting could not be stored' 151 self.assertTrue( 152 self.authm.storeAuthSetting('cafile', full_chain_path), msg) 153 154 msg = "Authority file 'allow invalids' setting could not be stored" 155 self.assertTrue( 156 self.authm.storeAuthSetting('cafileallowinvalid', False), msg) 157 158 rebuild_caches() 159 trusted_certs = trusted_ca_certs() 160 161 not_cached = any([ca not in trusted_certs for ca in ca_certs]) 162 msg = 'Authorities not in trusted authorities cache' 163 self.assertFalse(not_cached, msg) 164 165 # test CA file can be unset 166 msg = 'Authority file path setting could not be removed' 167 self.assertTrue(self.authm.removeAuthSetting('cafile'), msg) 168 169 msg = "Authority file 'allow invalids' setting could not be removed" 170 self.assertTrue( 171 self.authm.removeAuthSetting('cafileallowinvalid'), msg) 172 173 rebuild_caches() 174 trusted_certs = trusted_ca_certs() 175 176 still_cached = any([ca in trusted_certs for ca in ca_certs]) 177 msg = 'Authorities still in trusted authorities cache' 178 self.assertFalse(still_cached, msg) 179 180 # test CAs can be stored in database 181 msg = "Authority certs could not be stored in database" 182 self.assertTrue(self.authm.storeCertAuthorities(ca_certs)) 183 184 rebuild_caches() 185 trusted_certs = trusted_ca_certs() 186 187 not_cached = any([ca not in trusted_certs for ca in ca_certs]) 188 msg = 'Stored authorities not in trusted authorities cache' 189 self.assertFalse(not_cached, msg) 190 191 # dlg = QgsAuthTrustedCAsDialog() 192 # dlg.exec_() 193 194 def test_050_trust_policy(self): 195 pass 196 197 # noinspection PyArgumentList 198 def test_060_identities(self): 199 client_cert_path = os.path.join(PKIDATA, 'fra_cert.pem') 200 client_key_path = os.path.join(PKIDATA, 'fra_key_w-pass.pem') 201 client_key_pass = 'password' 202 client_p12_path = os.path.join(PKIDATA, 'gerardus_w-chain.p12') 203 client_p12_pass = 'password' 204 205 # store regular PEM cert/key and generate config 206 # noinspection PyTypeChecker 207 bundle1 = QgsPkiBundle.fromPemPaths(client_cert_path, client_key_path, 208 client_key_pass) 209 bundle1_cert = bundle1.clientCert() 210 bundle1_key = bundle1.clientKey() 211 bundle1_ca_chain = bundle1.caChain() 212 bundle1_cert_sha = bundle1.certId() 213 214 # with open(client_key_path, 'r') as f: 215 # key_data = f.read() 216 # 217 # client_cert = QgsAuthCertUtils.certsFromFile(client_cert_path)[0] 218 msg = 'Identity PEM certificate is null' 219 self.assertFalse(bundle1_cert.isNull(), msg) 220 221 # cert_sha = QgsAuthCertUtils.shaHexForCert(client_cert) 222 # 223 # client_key = QSslKey(key_data, QSsl.Rsa, QSsl.Pem, 224 # QSsl.PrivateKey, client_key_pass) 225 msg = 'Identity PEM key is null' 226 self.assertFalse(bundle1_key.isNull(), msg) 227 228 msg = 'Identity PEM certificate chain is not empty' 229 self.assertEqual(len(bundle1_ca_chain), 0, msg) 230 231 msg = "Identity PEM could not be stored in database" 232 self.assertTrue( 233 self.authm.storeCertIdentity(bundle1_cert, bundle1_key), msg) 234 235 msg = "Identity PEM not found in database" 236 self.assertTrue(self.authm.existsCertIdentity(bundle1_cert_sha), msg) 237 238 config1 = QgsAuthMethodConfig() 239 config1.setName('IdentityCert - PEM') 240 config1.setMethod('Identity-Cert') 241 config1.setConfig('certid', bundle1_cert_sha) 242 243 msg = 'Could not store PEM identity config' 244 self.assertTrue(self.authm.storeAuthenticationConfig(config1), msg) 245 246 configid1 = config1.id() 247 msg = 'Could not retrieve PEM identity config id from store op' 248 self.assertIsNotNone(configid1, msg) 249 250 config2 = QgsAuthMethodConfig() 251 msg = 'Could not load PEM identity config' 252 self.assertTrue( 253 self.authm.loadAuthenticationConfig(configid1, config2, True), 254 msg) 255 256 # store PKCS#12 bundled cert/key and generate config 257 # bundle = QgsPkcsBundle(client_p12_path, client_p12_pass) 258 # noinspection PyTypeChecker 259 bundle = QgsPkiBundle.fromPkcs12Paths(client_p12_path, client_p12_pass) 260 bundle_cert = bundle.clientCert() 261 bundle_key = bundle.clientKey() 262 bundle_ca_chain = bundle.caChain() 263 bundle_cert_sha = QgsAuthCertUtils.shaHexForCert(bundle_cert) 264 265 msg = 'Identity bundle certificate is null' 266 self.assertFalse(bundle_cert.isNull(), msg) 267 268 msg = 'Identity bundle key is null' 269 self.assertFalse(bundle_key.isNull(), msg) 270 271 msg = 'Identity bundle CA chain is not correct depth' 272 self.assertEqual(len(bundle_ca_chain), 3, msg) 273 274 msg = "Identity bundle could not be stored in database" 275 self.assertTrue( 276 self.authm.storeCertIdentity(bundle_cert, bundle_key), msg) 277 278 msg = "Identity bundle not found in database" 279 self.assertTrue(self.authm.existsCertIdentity(bundle_cert_sha), msg) 280 281 bundle_config = QgsAuthMethodConfig() 282 bundle_config.setName('IdentityCert - Bundle') 283 bundle_config.setMethod('Identity-Cert') 284 bundle_config.setConfig('certid', bundle_cert_sha) 285 286 msg = 'Could not store bundle identity config' 287 self.assertTrue( 288 self.authm.storeAuthenticationConfig(bundle_config), msg) 289 290 bundle_configid = bundle_config.id() 291 msg = 'Could not retrieve bundle identity config id from store op' 292 self.assertIsNotNone(bundle_configid, msg) 293 294 bundle_config2 = QgsAuthMethodConfig() 295 msg = 'Could not load bundle identity config' 296 self.assertTrue( 297 self.authm.loadAuthenticationConfig(bundle_configid, 298 bundle_config2, 299 True), 300 msg) 301 302 # TODO: add more tests 303 # self.show_editors_widget() 304 305 msg = 'Could not remove PEM identity config' 306 self.assertTrue(self.authm.removeAuthenticationConfig(configid1), msg) 307 308 msg = 'Could not remove bundle identity config' 309 self.assertTrue( 310 self.authm.removeAuthenticationConfig(bundle_configid), msg) 311 312 def test_070_servers(self): 313 # return 314 ssl_cert_path = os.path.join(PKIDATA, 'localhost_ssl_cert.pem') 315 316 ssl_cert = QgsAuthCertUtils.certsFromFile(ssl_cert_path)[0] 317 msg = 'SSL server certificate is null' 318 self.assertFalse(ssl_cert.isNull(), msg) 319 320 cert_sha = QgsAuthCertUtils.shaHexForCert(ssl_cert) 321 322 hostport = 'localhost:8443' 323 config = QgsAuthConfigSslServer() 324 config.setSslCertificate(ssl_cert) 325 config.setSslHostPort(hostport) 326 config.setSslIgnoredErrorEnums([QSslError.SelfSignedCertificate]) 327 config.setSslPeerVerifyMode(QSslSocket.VerifyNone) 328 config.setSslPeerVerifyDepth(3) 329 config.setSslProtocol(QSsl.TlsV1_1) 330 331 msg = 'SSL config is null' 332 self.assertFalse(config.isNull(), msg) 333 334 msg = 'Could not store SSL config' 335 self.assertTrue(self.authm.storeSslCertCustomConfig(config), msg) 336 337 msg = 'Could not verify storage of SSL config' 338 self.assertTrue( 339 self.authm.existsSslCertCustomConfig(cert_sha, hostport), msg) 340 341 msg = 'Could not verify SSL config in all configs' 342 self.assertIsNotNone(self.authm.sslCertCustomConfigs(), msg) 343 344 msg = 'Could not retrieve SSL config' 345 config2 = self.authm.sslCertCustomConfig(cert_sha, hostport) 346 """:type: QgsAuthConfigSslServer""" 347 self.assertFalse(config2.isNull(), msg) 348 349 msg = 'Certificate of retrieved SSL config does not match' 350 self.assertEqual(config.sslCertificate(), config2.sslCertificate(), msg) 351 352 msg = 'HostPort of retrieved SSL config does not match' 353 self.assertEqual(config.sslHostPort(), config2.sslHostPort(), msg) 354 355 msg = 'IgnoredErrorEnums of retrieved SSL config does not match' 356 enums = config2.sslIgnoredErrorEnums() 357 self.assertTrue(QSslError.SelfSignedCertificate in enums, msg) 358 359 msg = 'PeerVerifyMode of retrieved SSL config does not match' 360 self.assertEqual(config.sslPeerVerifyMode(), 361 config2.sslPeerVerifyMode(), msg) 362 363 msg = 'PeerVerifyDepth of retrieved SSL config does not match' 364 self.assertEqual(config.sslPeerVerifyDepth(), 365 config2.sslPeerVerifyDepth(), msg) 366 367 msg = 'Protocol of retrieved SSL config does not match' 368 self.assertEqual(config.sslProtocol(), config2.sslProtocol(), msg) 369 370 # dlg = QgsAuthSslConfigDialog(None, ssl_cert, hostport) 371 # dlg.exec_() 372 373 msg = 'Could not remove SSL config' 374 self.assertTrue( 375 self.authm.removeSslCertCustomConfig(cert_sha, hostport), msg) 376 377 msg = 'Could not verify removal of SSL config' 378 self.assertFalse( 379 self.authm.existsSslCertCustomConfig(cert_sha, hostport), msg) 380 381 def test_080_auth_configid(self): 382 msg = 'Could not generate a config id' 383 self.assertIsNotNone(self.authm.uniqueConfigId(), msg) 384 385 uids = [] 386 for _ in range(50): 387 # time.sleep(0.01) # or else the salt is not random enough 388 uids.append(self.authm.uniqueConfigId()) 389 msg = 'Generated 50 config ids are not unique:\n{0}\n{1}'.format( 390 uids, 391 list(set(uids)) 392 ) 393 self.assertEqual(len(uids), len(list(set(uids))), msg) 394 395 def config_list(self): 396 return ['Basic', 'PKI-Paths', 'PKI-PKCS#12'] 397 398 def config_obj(self, kind, base=True): 399 config = QgsAuthMethodConfig() 400 config.setName(kind) 401 config.setMethod(kind) 402 config.setUri('http://example.com') 403 if base: 404 return config 405 406 if kind == 'Basic': 407 config.setConfig('username', 'username') 408 config.setConfig('password', 'password') 409 config.setConfig('realm', 'Realm') 410 elif kind == 'PKI-Paths': 411 config.setConfig('certpath', 412 os.path.join(PKIDATA, 'gerardus_cert.pem')) 413 config.setConfig('keypath', 414 os.path.join(PKIDATA, 'gerardus_key_w-pass.pem')) 415 config.setConfig('keypass', 'password') 416 elif kind == 'PKI-PKCS#12': 417 config.setConfig('bundlepath', 418 os.path.join(PKIDATA, 'gerardus.p12')) 419 config.setConfig('bundlepass', 'password') 420 421 return config 422 423 def config_values_valid(self, kind, config): 424 """:type config: QgsAuthMethodConfig""" 425 if (config.name() != kind or 426 config.method() != kind or 427 config.uri() != 'http://example.com'): 428 return False 429 if kind == 'Basic': 430 return ( 431 config.config('username') == 'username' and 432 config.config('password') == 'password' and 433 config.config('realm') == 'Realm' 434 ) 435 elif kind == 'PKI-Paths': 436 return ( 437 config.config('certpath') == 438 os.path.join(PKIDATA, 'gerardus_cert.pem') and 439 config.config('keypath') == 440 os.path.join(PKIDATA, 'gerardus_key_w-pass.pem') and 441 config.config('keypass') == 'password' 442 ) 443 elif kind == 'PKI-PKCS#12': 444 return ( 445 config.config('bundlepath') == 446 os.path.join(PKIDATA, 'gerardus.p12') and 447 config.config('bundlepass') == 'password' 448 ) 449 450 def test_090_auth_configs(self): 451 # these list items need to match the QgsAuthType provider type strings 452 for kind in self.config_list(): 453 config = self.config_obj(kind, base=False) 454 msg = 'Could not validate {0} config'.format(kind) 455 self.assertTrue(config.isValid(), msg) 456 457 msg = 'Could not store {0} config'.format(kind) 458 self.assertTrue(self.authm.storeAuthenticationConfig(config), msg) 459 460 configid = config.id() 461 msg = 'Could not retrieve {0} config id from store op'.format(kind) 462 self.assertIsNotNone(configid, msg) 463 464 msg = 'Config id {0} not in db'.format(configid) 465 self.assertFalse(self.authm.configIdUnique(configid), msg) 466 467 msg = 'Could not retrieve {0} config id from db'.format(kind) 468 self.assertTrue(configid in self.authm.configIds(), msg) 469 470 msg = 'Could not retrieve method key for {0} config'.format(kind) 471 self.assertTrue( 472 self.authm.configAuthMethodKey(configid) == kind, msg) 473 474 msg = 'Could not retrieve method ptr for {0} config'.format(kind) 475 self.assertTrue( 476 isinstance(self.authm.configAuthMethod(configid), 477 QgsAuthMethod), msg) 478 479 config2 = self.config_obj(kind, base=True) 480 msg = 'Could not load {0} config'.format(kind) 481 self.assertTrue( 482 self.authm.loadAuthenticationConfig(configid, config2, True), 483 msg) 484 485 msg = 'Could not validate loaded {0} config values'.format(kind) 486 self.assertTrue(self.config_values_valid(kind, config2), msg) 487 488 # values haven't been changed, but the db update still takes place 489 msg = 'Could not update {0} config values'.format(kind) 490 self.assertTrue(self.authm.updateAuthenticationConfig(config2), msg) 491 492 config3 = self.config_obj(kind, base=True) 493 msg = 'Could not load updated {0} config'.format(kind) 494 self.assertTrue( 495 self.authm.loadAuthenticationConfig(configid, config3, True), 496 msg) 497 498 msg = 'Could not validate updated {0} config values'.format(kind) 499 self.assertTrue(self.config_values_valid(kind, config3), msg) 500 501 msg = 'Could not remove {0} config (by id) from db'.format(kind) 502 self.assertTrue( 503 self.authm.removeAuthenticationConfig(configid), msg) 504 505 msg = 'Did not remove {0} config id from db'.format(kind) 506 self.assertFalse(configid in self.authm.configIds(), msg) 507 508 def test_100_auth_db(self): 509 510 for kind in self.config_list(): 511 config = self.config_obj(kind, base=False) 512 msg = 'Could not store {0} config'.format(kind) 513 self.assertTrue(self.authm.storeAuthenticationConfig(config), msg) 514 515 msg = 'Could not store a sample of all configs in auth db' 516 self.assertTrue( 517 (len(self.authm.configIds()) == len(self.config_list())), msg) 518 519 msg = 'Could not retrieve available configs from auth db' 520 self.assertTrue(len(self.authm.availableAuthMethodConfigs()) > 0, msg) 521 522 backup = None 523 resetpass, backup = self.authm.resetMasterPassword( 524 'newpass', self.mpass, True, backup) 525 msg = 'Could not reset master password and/or re-encrypt configs' 526 self.assertTrue(resetpass, msg) 527 528 # qDebug('Backup db path: {0}'.format(backup)) 529 msg = 'Could not retrieve backup path for reset master password op' 530 self.assertIsNotNone(backup) 531 self.assertTrue(backup != self.authm.authenticationDatabasePath(), msg) 532 533 msg = 'Could not verify reset master password' 534 self.assertTrue(self.authm.setMasterPassword('newpass', True), msg) 535 536 msg = 'Could not remove all configs from auth db' 537 self.assertTrue(self.authm.removeAllAuthenticationConfigs(), msg) 538 539 msg = 'Configs were not removed from auth db' 540 self.assertTrue(len(self.authm.configIds()) == 0, msg) 541 542 msg = 'Auth db does not exist' 543 self.assertTrue(os.path.exists(self.authm.authenticationDatabasePath()), msg) 544 545 QTest.qSleep(1000) # necessary for new backup to have different name 546 547 msg = 'Could not erase auth db' 548 backup = None 549 reserase, backup = \ 550 self.authm.eraseAuthenticationDatabase(True, backup) 551 self.assertTrue(reserase, msg) 552 553 # qDebug('Erase db backup db path: {0}'.format(backup)) 554 msg = 'Could not retrieve backup path for erase db op' 555 self.assertIsNotNone(backup) 556 self.assertTrue(backup != self.authm.authenticationDatabasePath(), msg) 557 558 msg = 'Master password not erased from auth db' 559 self.assertTrue(not self.authm.masterPasswordIsSet() and 560 not self.authm.masterPasswordHashInDatabase(), msg) 561 562 self.set_master_password() 563 564 def test_110_pkcs12_cas(self): 565 """Test if CAs can be read from a pkcs12 bundle""" 566 path = PKIDATA + '/fra_w-chain.p12' 567 cas = QgsAuthCertUtils.pkcs12BundleCas(path, 'password') 568 569 self.assertEqual(cas[0].issuerInfo(b'CN'), ['QGIS Test Root CA']) 570 self.assertEqual(cas[0].subjectInfo(b'CN'), ['QGIS Test Issuer CA']) 571 self.assertEqual(cas[0].serialNumber(), b'02') 572 self.assertEqual(cas[1].issuerInfo(b'CN'), ['QGIS Test Root CA']) 573 self.assertEqual(cas[1].subjectInfo(b'CN'), ['QGIS Test Root CA']) 574 self.assertEqual(cas[1].serialNumber(), b'01') 575 576 def test_120_pem_cas_from_file(self): 577 """Test if CAs can be read from a pem bundle""" 578 path = PKIDATA + '/fra_w-chain.pem' 579 cas = QgsAuthCertUtils.casFromFile(path) 580 581 self.assertEqual(cas[0].issuerInfo(b'CN'), ['QGIS Test Root CA']) 582 self.assertEqual(cas[0].subjectInfo(b'CN'), ['QGIS Test Issuer CA']) 583 self.assertEqual(cas[0].serialNumber(), b'02') 584 self.assertEqual(cas[1].issuerInfo(b'CN'), ['QGIS Test Root CA']) 585 self.assertEqual(cas[1].subjectInfo(b'CN'), ['QGIS Test Root CA']) 586 self.assertEqual(cas[1].serialNumber(), b'01') 587 588 def test_130_cas_merge(self): 589 """Test CAs merge """ 590 trusted_path = PKIDATA + '/subissuer_ca_cert.pem' 591 extra_path = PKIDATA + '/fra_w-chain.pem' 592 593 trusted = QgsAuthCertUtils.casFromFile(trusted_path) 594 extra = QgsAuthCertUtils.casFromFile(extra_path) 595 merged = QgsAuthCertUtils.casMerge(trusted, extra) 596 597 self.assertEqual(len(trusted), 1) 598 self.assertEqual(len(extra), 2) 599 self.assertEqual(len(merged), 3) 600 601 for c in extra: 602 self.assertTrue(c in merged) 603 604 self.assertTrue(trusted[0] in merged) 605 606 def test_140_cas_remove_self_signed(self): 607 """Test CAs merge """ 608 extra_path = PKIDATA + '/fra_w-chain.pem' 609 610 extra = QgsAuthCertUtils.casFromFile(extra_path) 611 filtered = QgsAuthCertUtils.casRemoveSelfSigned(extra) 612 613 self.assertEqual(len(filtered), 1) 614 self.assertEqual(len(extra), 2) 615 616 self.assertTrue(extra[1].isSelfSigned()) 617 618 for c in filtered: 619 self.assertFalse(c.isSelfSigned()) 620 621 def test_150_verify_keychain(self): 622 """Test the verify keychain function""" 623 624 def testChain(path): 625 626 # Test that a chain with an untrusted CA is not valid 627 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path))) > 0) 628 629 # Test that a chain with an untrusted CA is valid when the addRootCa argument is true 630 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), None, True)) == 0) 631 632 # Test that a chain with an untrusted CA is not valid when the addRootCa argument is true 633 # and a wrong domainis true 634 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), 'my.wrong.domain', True)) > 0) 635 636 testChain(PKIDATA + '/chain_subissuer-issuer-root.pem') 637 testChain(PKIDATA + '/localhost_ssl_w-chain.pem') 638 testChain(PKIDATA + '/fra_w-chain.pem') 639 640 path = PKIDATA + '/localhost_ssl_w-chain.pem' 641 642 # Test that a chain with an untrusted CA is not valid when the addRootCa argument is true 643 # and a wrong domain is set 644 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), 'my.wrong.domain', True)) > 0) 645 646 # Test that a chain with an untrusted CA is valid when the addRootCa argument is true 647 # and a right domain is set 648 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), 'localhost', True)) == 0) 649 650 # Test that a chain with an untrusted CA is not valid when the addRootCa argument is false 651 # and a right domain is set 652 self.assertTrue(len(QgsAuthCertUtils.validateCertChain(QgsAuthCertUtils.certsFromFile(path), 'localhost', False)) > 0) 653 654 def test_validate_pki_bundle(self): 655 """Text the pki bundle validation""" 656 657 # Valid bundle: 658 bundle = self.mkPEMBundle('fra_cert.pem', 'fra_key.pem', 'password', 'chain_subissuer-issuer-root.pem') 659 660 # Test valid bundle with intermediates and without trusted root 661 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted']) 662 # Test valid without intermediates 663 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 664 # Test valid with intermediates and trusted root 665 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), []) 666 667 # Wrong chain 668 bundle = self.mkPEMBundle('fra_cert.pem', 'fra_key.pem', 'password', 'chain_issuer2-root2.pem') 669 # Test invalid bundle with intermediates and without trusted root 670 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 671 # Test valid without intermediates 672 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 673 # Test valid with intermediates and trusted root 674 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 675 676 # Wrong key 677 bundle = self.mkPEMBundle('fra_cert.pem', 'ptolemy_key.pem', 'password', 'chain_subissuer-issuer-root.pem') 678 # Test invalid bundle with intermediates and without trusted root 679 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted', 'Private key does not match client certificate public key.']) 680 # Test invalid without intermediates 681 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified', 'Private key does not match client certificate public key.']) 682 # Test invalid with intermediates and trusted root 683 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['Private key does not match client certificate public key.']) 684 685 # Expired root CA 686 bundle = self.mkPEMBundle('piri_cert.pem', 'piri_key.pem', 'password', 'chain_issuer3-root3-EXPIRED.pem') 687 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted', 'The certificate has expired']) 688 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 689 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The root certificate of the certificate chain is self-signed, and untrusted', 'The certificate has expired']) 690 691 # Expired intermediate CA 692 bundle = self.mkPEMBundle('marinus_cert-EXPIRED.pem', 'marinus_key_w-pass.pem', 'password', 'chain_issuer2-root2.pem') 693 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted', 'The certificate has expired']) 694 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 695 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The certificate has expired']) 696 697 # Expired client cert 698 bundle = self.mkPEMBundle('henricus_cert.pem', 'henricus_key_w-pass.pem', 'password', 'chain_issuer4-EXPIRED-root2.pem') 699 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle), ['The root certificate of the certificate chain is self-signed, and untrusted', 'The certificate has expired']) 700 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, False), ['The issuer certificate of a locally looked up certificate could not be found', 'No certificates could be verified']) 701 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The certificate has expired']) 702 703 # Untrusted root, positive test before untrust is applied 704 bundle = self.mkPEMBundle('nicholas_cert.pem', 'nicholas_key.pem', 'password', 'chain_issuer2-root2.pem') 705 # Test valid with intermediates and trusted root 706 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), []) 707 # Untrust this root 708 root2 = QgsAuthCertUtils.certFromFile(PKIDATA + '/' + 'root2_ca_cert.pem') 709 QgsApplication.authManager().storeCertAuthority(root2) 710 self.assertTrue(QgsApplication.authManager().storeCertTrustPolicy(root2, QgsAuthCertUtils.Untrusted)) 711 QgsApplication.authManager().rebuildCaCertsCache() 712 # Test valid with intermediates and untrusted root 713 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(bundle, True, True), ['The issuer certificate of a locally looked up certificate could not be found']) 714 715 def test_160_cert_viable(self): 716 """Text the viability of a given certificate""" 717 718 # null cert 719 cert = QSslCertificate() 720 self.assertFalse(QgsAuthCertUtils.certIsCurrent(cert)) 721 res = QgsAuthCertUtils.certViabilityErrors(cert) 722 self.assertTrue(len(res) == 0) 723 self.assertFalse(QgsAuthCertUtils.certIsViable(cert)) 724 725 cert.clear() 726 res.clear() 727 # valid cert 728 cert = QgsAuthCertUtils.certFromFile(PKIDATA + '/gerardus_cert.pem') 729 self.assertTrue(QgsAuthCertUtils.certIsCurrent(cert)) 730 res = QgsAuthCertUtils.certViabilityErrors(cert) 731 self.assertTrue(len(res) == 0) 732 self.assertTrue(QgsAuthCertUtils.certIsViable(cert)) 733 734 cert.clear() 735 res.clear() 736 # expired cert 737 cert = QgsAuthCertUtils.certFromFile(PKIDATA + '/marinus_cert-EXPIRED.pem') 738 self.assertFalse(QgsAuthCertUtils.certIsCurrent(cert)) 739 res = QgsAuthCertUtils.certViabilityErrors(cert) 740 self.assertTrue(len(res) > 0) 741 self.assertTrue(QSslError(QSslError.CertificateExpired, cert) in res) 742 self.assertFalse(QgsAuthCertUtils.certIsViable(cert)) 743 744 def test_170_pki_key_encoding(self): 745 """Test that a DER/PEM RSA/DSA/EC keys can be opened whatever the extension is""" 746 747 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'ptolemy_key.pem').isNull()) 748 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'ptolemy_key.der').isNull()) 749 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'ptolemy_key_pem.key').isNull()) 750 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'ptolemy_key_der.key').isNull()) 751 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_EC.pem').isNull()) 752 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_EC.der').isNull()) 753 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA.pem').isNull()) 754 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA.der').isNull()) 755 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA_crlf.pem').isNull()) 756 self.assertFalse(QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA_nonl.pem').isNull()) 757 donald_dsa = QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA.pem').toPem() 758 self.assertEqual(donald_dsa, QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA.der').toPem()) 759 self.assertEqual(donald_dsa, QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA_crlf.pem').toPem()) 760 self.assertEqual(donald_dsa, QgsAuthCertUtils.keyFromFile(PKIDATA + '/' + 'donald_key_DSA_nonl.pem').toPem()) 761 762 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(self.mkPEMBundle('ptolemy_cert.pem', 'ptolemy_key.pem', 'password', 'chain_subissuer-issuer-root.pem'), True, True), []) 763 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(self.mkPEMBundle('ptolemy_cert.pem', 'ptolemy_key.der', 'password', 'chain_subissuer-issuer-root.pem'), True, True), []) 764 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(self.mkPEMBundle('ptolemy_cert.pem', 'ptolemy_key_pem.key', 'password', 'chain_subissuer-issuer-root.pem'), True, True), []) 765 self.assertEqual(QgsAuthCertUtils.validatePKIBundle(self.mkPEMBundle('ptolemy_cert.pem', 'ptolemy_key_der.key', 'password', 'chain_subissuer-issuer-root.pem'), True, True), []) 766 767 768if __name__ == '__main__': 769 unittest.main() 770