diff --git a/.bandit.yml b/.bandit.yml new file mode 100644 index 000000000..257ede98b --- /dev/null +++ b/.bandit.yml @@ -0,0 +1,5 @@ +tests: +skips: +- B404 # Ignore warnings about importing subprocess +- B603 # Ignore warnings about calling subprocess.Popen without shell=True +- B607 # Ignore warnings about calling subprocess.Popen without a full path to executable diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..d298bdf4a --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,19 @@ +[bumpversion] +commit = False +tag = False +tag_name = {new_version} +current_version = 6.10.0 +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}.{release}{dev} + {major}.{minor}.{patch} +message = Release: {current_version} → {new_version} + +[bumpversion:file:raven/__init__.py] + +[bumpversion:part:release] +optional_value = production +values = + dev + production + diff --git a/.craft.yml b/.craft.yml new file mode 100644 index 000000000..c5193f01e --- /dev/null +++ b/.craft.yml @@ -0,0 +1,9 @@ +--- +minVersion: '0.7.0' +changelogPolicy: simple +github: + owner: getsentry + repo: raven-python +targets: + - name: pypi + - name: github diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..8383fff94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGES merge=union diff --git a/.gitignore b/.gitignore index 941b34865..333249c30 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ *.egg *.db *.pid -.coverage +.python-version +.coverage* .DS_Store .tox pip-log.txt @@ -13,12 +14,15 @@ pip-log.txt /cover /dist /example_project/local_settings.py -/docs/html -/docs/doctrees +/docs/_build /sentry_index/ /sentry_test_index /example_project/*.db bin/ include/ lib/ +.cache .idea +.eggs +venv +.vscode/tags diff --git a/tests/config/__init__.py b/.gitmodules similarity index 100% rename from tests/config/__init__.py rename to .gitmodules diff --git a/.python-version-example b/.python-version-example new file mode 100644 index 000000000..45b228af7 --- /dev/null +++ b/.python-version-example @@ -0,0 +1,8 @@ +2.6.9 +2.7.13 +3.2.6 +3.3.6 +3.4.6 +3.5.3 +3.6.1 +pypy2.7-5.8.0 diff --git a/.travis.yml b/.travis.yml index 3cea7319b..4c6a6467a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,48 +1,61 @@ language: python python: - - "2.6" - "2.7" - - "3.2" - - "3.3" - "pypy" -env: - - DJANGO=Django==1.2.7 - - DJANGO=Django==1.3.7 - - DJANGO=Django==1.4.5 - - DJANGO=Django==1.5.1 - - DJANGO="https://www.djangoproject.com/download/1.6a1/tarball/" - - DJANGO="-e git+git://github.com/django/django.git#egg=Django" + - "3.4" + - "3.5" + - "3.6" -install: - - sudo apt-get install libevent-dev - - pip install $DJANGO --use-mirrors - - make bootstrap - - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then pip install gevent --use-mirrors; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then pip install -I https://github.com/celery/celery/archive/3.0.zip; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip uninstall django-celery -y; fi" -script: - - "if [[ ${TRAVIS_PYTHON_VERSION} != 'pypy' ]]; then make lint; fi" - - py.test tests/ --cov raven --cov-report term-missing -notifications: - irc: - channels: "irc.freenode.org#sentry" - on_success: change - on_failure: change matrix: - exclude: - - python: "3.2" - env: DJANGO=Django==1.2.7 - - python: "3.2" - env: DJANGO=Django==1.3.7 - - python: "3.2" - env: DJANGO=Django==1.4.5 - - python: "3.3" - env: DJANGO=Django==1.2.7 - - python: "3.3" - env: DJANGO=Django==1.3.7 - - python: "3.3" - env: DJANGO=Django==1.4.5 - - python: "2.6" - env: DJANGO="-e git+git://github.com/django/django.git#egg=Django" + include: + - python: "3.7" + dist: xenial + + - name: Flake8 + dist: xenial + python: "3.7" + install: + - pip install tox + script: tox -e flake8 + + - name: Distribution packages + python: "3.6" + install: false + script: make travis-upload-dist + +sudo: false +addons: + apt: + packages: + - libevent-dev +cache: + directories: + - "$HOME/.cache/pip" +branches: + only: + - master + - /^(?i:feature)-.*$/ +jobs: + fast_finish: true +# allow_failures: +# - python: 3.5 +# env: TOXENV=py35-django-dev-fix + +script: sh ci/runtox.sh +install: + - make + - pip install codecov +before_script: + - pip freeze after_success: - - coveralls \ No newline at end of file + - codecov -e DJANGO + +notifications: + webhooks: + urls: + - https://zeus.ci/hooks/0b589ca4-1165-11e8-b03b-0a580a28023f/public/provider/travis/webhook + on_success: always + on_failure: always + on_start: always + on_cancel: always + on_error: always diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..bf57bb68d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "files.exclude": { + "**/*.pyc": true, + "**/*.min.js": true, + "**/*.js.map": true, + "htmlcov": true, + "build": true, + "*.log": true, + "*.egg-info": true + }, + "files.trimTrailingWhitespace": true, + + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true, + + "python.pythonPath": "${env.WORKON_HOME}/raven/bin/python", + "python.unitTest.pyTestEnabled": true, + "python.unitTest.unittestEnabled": false, + "python.unitTest.nosetestsEnabled": false +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..02367f78f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,777 @@ +Changelog +========= + +All notable changes to this project will be documented in this file. +Project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +6.10.0 +------ + +* [Core] Fixed stackframes in some situations being in inverse order. +* [Flask] Fix wrong exception handling logic (accidentally relied on Flask internals). +* [Core] No longer send NaN local vars as non-standard JSON. + +6.9.0 (2018-05-30) +------------------ +* [Core] Switched from culprit to transaction for automatic transaction reporting. +* [CI] Removed py3.3 from build +* [Django] resolved an issue where the log integration would override the user. + +6.8.0 (2018-05-12) +------------------ +* [Core] Fixed DSNs without secrets not sending events. +* [Core] Added lazy import for pkg_resources +* [Core] Added NamedTuple Serializer +* [Sanic] Fixed sanic integration dependencies +* [Django] Fixed sql hook bug + +6.7.0 (2018-04-18) +------------------ +* [Sanic] Added support for sanic. +* [Core] Disabled dill logger by default +* [Core] Added `SENTRY_NAME`, `SENTRY_ENVIRONMENT` and `SENTRY_RELEASE` + environment variables +* [Core] DSN secret is now optional +* [Core] Added fix for cases with exceptions in repr +* [core] Fixed bug with mutating `record.data` + +6.6.0 (2018-02-12) +------------------ +* [Core] Add trimming to breadcrumbs. +* [Core] Improve host message at startup. +* [Core] Update pytest to work on other environments + +6.5.0 (2018-01-15) +------------------ +* [Core] Fixed missing deprecation on `processors.SanitizePasswordsProcessor` +* [Core] Improve exception handling in `Serializer.transform` +* [Core] Fixed `celery.register_logger_signal` ignoring subclasses +* [Core] Fixed sanitizer skipping `byte` instances +* [Lambda] Fixed `AttributeError` when `requestContext` not present + +6.4.0 (2017-12-11) +------------------ +* [Core] Support for defining `sanitized_keys` on the client (pr/990) +* [Django] Support for Django 2.0 Urlresolver +* [Docs] Several fixes and improvements + +6.3.0 (2017-10-29) +------------------ +* [Core] Changed default timeout on http calls to 5 seconds +* [Core] Fixed relative paths for traces generated on Windows +* [Django] Fixed import issues for Django projects < 1.7 +* [Django] Fixed django management command data option +* [Django/DRF] Added `application/octet-stream` to non-cacheable types in middleware +* [Django] Added parsing X-Forwarded-For for `user.ip_address` +* [Flask] Added `request.remote_addr` as fallback for ip addresses +* [Lambda] Added initial AWS Lambda support with `contrib.awslambda.LambdaClient` + + +6.2.1 (2017-09-21) +------------------ + +* [Core] Fixed requirements in setup.py + + +6.2.0 (2017-09-21) +------------------ + +* [Core] `get_frame_locals` properly using `max_var_size` +* [Core] Fixed raven initialization when `logging._srcfile` is None +* [Core] Fixed import locking to avoid recursion +* [Django] Fixed several issues for Django 1.11 and Django 2.0 +* [Django/DRF] Fixed issue with unavailable request data +* [Flask] Added app.logger instrumentation +* [Flask] Added signal on setup_logging +* [ZConfig] Added standalone ZConfig support +* [Celery] Fixed several issues related to Celery + + +6.1.0 (2017-05-25) +------------------ + +* Support both string and class values for ``ignore_exceptions`` parameters. + Class values also support child exceptions. +* Ensure consistent fingerprint for SoftTimeLimitExceeded exceptions +* Add sample_rate configuration +* fix registration of hooks for Django + +6.0.0 (2017-02-17) +------------------ + +* Strip whitespace from DSNs automatically. +* Add `last_event_id` accessor to `Client`. +* Do not require `sys.argv` to be available any more. +* Tags defined on a logging handler will now be merged with individual log record's tags. +* Added explicit support for multidicts in the django client. +* Refactored transports to support multiple URLs. This might affect + you if you have custom subclasses of those. The main change is that + the URL parameter moved from the constructor into the `send` method. +* Corrected an issue with recursive route resolvers which commonly + affected things like django-tastyepie. +* Corrected an issue where Django's HTTP request was not always available + within events. + +5.32.0 (2016-11-15) +------------------- + +* Made raven python breadcrumb patches work when librato monkey + patches logging. + +5.31.0 (2016-10-21) +------------------- + +* Improved fix for the Django middleware regression. + +5.30.0 (2016-10-19) +------------------- + +* Keep the original type for the django middleware settings if we + change them. + +5.29.0 (2016-10-18) +------------------- + +* Added `register_logging_handler`. +* Removed bad mixin from django's WSGI middleware +* Removed "support for extracing data from rest_framework" because + this broke code. + +5.28.0 (2016-10-18) +------------------- + +* Corrected an issue that caused `close()` on WSGI iterables to not be + correctly called. +* Fixes the new Django 1.10 `MIDDLEWARE_CLASSES` warning. + +5.27.1 (2016-09-19) +------------------- + +* Bugfix for transaction based culprits. + +5.27.0 (2016-09-16) +------------------- + +* Added support for extracting data from rest_framework in Django integration +* Updated CA bundle. +* Added transaction-based culprits for Celery, Django, and Flask. +* Fixed an issue where ``ignore_exceptions`` wasn't respected. + +5.26.0 (2016-08-31) +------------------- + +* Fixed potential concurrency issue with event IDs in the Flask integration. +* Added a workaround for leakage when broken WSGI middlware or servers are + used that do not call `close()` on the iterat.r + +5.25.0 (2016-08-23) +------------------- + +* Added various improvements for the WSGI and Django support. +* Prevent chained exception recursion +* In environments which look like AWS Lambda or Google App Engine utilize the + synchronous transport. +* Added Celery config option to ignore expected exceptions +* Improved DSN handling in Flask client. + +5.24.0 (2016-08-04) +------------------- + +* Added support for Django 1.10. +* Added support for chained exceptions in Python 3. +* Fixed various behavior with handling template errors in Django 1.8+. + +5.23.0 (2016-07-14) +------------------- + +* Sentry failures now no longer log the failure data in the error + message. + +5.22.0 (2016-07-07) +------------------- + +* Fixed template reporting not working for certain versions of Django. + +5.21.0 (2016-06-16) +------------------- + +* Add formatted attribute to message events +* Fill in empty filename if django fails to give one for + template information on newer Django versions with disabled + debug mode. + +5.20.0 (2016-06-08) +------------------- + +* fixed an error that could cause certain SQL queries to fail to + record as breadcrumbs if no parameters were supplied. + +5.19.0 (2016-05-27) +------------------- + +* remove duration from SQL query breadcrumbs. This was not rendered + in the UI and will come back in future versions of Sentry with a + different interface. +* resolved a bug that caused crumbs to be recorded incorrectly. + +5.18.0 (2016-05-19) +-------------------- + +* Breadcrumbs are now attempted to be deduplicated to catch some common + cases where log messages just spam up the breadcrumbs. +* Improvements to the public breadcrumbs API and stabilized some. +* Automatically activate the context on calls to `merge` + +5.17.0 (2016-05-14) +------------------- + +* if breadcrumbs fail to process due to an error they are now skipped. + +5.16.0 (2016-05-09) +------------------- + +* exc_info is no longer included in logger based breadcrumbs. +* log the entire logger name as category. +* added a `enable_breadcrumbs` flag to the client to allow the enabling or + disabling of breadcrumbs quickly. +* corrected an issue where python interpreters with bytecode writing enabled + would report incorrect logging locations when breadcrumb patching for + logging was enabled. + +5.15.0 (2016-05-03) +------------------- + +* Improve thread binding for the context. This makes the main thread never + deactivate the client automatically on clear which means that more code + should automatically support breadcrumbs without changes. + +5.14.0 (2016-05-03) +------------------- + +* Added support for reading git sha's from packed references. +* Detect disabled thread support for uwsgi. +* Added preliminary support for breadcrumbs. + +Note: this version adds breadcrumbs to events. This means that if you run a +Sentry version older than 8.5 you will see some warnings in the UI. Consider +using an older version of the client if you do not want to see it. + +5.13.0 (2016-04-19) +------------------- + +* Resolved an issue where Raven would fail with an exception if the + package name did not match the setuptools name in some isolated + cases. + +5.12.0 (2016-03-30) +------------------- + +* Empty and otherwise falsy (None, False, 0) DSN values are now assumed + to be equivalent to no DSN being provided. + +5.11.2 (2016-03-25) +------------------- + +* Added a workaround for back traceback objects passed to raven. In these + cases we now wobble further along to at least log something. + +5.11.1 (2016-03-07) +------------------- + +* The raven client supports the stacktrace to be absent. This improves support + with celery and multiprocessing. + +5.11.0 (2016-02-29) +------------------- + +* ``Client.configure_logging`` has been removed, and handlers will not automatically + be added to 'sentry' and 'raven' namespaces. +* Improved double error check +* Restored support for exc_info is True. + +5.10.2 (2016-01-27) +------------------- + +* Remember exceptions in flight until the context is cleared so that two + reports with the same exception data do not result in two errors + being logged. +* Allow logging exclusions. + +5.10.1 (2016-01-21) +------------------- + +* Fixed a problem where bytes as keys in dictionaries caused problems + on data sanitization if those bytes were outside of the ASCII range. +* Django client no longer requires the user object to be a subclass + of the base model. +* Corrected an issue with the Django log handler which would cause a recursive import. + +5.10.0 (2016-01-14) +------------------- + +* Restore template debug support for Django 1.9 and newer. +* Correctly handle SSL verification disabling for newer Python versions. + +5.9.2 (2015-12-17) +------------------ + +* Correct behavior introduced for Django 1.9. + +5.9.1 (2015-12-16) +------------------ + +* Support for isolated apps in Django 1.9. + +5.9.0 (2015-12-10) +------------------ + +* The threaded worker will now correctly handle forking. +* The 'environment' parameter is now supported (requires a Sentry 8.0 server ). +* 'tags' can now be specified as part of a LoggingHandler's constructor. + +5.8.0 (2015-10-19) +------------------ + +* Added support for detecting `release` on Heroku. +* pkg_resources is now prioritized for default version detection. +* Updated `in_app` support to include exception frames. +* Fixed support for `SENTRY_USER_ATTRS` in Flask. +* Handle DSNs which are sent as unicode values in Python 2. + +5.7.2 (2015-09-18) +------------------ + +* Handle passing ``fingerprint`` through logging handler. + +5.7.1 (2015-09-16) +------------------ + +* Correctly handle SHAs in .git/HEAD. +* Fixed several cases of invalid Python3 syntax. + +5.7.0 (2015-09-16) +------------------ + +* Reverted changes to Celery which incorrectly caused some configurations + to log unwanted messages. +* Improved behavior in ``fetch_git_sha``. +* Removed ``is_authenticated`` property from most integrations. +* Better error handling for errors within Flask context. +* Support for new versions of Flask-Login. +* Update Tornado support for modern versions. +* Update stacktrace truncation code to match current versions of Sentry server. + +5.6.0 (2015-08-26) +------------------ + +* Content is no longer base64-encoded. +* ``fingerprint`` is now correctly supported. +* Django: 1.9 compatibility. +* Celery: Filter ``celery.redirect`` logger. + +5.5.0 (2015-07-22) +------------------ + +* Added ``sys.excepthook`` handler (installed by default). +* Fixed an issue where ``wrap_wsgi`` wasn't being respected. +* Various deprecated code removed. + +5.4.4 (2015-07-13) +------------------ + +* Enforce string-type imports. + +5.4.3 (2015-07-12) +------------------ + +* Python 3 compatibility fixes. + +5.4.2 (2015-07-11) +------------------ + +* Remove scheme checking on transports. +* Added ``SENTRY_TRANSPORT`` to Flask and Django configurations. + +5.4.1 (2015-07-08) +------------------ + +* Fixed packaging of 5.4.0 which erronously kept the ``aiohttp.py`` file in the wheel only. + +5.4.0 (2015-07-06) +------------------ + +* Binding transports via a scheme prefix on DSNs is now deprecated. +* ``raven.conf.load`` has been removed. +* Upstream-related configuration (such as url, project_id, and keys) is now contained in ``RemoteConfig`` + attached to ``Client.remote`` +* The ``aiohttp`` transport has been moved to ``raven-aiohttp`` package. + +5.3.1 (2015-05-01) +------------------ + +* Restored support for patching Django's BaseCommand.execute. + +5.3.0 (2015-04-30) +------------------ + +* The UDP transport has been removed. +* The integrated Sentry+Django client has been removed. This is now part of Sentry core. +* Server configuration *must* now be specified with a DSN. +* Upstream errors now have increased verbosity in logs. +* Unsent events now log to 'sentry.errors.uncaught'. +* Django management commands should now effectively autopatch (when run from the CLI). +* Flask wrapper now includes user_context, tags_context, and extra_context helpers. +* Python version is now reported with modules. + +5.2.0 (2015-02-11) +------------------ + +* Protocol version is now 6 (requires Sentry 7.0 or newer). +* Added ``release`` option to Client. +* Added ``fetch_git_sha`` helper. +* Added ``fetch_package_version`` helper. +* Added cookie string sanitizing. +* Added threaded request transport: "threaded+requests+http(s)". + +5.1.0 (2014-10-15) +------------------ + +* Added aiohttp transport. +* Corrected behavior with auto_log_stacks and exceptions. +* Add support for certifi. +* Expanded Flask support. +* Expanded Django support. +* Corrected an issue where processors were not correctly applying. + +5.0.0 (2014-05-28) +------------------ + +* Sentry client protocol is now version 5. +* Various improvements to threaded transport. + +4.2.0 (2014-04-14) +------------------ + +* SSL verification is now on by default. +* Rate limits and other valid API errors are now handled more gracefully. +* Added ``last_event_id`` and ``X-Sentry-ID`` header to Flask. + +4.1.0 (2014-03-19) +------------------ + +* Added verify_ssl option to HTTP transport (defaults to False). +* Added capture_locals option (defaults to True). +* message can now be passed to capture* functions. +* Django <1.4 is no longer supported. +* Function object serialization has been improved. +* SanitizePasswordsProcessor removes API keys. + +4.0.0 (2013-12-26) +------------------ + +* Sentry client protocol is now version 4. + +3.6.0 (2013-12-11) +------------------ + +This changelog does not attempt to account for all changes between 3.6.0 and 3.0.0, but +rather focuses on recent important changes + +* Transport modules paths have been refactored. +* The threaded transport is now the default. +* Client.context has changed. Please see documentation for new API. +* Client.user_context was added. +* Client.http_context was added. +* Client.extra_context was added. +* Client.tags_context was added. +* Flask support has been greatly improved. +* raven.contrib.celery.Client has been removed as it was invalid. + +3.0.0 (2012-12-27) +------------------ + +3.0 of Raven requires a Sentry server running at least version 5.1, as it implements +version 3 of the protocol. + +Support includes: + +* Sending 'python' as the platform. +* The 'tags' option (on all constructors that support options). +* Updated authentication header. + +Additionally, the following has changed: + +* Configuring the client with an empty DSN value will disable sending of messages. +* All clients should now check ``Client.is_enabled()`` to verify if they should send data. +* ``Client.create_from_text`` and ``Client.create_from_exception`` have been removed. +* ``Client.message`` and ``Client.exception`` have been removed. +* The ``key`` setting has been removed. +* The ``DEBUG`` setting in Django no longer disables Raven. +* The ``register_signals`` option in RAVEN_CONFIG (Django) is no longer used. +* A new helper, ``Client.context()`` is now available for scoping options. +* ``Client.captureExceptions`` is now deprecated in favor of ``Client.context``. +* Credit card values will now be sanitized with the default processors. +* A new eventlet+http transport exists. +* A new threaded+http transport exists. +* PyPy is now supported. +* Django 1.5 should now be supported (experimental). +* Gevent 1.0 should now be supported (experimental). +* Python 2.5 is no longer supported. +* [Django] The ``skip_sentry`` attribute is no longer supported. A new option config option has replaced this: ``SENTRY_IGNORE_EXCEPTIONS``. + +2.0.0 (2012-07-05) +------------------ + +* New serializers exist (and can be registered) against Raven. See ``raven.utils.serializer`` for more information. +* You can now pass ``tags`` to the ``capture`` method. This will require a Sentry server compatible with the new + tags protocol. +* A new gevent+http transport exists. +* A new tornado+http transport exists. +* A new twisted+http transport exists. +* Zope integration has been added. See docs for more information. +* PasteDeploy integration has been added. See docs for more information. +* A Django endpoint now exists for proxying requests to Sentry. See ``raven.contrib.django.views`` for more information. + +1.9.0 (2012-05-23) +------------------ + +* Signatures are no longer sent with messages. This requires the server version to be at least 4.4.6. +* Several fixes and additions were added to the Django report view. +* ``long`` types are now handled in transform(). +* Improved integration with Celery (and django-celery) for capturing errors. + +1.8.0 (2012-05-16) +------------------ + +* There is now a builtin view as part of the Django integration for sending events server-side + (from the client) to Sentry. The view is currently undocumented, but is available as ``{% url raven-report %}`` + and will use your server side credentials. To use this view you'd simply swap out the servers configuration in + raven-js and point it to the given URL. +* A new middleware for ZeroRPC now exists. +* A new protocol for registering transports now exists. +* Corrected some behavior in the UDP transport. +* Celery signals are now connected by default within the Django integration. + +1.7.0 (2012-04-18) +------------------ + +* The password sanitizer will now attempt to sanitize key=value pairs within strings (such as the querystring). +* Two new santiziers were added: RemoveStackLocalsProcessor and RemovePostDataProcessor + +1.6.0 (2012-04-13) +------------------ + +* Stacks must now be passed as a list of tuples (frame, lineno) rather than a list of frames. This + includes calls to logging (extra={'stack': []}), as well as explicit client calls (capture(stack=[])). + + This corrects some issues (mostly in tracebacks) with the wrong lineno being reported for a frame. + +1.4.0 (2012-02-05) +------------------ + +* Raven now tracks the state of the Sentry server. If it receives an error, it will slow down + requests to the server (by passing them into a named logger, sentry.errors), and increasingly + delay the next try with repeated failures, up to about a minute. + +1.3.6 (2012-02-04) +------------------ + +* gunicorn is now disabled in default logging configuration + +1.3.5 (2012-02-03) +------------------ + +* Moved exception and message methods to capture{Exception,Message}. +* Added captureQuery method. + +1.3.4 (2012-02-02) +------------------ + +* Corrected duplicate DSN behavior in Django client. + +1.3.3 (2012-02-02) +------------------ + +* Django can now be configured by setting SENTRY_DSN. +* Improve logging for send_remote failures (and correct issue created when + send_encoded was introduced). +* Renamed SantizePassworsProcessor to SanitizePassworsProcessor. + +1.3.2 (2012-02-01) +------------------ + +* Support sending the culprit with logging messages as part of extra. + +1.3.1 (2012-02-01) +------------- + +* Added client.exception and client.message shortcuts. + +1.3.0 (2012-01-31) +------------------ + +* Refactored client send API to be more easily extensible. +* MOAR TESTS! + +1.2.2 (2012-01-31) +------------------ + +* Gracefully handle exceptions in Django client when using integrated + setup. +* Added Client.error_logger as a new logger instance that points to + ``sentry.errors``. + +1.2.1 (2012-01-31) +------------------ + +* Corrected behavior with raven logging errors to send_remote + which could potentially cause a very large backlog to Sentry + when it should just log to ``sentry.errors``. +* Ensure the ``site`` argument is sent to the server. + +1.2.0 (2012-01-30) +------------------ + +* Made DSN a first-class citizen throughout Raven. +* Added a Pylons-specific WSGI middleware. +* Improved the generic WSGI middleware to capture HTTP information. +* Improved logging and logbook handlers. + +1.1.6 (2012-01-26) +------------------ + +* Corrected logging stack behavior so that it doesnt capture raven+logging + extensions are part of the frames. + +1.1.5 (2012-01-25) +------------------ + +* Remove logging attr magic. + +1.1.4 (2012-01-25) +------------------ + +* Correct encoding behavior on bool and float types. + +1.1.3 (2012-01-25) +------------------ + +* Fix 'request' attribute on Django logging. + +1.1.2 (2012-01-24) +------------------ + +* Corrected logging behavior with extra data to match pre 1.x behavior. + +1.1.1 (2012-01-23) +------------------ + +* Handle frames that are missing f_globals and f_locals. +* Stricter conversion of int and boolean values. +* Handle invalid sources for templates in Django. + +1.1.0 (2012-01-23) +------------------ + +* varmap was refactored to send keys back to callbacks. +* SanitizePasswordProcessor now handles http data. + +1.0.5 (2012-01-18) +------------------ + +* Renaming raven2 to raven as it causes too many issues. + +1.0.4 (2012-01-18) +------------------ + +* Corrected a bug in setup_logging. +* Raven now sends "sentry_version" header which is the expected + server version. + +1.0.3 (2012-01-17) +------------------ + +* Handle more edge cases on stack iteration. + +1.0.2 (2012-01-17) +------------------ + +* Gracefully handle invalid f_locals. + +1.0.1 (2012-01-15) +------------------ + +* All datetimes are assumed to be utcnow() as of Sentry 2.0.0-RC5 + +1.0.0 (2012-01-15) +------------------ + +* Now only works with Sentry>=2.0.0 server. +* Raven is now listed as raven2 on PyPi. + +0.8.0 (XXXX-XX-XX) +------------------ + +* raven.contrib.celery is now useable. +* raven.contrib.django.celery is now useable. +* Fixed a bug with request.raw_post_data buffering in Django. + +0.7.1 (2011-10-24) +------------------ + +* Servers would stop iterating after the first successful post which was not the + intended behavior. + +0.7.0 (2011-10-24) +------------------ + +* You can now explicitly pass a list of frame objects to the process method. + +0.6.1 (2011-10-19) +------------------ + +* The default logging handler (SentryHandler) will now accept a set of kwargs to instantiate + a new client with (GH-10). +* Fixed a bug with checksum generation when module or function were missing (GH-9). + +0.6.0 (2011-10-19) +------------------ + +* Added a Django-specific WSGI middleware. + +0.5.1 (2011-10-17) +------------------ + +* Two minor fixes for the Django client: +* Ensure the __sentry__ key exists in data in (GH-8). +* properly set kwargs['data'] to an empty list when its a NoneType (GH-6). + +0.5.0 (2011-10-14) +------------------ + +* Require ``servers`` on base Client. +* Added support for the ``site`` option in Client. +* Moved raven.contrib.django.logging to raven.contrib.django.handlers. + +0.4.0 (2011-10-11) +------------------ + +* Fixed an infinite loop in iter_tb. + +0.3.0 (2011-10-11) +------------------ + +* Removed the ``thrashed`` key in ``request.sentry`` for the Django integration. +* Changed the logging handler to correctly inherit old-style classes (GH-1). +* Added a ``client`` argument to ``raven.contrib.django.models.get_client()``. + +0.2.0 (2011-10-10) +------------------ + +* auto_log_stacks now works with create_from_text +* added Client.get_ident + +0.1.0 (XXXX-XX-XX) +------------------ + +* Initial version of Raven (extracted from django-sentry 1.12.1). diff --git a/CHANGES b/CHANGES deleted file mode 100644 index b170374a2..000000000 --- a/CHANGES +++ /dev/null @@ -1,288 +0,0 @@ -Version 3.0.0 -------------- - -Version 3.0 of Raven requires a Sentry server running at least version 5.1, as it implements -version 3 of the protocol. - -Support includes: - -* Sending 'python' as the platform. -* The 'tags' option (on all constructors that support options). -* Updated authentication header. - -Additionally, the following has changed: - -* Configuring the client with an empty DSN value will disable sending of messages. -* All clients should now check ``Client.is_enabled()`` to verify if they should send data. -* ``Client.create_from_text`` and ``Client.create_from_exception`` have been removed. -* ``Client.message`` and ``Client.exception`` have been removed. -* The ``key`` setting has been removed. -* The ``DEBUG`` setting in Django no longer disables Raven. -* The ``register_signals`` option in RAVEN_CONFIG (Django) is no longer used. -* A new helper, ``Client.context()`` is now available for scoping options. -* ``Client.captureExceptions`` is now deprecated in favor of ``Client.context``. -* Credit card values will now be sanitized with the default processors. -* A new eventlet+http transport exists. -* A new threaded+http transport exists. -* PyPy is now supported. -* Django 1.5 should now be supported (experimental). -* Gevent 1.0 should now be supported (experimental). -* Python 2.5 is no longer supported. -* [Django] The ``skip_sentry`` attribute is no longer supported. A new option config option has replaced this: ``SENTRY_IGNORE_EXCEPTIONS``. - -Version 2.0.0 -------------- - -* New serializers exist (and can be registered) against Raven. See ``raven.utils.serializer`` for more information. -* You can now pass ``tags`` to the ``capture`` method. This will require a Sentry server compatible with the new - tags protocol. -* A new gevent+http transport exists. -* A new tornado+http transport exists. -* A new twisted+http transport exists. -* Zope integration has been added. See docs for more information. -* PasteDeploy integration has been added. See docs for more information. -* A Django endpoint now exists for proxying requests to Sentry. See ``raven.contrib.django.views`` for more information. - -Version 1.9.0 -------------- - -* Signatures are no longer sent with messages. This requires the server version to be at least 4.4.6. -* Several fixes and additions were added to the Django report view. -* ``long`` types are now handled in transform(). -* Improved integration with Celery (and django-celery) for capturing errors. - -Version 1.8.0 -------------- - -* There is now a builtin view as part of the Django integration for sending events server-side - (from the client) to Sentry. The view is currently undocumented, but is available as ``{% url raven-report %}`` - and will use your server side credentials. To use this view you'd simply swap out the servers configuration in - raven-js and point it to the given URL. -* A new middleware for ZeroRPC now exists. -* A new protocol for registering transports now exists. -* Corrected some behavior in the UDP transport. -* Celery signals are now connected by default within the Django integration. - -Version 1.7.0 -------------- - -* The password sanitizer will now attempt to sanitize key=value pairs within strings (such as the querystring). -* Two new santiziers were added: RemoveStackLocalsProcessor and RemovePostDataProcessor - -Version 1.6.0 -------------- - -* Stacks must now be passed as a list of tuples (frame, lineno) rather than a list of frames. This - includes calls to logging (extra={'stack': []}), as well as explicit client calls (capture(stack=[])). - - This corrects some issues (mostly in tracebacks) with the wrong lineno being reported for a frame. - -Version 1.4.0 -------------- - -* Raven now tracks the state of the Sentry server. If it receives an error, it will slow down - requests to the server (by passing them into a named logger, sentry.errors), and increasingly - delay the next try with repeated failures, up to about a minute. - -Version 1.3.6 -------------- - -* gunicorn is now disabled in default logging configuration - -Version 1.3.5 -------------- - -* Moved exception and message methods to capture{Exception,Message}. -* Added captureQuery method. - -Version 1.3.4 -------------- - -* Corrected duplicate DSN behavior in Django client. - -Version 1.3.3 -------------- - -* Django can now be configured by setting SENTRY_DSN. -* Improve logging for send_remote failures (and correct issue created when - send_encoded was introduced). -* Renamed SantizePassworsProcessor to SanitizePassworsProcessor. - -Version 1.3.2 -------------- - -* Support sending the culprit with logging messages as part of extra. - -Version 1.3.1 -------------- - -* Added client.exception and client.message shortcuts. - -Version 1.3.0 -------------- - -* Refactored client send API to be more easily extensible. -* MOAR TESTS! - -Version 1.2.2 -------------- - -* Gracefully handle exceptions in Django client when using integrated - setup. -* Added Client.error_logger as a new logger instance that points to - ``sentry.errors``. - -Version 1.2.1 -------------- - -* Corrected behavior with raven logging errors to send_remote - which could potentially cause a very large backlog to Sentry - when it should just log to ``sentry.errors``. -* Ensure the ``site`` argument is sent to the server. - -Version 1.2.0 -------------- - -* Made DSN a first-class citizen throughout Raven. -* Added a Pylons-specific WSGI middleware. -* Improved the generic WSGI middleware to capture HTTP information. -* Improved logging and logbook handlers. - -Version 1.1.6 -------------- - -* Corrected logging stack behavior so that it doesnt capture raven+logging - extensions are part of the frames. - -Version 1.1.5 -------------- - -* Remove logging attr magic. - -Version 1.1.4 -------------- - -* Correct encoding behavior on bool and float types. - -Version 1.1.3 -------------- - -* Fix 'request' attribute on Django logging. - -Version 1.1.2 -------------- - -* Corrected logging behavior with extra data to match pre 1.x behavior. - -Version 1.1.1 -------------- - -* Handle frames that are missing f_globals and f_locals. -* Stricter conversion of int and boolean values. -* Handle invalid sources for templates in Django. - -Version 1.1.0 -------------- - -* varmap was refactored to send keys back to callbacks. -* SanitizePasswordProcessor now handles http data. - -Version 1.0.5 -------------- - -* Renaming raven2 to raven as it causes too many issues. - -Version 1.0.4 -------------- - -* Corrected a bug in setup_logging. -* Raven now sends "sentry_version" header which is the expected - server version. - -Version 1.0.3 -------------- - -* Handle more edge cases on stack iteration. - -Version 1.0.2 -------------- - -* Gracefully handle invalid f_locals. - -Version 1.0.1 -------------- - -* All datetimes are assumed to be utcnow() as of Sentry 2.0.0-RC5 - -Version 1.0.0 -------------- - -* Now only works with Sentry>=2.0.0 server. -* Raven is now listed as raven2 on PyPi. - -Version 0.8.0 -------------- - -* raven.contrib.celery is now useable. -* raven.contrib.django.celery is now useable. -* Fixed a bug with request.raw_post_data buffering in Django. - -Version 0.7.1 -------------- - -* Servers would stop iterating after the first successful post which was not the - intended behavior. - -Version 0.7.0 -------------- - -* You can now explicitly pass a list of frame objects to the process method. - -Version 0.6.1 -------------- - -* The default logging handler (SentryHandler) will now accept a set of kwargs to instantiate - a new client with (GH-10). -* Fixed a bug with checksum generation when module or function were missing (GH-9). - -Version 0.6.0 -------------- - -* Added a Django-specific WSGI middleware. - -Version 0.5.1 -------------- - -* Two minor fixes for the Django client: - * Ensure the __sentry__ key exists in data in (GH-8). - * properly set kwargs['data'] to an empty list when its a NoneType (GH-6). - -Version 0.5.0 -------------- - -* Require ``servers`` on base Client. -* Added support for the ``site`` option in Client. -* Moved raven.contrib.django.logging to raven.contrib.django.handlers. - -Version 0.4.0 -------------- - -* Fixed an infinite loop in iter_tb. - -Version 0.3.0 -------------- - -* Removed the ``thrashed`` key in ``request.sentry`` for the Django integration. -* Changed the logging handler to correctly inherit old-style classes (GH-1). -* Added a ``client`` argument to ``raven.contrib.django.models.get_client()``. - -Version 0.2.0 -------------- - -* auto_log_stacks now works with create_from_text -* added Client.get_ident - -Version 0.1.0 -------------- - -* Initial version of Raven (extracted from django-sentry 1.12.1). diff --git a/LICENSE b/LICENSE index 27a10c3b0..f87028d6d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2009 David Cramer and individual contributors. +Copyright (c) 2015 Functional Software, Inc and individual contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9,4 +9,4 @@ Redistribution and use in source and binary forms, with or without modification, 3. Neither the name of the Raven nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in index 09d98d349..031d5b972 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,6 @@ -include setup.py README.rst MANIFEST.in LICENSE *.txt +include setup.py conftest.py README.rst MANIFEST.in LICENSE *.txt recursive-include raven/contrib/zope *.xml +recursive-include raven/contrib/zconfig *.xml +recursive-include raven/data * +graft tests global-exclude *~ diff --git a/Makefile b/Makefile index 80823f65b..186cc05da 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,42 @@ bootstrap: - pip install -e . --use-mirrors - pip install "file://`pwd`#egg=raven[dev]" --use-mirrors - pip install "file://`pwd`#egg=raven[tests]" --use-mirrors + pip install -e "file://`pwd`#egg=raven[tests]" + make setup-git test: bootstrap lint @echo "Running Python tests" - py.test -x tests + py.test -f tests @echo "" lint: @echo "Linting Python files" - flake8 --exclude=migrations --ignore=E501,E225,E121,E123,E124,E125,E127,E128 raven || exit 1 + PYFLAKES_NODOCTEST=1 flake8 raven || exit 1 @echo "" coverage: coverage run runtests.py --include=raven/* && \ coverage html --omit=*/migrations/* -d cover + +setup-git: + git config branch.autosetuprebase always + cd .git/hooks && ln -sf ../../hooks/* ./ + +clean: + rm -rf dist build + +publish: clean + python setup.py sdist bdist_wheel upload + +dist: clean + python setup.py sdist bdist_wheel + +install-zeus-cli: + npm install -g @zeus-ci/cli + +travis-upload-dist: dist install-zeus-cli + zeus upload -t "application/zip+wheel" dist/* \ + || [[ ! "$(TRAVIS_BRANCH)" =~ ^release/ ]] + +update-ca: + curl -sSL https://mkcert.org/generate/ -o raven/data/cacert.pem + +.PHONY: bootstrap test lint coverage setup-git publish update-ca dist clean install-zeus-cli travis-upload-dist diff --git a/README.rst b/README.rst index a1e118565..817cdfd1d 100644 --- a/README.rst +++ b/README.rst @@ -1,27 +1,133 @@ -Raven -====== +.. raw:: html -.. image:: https://secure.travis-ci.org/getsentry/raven-python.png?branch=master - :target: http://travis-ci.org/getsentry/raven-python +

+ + + +
+

+ +Deprecated for sentry-sdk package +================================= -.. image:: https://coveralls.io/repos/getsentry/raven-python/badge.png?branch=master - :target: https://coveralls.io/r/getsentry/raven-python?branch=master +**Raven is deprecated** in favor of `Sentry-Python `_. -Raven is a Python client for `Sentry `_. It provides -full out-of-the-box support for many of the popular frameworks, including +Feature development and most bugfixes happen exclusively there, as Raven is in maintenance mode. + +---- + +Raven - Sentry for Python +========================= + +.. image:: https://img.shields.io/pypi/v/raven.svg + :target: https://pypi.python.org/pypi/raven + :alt: PyPi page link -- version + +.. image:: https://travis-ci.org/getsentry/raven-python.svg?branch=master + :target: https://travis-ci.org/getsentry/raven-python + +.. image:: https://img.shields.io/pypi/l/raven.svg + :target: https://pypi.python.org/pypi/raven + :alt: PyPi page link -- MIT licence + +.. image:: https://img.shields.io/pypi/pyversions/raven.svg + :target: https://pypi.python.org/pypi/raven + :alt: PyPi page link -- Python versions + +.. image:: https://codeclimate.com/github/getsentry/raven-python/badges/gpa.svg + :target: https://codeclimate.com/github/getsentry/raven-python + :alt: Code Climate + + +Raven is the official legacy Python client for `Sentry`_, officially supports +Python 2.6–2.7 & 3.3–3.7, and runs on PyPy and Google App Engine. + +It tracks errors and exceptions that happen during the +execution of your application and provides instant notification with detailed +information needed to prioritize, identify, reproduce and fix each issue. + +It provides full out-of-the-box support for many of the popular python frameworks, including Django, and Flask. Raven also includes drop-in support for any WSGI-compatible web application. Your application doesn't live on the web? No problem! Raven is easy to use in any Python application. -**Raven 1.0 requires at least version 2.0 of Sentry to be installed on the server** +For more information, see our `Python Documentation`_ for framework integrations and other goodies. + + +Features +-------- + +- Automatically report (un)handled exceptions and errors +- Send customized diagnostic data +- Process and sanitize data before sending it over the network + + +Quickstart +---------- + +It's really easy to get started with Raven. After you complete setting up a project in Sentry, +you’ll be given a value which we call a DSN, or Data Source Name. You will need it to configure the client. + + +Install the latest package with *pip* and configure the client:: + + pip install raven --upgrade + +Create a client and capture an example exception: + +.. sourcecode:: python + + from raven import Client + + client = Client('___DSN___') + + try: + 1 / 0 + except ZeroDivisionError: + client.captureException() + + +Raven Python is more than that however. Checkout our `Python Documentation`_. + + +Contributing +------------ + +Raven will continue to be maintained for bugfixes and contributions are more than welcome! New features should only go into the new sentry-python SDK. + +There are many ways to contribute: + +* Join in on discussions on our `Mailing List`_ or in our `IRC Channel`_. + +* Report bugs on our `Issue Tracker`_. + +* Submit a pull request! + Resources --------- -* `Documentation `_ -* `Bug Tracker `_ -* `Code `_ -* `Mailing List `_ -* `IRC `_ (irc.freenode.net, #sentry) +* `Sentry`_ +* `Python Documentation`_ +* `Issue Tracker`_ +* `Code`_ on Github +* `Mailing List`_ +* `IRC Channel`_ (irc.freenode.net, #sentry) +* `Travis CI`_ + +.. _Sentry: https://getsentry.com/ +.. _Python Documentation: https://docs.getsentry.com/hosted/clients/python/ +.. _SDKs for other platforms: https://docs.sentry.io/#platforms +.. _Issue Tracker: https://github.com/getsentry/raven-python/issues +.. _Code: https://github.com/getsentry/raven-python +.. _Mailing List: https://groups.google.com/group/getsentry +.. _IRC Channel: irc://irc.freenode.net/sentry +.. _Travis CI: http://travis-ci.org/getsentry/raven-python + + + + + +Not using Python? Check out our `SDKs for other platforms`_. diff --git a/ci/runtox.sh b/ci/runtox.sh new file mode 100644 index 000000000..a8368f890 --- /dev/null +++ b/ci/runtox.sh @@ -0,0 +1,8 @@ +#!/bin/sh +if [ -z "$1" ]; then + searchstring="$(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/g' -e 's/-dev//g')" +else + searchstring="$1" +fi + +exec tox -e $(tox -l | grep $searchstring | tr '\n' ',') diff --git a/ci/setup b/ci/setup new file mode 100755 index 000000000..cd0d046e5 --- /dev/null +++ b/ci/setup @@ -0,0 +1,39 @@ +#!/bin/bash -e + +# we want wheel support when possible +pip install "pip>=8.1" + +if [ -n "$DJANGO" ]; then + if [[ "$DJANGO" == "dev" ]]; then + pip install -e git+git://github.com/django/django.git#egg=Django + else + pip install "Django==$DJANGO" + fi +fi +if [ -n "$FLASK" ]; then + pip install "Flask==$FLASK" +fi +if [ -n "$CELERY" ]; then + pip install "celery==$CELERY" +fi +make bootstrap +if [[ ${TRAVIS_PYTHON_VERSION::1} == '2' ]]; then + pip install gevent +fi +# if [[ ${TRAVIS_PYTHON_VERSION} == '3.2' ]]; then +# pip install -I https://github.com/celery/celery/archive/3.0.zip +# fi + +DJANGO_BITS=(${DJANGO//./ }) +if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 8 ]]; then + # django-celery has fickle dependencies as its deprecated + pip install "django-celery>=3.1" "celery>=3.1,<4" + # newer versions of pytest-django dont support older versions of django + pip install "pytest-django<3.0" +elif [ -n "$DJANGO" ]; then + pip install "pytest-django>=3.0,<3.1" +fi + +if [[ ${DJANGO_BITS[0]} -eq 1 && ${DJANGO_BITS[1]} -lt 10 && ${DJANGO_BITS[1]} -gt 7 ]]; then + pip install "django-tastypie==0.13.3" +fi diff --git a/ci/test b/ci/test new file mode 100755 index 000000000..2da87e2dd --- /dev/null +++ b/ci/test @@ -0,0 +1,13 @@ +#!/bin/bash -e + +if [[ $TEST_SUITE == 'django' ]]; then + TEST_PATH=tests/contrib/django +elif [[ $TEST_SUITE == 'flask' ]]; then + TEST_PATH=tests/contrib/flask +elif [[ $TEST_SUITE == 'celery' ]]; then + TEST_PATH=tests/contrib/test_celery.py +else + TEST_PATH=tests +fi + +coverage run --source=raven -m py.test $TEST_PATH --timeout 10 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..713ab6076 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + default: + target: 0% + patch: + default: + target: 0% + ignore: + - hooks/.* + - ci/.* + +comment: false diff --git a/conftest.py b/conftest.py index f2f87509e..e442eb316 100644 --- a/conftest.py +++ b/conftest.py @@ -1,68 +1,79 @@ -from django.conf import settings +from __future__ import absolute_import + import os.path +import pytest import sys +collect_ignore = [ + 'tests/contrib/awslambda' +] -collect_ignore = [] if sys.version_info[0] > 2: - collect_ignore.append("tests/contrib/flask") - if sys.version_info[1] == 3: - collect_ignore.append("tests/handlers/logbook") + if sys.version_info[1] < 3: + collect_ignore.append('tests/contrib/flask') + if sys.version_info[1] == 2: + collect_ignore.append('tests/handlers/logbook') try: - import gevent + import gevent # NOQA except ImportError: - collect_ignore.append("tests/transport/gevent") + collect_ignore.append('tests/transport/gevent') +try: + import web # NOQA +except ImportError: + collect_ignore.append('tests/contrib/webpy') -INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.admin', - 'django.contrib.sessions', - 'django.contrib.sites', +try: + import django # NOQA +except ImportError: + django = None + collect_ignore.append('tests/contrib/django') - # Included to fix Disqus' test Django which solves IntegrityMessage case - 'django.contrib.contenttypes', +try: + import Sanic # NOQA +except ImportError: + collect_ignore.append('tests/contrib/sanic') - 'raven.contrib.django', -] +try: + import tastypie # NOQA +except ImportError: + collect_ignore.append('tests/contrib/django/test_tastypie.py') use_djcelery = True try: - import djcelery - INSTALLED_APPS.append('djcelery') + import djcelery # NOQA + # INSTALLED_APPS.append('djcelery') except ImportError: use_djcelery = False -def pytest_configure(config): - where_am_i = os.path.dirname(os.path.abspath(__file__)) - - if not settings.configured: - settings.configure( - DATABASE_ENGINE='sqlite3', - DATABASES={ - 'default': { - 'NAME': ':memory:', - 'ENGINE': 'django.db.backends.sqlite3', - 'TEST_NAME': ':memory:', - }, - }, - DATABASE_NAME=':memory:', - TEST_DATABASE_NAME=':memory:', - INSTALLED_APPS=INSTALLED_APPS, - ROOT_URLCONF='', - DEBUG=False, - SITE_ID=1, - BROKER_HOST="localhost", - BROKER_PORT=5672, - BROKER_USER="guest", - BROKER_PASSWORD="guest", - BROKER_VHOST="/", - SENTRY_ALLOW_ORIGIN='*', - CELERY_ALWAYS_EAGER=True, - TEMPLATE_DEBUG=True, - TEMPLATE_DIRS=[os.path.join(where_am_i, 'tests', 'contrib', 'django', 'templates')], - ALLOWED_HOSTS=['*'], - ) +def pytest_runtest_teardown(item): + if django: + from raven.contrib.django.models import client + client.events = [] + + +@pytest.fixture +def project_root(): + return os.path.dirname(os.path.abspath(__file__)) + + +@pytest.fixture +def mytest_model(): + + from tests.contrib.django.models import MyTestModel + return MyTestModel + + +@pytest.fixture(scope='function', autouse=False) +def user_instance(request, admin_user): + request.cls.user = admin_user + + +@pytest.fixture(autouse=True) +def has_git_requirements(request, project_root): + if request.node.get_marker('has_git_requirements'): + if not os.path.exists(os.path.join(project_root, '.git', 'refs', 'heads', 'master')): + pytest.skip('skipped test as project is not a git repo') diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index ed016ccb1..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,130 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ./ - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Sentry.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Sentry.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Sentry" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Sentry" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst deleted file mode 100644 index f8b67d626..000000000 --- a/docs/changelog/index.rst +++ /dev/null @@ -1,4 +0,0 @@ -Changelog -========= - -.. include:: ../../CHANGES diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index e284879b0..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,224 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sentry documentation build configuration file, created by -# sphinx-quickstart on Wed Oct 20 16:21:42 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -#extensions = ['sphinxtogithub'] -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Raven' -copyright = u'2010, David Cramer' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. - -version = __import__('pkg_resources').get_distribution('raven').version -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - -intersphinx_mapping = { - 'http://docs.python.org/2.7': None, - 'django': ('http://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/'), - 'http://raven.readthedocs.org/en/latest': None -} - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'nature' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Ravendoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'Raven.tex', u'Raven Documentation', - u'David Cramer', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'raven', u'Raven Documentation', - [u'David Cramer'], 1) -] diff --git a/docs/config/bottle.rst b/docs/config/bottle.rst deleted file mode 100644 index 30e901da6..000000000 --- a/docs/config/bottle.rst +++ /dev/null @@ -1,39 +0,0 @@ -Configuring Bottle -================= - -Setup ------ - -The first thing you'll need to do is to disable catchall in your Bottle app:: - - import bottle - - app = bottle.app() - app.catchall = False - -.. note:: Bottle will not propagate exceptions to the underlying WSGI middleware by default. Setting catchall to False disables that. - -Sentry will act as Middleware:: - - from raven.contrib.bottle import Sentry - app = Sentry(app, client) - -Usage ------ - -Once you've configured the Sentry application you need only call run with it:: - - run(app=app) - -If you want to send additional events, a couple of shortcuts are provided on the Bottle request app object. - -Capture an arbitrary exception by calling ``captureException``:: - - >>> try: - >>> 1 / 0 - >>> except ZeroDivisionError: - >>> request.app.sentry.captureException() - -Log a generic message with ``captureMessage``:: - - >>> request.app.sentry.captureMessage('hello, world!') diff --git a/docs/config/celery.rst b/docs/config/celery.rst deleted file mode 100644 index 54af93a07..000000000 --- a/docs/config/celery.rst +++ /dev/null @@ -1,10 +0,0 @@ -Configuring Celery -================== - -Celery provides a hook for catching task failures, and Raven can easily plug into that hook:: - - from raven.contrib.celery import register_signal - - register_signal(client) - -If you're using Django and ``djcelery`` exists in your ``INSTALLED_APPS``, we've already set this up for you. diff --git a/docs/config/django.rst b/docs/config/django.rst deleted file mode 100644 index b78c13ad8..000000000 --- a/docs/config/django.rst +++ /dev/null @@ -1,292 +0,0 @@ -Configuring Django -================== - -Setup ------ - -Using the Django integration is as simple as adding :mod:`raven.contrib.django.raven_compat` to your installed apps:: - - INSTALLED_APPS = ( - 'raven.contrib.django.raven_compat', - ) - -.. note:: This causes Raven to install a hook in Django that will automatically report uncaught exceptions. - -Additional settings for the client are configured using the ``RAVEN_CONFIG`` dictionary:: - - RAVEN_CONFIG = { - 'dsn': 'http://public:secret@example.com/1', - } - -Once you've configured the client, you can test it using the standard Django -management interface:: - - python manage.py raven test - -You'll be referencing the client slightly differently in Django as well:: - - from raven.contrib.django.raven_compat.models import client - - client.captureException() - - -Using with Raven.js -------------------- - -A Django template tag is provided to render a proper public DSN inside your templates, you must first load ``raven``:: - - {% load raven %} - -Inside your template, you can now use:: - - - -By default, the DSN is generated in a protocol relative fashion, e.g. ``//public@example.com/1``. If you need a specific protocol, you can override:: - - {% sentry_public_dsn 'https' %} - -See `Raven.js documentation `_ for more information. - - -Integration with :mod:`logging` -------------------------------- - -To integrate with the standard library's :mod:`logging` module: - -Django 1.3 or Newer -~~~~~~~~~~~~~~~~~~~ - -:: - - LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'root': { - 'level': 'WARNING', - 'handlers': ['sentry'], - }, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' - }, - }, - 'handlers': { - 'sentry': { - 'level': 'ERROR', - 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'verbose' - } - }, - 'loggers': { - 'django.db.backends': { - 'level': 'ERROR', - 'handlers': ['console'], - 'propagate': False, - }, - 'raven': { - 'level': 'DEBUG', - 'handlers': ['console'], - 'propagate': False, - }, - 'sentry.errors': { - 'level': 'DEBUG', - 'handlers': ['console'], - 'propagate': False, - }, - }, - } - - -Older Versions -~~~~~~~~~~~~~~ - -:: - - from raven.conf import setup_logging - from raven.contrib.django.raven_compat.handlers import SentryHandler - - setup_logging(SentryHandler()) - -Usage -~~~~~ - -Logging usage works the same way as it does outside of Django, with the -addition of an optional ``request`` key in the extra data:: - - logger.error('There was some crazy error', exc_info=True, extra={ - # Optionally pass a request and we'll grab any information we can - 'request': request, - }) - - -404 Logging ------------ - -In certain conditions you may wish to log 404 events to the Sentry server. To -do this, you simply need to enable a Django middleware:: - - MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( - 'raven.contrib.django.raven_compat.middleware.Sentry404CatchMiddleware', - ..., - ) - -Message References ------------------- - -Sentry supports sending a message ID to your clients so that they can be -tracked easily by your development team. There are two ways to access this -information, the first is via the ``X-Sentry-ID`` HTTP response header. Adding -this is as simple as appending a middleware to your stack:: - - MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ( - # We recommend putting this as high in the chain as possible - 'raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', - ..., - ) - -Another alternative method is rendering it within a template. By default, -Sentry will attach :attr:`request.sentry` when it catches a Django exception. -In our example, we will use this information to modify the default -:file:`500.html` which is rendered, and show the user a case reference ID. The -first step in doing this is creating a custom :func:`handler500` in your -:file:`urls.py` file:: - - from django.conf.urls.defaults import * - - from django.views.defaults import page_not_found, server_error - - def handler500(request): - """ - 500 error handler which includes ``request`` in the context. - - Templates: `500.html` - Context: None - """ - from django.template import Context, loader - from django.http import HttpResponseServerError - - t = loader.get_template('500.html') # You need to create a 500.html template. - return HttpResponseServerError(t.render(Context({ - 'request': request, - }))) - -Once we've successfully added the :data:`request` context variable, adding the -Sentry reference ID to our :file:`500.html` is simple: - -.. code-block:: django - -

You've encountered an error, oh noes!

- {% if request.sentry.id %} -

If you need assistance, you may reference this error as {{ request.sentry.id }}.

- {% endif %} - -WSGI Middleware ---------------- - -If you are using a WSGI interface to serve your app, you can also apply a -middleware which will ensure that you catch errors even at the fundamental -level of your Django application:: - - from raven.contrib.django.raven_compat.middleware.wsgi import Sentry - from django.core.handlers.wsgi import WSGIHandler - - application = Sentry(WSGIHandler()) - - -Additional Settings -------------------- - -SENTRY_CLIENT -~~~~~~~~~~~~~~ - -In some situations you may wish for a slightly different behavior to how Sentry -communicates with your server. For this, Raven allows you to specify a custom -client:: - - SENTRY_CLIENT = 'raven.contrib.django.raven_compat.DjangoClient' - -Caveats -------- - -Error Handling Middleware -~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you already have middleware in place that handles :func:`process_exception` -you will need to take extra care when using Sentry. - -For example, the following middleware would suppress Sentry logging due to it -returning a response:: - - class MyMiddleware(object): - def process_exception(self, request, exception): - return HttpResponse('foo') - -To work around this, you can either disable your error handling middleware, or -add something like the following:: - - from django.core.signals import got_request_exception - class MyMiddleware(object): - def process_exception(self, request, exception): - # Make sure the exception signal is fired for Sentry - got_request_exception.send(sender=self, request=request) - return HttpResponse('foo') - -Note that this technique may break unit tests using the Django test client -(:class:`django.test.client.Client`) if a view under test generates a -:exc:`Http404 ` or :exc:`PermissionDenied` exception, -because the exceptions won't be translated into the expected 404 or 403 -response codes. - -Or, alternatively, you can just enable Sentry responses:: - - from raven.contrib.django.raven_compat.models import sentry_exception_handler - class MyMiddleware(object): - def process_exception(self, request, exception): - # Make sure the exception signal is fired for Sentry - sentry_exception_handler(request=request) - return HttpResponse('foo') - - -Gunicorn -~~~~~~~~ - -If you are running Django with `gunicorn `_ and using the -``gunicorn`` executable, instead of the ``run_gunicorn`` management command, you -will need to add a hook to gunicorn to activate Raven:: - - def when_ready(server): - from django.core.management import call_command - call_command('validate') - -Circus -~~~~~~ - -If you are running Django with `circus `_ and -`chaussette `_ you will also need -to add a hook to circus to activate Raven:: - - def run_raven(*args, **kwargs): - """Set up raven for django by running a django command. - It is necessary because chaussette doesn't run a django command. - - """ - from django.core.management import call_command - call_command('validate') - return True - -And in your circus configuration:: - - [socket:dwebapp] - host = 127.0.0.1 - port = 8080 - - [watcher:dwebworker] - cmd = chaussette --fd $(circus.sockets.dwebapp) dproject.wsgi.application - use_sockets = True - numprocesses = 2 - hooks.after_start = dproject.hooks.run_raven - diff --git a/docs/config/flask.rst b/docs/config/flask.rst deleted file mode 100644 index fd92b0eb1..000000000 --- a/docs/config/flask.rst +++ /dev/null @@ -1,63 +0,0 @@ -Configuring Flask -================= - -Installation ------------- - -If you haven't already, install raven with its explicit Flask dependencies: - - pip install raven[flask] - -Setup ------ - -The first thing you'll need to do is to initialize Raven under your application:: - - from raven.contrib.flask import Sentry - sentry = Sentry(app, dsn='http://public_key:secret_key@example.com/1') - -If you don't specify the ``dsn`` value, we will attempt to read it from your environment under -the ``SENTRY_DSN`` key. - -Building applications on the fly? You can use Raven's ``init_app`` hook:: - - sentry = Sentry(dsn='http://public_key:secret_key@example.com/1') - - def create_app(): - app = Flask(__name__) - sentry.init_app(app) - return app - -Settings --------- - -Additional settings for the client can be configured using ``SENTRY_`` in your application's configuration:: - - class MyConfig(object): - SENTRY_DSN = 'http://public_key:secret_key@example.com/1' - SENTRY_INCLUDE_PATHS = ['myproject'] - -If `Flask-Login `_ is used by your application (including `Flask-Security `_), user information will be captured when an exception or message is captured. -By default, only the ``id`` (current_user.get_id()), ``is_authenticated``, and ``is_anonymous`` is captured for the user. If you would like additional attributes on the ``current_user`` to be captured, you can configure them using ``SENTRY_USER_ATTRS``:: - - class MyConfig(object): - SENTRY_USER_ATTRS = ['username', 'first_name', 'last_name', 'email'] - -``email`` will be captured as ``sentry.interfaces.User.email``, and any additionl attributes will be available under ``sentry.interfaces.User.data`` - -Usage ------ - -Once you've configured the Sentry application it will automatically capture uncaught exceptions within Flask. If you -want to send additional events, a couple of shortcuts are provided on the Sentry Flask middleware object. - -Capture an arbitrary exception by calling ``captureException``:: - - >>> try: - >>> 1 / 0 - >>> except ZeroDivisionError: - >>> sentry.captureException() - -Log a generic message with ``captureMessage``:: - - >>> sentry.captureMessage('hello, world!') \ No newline at end of file diff --git a/docs/config/index.rst b/docs/config/index.rst deleted file mode 100644 index 523a29e66..000000000 --- a/docs/config/index.rst +++ /dev/null @@ -1,262 +0,0 @@ -Configuration -============= - -This document describes configuration options available to Sentry. - -.. note:: Some integrations allow specifying these in a standard configuration, otherwise they are generally passed upon - instantiation of the Sentry client. - -.. toctree:: - :maxdepth: 2 - - bottle - celery - django - flask - logbook - logging - pylons - pyramid - wsgi - zerorpc - zope - tornado - - -Configuring the Client ----------------------- - -Settings are specified as part of the intialization of the client. - -As of Raven 1.2.0, you can now configure all clients through a standard DSN -string. This can be specified as a default using the ``SENTRY_DSN`` environment -variable, as well as passed to all clients by using the ``dsn`` argument. - -:: - - from raven import Client - - # Read configuration from the environment - client = Client() - - # Manually specify a DSN - client = Client('http://public:secret@example.com/1') - - -The Sentry DSN --------------- - -The DSN can be found in Sentry by navigation to Account -> Projects -> [Project Name] -> [Member Name]. Its template resembles the following:: - - '{PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}' - -It is composed of six important pieces: - -* The Protocol used. This can be one of the following: http, https, or udp. - -* The public and secret keys to authenticate the client. - -* The hostname of the Sentry server. - -* An optional path if Sentry is not located at the webserver root. This is specific to HTTP requests. - -* The project ID which the authenticated user is bound to. - -.. note:: Protocol may also contain transporter type: gevent+http, gevent+https, twisted+http, tornado+http, eventlet+http, eventlet+https - -Client Arguments ----------------- - -The following are valid arguments which may be passed to the Raven client: - -dsn -~~~ - -A sentry compatible DSN. - -:: - - dsn = 'http://public:secret@example.com/1' - -project -~~~~~~~ - -Set this to your Sentry project ID. The default value for installations is ``1``. - -:: - - project = 1 - - -public_key -~~~~~~~~~~ - -Set this to the public key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - public_key = 'fb9f9e31ea4f40d48855c603f15a2aa4' - - -secret_key -~~~~~~~~~~ - -Set this to the secret key of the project member which will authenticate as the -client. You can find this information on the member details page of your project -within Sentry. - -:: - - secret_key = '6e968b3d8ba240fcb50072ad9cba0810' - -site -~~~~ - -An optional, arbitrary string to identify this client installation. - -:: - - site = 'my site name' - - -name -~~~~ - -This will override the ``server_name`` value for this installation. Defaults to ``socket.gethostname()``. - -:: - - name = 'sentry_rocks_' + socket.gethostname() - -exclude_paths -~~~~~~~~~~~~~ - -Extending this allow you to ignore module prefixes when we attempt to discover which function an error comes from (typically a view) - -:: - - exclude_paths = [ - 'django', - 'sentry', - 'raven', - 'lxml.objectify', - ] - -include_paths -~~~~~~~~~~~~~ - -For example, in Django this defaults to your list of ``INSTALLED_APPS``, and is used for drilling down where an exception is located - -:: - - include_paths = [ - 'django', - 'sentry', - 'raven', - 'lxml.objectify', - ] - -list_max_length -~~~~~~~~~~~~~~~ - -The maximum number of items a list-like container should store. - -If an iterable is longer than the specified length, the left-most elements up to length will be kept. - -.. note:: This affects sets as well, which are unordered. - -:: - - list_max_length = 50 - -string_max_length -~~~~~~~~~~~~~~~~~ - -The maximum characters of a string that should be stored. - -If a string is longer than the given length, it will be truncated down to the specified size. - -:: - - string_max_length = 200 - -auto_log_stacks -~~~~~~~~~~~~~~~ - -Should Raven automatically log frame stacks (including locals) all calls as it would for exceptions. - -:: - - auto_log_stacks = True - -timeout -~~~~~~~ - -If supported, the timeout value for sending messages to remote. - -:: - - timeout = 1 - -processors -~~~~~~~~~~ - -A list of processors to apply to events before sending them to the Sentry server. Useful for sending -additional global state data or sanitizing data that you want to keep off of the server. - -:: - - processors = ( - 'raven.processors.SanitizePasswordsProcessor', - ) - -Sanitizing Data ---------------- - -Several processors are included with Raven to assist in data sanitiziation. These are configured with the -``processors`` value. - -.. data:: raven.processors.SanitizePasswordsProcessor - - Removes all keys which resemble ``password`` or ``secret`` within stacktrace contexts, and HTTP - bits (such as cookies, POST data, the querystring, and environment). - -.. data:: raven.processors.RemoveStackLocalsProcessor - - Removes all stacktrace context variables. This will cripple the functionality of Sentry, as you'll only - get raw tracebacks, but it will ensure no local scoped information is available to the server. - -.. data:: raven.processors.RemovePostDataProcessor - - Removes the ``body`` of all HTTP data. - -Testing the Client ------------------- - -Once you've got your server configured, you can test the Raven client by using it's CLI:: - - raven test - -If you've configured your environment to have SENTRY_DSN available, you can simply drop -the optional DSN argument:: - - raven test - -You should get something like the following, assuming you're configured everything correctly:: - - $ raven test http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1 - Using DSN configuration: - http://dd2c825ff9b1417d88a99573903ebf80:91631495b10b45f8a1cdbc492088da6a@localhost:9000/1 - - Client configuration: - servers : ['http://localhost:9000/api/store/'] - project : 1 - public_key : dd2c825ff9b1417d88a99573903ebf80 - secret_key : 91631495b10b45f8a1cdbc492088da6a - - Sending a test message... success! - - The test message can be viewed at the following URL: - http://localhost:9000/1/search/?q=c988bf5cb7db4653825c92f6864e7206$b8a6fbd29cc9113a149ad62cf7e0ddd5 diff --git a/docs/config/logbook.rst b/docs/config/logbook.rst deleted file mode 100644 index aaa9d2158..000000000 --- a/docs/config/logbook.rst +++ /dev/null @@ -1,28 +0,0 @@ -Configuring ``logbook`` -======================= - -Raven provides a `logbook `_ handler which will pipe -messages to Sentry. - -First you'll need to configure a handler:: - - from raven.handlers.logbook import SentryHandler - - # Manually specify a client - client = Client(...) - handler = SentryHandler(client) - -You can also automatically configure the default client with a DSN:: - - # Configure the default client - handler = SentryHandler('http://public:secret@example.com/1') - -Finally, bind your handler to your context:: - - from raven.handlers.logbook import SentryHandler - - client = Client(...) - sentry_handler = SentryHandler(client) - with sentry_handler.applicationbound(): - # everything logged here will go to sentry. - ... diff --git a/docs/config/logging.rst b/docs/config/logging.rst deleted file mode 100644 index df3821294..000000000 --- a/docs/config/logging.rst +++ /dev/null @@ -1,125 +0,0 @@ -Configuring ``logging`` -======================= - -Sentry supports the ability to directly tie into the :mod:`logging` module. To -use it simply add :class:`SentryHandler` to your logger. - -First you'll need to configure a handler:: - - from raven.handlers.logging import SentryHandler - - # Manually specify a client - client = Client(...) - handler = SentryHandler(client) - -You can also automatically configure the default client with a DSN:: - - # Configure the default client - handler = SentryHandler('http://public:secret@example.com/1') - -Finally, call the :func:`setup_logging` helper function:: - - from raven.conf import setup_logging - - setup_logging(handler) - -Another option is to use :mod:`logging.config.dictConfig`:: - - LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - - 'formatters': { - 'console': { - 'format': '[%(asctime)s][%(levelname)s] %(name)s %(filename)s:%(funcName)s:%(lineno)d | %(message)s', - 'datefmt': '%H:%M:%S', - }, - }, - - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'console' - }, - 'sentry': { - 'level': 'ERROR', - 'class': 'raven.handlers.logging.SentryHandler', - 'dsn': 'http://public:secret@example.com/1', - }, - }, - - 'loggers': { - '': { - 'handlers': ['console', 'sentry'], - 'level': 'DEBUG', - 'propagate': False, - }, - 'your_app': { - 'level': 'DEBUG', - 'propagate': True, - }, - } - } - -Usage -~~~~~ - -A recommended pattern in logging is to simply reference the modules name for -each logger, so for example, you might at the top of your module define the -following:: - - import logging - logger = logging.getLogger(__name__) - -You can also use the ``exc_info`` and ``extra={'stack': True}`` arguments on -your ``log`` methods. This will store the appropriate information and allow -Sentry to render it based on that information:: - - logger.error('There was some crazy error', exc_info=True, extra={ - 'culprit': 'my.view.name', - }) - -.. note:: Depending on the version of Python you're using, ``extra`` might not be an acceptable keyword argument for a logger's ``.exception()`` method (``.debug()``, ``.info()``, ``.warning()``, ``.error()`` and ``.critical()`` should work fine regardless of Python version). This should be fixed as of Python 3.2. Official issue here: [http://bugs.python.org/issue15541](http://bugs.python.org/issue15541). - -You may also pass additional information to be stored as meta information with -the event. As long as the key name is not reserved and not private (_foo) it -will be displayed on the Sentry dashboard. To do this, pass it as ``data`` -within your ``extra`` clause:: - - logger.error('There was some crazy error', exc_info=True, extra={ - # Optionally you can pass additional arguments to specify request info - 'culprit': 'my.view.name', - - 'data': { - # You may specify any values here and Sentry will log and output them - 'username': request.user.username, - } - }) - -.. note:: The ``url`` and ``view`` keys are used internally by Sentry within the extra data. -.. note:: Any key (in ``data``) prefixed with ``_`` will not automatically output on the Sentry details view. - -Sentry will intelligently group messages if you use proper string formatting. For example, the following messages would -be seen as the same message within Sentry:: - - logger.error('There was some %s error', 'crazy') - logger.error('There was some %s error', 'fun') - logger.error('There was some %s error', 1) - -As of Sentry 1.10.0 the :mod:`logging` integration also allows easy capture of -stack frames (and their locals) as if you were logging an exception. This can -be done automatically with the ``SENTRY_AUTO_LOG_STACKS`` setting, as well as -by passing the ``stack`` boolean to ``extra``:: - - logger.error('There was an error', extra={ - 'stack': True, - }) - -.. note:: - - Other languages that provide a logging package that is comparable to the - python :mod:`logging` package may define a Sentry handler. Check the - `Extending Sentry - `_ - documentation. diff --git a/docs/config/pylons.rst b/docs/config/pylons.rst deleted file mode 100644 index 2e033d5d3..000000000 --- a/docs/config/pylons.rst +++ /dev/null @@ -1,69 +0,0 @@ -Configuring Pylons -================== - -WSGI Middleware ---------------- - -A Pylons-specific middleware exists to enable easy configuration from settings: - -:: - - from raven.contrib.pylons import Sentry - - application = Sentry(application, config) - -Configuration is handled via the sentry namespace: - -.. code-block:: ini - - [sentry] - dsn=http://public:secret@example.com/1 - include_paths=my.package,my.other.package, - exclude_paths=my.package.crud - - -Logger setup ------------- - -Add the following lines to your project's `.ini` file to setup `SentryHandler`: - -.. code-block:: ini - - [loggers] - keys = root, sentry - - [handlers] - keys = console, sentry - - [formatters] - keys = generic - - [logger_root] - level = INFO - handlers = console, sentry - - [logger_sentry] - level = WARN - handlers = console - qualname = sentry.errors - propagate = 0 - - [handler_console] - class = StreamHandler - args = (sys.stderr,) - level = NOTSET - formatter = generic - - [handler_sentry] - class = raven.handlers.logging.SentryHandler - args = ('SENTRY_DSN') - level = NOTSET - formatter = generic - - [formatter_generic] - format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s - datefmt = %H:%M:%S - -.. note:: You may want to setup other loggers as well. - - diff --git a/docs/config/pyramid.rst b/docs/config/pyramid.rst deleted file mode 100644 index fe097954c..000000000 --- a/docs/config/pyramid.rst +++ /dev/null @@ -1,71 +0,0 @@ -Configuring Pyramid -=================== - -PasteDeploy Filter ------------------- - -A filter factory for `PasteDeploy `_ exists to allow easily inserting Raven into a WSGI pipeline: - -.. code-block:: ini - - [pipeline:main] - pipeline = - raven - tm - MyApp - - [filter:raven] - use = egg:raven#raven - dsn = http://public:secret@example.com/1 - include_paths = my.package, my.other.package - exclude_paths = my.package.crud - -In the ``[filter:raven]`` section, you must specify the entry-point for raven with the ``use =`` key. All other raven client parameters can be included in this section as well. - -See the `Pyramid PasteDeploy Configuration Documentation `_ for more information. - -Logger setup ------------- - -Add the following lines to your project's `.ini` file to setup `SentryHandler`: - -.. code-block:: ini - - [loggers] - keys = root, sentry - - [handlers] - keys = console, sentry - - [formatters] - keys = generic - - [logger_root] - level = INFO - handlers = console, sentry - - [logger_sentry] - level = WARN - handlers = console - qualname = sentry.errors - propagate = 0 - - [handler_console] - class = StreamHandler - args = (sys.stderr,) - level = NOTSET - formatter = generic - - [handler_sentry] - class = raven.handlers.logging.SentryHandler - args = ('http://public:secret@example.com/1',) - level = WARNING - formatter = generic - - [formatter_generic] - format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s - datefmt = %H:%M:%S - -.. note:: You may want to setup other loggers as well. See the `Pyramid Logging Documentation `_ for more information. - - diff --git a/docs/config/tornado.rst b/docs/config/tornado.rst deleted file mode 100644 index 0d8b912c8..000000000 --- a/docs/config/tornado.rst +++ /dev/null @@ -1,96 +0,0 @@ -Configuring Tornado -=================== - -Setup ------ - -The first thing you'll need to do is to initialize sentry client under -your application - -.. code-block:: python - :emphasize-lines: 2,11,12,13 - - import tornado.web - from raven.contrib.tornado import AsyncSentryClient - - class MainHandler(tornado.web.RequestHandler): - def get(self): - self.write("Hello, world") - - application = tornado.web.Application([ - (r"/", MainHandler), - ]) - application.sentry_client = AsyncSentryClient( - 'http://public_key:secret_key@host:port/project' - ) - - -Usage ------ - -Once the sentry client is attached to the application, request handlers -can automatically capture uncaught exceptions by inheriting the `SentryMixin` class. - -.. code-block:: python - - import tornado.web - from raven.contrib.tornado import SentryMixin - - class UncaughtExceptionExampleHandler(SentryMixin, tornado.web.RequestHandler): - def get(self): - 1/0 - - -You can also send events manually using the shortcuts defined in `SentryMixin`. -The shortcuts can be used for both asynchronous and synchronous usage. - - -Asynchronous -~~~~~~~~~~~~ - -.. code-block:: python - - import tornado.web - import tornado.gen - from raven.contrib.tornado import SentryMixin - - class AsyncMessageHandler(SentryMixin, tornado.web.RequestHandler): - @tornado.web.asynchronous - @tornado.gen.engine - def get(self): - self.write("You requested the main page") - yield tornado.gen.Task( - self.captureMessage, "Request for main page served" - ) - self.finish() - - class AsyncExceptionHandler(SentryMixin, tornado.web.RequestHandler): - @tornado.web.asynchronous - @tornado.gen.engine - def get(self): - try: - raise ValueError() - except Exception as e: - response = yield tornado.gen.Task( - self.captureException, exc_info=True - ) - self.finish() - - -.. tip:: - - The value returned by the yield is a HTTPResponse obejct. - - -Synchronous -~~~~~~~~~~~ - -.. code-block:: python - - import tornado.web - from raven.contrib.tornado import SentryMixin - - class AsyncExampleHandler(SentryMixin, tornado.web.RequestHandler): - def get(self): - self.write("You requested the main page") - self.captureMessage("Request for main page served") diff --git a/docs/config/wsgi.rst b/docs/config/wsgi.rst deleted file mode 100644 index a6f192b1e..000000000 --- a/docs/config/wsgi.rst +++ /dev/null @@ -1,16 +0,0 @@ -Configuring ``WSGI`` Middleware -=============================== - -Raven includes a simple to use WSGI middleware. - -:: - - from raven import Client - from raven.middleware import Sentry - - application = Sentry( - application, - Client('http://public:secret@example.com/1') - ) - -.. note:: Many frameworks will not propagate exceptions to the underlying WSGI middleware by default. diff --git a/docs/config/zerorpc.rst b/docs/config/zerorpc.rst deleted file mode 100644 index e64436f1f..000000000 --- a/docs/config/zerorpc.rst +++ /dev/null @@ -1,36 +0,0 @@ -Configuring ZeroRPC -=================== - -Setup ------ - -The ZeroRPC integration comes as middleware for ZeroRPC. The middleware can be -configured like the original Raven client (using keyword arguments) and -registered into ZeroRPC's context manager:: - - import zerorpc - - from raven.contrib.zerorpc import SentryMiddleware - - sentry = SentryMiddleware(dsn='udp://public_key:secret_key@example.com:4242/1') - zerorpc.Context.get_instance().register_middleware(sentry) - -By default, the middleware will hide internal frames from ZeroRPC when it -submits exceptions to Sentry. This behavior can be disabled by passing the -``hide_zerorpc_frames`` parameter to the middleware:: - - sentry = SentryMiddleware(hide_zerorpc_frames=False, dsn='udp://public_key:secret_key@example.com:4242/1') - -Compatibility -------------- - -- ZeroRPC-Python < 0.4.0 is compatible with Raven <= 3.1.0; -- ZeroRPC-Python >= 0.4.0 requires Raven > 3.1.0. - -Caveats -------- - -Since sending an exception to Sentry will basically block your RPC call, you are -*strongly* advised to use the UDP server of Sentry. In any cases, a cleaner and -long term solution would be to make Raven requests to the Sentry server -asynchronous. diff --git a/docs/config/zope.rst b/docs/config/zope.rst deleted file mode 100644 index 9b23c7425..000000000 --- a/docs/config/zope.rst +++ /dev/null @@ -1,47 +0,0 @@ -Configuring Zope/Plone -====================== - -zope.conf ---------- - -Zope has extensible logging configuration options. -A basic setup for logging looks like that: - -.. code-block:: xml - - - level INFO - - path ${buildout:directory}/var/{:_buildout_section_name_}.log - level INFO - - - %import raven.contrib.zope - - dsn YOUR_DSN - level ERROR - - - -This configuration keeps the regular logging to a logfile, but adds logging to sentry for ERRORs. - -All options of :py:class:`raven.base.Client` are supported. See :ref:`usage-label` - -Nobody writes zope.conf files these days, instead buildout recipe does that. -To add the equivalent configuration, you would do this: - -.. code-block:: ini - - [instance] - recipe = plone.recipe.zope2instance - ... - event-log-custom = - %import raven.contrib.zope - - path ${buildout:directory}/var/instance.log - level INFO - - - dsn YOUR_DSN - level ERROR - diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst deleted file mode 100644 index 560142d99..000000000 --- a/docs/contributing/index.rst +++ /dev/null @@ -1,42 +0,0 @@ -Contributing -============ - -Want to contribute back to Sentry? This page describes the general development flow, -our philosophy, the test suite, and issue tracking. - -(Though it actually doesn't describe all of that, yet) - -Setting up an Environment -------------------------- - -Sentry is designed to run off of setuptools with minimal work. Because of this -setting up a development environment requires only a few steps. - -The first thing you're going to want to do, is build a virtualenv and install -any base dependancies. - -:: - - virtualenv ~/.virtualenvs/raven - source ~/.virtualenvs/raven/bin/activate - make - -That's it :) - -Running the Test Suite ----------------------- - -The test suite is also powered off of py.test, and can be run in a number of ways. Usually though, -you'll just want to use our helper method to make things easy: - -:: - - make test - - -Contributing Back Code ----------------------- - -Ideally all patches should be sent as a pull request on GitHub, and include tests. If you're fixing a bug or making a large change the patch **must** include test coverage. - -You can see a list of open pull requests (pending changes) by visiting https://github.com/getsentry/raven-python/pulls diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index bdc30c1e6..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,66 +0,0 @@ -Raven -===== - -Raven is a standalone (and the official) Python client for `Sentry `_. - -This version of Raven requires Sentry 5.1 or newer. - -Users Guide ------------ - -.. toctree:: - :maxdepth: 2 - - install/index - config/index - usage - -Developers ----------- - -.. toctree:: - :maxdepth: 2 - - contributing/index - -Reference ---------- - -.. toctree:: - :maxdepth: 1 - - changelog/index - -Supported Platforms -------------------- - -- Python 2.6 -- Python 2.7 -- Python 3.2 -- Python 3.3 -- PyPy -- Google App Engine - -About Sentry ------------- - -Sentry provides you with a generic interface to view and interact with your error logs. With this -it allows you to interact and view near real-time information to discover issues and more -easily trace them in your application. - -More information about Sentry can be found at http://www.getsentry.com/ - -Resources ---------- - -* `Documentation `_ -* `Bug Tracker `_ -* `Code `_ -* `Mailing List `_ -* `IRC `_ (irc.freenode.net, #sentry) - -Deprecation Notes ------------------ - -Milestones releases are 1.3 or 1.4, and our deprecation policy is to a two version step. For example, -a feature will be deprecated in 1.3, and completely removed in 1.4. diff --git a/docs/install/index.rst b/docs/install/index.rst deleted file mode 100644 index fe8ec1c8b..000000000 --- a/docs/install/index.rst +++ /dev/null @@ -1,32 +0,0 @@ -Install -======= - -If you haven't already, start by downloading Raven. The easiest way is with **pip**:: - - pip install raven --upgrade - -Or with *setuptools*:: - - easy_install -U raven - -Requirements ------------- - -If you installed using pip or setuptools you shouldn't need to worry about requirements. Otherwise -you will need to install the following packages in your environment: - - - ``simplejson`` - -Upgrading from sentry.client ----------------------------- - -If you're upgrading from the original ``sentry.client`` there are a few things you will need to note: - -* SENTRY_SERVER is deprecated in favor of SENTRY_SERVERS (which is a list of URIs). -* ``sentry.client`` should be replaced with ``raven.contrib.django`` in ``INSTALLED_APPS``. -* ``sentry.client.celery`` should be replaced with ``raven.contrib.django.celery`` in ``INSTALLED_APPS``. -* ``sentry.handlers.SentryHandler`` should be replaced with ``raven.contrib.django.handlers.SentryHandler`` - in your logging configuration. -* All Django specific middleware has been moved to ``raven.contrib.django.middleware``. -* The default Django client is now ``raven.contrib.django.DjangoClient``. -* The Django Celery client is now ``raven.contrib.django.celery.CeleryClient``. diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 5c12e4a01..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,155 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Sentry.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Sentry.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index da2297694..000000000 --- a/docs/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. _usage-label: - -Usage -===== - -.. autoclass:: raven.base.Client - :members: diff --git a/tests/contrib/celery/__init__.py b/examples/django_110/app/__init__.py similarity index 100% rename from tests/contrib/celery/__init__.py rename to examples/django_110/app/__init__.py diff --git a/examples/django_110/app/management/__init__.py b/examples/django_110/app/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/django_110/app/management/commands/__init__.py b/examples/django_110/app/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/django_110/app/management/commands/example.py b/examples/django_110/app/management/commands/example.py new file mode 100644 index 000000000..a3477dcae --- /dev/null +++ b/examples/django_110/app/management/commands/example.py @@ -0,0 +1,8 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'Examples' + + def handle(self, *args, **options): + raise Exception('oops') diff --git a/examples/django_110/app/settings.py b/examples/django_110/app/settings.py new file mode 100644 index 000000000..1e0155f5e --- /dev/null +++ b/examples/django_110/app/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for django_110 project. + +Generated by 'django-admin startproject' using Django 1.10. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'jlyce0zs(ux$*gn3t9hzdh!+%pf4%6)i$v-usu!o1-ab-+s*m)' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'app', + 'raven.contrib.django.raven_compat', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' + +RAVEN_CONFIG = { + 'environment': 'development', + 'dsn': 'https://f0a3752c685647c6bf7830ce66a9ace5:cbf73b8a273b429a828e470e4c986bf8@app.getsentry.com/1254', + 'release': '1.0', +} diff --git a/examples/django_110/app/urls.py b/examples/django_110/app/urls.py new file mode 100644 index 000000000..00193fe9e --- /dev/null +++ b/examples/django_110/app/urls.py @@ -0,0 +1,24 @@ +"""django_110 URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +from .views import home + +urlpatterns = [ + url(r'^$', home), + url(r'^admin/', admin.site.urls), +] diff --git a/examples/django_110/app/views.py b/examples/django_110/app/views.py new file mode 100644 index 000000000..8ff0df98b --- /dev/null +++ b/examples/django_110/app/views.py @@ -0,0 +1,8 @@ +import logging + +logger = logging.getLogger('app') + + +def home(request): + logger.info('Doing some division') + 1 / 0 diff --git a/examples/django_110/app/wsgi.py b/examples/django_110/app/wsgi.py new file mode 100644 index 000000000..c852ea9c3 --- /dev/null +++ b/examples/django_110/app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for django_110 project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + +application = get_wsgi_application() diff --git a/examples/django_110/db.sqlite3 b/examples/django_110/db.sqlite3 new file mode 100644 index 000000000..8e14485da Binary files /dev/null and b/examples/django_110/db.sqlite3 differ diff --git a/examples/django_110/manage.py b/examples/django_110/manage.py new file mode 100755 index 000000000..b62ddda69 --- /dev/null +++ b/examples/django_110/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django # NOQA + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/examples/django_110/requirements.txt b/examples/django_110/requirements.txt new file mode 100644 index 000000000..acc0045da --- /dev/null +++ b/examples/django_110/requirements.txt @@ -0,0 +1,2 @@ +-e ../../ +Django>=1.10,<1.11 diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 000000000..661c78e8b --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/bash + +failed=0 +for filename in hooks/pre-commit.*; do + if [ -f "$filename" ]; then + if ! $filename; then + failed=1 + fi + fi +done + +exit $failed diff --git a/hooks/pre-commit.flake8 b/hooks/pre-commit.flake8 new file mode 100755 index 000000000..0d61a2e51 --- /dev/null +++ b/hooks/pre-commit.flake8 @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +import glob +import os +import sys + +os.environ['PYFLAKES_NODOCTEST'] = '1' + +# pycodestyle.py uses sys.argv to find setup.cfg +sys.argv = [os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)] + +# git usurbs your bin path for hooks and will always run system python +if 'VIRTUAL_ENV' in os.environ: + site_packages = glob.glob( + '%s/lib/*/site-packages' % os.environ['VIRTUAL_ENV'])[0] + sys.path.insert(0, site_packages) + + +def main(): + from flake8.api.legacy import get_style_guide + from flake8.hooks import run + + gitcmd = "git diff-index --cached --name-only HEAD" + + _, files_modified, _ = run(gitcmd) + + try: + text_type = unicode + except NameError: + text_type = str + + files_modified = [text_type(x) for x in files_modified] + + # remove non-py files and files which no longer exist + files_modified = filter( + lambda x: x.endswith('.py') and os.path.exists(x), + files_modified) + + flake8_style = get_style_guide(parse_argv=True) + report = flake8_style.check_files(files_modified) + + return report.total_errors != 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/raven/__init__.py b/raven/__init__.py index 8e37e4106..d6d9347c4 100644 --- a/raven/__init__.py +++ b/raven/__init__.py @@ -9,28 +9,17 @@ import os import os.path -from raven.base import * # NOQA -from raven.conf import * # NOQA +__all__ = ('VERSION', 'Client', 'get_version') # noqa -__all__ = ('VERSION', 'Client', 'load', 'get_version') - -try: - VERSION = __import__('pkg_resources') \ - .get_distribution('raven').version -except Exception as e: - VERSION = 'unknown' +VERSION = '6.10.0' def _get_git_revision(path): revision_file = os.path.join(path, 'refs', 'heads', 'master') - if not os.path.exists(revision_file): - return None - fh = open(revision_file, 'r') - try: - return fh.read().strip()[:7] - finally: - fh.close() + if os.path.exists(revision_file): + with open(revision_file) as fh: + return fh.read().strip()[:7] def get_revision(): @@ -43,7 +32,6 @@ def get_revision(): path = os.path.join(checkout_dir, '.git') if os.path.exists(path): return _get_git_revision(path) - return None def get_version(): @@ -52,5 +40,12 @@ def get_version(): base = '%s (%s)' % (base, __build__) return base + __build__ = get_revision() __docformat__ = 'restructuredtext en' + + +# Declare child imports last to prevent recursion +from raven.base import * # NOQA +from raven.conf import * # NOQA +from raven.versioning import * # NOQA diff --git a/raven/base.py b/raven/base.py index 6c862a9df..699f02708 100644 --- a/raven/base.py +++ b/raven/base.py @@ -8,9 +8,7 @@ from __future__ import absolute_import -import base64 import zlib -import datetime import logging import os import sys @@ -18,32 +16,66 @@ import uuid import warnings +from datetime import datetime +from inspect import isclass +from random import Random +from types import FunctionType +from threading import local + +if sys.version_info >= (3, 2): + import contextlib +else: + import contextlib2 as contextlib + +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + import raven from raven.conf import defaults -from raven.context import Context -from raven.utils import json, get_versions, get_auth_header +from raven.conf.remote import RemoteConfig +from raven.exceptions import APIError, RateLimited +from raven.utils import json, get_versions, get_auth_header, merge_dicts +from raven.utils.compat import text_type, iteritems from raven.utils.encoding import to_unicode from raven.utils.serializer import transform -from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit -from raven.utils.urlparse import urlparse -from raven.utils.compat import HTTPError -from raven.utils import six +from raven.utils.stacks import get_stack_info, iter_stack_frames +from raven.utils.transaction import TransactionStack from raven.transport.registry import TransportRegistry, default_transports +# enforce imports to avoid obscure stacktraces with MemoryError +import raven.events # NOQA + + __all__ = ('Client',) +__excepthook__ = None + PLATFORM_NAME = 'python' +SDK_VALUE = { + 'name': 'raven-python', + 'version': raven.VERSION, +} + # singleton for the client Raven = None +if sys.version_info >= (3, 2): + basestring = str + + +def get_excepthook_client(): + return getattr(sys.excepthook, 'raven_client', None) + class ModuleProxyCache(dict): def __missing__(self, key): module, class_name = key.rsplit('.', 1) - handler = getattr(__import__(module, {}, - {}, [class_name]), class_name) + handler = getattr(__import__( + module, {}, {}, [class_name]), class_name) self[key] = handler @@ -58,27 +90,30 @@ def __init__(self): self.status = self.ONLINE self.last_check = None self.retry_number = 0 + self.retry_after = 0 def should_try(self): if self.status == self.ONLINE: return True - interval = min(self.retry_number, 6) ** 2 + interval = self.retry_after or min(self.retry_number, 6) ** 2 if time.time() - self.last_check > interval: return True return False - def set_fail(self): + def set_fail(self, retry_after=0): self.status = self.ERROR self.retry_number += 1 self.last_check = time.time() + self.retry_after = retry_after def set_success(self): self.status = self.ONLINE self.last_check = None self.retry_number = 0 + self.retry_after = 0 def did_fail(self): return self.status == self.ERROR @@ -86,9 +121,7 @@ def did_fail(self): class Client(object): """ - The base Raven client, which handles both local direct - communication with Sentry (through the GroupedMessage API), as - well as communicating over the HTTP API to multiple servers. + The base Raven client. Will read default configuration from the environment variable ``SENTRY_DSN`` if available. @@ -101,15 +134,6 @@ class Client(object): >>> # Specify a DSN explicitly >>> client = Client(dsn='https://public_key:secret_key@sentry.local/project_id') - >>> # Configure the client manually - >>> client = Client( - >>> servers=['http://sentry.local/api/store/'], - >>> include_paths=['my.package'], - >>> project='project_id', - >>> public_key='public_key', - >>> secret_key='secret_key', - >>> ) - >>> # Record an exception >>> try: >>> 1/0 @@ -117,17 +141,23 @@ class Client(object): >>> ident = client.get_ident(client.captureException()) >>> print "Exception caught; reference is %s" % ident """ + logger = logging.getLogger('raven') - protocol_version = '2.0' + protocol_version = '6' _registry = TransportRegistry(transports=default_transports) - def __init__(self, dsn=None, **options): + def __init__(self, dsn=None, raise_send_errors=False, transport=None, + install_sys_hook=True, install_logging_hook=True, + hook_libraries=None, enable_breadcrumbs=True, + _random_seed=None, **options): global Raven o = options - self.configure_logging() + self._local_state = local() + + self.raise_send_errors = raise_send_errors # configure loggers first cls = self.__class__ @@ -135,57 +165,56 @@ def __init__(self, dsn=None, **options): self.logger = logging.getLogger( '%s.%s' % (cls.__module__, cls.__name__)) self.error_logger = logging.getLogger('sentry.errors') + self.uncaught_logger = logging.getLogger('sentry.errors.uncaught') - if dsn is None and os.environ.get('SENTRY_DSN'): - msg = "Configuring Raven from environment variable 'SENTRY_DSN'" - self.logger.debug(msg) - dsn = os.environ['SENTRY_DSN'] - - if dsn: - # TODO: should we validate other options werent sent? - urlparts = urlparse(dsn) - self.logger.debug( - "Configuring Raven for host: %s://%s:%s" % (urlparts.scheme, - urlparts.netloc, urlparts.path)) - dsn_config = raven.load(dsn, transport_registry=self._registry) - servers = dsn_config['SENTRY_SERVERS'] - project = dsn_config['SENTRY_PROJECT'] - public_key = dsn_config['SENTRY_PUBLIC_KEY'] - secret_key = dsn_config['SENTRY_SECRET_KEY'] - else: - servers = o.get('servers') - project = o.get('project') - public_key = o.get('public_key') - secret_key = o.get('secret_key') - - self.servers = servers - self.public_key = public_key - self.secret_key = secret_key - self.project = project or defaults.PROJECT + self._transport_cache = {} + self.set_dsn(dsn, transport) self.include_paths = set(o.get('include_paths') or []) self.exclude_paths = set(o.get('exclude_paths') or []) - self.name = six.text_type(o.get('name') or defaults.NAME) + self.name = text_type( + o.get('name') or os.environ.get('SENTRY_NAME') + or o.get('machine') or defaults.NAME + ) self.auto_log_stacks = bool( - o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS) + o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS + ) + self.capture_locals = bool( + o.get('capture_locals', defaults.CAPTURE_LOCALS)) self.string_max_length = int( o.get('string_max_length') or defaults.MAX_LENGTH_STRING) self.list_max_length = int( o.get('list_max_length') or defaults.MAX_LENGTH_LIST) - self.site = o.get('site', defaults.SITE) + self.site = o.get('site') self.include_versions = o.get('include_versions', True) self.processors = o.get('processors') + self.sanitize_keys = o.get('sanitize_keys') if self.processors is None: self.processors = defaults.PROCESSORS context = o.get('context') if context is None: - context = {'sys.argv': sys.argv[:]} + context = {'sys.argv': getattr(sys, 'argv', [])[:]} self.extra = context + self.tags = o.get('tags') or {} + self.environment = ( + o.get('environment') or os.environ.get('SENTRY_ENVIRONMENT', None)) + self.release = ( + o.get('release') or os.environ.get('SENTRY_RELEASE') + or os.environ.get('HEROKU_SLUG_COMMIT')) + self.repos = self._format_repos(o.get('repos')) + self.sample_rate = ( + o.get('sample_rate') + if o.get('sample_rate') is not None + else 1 + ) + self.transaction = TransactionStack() + self.ignore_exceptions = set(o.get('ignore_exceptions') or ()) self.module_cache = ModuleProxyCache() - # servers may be set to a NoneType (for Django) + self._random = Random(_random_seed) + if not self.is_enabled(): self.logger.info( 'Raven is not configured (logging is disabled). Please see the' @@ -194,18 +223,78 @@ def __init__(self, dsn=None, **options): if Raven is None: Raven = self + # We want to remember the creating thread id here because this + # comes in useful for the context special handling + self.main_thread_id = get_thread_ident() + self.enable_breadcrumbs = enable_breadcrumbs + + from raven.context import Context + self._context = Context(self) + + if install_sys_hook: + self.install_sys_hook() + + if install_logging_hook: + self.install_logging_hook() + + self.hook_libraries(hook_libraries) + + def _format_repos(self, value): + result = {} + if value: + for path, config in iteritems(value): + if path[0] != '/': + # assume its a module + path = os.path.abspath(__import__(path).__file__) + result[path] = config + return result + + def set_dsn(self, dsn=None, transport=None): + if not dsn and os.environ.get('SENTRY_DSN'): + msg = "Configuring Raven from environment variable 'SENTRY_DSN'" + self.logger.debug(msg) + dsn = os.environ['SENTRY_DSN'] + + if dsn not in self._transport_cache: + if not dsn: + result = RemoteConfig(transport=transport) + else: + result = RemoteConfig.from_string( + dsn, + transport=transport, + transport_registry=self._registry, + ) + self._transport_cache[dsn] = result + self.remote = result + else: + self.remote = self._transport_cache[dsn] + + self.logger.debug("Configuring Raven for host: {0}".format(self.remote)) + + def install_sys_hook(self): + global __excepthook__ + + if __excepthook__ is None: + __excepthook__ = sys.excepthook + + def handle_exception(*exc_info): + self.captureException(exc_info=exc_info, level='fatal') + __excepthook__(*exc_info) + handle_exception.raven_client = self + sys.excepthook = handle_exception + + def install_logging_hook(self): + from raven.breadcrumbs import install_logging_hook + install_logging_hook() + + def hook_libraries(self, libraries): + from raven.breadcrumbs import hook_libraries + hook_libraries(libraries) + @classmethod def register_scheme(cls, scheme, transport_class): cls._registry.register_scheme(scheme, transport_class) - def configure_logging(self): - for name in ('raven', 'sentry'): - logger = logging.getLogger(name) - if logger.handlers: - continue - logger.addHandler(logging.StreamHandler()) - logger.setLevel(logging.INFO) - def get_processors(self): for processor in self.processors: yield self.module_cache[processor](self) @@ -213,28 +302,31 @@ def get_processors(self): def get_module_versions(self): if not self.include_versions: return {} - return get_versions(self.include_paths) + + version_info = sys.version_info + + modules = get_versions(self.include_paths) + modules['python'] = '{0}.{1}.{2}'.format( + version_info[0], version_info[1], version_info[2], + ) + + return modules def get_ident(self, result): """ Returns a searchable string representing a message. - >>> result = client.process(**kwargs) + >>> result = client.capture(**kwargs) >>> ident = client.get_ident(result) """ - return '$'.join(result) + warnings.warn('Client.get_ident is deprecated. The event ID is now ' + 'returned as the result of capture.', + DeprecationWarning) + return result def get_handler(self, name): return self.module_cache[name](self) - def _get_public_dsn(self): - url = urlparse(self.servers[0]) - netloc = url.hostname - if url.port: - netloc += ':%s' % url.port - path = url.path.replace('api/%s/store/' % (self.project,), self.project) - return '//%s@%s%s' % (self.public_key, netloc, path) - def get_public_dsn(self, scheme=None): """ Returns a public DSN which is consumable by raven-js @@ -245,34 +337,54 @@ def get_public_dsn(self, scheme=None): >>> # Specify a scheme to use (http or https) >>> print client.get_public_dsn('https') """ - if not self.is_enabled(): - return - url = self._get_public_dsn() - if not scheme: + if self.is_enabled(): + url = self.remote.get_public_dsn() + if scheme: + return '%s:%s' % (scheme, url) + return url - return '%s:%s' % (scheme, url) + + def _get_exception_key(self, exc_info): + # On certain celery versions the tb_frame attribute might + # not exist or be `None`. + code_id = last_id = 0 + try: + code_id = id(exc_info[2] and exc_info[2].tb_frame.f_code) + last_id = exc_info[2] and exc_info[2].tb_lasti or 0 + except (AttributeError, IndexError): + pass + return ( + exc_info[0], + id(exc_info[1]), + code_id, + id(exc_info[2]), + last_id, + ) + + def skip_error_for_logging(self, exc_info): + key = self._get_exception_key(exc_info) + return key in self.context.exceptions_to_skip + + def record_exception_seen(self, exc_info): + key = self._get_exception_key(exc_info) + self.context.exceptions_to_skip.add(key) def build_msg(self, event_type, data=None, date=None, time_spent=None, extra=None, stack=None, public_key=None, - tags=None, **kwargs): + tags=None, fingerprint=None, **kwargs): """ Captures, processes and serializes an event into a dict object The result of ``build_msg`` should be a standardized dict, with all default values available. """ - # create ID client-side so that it can be passed to application event_id = uuid.uuid4().hex - if data is None: - data = {} - if extra is None: - extra = {} - if not date: - date = datetime.datetime.utcnow() - if stack is None: - stack = self.auto_log_stacks + data = merge_dicts(self.context.data, data) + + data.setdefault('tags', {}) + data.setdefault('extra', {}) if '.' not in event_type: # Assume it's a builtin @@ -286,45 +398,52 @@ def build_msg(self, event_type, data=None, date=None, if data.get('culprit'): culprit = data['culprit'] - for k, v in six.iteritems(result): + for k, v in iteritems(result): if k not in data: data[k] = v - if stack and 'sentry.interfaces.Stacktrace' not in data: + # auto_log_stacks only applies to events that are not exceptions + # due to confusion about which stack is which and the automatic + # application of stacktrace to exception objects by Sentry + if stack is None and 'exception' not in data: + stack = self.auto_log_stacks + + if stack and 'stacktrace' not in data: if stack is True: frames = iter_stack_frames() else: frames = stack + stack_info = get_stack_info( + frames, + transformer=self.transform, + capture_locals=self.capture_locals, + ) data.update({ - 'sentry.interfaces.Stacktrace': { - 'frames': get_stack_info(frames, - transformer=self.transform) - }, + 'stacktrace': stack_info, }) - if 'sentry.interfaces.Stacktrace' in data: - if self.include_paths: - for frame in data['sentry.interfaces.Stacktrace']['frames']: - if frame.get('in_app') is not None: - continue + if self.include_paths: + for frame in self._iter_frames(data): + if frame.get('in_app') is not None: + continue - path = frame.get('module') - if not path: - continue + path = frame.get('module') + if not path: + continue - if path.startswith('raven.'): - frame['in_app'] = False - else: - frame['in_app'] = ( - any(path.startswith(x) for x in self.include_paths) - and not - any(path.startswith(x) for x in self.exclude_paths) - ) + if path.startswith('raven.'): + frame['in_app'] = False + else: + frame['in_app'] = ( + any(path.startswith(x) for x in self.include_paths) + and not any(path.startswith(x) for x in self.exclude_paths) + ) - if not culprit: - culprit = get_culprit(data['sentry.interfaces.Stacktrace']['frames']) + transaction = None + if not culprit: + transaction = self.transaction.peek() if not data.get('level'): data['level'] = kwargs.get('level') or logging.ERROR @@ -335,49 +454,63 @@ def build_msg(self, event_type, data=None, date=None, if not data.get('modules'): data['modules'] = self.get_module_versions() - data['tags'] = tags or {} - data.setdefault('extra', {}) - data.setdefault('level', logging.ERROR) + if self.release is not None: + data['release'] = self.release + + if self.environment is not None: + data['environment'] = self.environment - # Add extra context - if self.extra: - for k, v in six.iteritems(self.extra): - data['extra'].setdefault(k, v) + data['tags'] = merge_dicts(self.tags, data['tags'], tags) + data['extra'] = merge_dicts(self.extra, data['extra'], extra) - for k, v in six.iteritems(extra): - data['extra'][k] = v + # Legacy support for site attribute + site = data.pop('site', None) or self.site + if site: + data['tags'].setdefault('site', site) - if culprit: + if transaction: + data['transaction'] = transaction + elif culprit: data['culprit'] = culprit + if fingerprint: + data['fingerprint'] = fingerprint + # Run the data through processors for processor in self.get_processors(): data.update(processor.process(data)) if 'message' not in data: - data['message'] = handler.to_string(data) - - data.setdefault('project', self.project) - - # Legacy support for site attribute - site = data.pop('site', None) or self.site - if site: - data['tags'].setdefault('site', site) + data['message'] = kwargs.get('message', handler.to_string(data)) - for key, value in six.iteritems(data['tags']): + # tags should only be key=>u'value' + for key, value in iteritems(data['tags']): data['tags'][key] = to_unicode(value) - # Make sure custom data is coerced - for k, v in six.iteritems(data['extra']): + # extra data can be any arbitrary value + for k, v in iteritems(data['extra']): data['extra'][k] = self.transform(v) # It's important date is added **after** we serialize - data.update({ - 'timestamp': date, - 'time_spent': time_spent, - 'event_id': event_id, - 'platform': PLATFORM_NAME, - }) + data.setdefault('project', self.remote.project) + data.setdefault('timestamp', date or datetime.utcnow()) + data.setdefault('time_spent', time_spent) + data.setdefault('event_id', event_id) + data.setdefault('platform', PLATFORM_NAME) + data.setdefault('sdk', SDK_VALUE) + data.setdefault('repos', self.repos) + + # insert breadcrumbs + if self.enable_breadcrumbs: + crumbs = self.context.breadcrumbs.get_buffer() + if crumbs: + # Make sure we send the crumbs here as "values" as we use the + # raven client internally in sentry and the alternative + # submission option of a list here is not supported by the + # internal sender. + data.setdefault('breadcrumbs', { + 'values': crumbs + }) return data @@ -386,28 +519,71 @@ def transform(self, data): data, list_max_length=self.list_max_length, string_max_length=self.string_max_length) - def context(self, **kwargs): + @property + def context(self): + """ + Updates this clients thread-local context for future events. + + >>> def view_handler(view_func, *args, **kwargs): + >>> client.context.merge(tags={'key': 'value'}) + >>> try: + >>> return view_func(*args, **kwargs) + >>> finally: + >>> client.context.clear() """ - Create default context around a block of code for exception management. + return self._context - >>> with client.context(tags={'key': 'value'}) as raven: - >>> # use the context manager's client reference - >>> raven.captureMessage('hello!') - >>> - >>> # uncaught exceptions also contain the context - >>> 1 / 0 + def user_context(self, data): + """ + Update the user context for future events. + + >>> client.user_context({'email': 'foo@example.com'}) + """ + return self.context.merge({ + 'user': data, + }) + + def http_context(self, data, **kwargs): """ - return Context(self, **kwargs) + Update the http context for future events. + + >>> client.http_context({'url': 'http://example.com'}) + """ + return self.context.merge({ + 'request': data, + }) + + def extra_context(self, data, **kwargs): + """ + Update the extra context for future events. + + >>> client.extra_context({'foo': 'bar'}) + """ + return self.context.merge({ + 'extra': data, + }) + + def tags_context(self, data, **kwargs): + """ + Update the tags context for future events. + + >>> client.tags_context({'version': '1.0'}) + + """ + return self.context.merge({ + 'tags': data, + }) def capture(self, event_type, data=None, date=None, time_spent=None, - extra=None, stack=None, tags=None, **kwargs): + extra=None, stack=None, tags=None, sample_rate=None, + **kwargs): """ Captures and processes an event and pipes it off to SentryClient.send. To use structured data (interfaces) with capture: >>> capture('raven.events.Message', message='foo', data={ - >>> 'sentry.interfaces.Http': { + >>> 'request': { >>> 'url': '...', >>> 'data': {}, >>> 'query_string': '...', @@ -443,82 +619,126 @@ def capture(self, event_type, data=None, date=None, time_spent=None, interfaces. Any key which contains a '.' will be assumed to be a data interface. :param date: the datetime of this event - :param time_spent: a float value representing the duration of the event - :param event_id: a 32-length unique string identifying this event + :param time_spent: a integer value representing the duration of the + event (in milliseconds) :param extra: a dictionary of additional standard metadata - :param culprit: a string representing the cause of this event - (generally a path to a function) + :param stack: a stacktrace for the event + :param tags: dict of extra tags + :param sample_rate: a float in the range [0, 1] to sample this message :return: a 32-length string identifying this event """ - if not self.is_enabled(): return + exc_info = kwargs.get('exc_info') + if exc_info is not None: + if self.skip_error_for_logging(exc_info): + return + elif not self.should_capture(exc_info): + self.logger.info( + 'Not capturing exception due to filters: %s', exc_info[0], + exc_info=sys.exc_info()) + return + self.record_exception_seen(exc_info) + data = self.build_msg( event_type, data, date, time_spent, extra, stack, tags=tags, **kwargs) - self.send(**data) + # should this event be sampled? + if sample_rate is None: + sample_rate = self.sample_rate - return (data.get('event_id'),) + if self._random.random() < sample_rate: + self.send(**data) - def _get_log_message(self, data): - # decode message so we can show the actual event - try: - data = self.decode(data) - except: - message = '' - else: - message = data.pop('message', '') - return message + self._local_state.last_event_id = data['event_id'] + + return data['event_id'] def is_enabled(self): """ Return a boolean describing whether the client should attempt to send events. """ - return bool(self.servers) + return self.remote.is_active() + + def _iter_frames(self, data): + if 'stacktrace' in data: + for frame in data['stacktrace']['frames']: + yield frame + if 'exception' in data: + for frame in data['exception']['values'][-1]['stacktrace'].get('frames', []): + yield frame def _successful_send(self): self.state.set_success() - def _failed_send(self, e, url, data): - if isinstance(e, HTTPError): - body = e.read() + def _failed_send(self, exc, url, data): + retry_after = 0 + if isinstance(exc, APIError): + if isinstance(exc, RateLimited): + retry_after = exc.retry_after self.error_logger.error( - 'Unable to reach Sentry log server: %s (url: %s, body: %s)', - e, url, body, exc_info=True, - extra={'data': {'body': body[:200], 'remote_url': url}}) + 'Sentry responded with an API error: %s(%s)', + type(exc).__name__, exc.message) else: self.error_logger.error( - 'Unable to reach Sentry log server: %s (url: %s)', e, url, - exc_info=True, extra={'data': {'remote_url': url}}) + 'Sentry responded with an error: %s (url: %s)', + exc, url, + exc_info=True, + extra={'data': data} + ) - message = self._get_log_message(data) - self.error_logger.error('Failed to submit message: %r', message) - self.state.set_fail() + self._log_failed_submission(data) + self.state.set_fail(retry_after=retry_after) - def send_remote(self, url, data, headers={}): - if not self.state.should_try(): - message = self._get_log_message(data) - self.error_logger.error(message) + def _log_failed_submission(self, data): + """ + Log a reasonable representation of an event that should have been sent + to Sentry + """ + message = data.pop('message', '') + output = [message] + if 'exception' in data and 'stacktrace' in data['exception']['values'][-1]: + # try to reconstruct a reasonable version of the exception + for frame in data['exception']['values'][-1]['stacktrace'].get('frames', []): + output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % { + 'fn': frame.get('filename', 'unknown_filename'), + 'lineno': frame.get('lineno', -1), + 'func': frame.get('function', 'unknown_function'), + }) + + self.uncaught_logger.error(output) + + def send_remote(self, url, data, headers=None): + # If the client is configured to raise errors on sending, + # the implication is that the backoff and retry strategies + # will be handled by the calling application + if headers is None: + headers = {} + + if not self.raise_send_errors and not self.state.should_try(): + data = self.decode(data) + self._log_failed_submission(data) return self.logger.debug('Sending message of length %d to %s', len(data), url) def failed_send(e): - self._failed_send(e, url, data) + self._failed_send(e, url, self.decode(data)) try: - parsed = urlparse(url) - transport = self._registry.get_transport(parsed) - if transport.async: - transport.async_send(data, headers, self._successful_send, + transport = self.remote.get_transport() + if transport.is_async: + transport.async_send(url, data, headers, self._successful_send, failed_send) else: - transport.send(data, headers) + transport.send(url, data, headers) self._successful_send() except Exception as e: + if self.raise_send_errors: + raise failed_send(e) def send(self, auth_header=None, **data): @@ -532,8 +752,7 @@ def send(self, auth_header=None, **data): def send_encoded(self, message, auth_header=None, **kwargs): """ Given an already serialized message, signs the message and passes the - payload off to ``send_remote`` for each server specified in the servers - configuration. + payload off to ``send_remote``. """ client_string = 'raven-python/%s' % (raven.VERSION,) @@ -543,30 +762,38 @@ def send_encoded(self, message, auth_header=None, **kwargs): protocol=self.protocol_version, timestamp=timestamp, client=client_string, - api_key=self.public_key, - api_secret=self.secret_key, + api_key=self.remote.public_key, + api_secret=self.remote.secret_key, ) - for url in self.servers: - headers = { - 'User-Agent': client_string, - 'X-Sentry-Auth': auth_header, - 'Content-Type': 'application/octet-stream', - } + headers = { + 'User-Agent': client_string, + 'X-Sentry-Auth': auth_header, + 'Content-Encoding': self.get_content_encoding(), + 'Content-Type': 'application/octet-stream', + } - self.send_remote(url=url, data=message, headers=headers) + return self.send_remote( + url=self.remote.store_endpoint, + data=message, + headers=headers, + **kwargs + ) + + def get_content_encoding(self): + return 'deflate' def encode(self, data): """ Serializes ``data`` into a raw string. """ - return base64.b64encode(zlib.compress(json.dumps(data).encode('utf8'))) + return zlib.compress(json.dumps(data).encode('utf8')) def decode(self, data): """ Unserializes a string, ``data``. """ - return json.loads(zlib.decompress(base64.b64decode(data)).decode('utf8')) + return json.loads(zlib.decompress(data).decode('utf8')) def captureMessage(self, message, **kwargs): """ @@ -589,10 +816,79 @@ def captureException(self, exc_info=None, **kwargs): If exc_info is not provided, or is set to True, then this method will perform the ``exc_info = sys.exc_info()`` and the requisite clean-up for you. + + ``kwargs`` are passed through to ``.capture``. """ + if exc_info is None or exc_info is True: + exc_info = sys.exc_info() + return self.capture( 'raven.events.Exception', exc_info=exc_info, **kwargs) + def should_capture(self, exc_info): + exc_type = exc_info[0] + exc_name = '%s.%s' % (exc_type.__module__, exc_type.__name__) + exclusions = self.ignore_exceptions + string_exclusions = (e for e in exclusions if isinstance(e, basestring)) + wildcard_exclusions = (e for e in string_exclusions if e.endswith('*')) + class_exclusions = (e for e in exclusions if isclass(e)) + + if (exc_type in exclusions + or exc_type.__name__ in exclusions + or exc_name in exclusions + or any(issubclass(exc_type, e) for e in class_exclusions) + or any(exc_name.startswith(e[:-1]) for e in wildcard_exclusions)): + return False + return True + + def capture_exceptions(self, function_or_exceptions=None, **kwargs): + """ + Wrap a function or code block in try/except and automatically call + ``.captureException`` if it raises an exception, then the exception + is reraised. + + By default, it will capture ``Exception`` + + >>> @client.capture_exceptions + >>> def foo(): + >>> raise Exception() + + >>> with client.capture_exceptions(): + >>> raise Exception() + + You can also specify exceptions to be caught specifically + + >>> @client.capture_exceptions((IOError, LookupError)) + >>> def bar(): + >>> ... + + >>> with client.capture_exceptions((IOError, LookupError)): + >>> ... + + ``kwargs`` are passed through to ``.captureException``. + """ + function = None + exceptions = (Exception,) + if isinstance(function_or_exceptions, FunctionType): + function = function_or_exceptions + elif function_or_exceptions is not None: + exceptions = function_or_exceptions + + # In python3.2 contextmanager acts both as contextmanager and decorator + @contextlib.contextmanager + def make_decorator(exceptions): + try: + yield + except exceptions: + self.captureException(**kwargs) + raise + + decorator = make_decorator(exceptions) + + if function: + return decorator(function) + return decorator + def captureQuery(self, query, params=(), engine=None, **kwargs): """ Creates an event for a SQL query. @@ -609,8 +905,29 @@ def captureExceptions(self, **kwargs): DeprecationWarning) return self.context(**kwargs) + def captureBreadcrumb(self, *args, **kwargs): + """ + Records a breadcrumb with the current context. They will be + sent with the next event. + """ + # Note: framework integration should not call this method but + # instead use the raven.breadcrumbs.record_breadcrumb function + # which will record to the correct client automatically. + self.context.breadcrumbs.record(*args, **kwargs) + + capture_breadcrumb = captureBreadcrumb + + @property + def last_event_id(self): + return getattr(self._local_state, 'last_event_id', None) + + @last_event_id.setter + def last_event_id(self, value): + self._local_state.last_event_id = value + class DummyClient(Client): - "Sends messages into an empty void" + """Sends messages into an empty void.""" + def send(self, **kwargs): return None diff --git a/raven/breadcrumbs.py b/raven/breadcrumbs.py new file mode 100644 index 000000000..912a0f663 --- /dev/null +++ b/raven/breadcrumbs.py @@ -0,0 +1,411 @@ +from __future__ import absolute_import + +import os +import logging + +try: + from collections.abc import Mapping +except ImportError: + # Python < 3.3 + from collections import Mapping +from time import time +from types import FunctionType + +from raven.utils import once +from raven.utils.encoding import to_unicode +from raven.utils.compat import iteritems, get_code, text_type, string_types + +CATEGORY_MAX_LENGTH = 128 + +LEVEL_MAX_LENGTH = 16 + +special_logging_handlers = [] +special_logger_handlers = {} + +logger = logging.getLogger('raven') + + +def event_payload_considered_equal(a, b): + return ( + a['type'] == b['type'] + and a['level'] == b['level'] + and a['message'] == b['message'] + and a['category'] == b['category'] + and a['data'] == b['data'] + ) + + +class BreadcrumbBuffer(object): + + def __init__(self, limit=100, message_max_length=1024): + self.buffer = [] + self.limit = limit + self.message_max_length = message_max_length + + def record(self, timestamp=None, level=None, message=None, + category=None, data=None, type=None, processor=None): + if not (message or data or processor): + raise ValueError('You must pass either `message`, `data`, ' + 'or `processor`') + if timestamp is None: + timestamp = time() + + # we format here to ensure we dont bloat memory due to message size + result = (self.format({ + 'type': type or 'default', + 'timestamp': float(timestamp), + 'level': level, + # hardcode message length to prevent huge crumbs + 'message': message, + 'category': category, + # TODO(dcramer): we should trim data + 'data': data, + }), processor) + self.buffer.append(result) + del self.buffer[:-self.limit] + + def clear(self): + del self.buffer[:] + + def format(self, result): + result['message'] = to_unicode(result['message'])[:self.message_max_length] if result['message'] else None + result['category'] = to_unicode(result['category'])[:CATEGORY_MAX_LENGTH] if result['category'] else None + result['level'] = to_unicode(result['level'])[:LEVEL_MAX_LENGTH].lower() if result['level'] else None + return result + + def get_buffer(self): + rv = [] + for idx, (payload, processor) in enumerate(self.buffer): + if processor is not None: + try: + processor(payload) + except Exception: + raise + logger.exception('Failed to process breadcrumbs. Ignored') + payload = None + else: + # we format here to ensure we dont bloat memory due to message size + payload = self.format(payload) if payload else None + self.buffer[idx] = (payload, None) + elif payload is not None: + payload = self.format(payload) + + if payload is not None and \ + (not rv or not event_payload_considered_equal(rv[-1], payload)): + rv.append(payload) + return rv + + +class BlackholeBreadcrumbBuffer(BreadcrumbBuffer): + def record(self, *args, **kwargs): + pass + + +def make_buffer(enabled=True): + if enabled: + return BreadcrumbBuffer() + return BlackholeBreadcrumbBuffer() + + +def record_breadcrumb(type, *args, **kwargs): + # Legacy alias + kwargs['type'] = type + return record(*args, **kwargs) + + +def record(message=None, timestamp=None, level=None, category=None, + data=None, type=None, processor=None): + """Records a breadcrumb for all active clients. This is what integration + code should use rather than invoking the `captureBreadcrumb` method + on a specific client. + """ + if timestamp is None: + timestamp = time() + for ctx in raven.context.get_active_contexts(): + ctx.breadcrumbs.record(timestamp, level, message, category, + data, type, processor) + + +def _record_log_breadcrumb(logger, level, msg, *args, **kwargs): + for handler in special_logging_handlers: + if handler(logger, level, msg, args, kwargs): + return + + handler = special_logger_handlers.get(logger.name) + if handler is not None and handler(logger, level, msg, args, kwargs): + return + + def processor(data): + formatted_msg = msg + format_args = args + + # Extract 'extra' key from kwargs and merge into data + extra = kwargs.pop('extra', {}) + data_value = kwargs + data_value.update(extra) + + if args and len(args) == 1 and isinstance(args[0], Mapping) and args[0]: + format_args = args[0] + data_value.update(format_args) + + # If people log bad things, this can happen. Then just don't do + # anything. + try: + formatted_msg = text_type(msg) + if format_args: + formatted_msg = msg % format_args + except Exception: + pass + + # We do not want to include exc_info as argument because it often + # lies (set to a constant value like 1 or True) or even if it's a + # tuple it will not be particularly useful for us as we cannot + # process it anyways. + kwargs.pop('exc_info', None) + data.update({ + 'message': formatted_msg, + 'category': logger.name, + 'level': logging.getLevelName(level).lower(), + 'data': data_value, + }) + record(processor=processor) + + +def _wrap_logging_method(meth, level=None): + if not isinstance(meth, FunctionType): + func = meth.im_func + else: + func = meth + + # We were patched for raven before + if getattr(func, '__patched_for_raven__', False): + return + + if level is None: + args = ('level', 'msg') + fwd = 'level, msg' + else: + args = ('msg',) + fwd = '%d, msg' % level + + code = get_code(func) + + logging_srcfile = logging._srcfile + if logging_srcfile is None: + logging_srcfile = os.path.normpath( + logging.currentframe.__code__.co_filename + ) + + # This requires a bit of explanation why we're doing this. Due to how + # logging itself works we need to pretend that the method actually was + # created within the logging module. There are a few ways to detect + # this and we fake all of them: we use the same function globals (the + # one from the logging module), we create it entirely there which + # means that also the filename is set correctly. This fools the + # detection code in logging and it makes logging itself skip past our + # code when determining the code location. + # + # Because we point the globals to the logging module we now need to + # refer to our own functions (original and the crumb recording + # function) through a closure instead of the global scope. + # + # We also add a lot of newlines in front of the code so that the + # code location lines up again in case someone runs inspect.getsource + # on the function. + ns = {} + eval(compile('''%(offset)sif 1: + def factory(original, record_crumb): + def %(name)s(self, %(args)s, *args, **kwargs): + record_crumb(self, %(fwd)s, *args, **kwargs) + return original(self, %(args)s, *args, **kwargs) + return %(name)s + \n''' % { + 'offset': '\n' * (code.co_firstlineno - 3), + 'name': func.__name__, + 'args': ', '.join(args), + 'fwd': fwd, + 'level': level, + }, logging_srcfile, 'exec'), logging.__dict__, ns) + + new_func = ns['factory'](meth, _record_log_breadcrumb) + new_func.__doc__ = func.__doc__ + + assert code.co_firstlineno == get_code(func).co_firstlineno + + # In theory this should already be set correctly, but in some cases + # it is not. So override it. + new_func.__module__ == func.__module__ + new_func.__name__ == func.__name__ + new_func.__patched_for_raven__ = True + + return new_func + + +def _patch_logger(): + cls = logging.Logger + + methods = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'warn': logging.WARN, + 'error': logging.ERROR, + 'exception': logging.ERROR, + 'critical': logging.CRITICAL, + 'fatal': logging.FATAL + } + + for method_name, level in iteritems(methods): + new_func = _wrap_logging_method( + getattr(cls, method_name), level) + setattr(logging.Logger, method_name, new_func) + + logging.Logger.log = _wrap_logging_method( + logging.Logger.log) + + +@once +def install_logging_hook(): + """Installs the logging hook if it was not installed yet. Otherwise + does nothing. + """ + _patch_logger() + + +def ignore_logger(name_or_logger, allow_level=None): + """Ignores a logger during breadcrumb recording. + """ + def handler(logger, level, msg, args, kwargs): + if allow_level is not None and \ + level >= allow_level: + return False + return True + register_special_log_handler(name_or_logger, handler) + + +def register_special_log_handler(name_or_logger, callback): + """Registers a callback for log handling. The callback is invoked + with given arguments: `logger`, `level`, `msg`, `args` and `kwargs` + which are the values passed to the logging system. If the callback + returns true value the default handling is disabled. Only one callback + can be registered per one logger name. Logger tree is not traversed + so calling this method with `spammy_module` argument will not silence + messages from `spammy_module.child`. + """ + if isinstance(name_or_logger, string_types): + name = name_or_logger + else: + name = name_or_logger.name + special_logger_handlers[name] = callback + + +def register_logging_handler(callback): + """Registers a callback for log handling. The callback is invoked + with given arguments: `logger`, `level`, `msg`, `args` and `kwargs` + which are the values passed to the logging system. If the callback + returns true value the default handling is disabled. Registering + multiple handlers is allowed. + """ + special_logging_handlers.append(callback) + + +hooked_libraries = {} + + +def libraryhook(name): + def decorator(f): + f = once(f) + hooked_libraries[name] = f + return f + return decorator + + +@libraryhook('requests') +def _hook_requests(): + try: + from requests.sessions import Session + except ImportError: + return + + real_send = Session.send + + def send(self, request, *args, **kwargs): + def _record_request(response): + record(type='http', category='requests', data={ + 'url': request.url, + 'method': request.method, + 'status_code': response and response.status_code or None, + 'reason': response and response.reason or None, + }) + try: + resp = real_send(self, request, *args, **kwargs) + except Exception: + _record_request(None) + raise + else: + _record_request(resp) + return resp + + Session.send = send + + ignore_logger('requests.packages.urllib3.connectionpool', + allow_level=logging.WARNING) + + +@libraryhook('httplib') +def _install_httplib(): + try: + from httplib import HTTPConnection + except ImportError: + from http.client import HTTPConnection + + real_putrequest = HTTPConnection.putrequest + real_getresponse = HTTPConnection.getresponse + + def putrequest(self, method, url, *args, **kwargs): + self._raven_status_dict = status = {} + host = self.host + port = self.port + default_port = self.default_port + + def processor(data): + real_url = url + if not real_url.startswith(('http://', 'https://')): + real_url = '%s://%s%s%s' % ( + default_port == 443 and 'https' or 'http', + host, + port != default_port and ':%s' % port or '', + url, + ) + data['data'] = { + 'url': real_url, + 'method': method, + } + data['data'].update(status) + return data + record(type='http', category='requests', processor=processor) + return real_putrequest(self, method, url, *args, **kwargs) + + def getresponse(self, *args, **kwargs): + rv = real_getresponse(self, *args, **kwargs) + status = getattr(self, '_raven_status_dict', None) + if status is not None and 'status_code' not in status: + status['status_code'] = rv.status + status['reason'] = rv.reason + return rv + + HTTPConnection.putrequest = putrequest + HTTPConnection.getresponse = getresponse + + +def hook_libraries(libraries): + if libraries is None: + libraries = hooked_libraries.keys() + for lib in libraries: + func = hooked_libraries.get(lib) + if func is None: + raise RuntimeError('Unknown library %r for hooking' % lib) + func() + + +import raven.context diff --git a/raven/conf/__init__.py b/raven/conf/__init__.py index 4e5e56e8a..a7acfa8bf 100644 --- a/raven/conf/__init__.py +++ b/raven/conf/__init__.py @@ -8,9 +8,8 @@ from __future__ import absolute_import import logging -from raven.utils.urlparse import urlparse -__all__ = ('load', 'setup_logging') +__all__ = ['setup_logging'] EXCLUDE_LOGGER_DEFAULTS = ( 'raven', @@ -18,45 +17,12 @@ 'south', 'sentry.errors', 'django.request', + # dill produces a lot of garbage debug logs that are just a stream of what + # another developer would use print/pdb for + 'dill', ) -# TODO (vng): this seems weirdly located in raven.conf. Seems like -# it's really a part of raven.transport.TransportRegistry -# Not quite sure what to do with this -def load(dsn, scope=None, transport_registry=None): - """ - Parses a Sentry compatible DSN and loads it - into the given scope. - - >>> import raven - - >>> dsn = 'https://public_key:secret_key@sentry.local/project_id' - - >>> # Apply configuration to local scope - >>> raven.load(dsn, locals()) - - >>> # Return DSN configuration - >>> options = raven.load(dsn) - """ - - if not transport_registry: - from raven.transport import TransportRegistry, default_transports - transport_registry = TransportRegistry(default_transports) - - url = urlparse(dsn) - - if not transport_registry.supported_scheme(url.scheme): - raise ValueError('Unsupported Sentry DSN scheme: %r' % url.scheme) - - if scope is None: - scope = {} - scope_extras = transport_registry.compute_scope(url, scope) - scope.update(scope_extras) - - return scope - - def setup_logging(handler, exclude=EXCLUDE_LOGGER_DEFAULTS): """ Configures logging to pipe to Sentry. diff --git a/raven/conf/defaults.py b/raven/conf/defaults.py index 77b6985d2..3505a098f 100644 --- a/raven/conf/defaults.py +++ b/raven/conf/defaults.py @@ -15,10 +15,7 @@ ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir)) -# This should be the full URL to sentries store view -SERVERS = None - -TIMEOUT = 1 +TIMEOUT = 5 # TODO: this is specific to Django CLIENT = 'raven.contrib.django.DjangoClient' @@ -28,20 +25,6 @@ # will set it to None and require it passed in to ``Client`` on initializtion. NAME = socket.gethostname() if hasattr(socket, 'gethostname') else None -# Superuser key -- will be used if set, otherwise defers to -# SECRET_KEY and PUBLIC_KEY -KEY = None - -# Credentials to authenticate with the Sentry server -SECRET_KEY = None -PUBLIC_KEY = None - -# We allow setting the site name either by explicitly setting it with the -# SENTRY_SITE setting, or using the django.contrib.sites framework for -# fetching the current site. Since we can't reliably query the database -# from this module, the specific logic is within the SiteFilter -SITE = None - # The maximum number of elements to store for a list-like structure. MAX_LENGTH_LIST = 50 @@ -51,10 +34,17 @@ # Automatically log frame stacks from all ``logging`` messages. AUTO_LOG_STACKS = False +# Collect locals variables +CAPTURE_LOCALS = True + # Client-side data processors to apply PROCESSORS = ( 'raven.processors.SanitizePasswordsProcessor', ) -# Default Project ID -PROJECT = 1 +try: + # Try for certifi first since they likely keep their bundle more up to date + import certifi + CA_BUNDLE = certifi.where() +except ImportError: + CA_BUNDLE = os.path.join(ROOT, 'data', 'cacert.pem') diff --git a/raven/conf/remote.py b/raven/conf/remote.py new file mode 100644 index 000000000..af4cb4b35 --- /dev/null +++ b/raven/conf/remote.py @@ -0,0 +1,126 @@ +from __future__ import absolute_import + +import logging +import os +import warnings + +from raven.utils.compat import PY2, text_type +from raven.exceptions import InvalidDsn +from raven.utils.encoding import to_string +from raven.utils.urlparse import parse_qsl, urlparse + +ERR_UNKNOWN_SCHEME = 'Unsupported Sentry DSN scheme: {0} ({1})' + +logger = logging.getLogger('raven') + + +def discover_default_transport(): + from raven.transport.threaded import ThreadedHTTPTransport + from raven.transport.http import HTTPTransport + + # Google App Engine + # https://cloud.google.com/appengine/docs/python/how-requests-are-handled#Python_The_environment + if 'CURRENT_VERSION_ID' in os.environ and 'INSTANCE_ID' in os.environ: + logger.info('Detected environment to be Google App Engine. Using synchronous HTTP transport.') + return HTTPTransport + + # AWS Lambda + # https://alestic.com/2014/11/aws-lambda-environment/ + if 'LAMBDA_TASK_ROOT' in os.environ: + logger.info('Detected environment to be AWS Lambda. Using synchronous HTTP transport.') + return HTTPTransport + + return ThreadedHTTPTransport + + +DEFAULT_TRANSPORT = discover_default_transport() + + +class RemoteConfig(object): + def __init__(self, base_url=None, project=None, public_key=None, + secret_key=None, transport=None, options=None): + if base_url: + base_url = base_url.rstrip('/') + store_endpoint = '%s/api/%s/store/' % (base_url, project) + else: + store_endpoint = None + + self.base_url = base_url + self.project = project + self.public_key = public_key + self.secret_key = secret_key + self.options = options or {} + self.store_endpoint = store_endpoint + + self._transport_cls = transport or DEFAULT_TRANSPORT + + def __unicode__(self): + return text_type(self.base_url) + + def __str__(self): + return text_type(self.base_url) + + def is_active(self): + return all([self.base_url, self.project, self.public_key]) + + def get_transport(self): + if not self.store_endpoint: + return + + if not hasattr(self, '_transport'): + self._transport = self._transport_cls(**self.options) + return self._transport + + def get_public_dsn(self): + url = urlparse(self.base_url) + netloc = url.hostname + if url.port: + netloc += ':%s' % url.port + return '//%s@%s%s/%s' % (self.public_key, netloc, url.path, self.project) + + @classmethod + def from_string(cls, value, transport=None, transport_registry=None): + # in Python 2.x sending the DSN as a unicode value will eventually + # cause issues in httplib + if PY2: + value = to_string(value) + + url = urlparse(value.strip()) + + if url.scheme not in ('http', 'https'): + warnings.warn('Transport selection via DSN is deprecated. You should explicitly pass the transport class to Client() instead.') + + if transport is None: + if not transport_registry: + from raven.transport import TransportRegistry, default_transports + transport_registry = TransportRegistry(default_transports) + + if not transport_registry.supported_scheme(url.scheme): + raise InvalidDsn(ERR_UNKNOWN_SCHEME.format(url.scheme, value)) + + transport = transport_registry.get_transport_cls(url.scheme) + + netloc = url.hostname + if url.port: + netloc += ':%s' % url.port + + path_bits = url.path.rsplit('/', 1) + if len(path_bits) > 1: + path = path_bits[0] + else: + path = '' + project = path_bits[-1] + + if not all([netloc, project, url.username]): + raise InvalidDsn('Invalid Sentry DSN: %r' % url.geturl()) + + base_url = '%s://%s%s' % (url.scheme.rsplit('+', 1)[-1], netloc, path) + + return cls( + base_url=base_url, + project=project, + public_key=url.username, + secret_key=url.password, + options=dict(parse_qsl(url.query)), + transport=transport, + ) diff --git a/raven/context.py b/raven/context.py index 5d1e0683d..072be935a 100644 --- a/raven/context.py +++ b/raven/context.py @@ -7,41 +7,139 @@ """ from __future__ import absolute_import -from raven.utils import six +try: + from collections.abc import Mapping, Iterable +except ImportError: + # Python < 3.3 + from collections import Mapping, Iterable +from threading import local +from weakref import ref as weakref +from raven.utils.compat import iteritems -class Context(object): +try: + from thread import get_ident as get_thread_ident +except ImportError: + from _thread import get_ident as get_thread_ident + + +_active_contexts = local() + + +def get_active_contexts(): + """Returns all the active contexts for the current thread.""" + try: + return list(_active_contexts.contexts) + except AttributeError: + return [] + + +class Context(local, Mapping, Iterable): """ - Create default context around a block of code for exception management. - - >>> with Context(client, tags={'key': 'value'}) as raven: - >>> # use the context manager's client reference - >>> raven.captureMessage('hello!') - >>> - >>> # uncaught exceptions also contain the context - >>> 1 / 0 + Stores context until cleared. + + >>> def view_handler(view_func, *args, **kwargs): + >>> context = Context() + >>> context.merge(tags={'key': 'value'}) + >>> try: + >>> return view_func(*args, **kwargs) + >>> finally: + >>> context.clear() """ - def __init__(self, client, **defaults): - self.client = client - self.defaults = defaults - self.result = None + + def __init__(self, client=None): + breadcrumbs = raven.breadcrumbs.make_buffer( + client is None or client.enable_breadcrumbs) + if client is not None: + client = weakref(client) + self._client = client + # Because the thread auto activates the thread local this also + # means that we auto activate this thing. Only if someone decides + # to deactivate manually later another call to activate is + # technically necessary. + self.activate() + self.data = {} + self.exceptions_to_skip = set() + self.breadcrumbs = breadcrumbs + + @property + def client(self): + if self._client is None: + return None + return self._client() + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return self is other + + def __ne__(self, other): + return not self.__eq__(other) + + def __getitem__(self, key): + return self.data[key] + + def __iter__(self): + return iter(self.data) + + def __len__(self): + return len(self.data) + + def __repr__(self): + return '<%s: %s>' % (type(self).__name__, self.data) def __enter__(self): + self.activate() return self - def __exit__(self, *exc_info): - if all(exc_info): - self.result = self.captureException(exc_info) + def __exit__(self, exc_type, exc_value, tb): + self.deactivate() + + def activate(self, sticky=False): + if sticky: + self._sticky_thread = get_thread_ident() + _active_contexts.__dict__.setdefault('contexts', set()).add(self) + + def deactivate(self): + try: + _active_contexts.contexts.discard(self) + except AttributeError: + pass + + def merge(self, data, activate=True): + if activate: + self.activate() + d = self.data + for key, value in iteritems(data): + if key in ('tags', 'extra'): + d.setdefault(key, {}) + for t_key, t_value in iteritems(value): + d[key][t_key] = t_value + else: + d[key] = value + + def set(self, data): + self.data = data + + def get(self): + return self.data + + def clear(self, deactivate=None): + self.data = {} + self.exceptions_to_skip.clear() + self.breadcrumbs.clear() - def __call(self, function, *args, **kwargs): - for key, value in six.iteritems(self.defaults): - if key not in kwargs: - kwargs[key] = value + # If the caller did not specify if it wants to deactivate the + # context for the thread we only deactivate it if we're not the + # thread that created the context (main thread). + if deactivate is None: + client = self.client + if client is not None: + deactivate = get_thread_ident() != client.main_thread_id - return function(*args, **kwargs) + if deactivate: + self.deactivate() - def captureException(self, *args, **kwargs): - return self.__call(self.client.captureException, *args, **kwargs) - def captureMessage(self, *args, **kwargs): - return self.__call(self.client.captureMessage, *args, **kwargs) +import raven.breadcrumbs diff --git a/raven/contrib/async.py b/raven/contrib/async.py index a272dba40..251e4f8e5 100644 --- a/raven/contrib/async.py +++ b/raven/contrib/async.py @@ -17,6 +17,7 @@ class AsyncClient(Client): """ This client uses a single background thread to dispatch errors. """ + def __init__(self, worker=None, *args, **kwargs): warnings.warn('AsyncClient is deprecated. Use the threaded+http transport instead.', DeprecationWarning) self.worker = worker or AsyncWorker() @@ -26,4 +27,4 @@ def send_sync(self, **kwargs): super(AsyncClient, self).send(**kwargs) def send(self, **kwargs): - self.worker.queue(self.send_sync, kwargs) + self.worker.queue(self.send_sync, **kwargs) diff --git a/raven/contrib/awslambda/__init__.py b/raven/contrib/awslambda/__init__.py new file mode 100644 index 000000000..56b2c3a9f --- /dev/null +++ b/raven/contrib/awslambda/__init__.py @@ -0,0 +1,177 @@ +""" +raven.contrib.awslambda +~~~~~~~~~~~~~~~~~~~~ + +Raven wrapper for AWS Lambda handlers. + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +# flake8: noqa + +from __future__ import absolute_import + +import os +import logging +import functools +from types import FunctionType + +from raven.base import Client +from raven.transport.http import HTTPTransport + +logger = logging.getLogger('sentry.errors.client') + + +def get_default_tags(): + return { + 'lambda': 'AWS_LAMBDA_FUNCTION_NAME', + 'version': 'AWS_LAMBDA_FUNCTION_VERSION', + 'memory_size': 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE', + 'log_group': 'AWS_LAMBDA_LOG_GROUP_NAME', + 'log_stream': 'AWS_LAMBDA_LOG_STREAM_NAME', + 'region': 'AWS_REGION' + } + + +class LambdaClient(Client): + """ + Raven decorator for AWS Lambda. + + By default, the lambda integration will capture unhandled exceptions and instrument logging. + + Usage: + + >>> from raven.contrib.awslambda import LambdaClient + >>> + >>> + >>> client = LambdaClient() + >>> + >>> @client.capture_exceptions + >>> def handler(event, context): + >>> ... + >>> raise Exception('I will be sent to sentry!') + + """ + + def __init__(self, *args, **kwargs): + transport = kwargs.pop('transport', HTTPTransport) + super(LambdaClient, self).__init__(*args, transport=transport, **kwargs) + + def capture(self, *args, **kwargs): + if 'data' not in kwargs: + kwargs['data'] = data = {} + else: + data = kwargs['data'] + event = kwargs.get('event', None) + context = kwargs.get('context', None) + + if event: + http_info = self._get_http_interface(event) + user_info = self._get_user_interface(event) + if http_info: + data.update(http_info) + if user_info: + data.update(user_info) + + if event and context: + data['extra'] = self._get_extra_data(event, context) + + return super(LambdaClient, self).capture(*args, **kwargs) + + def build_msg(self, *args, **kwargs): + + data = super(LambdaClient, self).build_msg(*args, **kwargs) + for option, default in get_default_tags().items(): + data['tags'].setdefault(option, os.environ.get(default)) + data.setdefault('release', os.environ.get('SENTRY_RELEASE')) + data.setdefault('environment', os.environ.get('SENTRY_ENVIRONMENT')) + return data + + def capture_exceptions(self, f=None, exceptions=None): # TODO: Ash fix kwargs in base + """ + Wrap a function or code block in try/except and automatically call + ``.captureException`` if it raises an exception, then the exception + is reraised. + + By default, it will capture ``Exception`` + + >>> @client.capture_exceptions + >>> def foo(): + >>> raise Exception() + + >>> with client.capture_exceptions(): + >>> raise Exception() + + You can also specify exceptions to be caught specifically + + >>> @client.capture_exceptions((IOError, LookupError)) + >>> def bar(): + >>> ... + + ``kwargs`` are passed through to ``.captureException``. + """ + if not isinstance(f, FunctionType): + # when the decorator has args which is not a function we except + # f to be the exceptions tuple + return functools.partial(self.capture_exceptions, exceptions=f) + + exceptions = exceptions or (Exception,) + + @functools.wraps(f) + def wrapped(event, context, *args, **kwargs): + try: + return f(event, context, *args, **kwargs) + except exceptions: + self.captureException(event=event, context=context, **kwargs) + self.context.clear() + raise + return wrapped + + @staticmethod + def _get_user_interface(event): + if event.get('requestContext'): + identity = event['requestContext']['identity'] + if identity: + user = { + 'id': identity.get('cognitoIdentityId', None) or identity.get('user', None), + 'username': identity.get('user', None), + 'ip_address': identity.get('sourceIp', None), + 'cognito_identity_pool_id': identity.get('cognitoIdentityPoolId', None), + 'cognito_authentication_type': identity.get('cognitoAuthenticationType', None), + 'user_agent': identity.get('userAgent') + } + return {'user': user} + + @staticmethod + def _get_http_interface(event): + if event.get('path') and event.get('httpMethod'): + request = { + "url": event.get('path'), + "method": event.get('httpMethod'), + "query_string": event.get('queryStringParameters', None), + "headers": event.get('headers', None) or [], + } + return {'request': request} + + @staticmethod + def _get_extra_data(event, context): + extra_context = { + 'event': event, + 'aws_request_id': context.aws_request_id, + 'context': vars(context), + } + + if context.client_context: + extra_context['client_context'] = { + 'client.installation_id': context.client_context.client.installation_id, + 'client.app_title': context.client_context.client.app_title, + 'client.app_version_name': context.client_context.client.app_version_name, + 'client.app_version_code': context.client_context.client.app_version_code, + 'client.app_package_name': context.client_context.client.app_package_name, + 'custom': context.client_context.custom, + 'env': context.client_context.env, + } + return extra_context + + + diff --git a/raven/contrib/bottle/__init__.py b/raven/contrib/bottle/__init__.py index 90e56f28c..de82c8585 100644 --- a/raven/contrib/bottle/__init__.py +++ b/raven/contrib/bottle/__init__.py @@ -37,6 +37,7 @@ class Sentry(object): >>> sentry.captureMessage('hello, world!') """ + def __init__(self, app, client, logging=False): self.app = app self.client = client @@ -65,7 +66,8 @@ def session_start_response(status, headers, exc_info=None): # catch ANY exception that goes through... except Exception: self.handle_exception(exc_info=sys.exc_info()) - return self.app(environ, session_start_response) + # re-raise the exception to let parent handlers deal with it + raise def captureException(self, *args, **kwargs): assert self.client, 'captureException called before application configured' diff --git a/raven/contrib/bottle/utils.py b/raven/contrib/bottle/utils.py index 9c7557782..fe7b18991 100644 --- a/raven/contrib/bottle/utils.py +++ b/raven/contrib/bottle/utils.py @@ -8,7 +8,7 @@ from __future__ import absolute_import import logging -from raven.utils.compat import _urlparse +from raven.utils.compat import urlparse from raven.utils.wsgi import get_headers, get_environ @@ -16,7 +16,7 @@ def get_data_from_request(request): - urlparts = _urlparse.urlsplit(request.url) + urlparts = urlparse.urlsplit(request.url) try: form_dict = request.forms.dict @@ -26,7 +26,7 @@ def get_data_from_request(request): formdata = {} data = { - 'sentry.interfaces.Http': { + 'request': { 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), 'query_string': urlparts.query, 'method': request.method, diff --git a/raven/contrib/celery/__init__.py b/raven/contrib/celery/__init__.py index a46b4ca7e..02f3ab00e 100644 --- a/raven/contrib/celery/__init__.py +++ b/raven/contrib/celery/__init__.py @@ -8,27 +8,12 @@ from __future__ import absolute_import import logging -try: - from celery.task import task -except ImportError: - from celery.decorators import task # NOQA -from celery.signals import after_setup_logger, task_failure -from raven.base import Client -from raven.handlers.logging import SentryHandler - - -class CeleryMixin(object): - def send_encoded(self, *args, **kwargs): - "Errors through celery" - self.send_raw.delay(*args, **kwargs) - - @task(routing_key='sentry') - def send_raw(self, *args, **kwargs): - return super(CeleryMixin, self).send_encoded(*args, **kwargs) - -class CeleryClient(CeleryMixin, Client): - pass +from celery.exceptions import SoftTimeLimitExceeded +from celery.signals import ( + after_setup_logger, task_failure, task_prerun, task_postrun +) +from raven.handlers.logging import SentryHandler class CeleryFilter(logging.Filter): @@ -41,28 +26,16 @@ def filter(self, record): return extra_data.get('internal', record.funcName != '_log_error') -def register_signal(client): - def process_failure_signal(sender, task_id, args, kwargs, **kw): - # This signal is fired inside the stack so let raven do its magic - client.captureException( - extra={ - 'task_id': task_id, - 'task': sender, - 'args': args, - 'kwargs': kwargs, - }) - - task_failure.connect(process_failure_signal, weak=False) +def register_signal(client, ignore_expected=False): + SentryCeleryHandler(client, ignore_expected=ignore_expected).install() -def register_logger_signal(client, logger=None): +def register_logger_signal(client, logger=None, loglevel=logging.ERROR): filter_ = CeleryFilter() - if logger is None: - logger = logging.getLogger() - handler = SentryHandler(client) - handler.setLevel(logging.ERROR) - handler.addFilter(filter_) + handler = SentryHandler(client) + handler.setLevel(loglevel) + handler.addFilter(filter_) def process_logger_event(sender, logger, loglevel, logfile, format, colorize, **kw): @@ -70,10 +43,54 @@ def process_logger_event(sender, logger, loglevel, logfile, format, # that the CeleryFilter is installed. # If one is found, we do not attempt to install another one. for h in logger.handlers: - if type(h) == SentryHandler: + if isinstance(h, SentryHandler): h.addFilter(filter_) return False logger.addHandler(handler) after_setup_logger.connect(process_logger_event, weak=False) + + +class SentryCeleryHandler(object): + def __init__(self, client, ignore_expected=False): + self.client = client + self.ignore_expected = ignore_expected + + def install(self): + task_prerun.connect(self.handle_task_prerun, weak=False) + task_postrun.connect(self.handle_task_postrun, weak=False) + task_failure.connect(self.process_failure_signal, weak=False) + + def uninstall(self): + task_prerun.disconnect(self.handle_task_prerun) + task_postrun.disconnect(self.handle_task_postrun) + task_failure.disconnect(self.process_failure_signal) + + def process_failure_signal(self, sender, task_id, args, kwargs, einfo, **kw): + if self.ignore_expected and hasattr(sender, 'throws') and isinstance(einfo.exception, sender.throws): + return + + # This signal is fired inside the stack so let raven do its magic + if isinstance(einfo.exception, SoftTimeLimitExceeded): + fingerprint = ['celery', 'SoftTimeLimitExceeded', getattr(sender, 'name', sender)] + else: + fingerprint = None + + self.client.captureException( + extra={ + 'task_id': task_id, + 'task': sender, + 'args': args, + 'kwargs': kwargs, + }, + fingerprint=fingerprint, + ) + + def handle_task_prerun(self, sender, task_id, task, **kw): + self.client.context.activate() + self.client.transaction.push(task.name) + + def handle_task_postrun(self, sender, task_id, task, **kw): + self.client.transaction.pop(task.name) + self.client.context.clear() diff --git a/raven/contrib/django/__init__.py b/raven/contrib/django/__init__.py index 7184f94a2..4ce0b9538 100644 --- a/raven/contrib/django/__init__.py +++ b/raven/contrib/django/__init__.py @@ -7,4 +7,6 @@ """ from __future__ import absolute_import +default_app_config = 'raven.contrib.django.apps.RavenConfig' + from .client import DjangoClient # NOQA diff --git a/raven/contrib/django/apps.py b/raven/contrib/django/apps.py new file mode 100644 index 000000000..db33b38a7 --- /dev/null +++ b/raven/contrib/django/apps.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import + +from django.apps import AppConfig + + +class RavenConfig(AppConfig): + name = 'raven.contrib.django' + label = 'raven_contrib_django' + verbose_name = 'Raven' + + def ready(self): + from .models import initialize + initialize() diff --git a/raven/contrib/django/celery/__init__.py b/raven/contrib/django/celery/__init__.py index 708bab72a..d38df731b 100644 --- a/raven/contrib/django/celery/__init__.py +++ b/raven/contrib/django/celery/__init__.py @@ -7,7 +7,6 @@ """ from __future__ import absolute_import -from raven.contrib.celery import CeleryMixin from raven.contrib.django.client import DjangoClient try: from celery.task import task @@ -15,13 +14,11 @@ from celery.decorators import task # NOQA -class CeleryClient(CeleryMixin, DjangoClient): - +class CeleryClient(DjangoClient): def send_integrated(self, kwargs): return send_raw_integrated.delay(kwargs) def send_encoded(self, *args, **kwargs): - "Errors through celery" return send_raw.delay(*args, **kwargs) diff --git a/raven/contrib/django/client.py b/raven/contrib/django/client.py index 43e73ccae..fa424bc70 100644 --- a/raven/contrib/django/client.py +++ b/raven/contrib/django/client.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ raven.contrib.django.client ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -8,56 +9,190 @@ from __future__ import absolute_import +import time import logging +from django import VERSION as DJANGO_VERSION from django.conf import settings from django.core.exceptions import SuspiciousOperation from django.http import HttpRequest from django.template import TemplateSyntaxError -from django.template.loader import LoaderOrigin +from django.utils.datastructures import MultiValueDict + +try: + # support Django 1.9 + from django.template.base import Origin +except ImportError: + # backward compatibility + from django.template.loader import LoaderOrigin as Origin from raven.base import Client from raven.contrib.django.utils import get_data_from_template, get_host -from raven.contrib.django.middleware import SentryLogMiddleware -from raven.utils.wsgi import get_headers, get_environ +from raven.contrib.django.middleware import SentryMiddleware +from raven.utils.compat import string_types, binary_type, iterlists +from raven.contrib.django.resolver import RouteResolver +from raven.utils.wsgi import get_headers, get_environ, get_client_ip +from raven.utils import once +from raven import breadcrumbs __all__ = ('DjangoClient',) +if DJANGO_VERSION < (1, 10): + def is_authenticated(request_user): + return request_user.is_authenticated() +else: + def is_authenticated(request_user): + return request_user.is_authenticated + + +class _FormatConverter(object): + + def __init__(self, param_mapping): + self.param_mapping = param_mapping + self.params = [] + + def __getitem__(self, val): + self.params.append(self.param_mapping.get(val)) + return '%s' + + +def format_sql(sql, params): + rv = [] + + if isinstance(params, dict): + conv = _FormatConverter(params) + if params: + sql = sql % conv + params = conv.params + else: + params = () + + for param in params or (): + if param is None: + rv.append('NULL') + elif isinstance(param, string_types): + if isinstance(param, binary_type): + param = param.decode('utf-8', 'replace') + if len(param) > 256: + param = param[:256] + u'…' + rv.append("'%s'" % param.replace("'", "''")) + else: + rv.append(repr(param)) + + return sql, rv + + +def record_sql(vendor, alias, start, duration, sql, params): + def processor(data): + real_sql, real_params = format_sql(sql, params) + if real_params: + try: + real_sql = real_sql % tuple(real_params) + except TypeError: + pass + # maybe category to 'django.%s.%s' % (vendor, alias or + # 'default') ? + data.update({ + 'message': real_sql, + 'category': 'query', + }) + breadcrumbs.record(processor=processor) + + +@once +def install_sql_hook(): + """If installed this causes Django's queries to be captured.""" + try: + from django.db.backends.utils import CursorWrapper + except ImportError: + from django.db.backends.util import CursorWrapper + + try: + real_execute = CursorWrapper.execute + real_executemany = CursorWrapper.executemany + except AttributeError: + # XXX(mitsuhiko): On some very old django versions (<1.6) this + # trickery would have to look different but I can't be bothered. + return + + def record_many_sql(vendor, alias, start, sql, param_list): + duration = time.time() - start + for params in param_list: + record_sql(vendor, alias, start, duration, sql, params) + + def execute(self, sql, params=None): + start = time.time() + try: + return real_execute(self, sql, params) + finally: + record_sql(self.db.vendor, getattr(self.db, 'alias', None), + start, time.time() - start, sql, params) + + def executemany(self, sql, param_list): + start = time.time() + try: + return real_executemany(self, sql, param_list) + finally: + record_many_sql(self.db.vendor, getattr(self.db, 'alias', None), + start, sql, param_list) + + CursorWrapper.execute = execute + CursorWrapper.executemany = executemany + breadcrumbs.ignore_logger('django.db.backends') + + class DjangoClient(Client): logger = logging.getLogger('sentry.errors.client.django') + resolver = RouteResolver() + + def __init__(self, *args, **kwargs): + install_sql_hook = kwargs.pop('install_sql_hook', True) + Client.__init__(self, *args, **kwargs) + if install_sql_hook: + self.install_sql_hook() - def is_enabled(self): - return bool(self.servers or 'sentry' in settings.INSTALLED_APPS) + def install_sql_hook(self): + install_sql_hook() - def get_user_info(self, user): - if not user.is_authenticated(): - return {} + def get_user_info(self, request): user_info = { - 'id': user.pk, + 'ip_address': get_client_ip(request.META), } + user = getattr(request, 'user', None) + if user is None: + return user_info - if hasattr(user, 'email'): - user_info['email'] = user.email - - if hasattr(user, 'get_username'): - user_info['username'] = user.get_username() - elif hasattr(user, 'username'): - user_info['username'] = user.username + try: + authenticated = is_authenticated(user) + if not authenticated: + return user_info + user_info['id'] = user.pk + + if hasattr(user, 'email'): + user_info['email'] = user.email + + if hasattr(user, 'get_username'): + user_info['username'] = user.get_username() + elif hasattr(user, 'username'): + user_info['username'] = user.username + except Exception: + # We expect that user objects can be somewhat broken at times + # and try to just handle as much as possible and ignore errors + # as good as possible here. + pass return user_info def get_data_from_request(self, request): - try: - from django.contrib.auth.models import AbstractBaseUser as BaseUser - except ImportError: - from django.contrib.auth.models import User as BaseUser # NOQA - - result = {} + rv = {} + self.update_data_from_request(request, rv) + return rv - if hasattr(request, 'user') and isinstance(request.user, BaseUser): - result['sentry.interfaces.User'] = self.get_user_info(request.user) + def update_data_from_request(self, request, result): + if result.get('user') is None: + result['user'] = self.get_user_info(request) try: uri = request.build_absolute_uri() @@ -71,22 +206,30 @@ def get_data_from_request(self, request): host = get_host(request) uri = '%s://%s%s' % (scheme, host, request.path) - if request.method != 'GET': + if request.method not in ('GET', 'HEAD'): try: data = request.body - except: + except Exception: try: - data = request.raw_post_data and request.raw_post_data or request.POST + data = request.raw_post_data except Exception: - # assume we had a partial read: - data = '' + # assume we had a partial read. + try: + data = request.POST or '' + except Exception: + data = '' + else: + if isinstance(data, MultiValueDict): + data = dict( + (k, v[0] if len(v) == 1 else v) + for k, v in iterlists(data)) else: data = None environ = request.META result.update({ - 'sentry.interfaces.Http': { + 'request': { 'method': request.method, 'url': uri, 'query_string': request.META.get('QUERY_STRING'), @@ -97,20 +240,16 @@ def get_data_from_request(self, request): } }) - return result - def build_msg(self, *args, **kwargs): data = super(DjangoClient, self).build_msg(*args, **kwargs) - stacktrace = data.get('sentry.interfaces.Stacktrace') - if stacktrace: - for frame in stacktrace['frames']: - module = frame.get('module') - if not module: - continue + for frame in self._iter_frames(data): + module = frame.get('module') + if not module: + continue - if module.startswith('django.'): - frame['in_app'] = False + if module.startswith('django.'): + frame['in_app'] = False if not self.site and 'django.contrib.sites' in settings.INSTALLED_APPS: try: @@ -120,63 +259,57 @@ def build_msg(self, *args, **kwargs): data['tags'].setdefault('site', site_name) except Exception: # Database error? Fallback to the id - data['tags'].setdefault('site', settings.SITE_ID) + try: + data['tags'].setdefault('site', settings.SITE_ID) + except AttributeError: + # SITE_ID wasn't set, so just ignore + pass return data def capture(self, event_type, request=None, **kwargs): - if 'data' not in kwargs: + if kwargs.get('data') is None: kwargs['data'] = data = {} else: data = kwargs['data'] if request is None: - request = getattr(SentryLogMiddleware.thread, 'request', None) + request = getattr(SentryMiddleware.thread, 'request', None) is_http_request = isinstance(request, HttpRequest) if is_http_request: - data.update(self.get_data_from_request(request)) + self.update_data_from_request(request, data) if kwargs.get('exc_info'): exc_value = kwargs['exc_info'][1] - # As of r16833 (Django) all exceptions may contain a ``django_template_source`` attribute (rather than the - # legacy ``TemplateSyntaxError.source`` check) which describes template information. - if hasattr(exc_value, 'django_template_source') or ((isinstance(exc_value, TemplateSyntaxError) and - isinstance(getattr(exc_value, 'source', None), (tuple, list)) and isinstance(exc_value.source[0], LoaderOrigin))): - source = getattr(exc_value, 'django_template_source', getattr(exc_value, 'source', None)) + # As of r16833 (Django) all exceptions may contain a + # ``django_template_source`` attribute (rather than the legacy + # ``TemplateSyntaxError.source`` check) which describes + # template information. As of Django 1.9 or so the new + # template debug thing showed up. + if hasattr(exc_value, 'django_template_source') or \ + ((isinstance(exc_value, TemplateSyntaxError) + and isinstance(getattr(exc_value, 'source', None), + (tuple, list)) + and isinstance(exc_value.source[0], Origin))) or \ + hasattr(exc_value, 'template_debug'): + source = getattr(exc_value, 'django_template_source', + getattr(exc_value, 'source', None)) + debug = getattr(exc_value, 'template_debug', None) if source is None: self.logger.info('Unable to get template source from exception') - data.update(get_data_from_template(source)) + data.update(get_data_from_template(source, debug)) result = super(DjangoClient, self).capture(event_type, **kwargs) if is_http_request and result: # attach the sentry object to the request request.sentry = { - 'project_id': data.get('project', self.project), - 'id': self.get_ident(result), + 'project_id': data.get('project', self.remote.project), + 'id': result, } return result - def send(self, **kwargs): - """ - Serializes and signs ``data`` and passes the payload off to ``send_remote`` - - If ``servers`` was passed into the constructor, this will serialize the data and pipe it to - each server using ``send_remote()``. Otherwise, this will communicate with ``sentry.models.GroupedMessage`` - directly. - """ - if self.servers: - return super(DjangoClient, self).send(**kwargs) - elif 'sentry' in settings.INSTALLED_APPS: - try: - return self.send_integrated(kwargs) - except Exception as e: - self.error_logger.error( - 'Unable to record event: %s\nEvent was: %r', e, - kwargs['message'], exc_info=True) - - def send_integrated(self, kwargs): - from sentry.models import Group - return Group.objects.from_kwargs(**kwargs) + def get_transaction_from_request(self, request): + return self.resolver.resolve(request.path) diff --git a/raven/contrib/django/handlers.py b/raven/contrib/django/handlers.py index 8ae6a0060..2a2942e5d 100644 --- a/raven/contrib/django/handlers.py +++ b/raven/contrib/django/handlers.py @@ -9,21 +9,25 @@ from __future__ import absolute_import import logging + from raven.handlers.logging import SentryHandler as BaseSentryHandler +from raven.utils import memoize class SentryHandler(BaseSentryHandler): - def __init__(self): - logging.Handler.__init__(self) + def __init__(self, *args, **kwargs): + # TODO(dcramer): we'd like to avoid this duplicate code, but we need + # to currently defer loading client due to Django loading patterns. + self.tags = kwargs.pop('tags', None) - def _get_client(self): - from raven.contrib.django.models import client + logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET)) + @memoize + def client(self): + # Import must be lazy for deffered Django loading + from raven.contrib.django.models import client return client - client = property(_get_client) - def _emit(self, record): request = getattr(record, 'request', None) - return super(SentryHandler, self)._emit(record, request=request) diff --git a/raven/contrib/django/management/__init__.py b/raven/contrib/django/management/__init__.py index b72d4527a..b0860a4ba 100644 --- a/raven/contrib/django/management/__init__.py +++ b/raven/contrib/django/management/__init__.py @@ -1,62 +1 @@ -""" -raven.contrib.django.raven.management -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details -:license: BSD, see LICENSE for more details. -""" from __future__ import absolute_import, print_function - -import sys - -from functools import wraps - -from django.conf import settings - - -def patch_base_command(cls): - """ - Patches ``cls.execute``, returning a boolean describing if the - attempt was successful. - """ - try: - original_func = cls.execute - except AttributeError: - # must not be a capable version of Django - return False - - if hasattr(original_func, '__raven_patched'): - return False - - def can_capture(cls): - return 'sentry' not in settings.INSTALLED_APPS - - @wraps(original_func) - def new_execute(self, *args, **kwargs): - try: - return original_func(self, *args, **kwargs) - except Exception: - - if can_capture(type(self)): - from raven.contrib.django.models import client - - client.captureException(extra={ - 'argv': sys.argv - }) - raise - - new_execute.__raven_patched = True - BaseCommand.execute = new_execute - - return True - -if ('raven.contrib.django' in settings.INSTALLED_APPS - or 'raven.contrib.django.raven_compat' in settings.INSTALLED_APPS): - - try: - from django.core.management.base import BaseCommand - except ImportError: - # give up - pass - else: - patch_base_command(BaseCommand) diff --git a/raven/contrib/django/management/commands/raven.py b/raven/contrib/django/management/commands/raven.py index 0a47e44c1..82c6d652b 100644 --- a/raven/contrib/django/management/commands/raven.py +++ b/raven/contrib/django/management/commands/raven.py @@ -2,7 +2,7 @@ raven.contrib.django.management.commands.raven ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details +:copyright: (c) 2010-2016 by the Sentry Team, see AUTHORS for more details :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import, print_function @@ -10,26 +10,62 @@ from django.core.management.base import BaseCommand from optparse import make_option from raven.scripts.runner import store_json, send_test_message + +import argparse +import django +import json import sys +import time + +DJANGO_18 = django.VERSION >= (1, 8, 0) + + +class StoreJsonAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + try: + value = json.loads(values[0]) + except ValueError: + print("Invalid JSON was used for option %s. Received: %s" % (self.dest, values[0])) + sys.exit(1) + + setattr(namespace, self.dest, value) class Command(BaseCommand): help = 'Commands to interact with the Sentry client' - option_list = BaseCommand.option_list + ( - make_option( - "--data", action="callback", callback=store_json, - type="string", nargs=1, dest="data"), - make_option( - "--tags", action="callback", callback=store_json, - type="string", nargs=1, dest="tags"), - ) - - def handle(self, *args, **options): - if len(args) != 1 or args[0] != 'test': + if not DJANGO_18: + option_list = BaseCommand.option_list + ( + make_option( + '--data', action='callback', callback=store_json, + type='string', nargs=1, dest='data'), + make_option( + '--tags', action='callback', callback=store_json, + type='string', nargs=1, dest='tags'), + ) + else: + def add_arguments(self, parser): + parser.add_argument( + 'command', nargs=1, + ) + parser.add_argument( + '--data', action=StoreJsonAction, + nargs=1, dest='data', + ) + parser.add_argument( + '--tags', action=StoreJsonAction, + nargs=1, dest='tags', + ) + + def handle(self, command=None, *args, **options): + if command not in ('test', ['test']): print('Usage: manage.py raven test') sys.exit(1) from raven.contrib.django.models import client - send_test_message(client, options) + send_test_message(client, { + 'tags': options.get('tags'), + 'data': options.get('data'), + }) + time.sleep(3) diff --git a/raven/contrib/django/middleware/__init__.py b/raven/contrib/django/middleware/__init__.py index 9be15f113..f7e80d88d 100644 --- a/raven/contrib/django/middleware/__init__.py +++ b/raven/contrib/django/middleware/__init__.py @@ -8,10 +8,19 @@ from __future__ import absolute_import -import threading import logging +import threading from django.conf import settings +from django.core.signals import request_finished + +try: + # Django >= 1.10 + from django.utils.deprecation import MiddlewareMixin +except ImportError: + # Not required for Django <= 1.9, see: + # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware + MiddlewareMixin = object def is_ignorable_404(uri): @@ -24,11 +33,17 @@ def is_ignorable_404(uri): ) -class Sentry404CatchMiddleware(object): +class Sentry404CatchMiddleware(MiddlewareMixin): def process_response(self, request, response): + if response.status_code != 404: + return response + + if is_ignorable_404(request.get_full_path()): + return response + from raven.contrib.django.models import client - if response.status_code != 404 or is_ignorable_404(request.get_full_path()) or not client.is_enabled(): + if not client.is_enabled(): return response data = client.get_data_from_request(request) @@ -37,8 +52,11 @@ def process_response(self, request, response): 'logger': 'http404', }) result = client.captureMessage(message='Page Not Found: %s' % request.build_absolute_uri(), data=data) + if not result: + return + request.sentry = { - 'project_id': data.get('project', client.project), + 'project_id': data.get('project', client.remote.project), 'id': client.get_ident(result), } return response @@ -46,11 +64,12 @@ def process_response(self, request, response): # sentry_exception_handler(sender=Sentry404CatchMiddleware, request=request) -class SentryResponseErrorIdMiddleware(object): +class SentryResponseErrorIdMiddleware(MiddlewareMixin): """ Appends the X-Sentry-ID response header for referencing a message within the Sentry datastore. """ + def process_response(self, request, response): if not getattr(request, 'sentry', None): return response @@ -58,9 +77,64 @@ def process_response(self, request, response): return response -class SentryLogMiddleware(object): - # Create a threadlocal variable to store the session in for logging +class SentryMiddleware(MiddlewareMixin): thread = threading.local() def process_request(self, request): - self.thread.request = request + self._txid = None + + SentryMiddleware.thread.request = request + # we utilize request_finished as the exception gets reported + # *after* process_response is executed, and thus clearing the + # transaction there would leave it empty + # XXX(dcramer): weakref's cause a threading issue in certain + # versions of Django (e.g. 1.6). While they'd be ideal, we're under + # the assumption that Django will always call our function except + # in the situation of a process or thread dying. + request_finished.connect(self.request_finished, weak=False) + + def process_view(self, request, func, args, kwargs): + from raven.contrib.django.models import client + + try: + self._txid = client.transaction.push( + client.get_transaction_from_request(request) + ) + except Exception as exc: + client.error_logger.exception(repr(exc), extra={'request': request}) + + return None + + def request_finished(self, **kwargs): + from raven.contrib.django.models import client + + if getattr(self, '_txid', None): + client.transaction.pop(self._txid) + self._txid = None + + SentryMiddleware.thread.request = None + + request_finished.disconnect(self.request_finished) + + +SentryLogMiddleware = SentryMiddleware + + +class DjangoRestFrameworkCompatMiddleware(MiddlewareMixin): + + non_cacheable_types = ( + 'application/x-www-form-urlencoded', + 'multipart/form-data', + 'application/octet-stream' + ) + + def process_request(self, request): + """ + Access request.body, otherwise it might not be accessible later + after request has been read/streamed + """ + content_type = request.META.get('CONTENT_TYPE', '') + for non_cacheable_type in self.non_cacheable_types: + if non_cacheable_type in content_type: + return + request.body # forces stream to be read into memory diff --git a/raven/contrib/django/middleware/wsgi.py b/raven/contrib/django/middleware/wsgi.py index 643fb328c..e384bb08b 100644 --- a/raven/contrib/django/middleware/wsgi.py +++ b/raven/contrib/django/middleware/wsgi.py @@ -8,6 +8,7 @@ from __future__ import absolute_import from raven.middleware import Sentry +from raven.utils import memoize class Sentry(Sentry): @@ -18,10 +19,11 @@ class Sentry(Sentry): >>> from raven.contrib.django.middleware.wsgi import Sentry >>> application = Sentry(application) """ + def __init__(self, application): self.application = application - @property + @memoize def client(self): from raven.contrib.django.models import client return client diff --git a/raven/contrib/django/models.py b/raven/contrib/django/models.py index c6be79ed1..d2e350e4c 100644 --- a/raven/contrib/django/models.py +++ b/raven/contrib/django/models.py @@ -7,18 +7,22 @@ :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ +# flake8: noqa -from __future__ import absolute_import -from __future__ import unicode_literals +from __future__ import absolute_import, unicode_literals -from hashlib import md5 import logging import sys import warnings -from raven.utils import six +import django +from django.conf import settings +from django.core.signals import got_request_exception, request_started +from threading import Lock -from django.conf import settings as django_settings +from raven.utils.conf import convert_options +from raven.utils.compat import PY2, binary_type, text_type +from raven.utils.imports import import_string logger = logging.getLogger('sentry.errors.client') @@ -27,7 +31,7 @@ def get_installed_apps(): """ Modules in settings.INSTALLED_APPS as a set. """ - return set(django_settings.INSTALLED_APPS) + return set(settings.INSTALLED_APPS) _client = (None, None) @@ -35,7 +39,7 @@ def get_installed_apps(): class ProxyClient(object): """ - A proxy which represents the currenty client at all times. + A proxy which represents the currently client at all times. """ # introspection support: __members__ = property(lambda x: x.__dir__()) @@ -57,7 +61,7 @@ class ProxyClient(object): __ne__ = lambda x, o: get_client() != o __gt__ = lambda x, o: get_client() > o __ge__ = lambda x, o: get_client() >= o - if not six.PY3: + if PY2: __cmp__ = lambda x, o: cmp(get_client(), o) # NOQA __hash__ = lambda x: hash(get_client()) # attributes are currently not callable @@ -88,11 +92,11 @@ class ProxyClient(object): __invert__ = lambda x: ~(get_client()) __complex__ = lambda x: complex(get_client()) __int__ = lambda x: int(get_client()) - if not six.PY3: + if PY2: __long__ = lambda x: long(get_client()) # NOQA __float__ = lambda x: float(get_client()) - __str__ = lambda x: six.binary_type(get_client()) - __unicode__ = lambda x: six.text_type(get_client()) + __str__ = lambda x: binary_type(get_client()) + __unicode__ = lambda x: text_type(get_client()) __oct__ = lambda x: oct(get_client()) __hex__ = lambda x: hex(get_client()) __index__ = lambda x: get_client().__index__() @@ -103,63 +107,38 @@ class ProxyClient(object): client = ProxyClient() -def get_option(x, d=None): - options = getattr(django_settings, 'RAVEN_CONFIG', {}) - - return getattr(django_settings, 'SENTRY_%s' % x, options.get(x, d)) - - -def get_client(client=None): +def get_client(client=None, reset=False): global _client tmp_client = client is not None if not tmp_client: - client = getattr(django_settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient') - - if _client[0] != client: - module, class_name = client.rsplit('.', 1) - - ga = lambda x, d=None: getattr(django_settings, 'SENTRY_%s' % x, d) - options = getattr(django_settings, 'RAVEN_CONFIG', {}) - options.setdefault('servers', ga('SERVERS')) - options.setdefault('include_paths', ga('INCLUDE_PATHS', [])) - options['include_paths'] = set(options['include_paths']) | get_installed_apps() - options.setdefault('exclude_paths', ga('EXCLUDE_PATHS')) - options.setdefault('timeout', ga('TIMEOUT')) - options.setdefault('name', ga('NAME')) - options.setdefault('auto_log_stacks', ga('AUTO_LOG_STACKS')) - options.setdefault('key', ga('KEY', md5(django_settings.SECRET_KEY.encode('utf8')).hexdigest())) - options.setdefault('string_max_length', ga('MAX_LENGTH_STRING')) - options.setdefault('list_max_length', ga('MAX_LENGTH_LIST')) - options.setdefault('site', ga('SITE')) - options.setdefault('public_key', ga('PUBLIC_KEY')) - options.setdefault('secret_key', ga('SECRET_KEY')) - options.setdefault('project', ga('PROJECT')) - options.setdefault('processors', ga('PROCESSORS')) - options.setdefault('dsn', ga('DSN')) - options.setdefault('context', ga('CONTEXT')) - - class_name = str(class_name) - - instance = getattr(__import__(module, {}, {}, class_name), class_name)(**options) - if not tmp_client: - _client = (client, instance) - return instance - return _client[1] - + client = getattr(settings, 'SENTRY_CLIENT', 'raven.contrib.django.DjangoClient') -def sentry_exception_handler(request=None, **kwargs): - exc_type = sys.exc_info()[0] + if _client[0] != client or reset: + options = convert_options( + settings, + defaults={ + 'include_paths': get_installed_apps(), + }, + ) - exclusions = set(get_option('IGNORE_EXCEPTIONS', ())) + try: + Client = import_string(client) + except ImportError: + logger.exception('Failed to import client: %s', client) + if not _client[1]: + # If there is no previous client, set the default one. + client = 'raven.contrib.django.DjangoClient' + _client = (client, get_client(client)) + else: + instance = Client(**options) + if not tmp_client: + _client = (client, instance) + return instance + return _client[1] - exc_name = '%s.%s' % (exc_type.__module__, exc_type.__name__) - if exc_type.__name__ in exclusions or exc_name in exclusions or any(exc_name.startswith(e[:-1]) for e in exclusions if e.endswith('*')): - logger.info( - 'Not capturing exception due to filters: %s', exc_type, - exc_info=sys.exc_info()) - return +def sentry_exception_handler(request=None, **kwargs): try: client.captureException(exc_info=sys.exc_info(), request=request) except Exception as exc: @@ -169,51 +148,131 @@ def sentry_exception_handler(request=None, **kwargs): warnings.warn('Unable to process log entry: %s' % (exc,)) -def register_handlers(): - from django.core.signals import got_request_exception +class SentryDjangoHandler(object): + def __init__(self, client=client): + self.client = client - # HACK: support Sentry's internal communication - if 'sentry' in django_settings.INSTALLED_APPS: - from django.db import transaction + try: + import celery + except ImportError: + self.has_celery = False + else: + self.has_celery = celery.VERSION >= (2, 5) - @transaction.commit_on_success - def wrap_sentry(request, **kwargs): - if transaction.is_dirty(): - transaction.rollback() - return sentry_exception_handler(request, **kwargs) + self.celery_handler = None - exception_handler = wrap_sentry - else: - exception_handler = sentry_exception_handler + def install_celery(self): + from raven.contrib.celery import ( + SentryCeleryHandler, register_logger_signal + ) - # Connect to Django's internal signal handler - got_request_exception.connect(exception_handler, weak=False) + ignore_expected = getattr(settings, + 'SENTRY_CELERY_IGNORE_EXPECTED', + False) - # If Celery is installed, register a signal handler - if 'djcelery' in django_settings.INSTALLED_APPS: - try: - # Celery < 2.5? is not supported - from raven.contrib.celery import ( - register_signal, register_logger_signal) - except ImportError: - logger.exception('Failed to install Celery error handler') - else: + self.celery_handler = SentryCeleryHandler(client, + ignore_expected=ignore_expected)\ + .install() + + # try: + # ga = lambda x, d=None: getattr(settings, 'SENTRY_%s' % x, d) + # options = getattr(settings, 'RAVEN_CONFIG', {}) + # loglevel = options.get('celery_loglevel', + # ga('CELERY_LOGLEVEL', logging.ERROR)) + + # register_logger_signal(client, loglevel=loglevel) + # except Exception: + # logger.exception('Failed to install Celery error handler') + + def install(self): + request_started.connect(self.before_request, weak=False) + got_request_exception.connect(self.exception_handler, weak=False) + + if self.has_celery: try: - register_signal(client) + self.install_celery() except Exception: logger.exception('Failed to install Celery error handler') + def uninstall(self): + request_started.disconnect(self.before_request) + got_request_exception.disconnect(self.exception_handler) + + if self.celery_handler: + self.celery_handler.uninstall() + + def exception_handler(self, request=None, **kwargs): + try: + self.client.captureException(exc_info=sys.exc_info(), request=request) + except Exception as exc: try: - register_logger_signal(client) - except Exception: - logger.exception('Failed to install Celery error handler') + logger.exception('Unable to process log entry: %s' % (exc,)) + except Exception as exc: + warnings.warn('Unable to process log entry: %s' % (exc,)) + + def before_request(self, *args, **kwargs): + self.client.context.activate() def register_serializers(): # force import so serializers can call register import raven.contrib.django.serializers # NOQA -if ('raven.contrib.django' in django_settings.INSTALLED_APPS - or 'raven.contrib.django.raven_compat' in django_settings.INSTALLED_APPS): - register_handlers() - register_serializers() + +def install_middleware(middleware_name, lookup_names=None): + """ + Install specified middleware + """ + if lookup_names is None: + lookup_names = (middleware_name,) + # default settings.MIDDLEWARE is None + middleware_attr = 'MIDDLEWARE' if getattr(settings, + 'MIDDLEWARE', + None) is not None \ + else 'MIDDLEWARE_CLASSES' + # make sure to get an empty tuple when attr is None + middleware = getattr(settings, middleware_attr, ()) or () + if set(lookup_names).isdisjoint(set(middleware)): + setattr(settings, + middleware_attr, + type(middleware)((middleware_name,)) + middleware) + + +_setup_lock = Lock() + +_initialized = False + +def initialize(): + global _initialized + + with _setup_lock: + if _initialized: + return + + # mark this as initialized immediatley to avoid recursive import issues + _initialized = True + + try: + register_serializers() + install_middleware( + 'raven.contrib.django.middleware.SentryMiddleware', + ( + 'raven.contrib.django.middleware.SentryMiddleware', + 'raven.contrib.django.middleware.SentryLogMiddleware')) + install_middleware( + 'raven.contrib.django.middleware.DjangoRestFrameworkCompatMiddleware') + + # XXX(dcramer): maybe this setting should disable ALL of this? + if not getattr(settings, 'DISABLE_SENTRY_INSTRUMENTATION', False): + handler = SentryDjangoHandler() + handler.install() + + # instantiate client so hooks get registered + get_client() # NOQA + except Exception: + _initialized = False + +# Django 1.7 uses ``raven.contrib.django.apps.RavenConfig`` +if django.VERSION < (1, 7, 0): + initialize() + diff --git a/raven/contrib/django/resolver.py b/raven/contrib/django/resolver.py new file mode 100644 index 000000000..3f3c74f4c --- /dev/null +++ b/raven/contrib/django/resolver.py @@ -0,0 +1,99 @@ +from __future__ import absolute_import + +import re + +try: + from django.urls import get_resolver +except ImportError: + from django.core.urlresolvers import get_resolver + + +def get_regex(resolver_or_pattern): + """Utility method for django's deprecated resolver.regex""" + try: + regex = resolver_or_pattern.regex + except AttributeError: + regex = resolver_or_pattern.pattern.regex + return regex + + +class RouteResolver(object): + _optional_group_matcher = re.compile(r'\(\?\:([^\)]+)\)') + _named_group_matcher = re.compile(r'\(\?P<(\w+)>[^\)]+\)') + _non_named_group_matcher = re.compile(r'\([^\)]+\)') + # [foo|bar|baz] + _either_option_matcher = re.compile(r'\[([^\]]+)\|([^\]]+)\]') + _camel_re = re.compile(r'([A-Z]+)([a-z])') + + _cache = {} + + def _simplify(self, pattern): + r""" + Clean up urlpattern regexes into something readable by humans: + + From: + > "^(?P\w+)/athletes/(?P\w+)/$" + + To: + > "{sport_slug}/athletes/{athlete_slug}/" + """ + # remove optional params + # TODO(dcramer): it'd be nice to change these into [%s] but it currently + # conflicts with the other rules because we're doing regexp matches + # rather than parsing tokens + result = self._optional_group_matcher.sub(lambda m: '%s' % m.group(1), pattern) + + # handle named groups first + result = self._named_group_matcher.sub(lambda m: '{%s}' % m.group(1), result) + + # handle non-named groups + result = self._non_named_group_matcher.sub('{var}', result) + + # handle optional params + result = self._either_option_matcher.sub(lambda m: m.group(1), result) + + # clean up any outstanding regex-y characters. + result = result.replace('^', '').replace('$', '') \ + .replace('?', '').replace('//', '/').replace('\\', '') + + return result + + def _resolve(self, resolver, path, parents=None): + + match = get_regex(resolver).search(path) # Django < 2.0 + + if not match: + return + + if parents is None: + parents = [resolver] + elif resolver not in parents: + parents = parents + [resolver] + + new_path = path[match.end():] + for pattern in resolver.url_patterns: + # this is an include() + if not pattern.callback: + match = self._resolve(pattern, new_path, parents) + if match: + return match + continue + elif not get_regex(pattern).search(new_path): + continue + + try: + return self._cache[pattern] + except KeyError: + pass + + prefix = ''.join(self._simplify(get_regex(p).pattern) for p in parents) + result = prefix + self._simplify(get_regex(pattern).pattern) + if not result.startswith('/'): + result = '/' + result + self._cache[pattern] = result + return result + + def resolve(self, path, urlconf=None): + resolver = get_resolver(urlconf) + match = self._resolve(resolver, path) + return match or path diff --git a/raven/contrib/django/serializers.py b/raven/contrib/django/serializers.py index b0e1e0f5a..f18f28353 100644 --- a/raven/contrib/django/serializers.py +++ b/raven/contrib/django/serializers.py @@ -12,7 +12,7 @@ from django.http import HttpRequest from django.utils.functional import Promise from raven.utils.serializer import Serializer, register -from raven.utils import six +from raven.utils.compat import text_type __all__ = ('PromiseSerializer',) @@ -25,23 +25,27 @@ def can(self, value): return False pre = value.__class__.__name__[1:] - if not (hasattr(value, '%s__func' % pre) or - hasattr(value, '%s__unicode_cast' % pre) or - hasattr(value, '%s__text_cast' % pre)): + if not (hasattr(value, '%s__func' % pre) + or hasattr(value, '%s__unicode_cast' % pre) + or hasattr(value, '%s__text_cast' % pre)): return False return True def serialize(self, value, **kwargs): # EPIC HACK - # handles lazy model instances (which are proxy values that dont easily give you the actual function) + # handles lazy model instances (which are proxy values that don't + # easily give you the actual function) pre = value.__class__.__name__[1:] if hasattr(value, '%s__func' % pre): - value = getattr(value, '%s__func' % pre)(*getattr(value, '%s__args' % pre), **getattr(value, '%s__kw' % pre)) + value = getattr(value, '%s__func' % pre)( + *getattr(value, '%s__args' % pre), + **getattr(value, '%s__kw' % pre)) else: - return self.recurse(six.text_type(value)) + return self.recurse(text_type(value)) return self.recurse(value, **kwargs) + register(PromiseSerializer) @@ -51,6 +55,7 @@ class HttpRequestSerializer(Serializer): def serialize(self, value, **kwargs): return '<%s at 0x%s>' % (type(value).__name__, id(value)) + register(HttpRequestSerializer) diff --git a/raven/contrib/django/urls.py b/raven/contrib/django/urls.py index f9c17b38a..884621c6d 100644 --- a/raven/contrib/django/urls.py +++ b/raven/contrib/django/urls.py @@ -8,12 +8,14 @@ from __future__ import absolute_import try: - from django.conf.urls import patterns, url + from django.conf.urls import url except ImportError: - # for Django version less then 1.4 - from django.conf.urls.defaults import patterns, url # NOQA + # for Django version less than 1.4 + from django.conf.urls.defaults import url # NOQA -urlpatterns = patterns('', - url(r'^api/(?:(?P[\w_-]+)/)?store/$', 'raven.contrib.django.views.report', name='raven-report'), - url(r'^report/', 'raven.contrib.django.views.report'), +import raven.contrib.django.views + +urlpatterns = ( + url(r'^api/(?P[\w_-]+)/store/$', raven.contrib.django.views.report, name='raven-report'), + url(r'^report/', raven.contrib.django.views.report), ) diff --git a/raven/contrib/django/utils.py b/raven/contrib/django/utils.py index 39d7c8a35..e4738c308 100644 --- a/raven/contrib/django/utils.py +++ b/raven/contrib/django/utils.py @@ -8,6 +8,7 @@ from __future__ import absolute_import +import os from django.conf import settings @@ -20,36 +21,63 @@ def linebreak_iter(template_source): yield len(template_source) + 1 -def get_data_from_template(source): - origin, (start, end) = source - template_source = origin.reload() +def get_data_from_template(source, debug=None): + def _remove_numbers(items): + rv = [] + for item in items: + # Some debug info from django has tuples in the form (lineno, + # code) instead of just the code there. + if isinstance(item, (list, tuple)) and len(item) == 2: + item = item[1] + rv.append(item) + return rv - lineno = None - upto = 0 - source_lines = [] - for num, next in enumerate(linebreak_iter(template_source)): - if start >= upto and end <= next: - lineno = num - source_lines.append(template_source[upto:next]) - upto = next + if debug is not None: + lineno = debug['line'] + filename = debug['name'] + source_lines = [] + source_lines += [''] * (debug['source_lines'][0][0]) + for num, line in debug['source_lines']: + source_lines.append(line) + source_lines += [''] * 4 + elif source: + origin, (start, end) = source + filename = culprit = getattr(origin, 'loadname', None) + template_source = origin.reload() + lineno = None + upto = 0 + source_lines = [] + for num, next in enumerate(linebreak_iter(template_source)): + if start >= upto and end <= next: + lineno = num + source_lines.append(template_source[upto:next]) + upto = next - if not source_lines or lineno is None: - return {} + if not source_lines or lineno is None: + return {} + else: + raise TypeError('Source or debug needed') + + if filename is None: + filename = '' + culprit = '' + else: + culprit = filename.split('/templates/')[-1] - pre_context = source_lines[max(lineno - 3, 0):lineno] - post_context = source_lines[(lineno + 1):(lineno + 4)] - context_line = source_lines[lineno] + pre_context = _remove_numbers(source_lines[max(lineno - 3, 0):lineno]) + post_context = _remove_numbers(source_lines[(lineno + 1):(lineno + 4)]) + context_line = _remove_numbers([source_lines[lineno]])[0] return { - 'sentry.interfaces.Template': { - 'filename': origin.loadname, - 'abs_path': origin.name, + 'template': { + 'filename': os.path.basename(filename), + 'abs_path': filename, 'pre_context': pre_context, 'context_line': context_line, 'lineno': lineno, 'post_context': post_context, }, - 'culprit': origin.loadname, + 'culprit': culprit, } diff --git a/raven/contrib/django/views.py b/raven/contrib/django/views.py index bba95e22c..2e7dd6d0f 100644 --- a/raven/contrib/django/views.py +++ b/raven/contrib/django/views.py @@ -15,9 +15,9 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods +from raven.utils.compat import string_types from raven.contrib.django.models import client from raven.utils import json -from raven.utils import six def is_valid_origin(origin): @@ -32,7 +32,7 @@ def is_valid_origin(origin): origin = origin.lower() for value in settings.SENTRY_ALLOW_ORIGIN: - if isinstance(value, six.string_types): + if isinstance(value, string_types): if value.lower() == origin: return True else: diff --git a/raven/contrib/flask.py b/raven/contrib/flask.py new file mode 100644 index 000000000..0a3f75065 --- /dev/null +++ b/raven/contrib/flask.py @@ -0,0 +1,344 @@ +""" +raven.contrib.flask +~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" + +from __future__ import absolute_import + +try: + from flask_login import current_user +except ImportError: + has_flask_login = False +else: + has_flask_login = True + +import logging + +import blinker +from flask import request, current_app, g +from flask.signals import got_request_exception, request_finished +from werkzeug.exceptions import ClientDisconnected + +from raven.conf import setup_logging +from raven.base import Client +from raven.middleware import Sentry as SentryMiddleware +from raven.handlers.logging import SentryHandler +from raven.utils.compat import urlparse +from raven.utils.encoding import to_unicode +from raven.utils.wsgi import get_headers, get_environ +from raven.utils.conf import convert_options + + +raven_signals = blinker.Namespace() +logging_configured = raven_signals.signal('logging_configured') + + +def make_client(client_cls, app, dsn=None): + return client_cls( + **convert_options( + app.config, + defaults={ + 'dsn': dsn, + 'include_paths': ( + set(app.config.get('SENTRY_INCLUDE_PATHS', [])) + | set([app.import_name]) + ), + # support legacy RAVEN_IGNORE_EXCEPTIONS + 'ignore_exceptions': app.config.get('RAVEN_IGNORE_EXCEPTIONS', []), + 'extra': { + 'app': app, + }, + }, + ) + ) + + +class Sentry(object): + """ + Flask application for Sentry. + + Look up configuration from ``os.environ['SENTRY_DSN']``:: + + >>> sentry = Sentry(app) + + Pass an arbitrary DSN:: + + >>> sentry = Sentry(app, dsn='http://public:secret@example.com/1') + + Pass an explicit client:: + + >>> sentry = Sentry(app, client=client) + + Automatically configure logging:: + + >>> sentry = Sentry(app, logging=True, level=logging.ERROR) + + Capture an exception:: + + >>> try: + >>> 1 / 0 + >>> except ZeroDivisionError: + >>> sentry.captureException() + + Capture a message:: + + >>> sentry.captureMessage('hello, world!') + + By default, the Flask integration will do the following: + + - Hook into the `got_request_exception` signal. This can be disabled by + passing `register_signal=False`. + - Wrap the WSGI application. This can be disabled by passing + `wrap_wsgi=False`. + - Capture information from Flask-Login (if available). + """ + + # TODO(dcramer): the client isn't using local context and therefore + # gets shared by every app that does init on it + def __init__(self, app=None, client=None, client_cls=Client, dsn=None, + logging=False, logging_exclusions=None, level=logging.NOTSET, + wrap_wsgi=None, register_signal=True): + if client and not isinstance(client, Client): + raise TypeError('client should be an instance of Client') + + self.dsn = dsn + self.logging = logging + self.logging_exclusions = logging_exclusions + self.client_cls = client_cls + self.client = client + self.level = level + self.wrap_wsgi = wrap_wsgi + self.register_signal = register_signal + + if app: + self.init_app(app) + + @property + def last_event_id(self): + try: + return g.sentry_event_id + except Exception: + pass + return getattr(self, '_last_event_id', None) + + @last_event_id.setter + def last_event_id(self, value): + self._last_event_id = value + try: + g.sentry_event_id = value + except Exception: + pass + + def handle_exception(self, *args, **kwargs): + if not self.client: + return + + # got_request_exception signal passes the exception as 'exception' + exception = kwargs.get('exception') + if exception is not None and hasattr(exception, '__traceback__'): + # On Python 3 we can contruct the exc_info via __traceback__ + exc_info = (type(exception), exception, exception.__traceback__) + else: + # The user may call the method with 'exc_info' manually + exc_info = kwargs.get('exc_info') + + self.captureException(exc_info=exc_info) + + def get_user_info(self, request): + """ + Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/) + to be installed and setup. + """ + user_info = {} + + try: + ip_address = request.access_route[0] + except IndexError: + ip_address = request.remote_addr + + if ip_address: + user_info['ip_address'] = ip_address + + if not has_flask_login: + return user_info + + if not hasattr(current_app, 'login_manager'): + return user_info + + try: + is_authenticated = current_user.is_authenticated + except AttributeError: + # HACK: catch the attribute error thrown by flask-login is not attached + # > current_user = LocalProxy(lambda: _request_ctx_stack.top.user) + # E AttributeError: 'RequestContext' object has no attribute 'user' + return user_info + + if callable(is_authenticated): + is_authenticated = is_authenticated() + + if not is_authenticated: + return user_info + + user_info['id'] = current_user.get_id() + + if 'SENTRY_USER_ATTRS' in current_app.config: + for attr in current_app.config['SENTRY_USER_ATTRS']: + if hasattr(current_user, attr): + user_info[attr] = getattr(current_user, attr) + + return user_info + + def get_http_info(self, request): + """ + Determine how to retrieve actual data by using request.mimetype. + """ + if self.is_json_type(request.mimetype): + retriever = self.get_json_data + else: + retriever = self.get_form_data + return self.get_http_info_with_retriever(request, retriever) + + def is_json_type(self, content_type): + return content_type == 'application/json' + + def get_form_data(self, request): + return request.form + + def get_json_data(self, request): + return request.data + + def get_http_info_with_retriever(self, request, retriever=None): + """ + Exact method for getting http_info but with form data work around. + """ + if retriever is None: + retriever = self.get_form_data + + urlparts = urlparse.urlsplit(request.url) + + try: + data = retriever(request) + except ClientDisconnected: + data = {} + + return { + 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), + 'query_string': urlparts.query, + 'method': request.method, + 'data': data, + 'headers': dict(get_headers(request.environ)), + 'env': dict(get_environ(request.environ)), + } + + def before_request(self, *args, **kwargs): + self.last_event_id = None + + if request.url_rule: + self.client.transaction.push(request.url_rule.rule) + + try: + self.client.http_context(self.get_http_info(request)) + except Exception as e: + self.client.logger.exception(to_unicode(e)) + try: + self.client.user_context(self.get_user_info(request)) + except Exception as e: + self.client.logger.exception(to_unicode(e)) + + def after_request(self, sender, response, *args, **kwargs): + if self.last_event_id: + response.headers['X-Sentry-ID'] = self.last_event_id + self.client.context.clear() + if request.url_rule: + self.client.transaction.pop(request.url_rule.rule) + return response + + def init_app(self, app, dsn=None, logging=None, level=None, + logging_exclusions=None, wrap_wsgi=None, + register_signal=None): + if dsn is not None: + self.dsn = dsn + + if level is not None: + self.level = level + + if wrap_wsgi is not None: + self.wrap_wsgi = wrap_wsgi + elif self.wrap_wsgi is None: + # Fix https://github.com/getsentry/raven-python/issues/412 + # the gist is that we get errors twice in debug mode if we don't do this + if app and app.debug: + self.wrap_wsgi = False + else: + self.wrap_wsgi = True + + if register_signal is not None: + self.register_signal = register_signal + + if logging is not None: + self.logging = logging + + if logging_exclusions is not None: + self.logging_exclusions = logging_exclusions + + if not self.client: + self.client = make_client(self.client_cls, app, self.dsn) + + if self.logging: + kwargs = {} + if self.logging_exclusions is not None: + kwargs['exclude'] = self.logging_exclusions + handler = SentryHandler(self.client, level=self.level) + setup_logging(handler, **kwargs) + + if app.logger.propagate is False: + app.logger.addHandler(handler) + + logging_configured.send( + self, sentry_handler=SentryHandler, **kwargs) + + if self.wrap_wsgi: + app.wsgi_app = SentryMiddleware(app.wsgi_app, self.client) + + app.before_request(self.before_request) + request_finished.connect(self.after_request, sender=app) + + if self.register_signal: + got_request_exception.connect(self.handle_exception, sender=app) + + if not hasattr(app, 'extensions'): + app.extensions = {} + app.extensions['sentry'] = self + + def captureException(self, *args, **kwargs): + assert self.client, 'captureException called before application configured' + result = self.client.captureException(*args, **kwargs) + if result: + self.last_event_id = self.client.get_ident(result) + else: + self.last_event_id = None + return result + + def captureMessage(self, *args, **kwargs): + assert self.client, 'captureMessage called before application configured' + result = self.client.captureMessage(*args, **kwargs) + if result: + self.last_event_id = self.client.get_ident(result) + else: + self.last_event_id = None + return result + + def user_context(self, *args, **kwargs): + assert self.client, 'user_context called before application configured' + return self.client.user_context(*args, **kwargs) + + def tags_context(self, *args, **kwargs): + assert self.client, 'tags_context called before application configured' + return self.client.tags_context(*args, **kwargs) + + def extra_context(self, *args, **kwargs): + assert self.client, 'extra_context called before application configured' + return self.client.extra_context(*args, **kwargs) diff --git a/raven/contrib/flask/__init__.py b/raven/contrib/flask/__init__.py deleted file mode 100644 index 00c43bba5..000000000 --- a/raven/contrib/flask/__init__.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -raven.contrib.flask -~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. -:license: BSD, see LICENSE for more details. -""" - -from __future__ import absolute_import - -import os - -from flask import request -from flask.signals import got_request_exception -from raven.conf import setup_logging -from raven.base import Client -from raven.contrib.flask.utils import get_data_from_request -from raven.handlers.logging import SentryHandler - - -def make_client(client_cls, app, dsn=None): - return client_cls( - dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'), - include_paths=set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]), - exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'), - servers=app.config.get('SENTRY_SERVERS'), - name=app.config.get('SENTRY_NAME'), - public_key=app.config.get('SENTRY_PUBLIC_KEY'), - secret_key=app.config.get('SENTRY_SECRET_KEY'), - project=app.config.get('SENTRY_PROJECT'), - site=app.config.get('SENTRY_SITE_NAME'), - processors=app.config.get('SENTRY_PROCESSORS'), - string_max_length=app.config.get('SENTRY_MAX_LENGTH_STRING'), - list_max_length=app.config.get('SENTRY_MAX_LENGTH_LIST'), - ) - - -class Sentry(object): - """ - Flask application for Sentry. - - Look up configuration from ``os.environ['SENTRY_DSN']``:: - - >>> sentry = Sentry(app) - - Pass an arbitrary DSN:: - - >>> sentry = Sentry(app, dsn='http://public:secret@example.com/1') - - Pass an explicit client:: - - >>> sentry = Sentry(app, client=client) - - Automatically configure logging:: - - >>> sentry = Sentry(app, logging=True) - - Capture an exception:: - - >>> try: - >>> 1 / 0 - >>> except ZeroDivisionError: - >>> sentry.captureException() - - Capture a message:: - - >>> sentry.captureMessage('hello, world!') - """ - def __init__(self, app=None, client=None, client_cls=Client, dsn=None, - logging=False): - self.dsn = dsn - self.logging = logging - self.client_cls = client_cls - self.client = client - - if app: - self.init_app(app) - - def handle_exception(self, *args, **kwargs): - if not self.client: - return - - self.client.captureException( - exc_info=kwargs.get('exc_info'), - data=get_data_from_request(request), - extra={ - 'app': self.app, - }, - ) - - def init_app(self, app, dsn=None): - self.app = app - - if dsn is not None: - self.dsn = dsn - - if not self.client: - self.client = make_client(self.client_cls, app, self.dsn) - - if self.logging: - setup_logging(SentryHandler(self.client)) - - got_request_exception.connect(self.handle_exception, sender=app) - - if not hasattr(app, 'extensions'): - app.extensions = {} - app.extensions['sentry'] = self - - def captureException(self, *args, **kwargs): - assert self.client, 'captureException called before application configured' - data = kwargs.get('data') - if data is None: - try: - kwargs['data'] = get_data_from_request(request) - except RuntimeError: - # app is probably not configured yet - pass - return self.client.captureException(*args, **kwargs) - - def captureMessage(self, *args, **kwargs): - assert self.client, 'captureMessage called before application configured' - data = kwargs.get('data') - if data is None: - try: - kwargs['data'] = get_data_from_request(request) - except RuntimeError: - # app is probably not configured yet - pass - return self.client.captureMessage(*args, **kwargs) diff --git a/raven/contrib/flask/utils.py b/raven/contrib/flask/utils.py deleted file mode 100644 index 174322694..000000000 --- a/raven/contrib/flask/utils.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -raven.contrib.flask.utils -~~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. -:license: BSD, see LICENSE for more details. -""" -from __future__ import absolute_import - -import logging -from raven.utils.compat import _urlparse - -from raven.utils.wsgi import get_headers, get_environ -from werkzeug.exceptions import ClientDisconnected -from flask import current_app - -logger = logging.getLogger(__name__) - - -try: - from flask_login import current_user -except ImportError: - has_flask_login = False -else: - has_flask_login = True - - -def get_user_info(request): - """ - Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/) to be installed - and setup - """ - if not has_flask_login: - return - - if not hasattr(current_app, 'login_manager'): - return - - try: - is_authenticated = current_user.is_authenticated() - except AttributeError: - # HACK: catch the attribute error thrown by flask-login is not attached - # > current_user = LocalProxy(lambda: _request_ctx_stack.top.user) - # E AttributeError: 'RequestContext' object has no attribute 'user' - return {} - - if is_authenticated: - user_info = { - 'is_authenticated': True, - 'is_anonymous': current_user.is_anonymous(), - 'id': current_user.get_id(), - } - - if 'SENTRY_USER_ATTRS' in current_app.config: - for attr in current_app.config['SENTRY_USER_ATTRS']: - if hasattr(current_user, attr): - user_info[attr] = getattr(current_user, attr) - else: - user_info = { - 'is_authenticated': False, - 'is_anonymous': current_user.is_anonymous(), - } - - return user_info - - -def get_data_from_request(request): - urlparts = _urlparse.urlsplit(request.url) - - try: - formdata = request.form - except ClientDisconnected: - formdata = {} - - data = { - 'sentry.interfaces.Http': { - 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), - 'query_string': urlparts.query, - 'method': request.method, - 'data': formdata, - 'headers': dict(get_headers(request.environ)), - 'env': dict(get_environ(request.environ)), - } - } - try: - user_data = get_user_info(request) - except Exception as e: - logger.exception(e) - else: - if user_data: - data['sentry.interfaces.User'] = user_data - - return data diff --git a/raven/contrib/pylons/__init__.py b/raven/contrib/pylons/__init__.py index 4c3889a0e..353de9673 100644 --- a/raven/contrib/pylons/__init__.py +++ b/raven/contrib/pylons/__init__.py @@ -22,11 +22,7 @@ class Sentry(Middleware): def __init__(self, app, config, client_cls=Client): client = client_cls( dsn=config.get('sentry.dsn'), - servers=list_from_setting(config, 'sentry.servers'), name=config.get('sentry.name'), - public_key=config.get('sentry.public_key'), - secret_key=config.get('sentry.secret_key'), - project=config.get('sentry.project'), site=config.get('sentry.site'), include_paths=list_from_setting(config, 'sentry.include_paths'), exclude_paths=list_from_setting(config, 'sentry.exclude_paths'), diff --git a/raven/contrib/sanic.py b/raven/contrib/sanic.py new file mode 100644 index 000000000..cdc592097 --- /dev/null +++ b/raven/contrib/sanic.py @@ -0,0 +1,224 @@ +""" +raven.contrib.sanic +~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2018 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" + +from __future__ import absolute_import + +import logging + +import blinker + +from raven.conf import setup_logging +from raven.base import Client +from raven.handlers.logging import SentryHandler +from raven.utils.compat import urlparse +from raven.utils.encoding import to_unicode +from raven.utils.conf import convert_options + + +raven_signals = blinker.Namespace() +logging_configured = raven_signals.signal('logging_configured') + + +def make_client(client_cls, app, dsn=None): + return client_cls( + **convert_options( + app.config, + defaults={ + 'dsn': dsn, + 'include_paths': ( + set(app.config.get('SENTRY_INCLUDE_PATHS', [])) + | set([app.name]) + ), + 'extra': { + 'app': app, + }, + }, + ) + ) + + +class Sentry(object): + """ + Sanic application for Sentry. + + Look up configuration from ``os.environ['SENTRY_DSN']``:: + + >>> sentry = Sentry(app) + + Pass an arbitrary DSN:: + + >>> sentry = Sentry(app, dsn='http://public:secret@example.com/1') + + Pass an explicit client:: + + >>> sentry = Sentry(app, client=client) + + Automatically configure logging:: + + >>> sentry = Sentry(app, logging=True, level=logging.ERROR) + + Capture an exception:: + + >>> try: + >>> 1 / 0 + >>> except ZeroDivisionError: + >>> sentry.captureException() + + Capture a message:: + + >>> sentry.captureMessage('hello, world!') + """ + + def __init__(self, app, client=None, client_cls=Client, dsn=None, + logging=False, logging_exclusions=None, level=logging.NOTSET): + if client and not isinstance(client, Client): + raise TypeError('client should be an instance of Client') + + self.client = client + self.client_cls = client_cls + self.dsn = dsn + self.logging = logging + self.logging_exclusions = logging_exclusions + self.level = level + self.init_app(app) + + def handle_exception(self, request, exception): + if not self.client: + return + try: + self.client.http_context(self.get_http_info(request)) + except Exception as e: + self.client.logger.exception(to_unicode(e)) + + # Since Sanic is restricted to Python 3, let's be explicit with what + # we pass for exception info, rather than relying on sys.exc_info(). + exception_info = (type(exception), exception, exception.__traceback__) + self.captureException(exc_info=exception_info) + + def get_form_data(self, request): + return request.form + + def get_http_info(self, request): + """ + Determine how to retrieve actual data by using request.mimetype. + """ + if self.is_json_type(request): + retriever = self.get_json_data + else: + retriever = self.get_form_data + return self.get_http_info_with_retriever(request, retriever) + + def get_json_data(self, request): + return request.json + + def get_http_info_with_retriever(self, request, retriever): + """ + Exact method for getting http_info but with form data work around. + """ + urlparts = urlparse.urlsplit(request.url) + + try: + data = retriever(request) + except Exception: + data = {} + + return { + 'url': '{0}://{1}{2}'.format( + urlparts.scheme, urlparts.netloc, urlparts.path), + 'query_string': urlparts.query, + 'method': request.method, + 'data': data, + 'cookies': request.cookies, + 'headers': request.headers, + 'env': { + 'REMOTE_ADDR': request.remote_addr, + } + } + + def is_json_type(self, request): + content_type = request.headers.get('content-type') + return content_type == 'application/json' + + def init_app(self, app, dsn=None, logging=None, level=None, + logging_exclusions=None): + if dsn is not None: + self.dsn = dsn + + if level is not None: + self.level = level + + if logging is not None: + self.logging = logging + + if logging_exclusions is None: + self.logging_exclusions = ( + 'root', 'sanic.access', 'sanic.error') + else: + self.logging_exclusions = logging_exclusions + + if not self.client: + self.client = make_client(self.client_cls, app, self.dsn) + + if self.logging: + kwargs = {} + if self.logging_exclusions is not None: + kwargs['exclude'] = self.logging_exclusions + handler = SentryHandler(self.client, level=self.level) + setup_logging(handler, **kwargs) + logging_configured.send( + self, sentry_handler=SentryHandler, **kwargs) + + if not hasattr(app, 'extensions'): + app.extensions = {} + app.extensions['sentry'] = self + + app.error_handler.add(Exception, self.handle_exception) + app.register_middleware(self.before_request, attach_to='request') + app.register_middleware(self.after_request, attach_to='response') + + def before_request(self, request): + self.last_event_id = None + try: + self.client.http_context(self.get_http_info(request)) + except Exception as e: + self.client.logger.exception(to_unicode(e)) + + def after_request(self, request, response): + if self.last_event_id: + response.headers['X-Sentry-ID'] = self.last_event_id + self.client.context.clear() + + def captureException(self, *args, **kwargs): + assert self.client, 'captureException called before application configured' + result = self.client.captureException(*args, **kwargs) + self.set_last_event_id_from_result(result) + return result + + def captureMessage(self, *args, **kwargs): + assert self.client, 'captureMessage called before application configured' + result = self.client.captureMessage(*args, **kwargs) + self.set_last_event_id_from_result(result) + return result + + def set_last_event_id_from_result(self, result): + if result: + self.last_event_id = self.client.get_ident(result) + else: + self.last_event_id = None + + def user_context(self, *args, **kwargs): + assert self.client, 'user_context called before application configured' + return self.client.user_context(*args, **kwargs) + + def tags_context(self, *args, **kwargs): + assert self.client, 'tags_context called before application configured' + return self.client.tags_context(*args, **kwargs) + + def extra_context(self, *args, **kwargs): + assert self.client, 'extra_context called before application configured' + return self.client.extra_context(*args, **kwargs) diff --git a/raven/contrib/tornado/__init__.py b/raven/contrib/tornado/__init__.py index b6660cf0e..f4d6d5e5a 100644 --- a/raven/contrib/tornado/__init__.py +++ b/raven/contrib/tornado/__init__.py @@ -7,35 +7,42 @@ """ from __future__ import absolute_import -import time +from functools import partial -import raven -from raven.base import Client -from raven.utils import get_auth_header +from tornado import ioloop from tornado.httpclient import AsyncHTTPClient, HTTPError +from tornado.web import HTTPError as WebHTTPError + +from raven.base import Client class AsyncSentryClient(Client): - """A mixin class that could be used along with request handlers to + """ + A mixin class that could be used along with request handlers to asynchronously send errors to sentry. The client also captures the information from the request handlers """ + + def __init__(self, *args, **kwargs): + self.validate_cert = kwargs.pop('validate_cert', True) + super(AsyncSentryClient, self).__init__(*args, **kwargs) + def capture(self, *args, **kwargs): """ Takes the same arguments as the super function in :py:class:`Client` and extracts the keyword argument callback which will be called on asynchronous sending of the request - :return: a 32-length string identifying this event and checksum + :return: a 32-length string identifying this event """ if not self.is_enabled(): return data = self.build_msg(*args, **kwargs) - self.send(callback=kwargs.get('callback', None), **data) + future = self.send(callback=kwargs.get('callback', None), **data) - return (data['event_id'],) + return (data['event_id'], future) def send(self, auth_header=None, callback=None, **data): """ @@ -45,67 +52,36 @@ def send(self, auth_header=None, callback=None, **data): return self.send_encoded(message, auth_header=auth_header, callback=callback) - def send_encoded(self, message, auth_header=None, **kwargs): - """ - Given an already serialized message, signs the message and passes the - payload off to ``send_remote`` for each server specified in the servers - configuration. - - callback can be specified as a keyword argument - """ - if not auth_header: - timestamp = time.time() - auth_header = get_auth_header( - protocol=self.protocol_version, - timestamp=timestamp, - client='raven-python/%s' % (raven.VERSION,), - api_key=self.public_key, - api_secret=self.secret_key, - ) - - for url in self.servers: - headers = { - 'X-Sentry-Auth': auth_header, - 'Content-Type': 'application/octet-stream', - } - - self.send_remote( - url=url, data=message, headers=headers, - callback=kwargs.get('callback', None) - ) + def send_remote(self, url, data, headers=None, callback=None): + if headers is None: + headers = {} - def send_remote(self, url, data, headers={}, callback=None): if not self.state.should_try(): - message = self._get_log_message(data) - self.error_logger.error(message) + data = self.decode(data) + self._log_failed_submission(data) return + future = self._send_remote( + url=url, data=data, headers=headers, callback=callback + ) + ioloop.IOLoop.current().add_future(future, partial(self._handle_result, url, data)) + return future + + def _handle_result(self, url, data, future): try: - self._send_remote( - url=url, data=data, headers=headers, callback=callback - ) + future.result() except HTTPError as e: - body = e.response.body - self.error_logger.error( - 'Unable to reach Sentry log server: %s ' - '(url: %%s, body: %%s)' % (e,), - url, body, exc_info=True, - extra={'data': {'body': body, 'remote_url': url}} - ) + data = self.decode(data) + self._failed_send(e, url, data) except Exception as e: - self.error_logger.error( - 'Unable to reach Sentry log server: %s (url: %%s)' % (e,), - url, exc_info=True, extra={'data': {'remote_url': url}} - ) - message = self._get_log_message(data) - self.error_logger.error('Failed to submit message: %r', message) - self.state.set_fail() + data = self.decode(data) + self._failed_send(e, url, data) else: self.state.set_success() def _send_remote(self, url, data, headers=None, callback=None): """ - Initialise a Tornado AsyncClient and send the reuqest to the sentry + Initialise a Tornado AsyncClient and send the request to the sentry server. If the callback is a callable, it will be called with the response. """ @@ -113,7 +89,8 @@ def _send_remote(self, url, data, headers=None, callback=None): headers = {} return AsyncHTTPClient().fetch( - url, callback, method="POST", body=data, headers=headers + url, callback, method="POST", body=data, headers=headers, + validate_cert=self.validate_cert ) @@ -175,10 +152,10 @@ def get_sentry_data_from_request(self): :param return: A dictionary. """ return { - 'sentry.interfaces.Http': { + 'request': { 'url': self.request.full_url(), 'method': self.request.method, - 'data': self.request.arguments, + 'data': self.request.body, 'query_string': self.request.query, 'cookies': self.request.headers.get('Cookie', None), 'headers': dict(self.request.headers), @@ -190,12 +167,16 @@ def get_sentry_user_info(self): Data for sentry.interfaces.User Default implementation only sends `is_authenticated` by checking if - `tornado.web.RequestHandler.get_current_user` tests postitively for on + `tornado.web.RequestHandler.get_current_user` tests positively for on Truth calue testing """ + try: + user = self.get_current_user() + except Exception: + return {} return { - 'sentry.interfaces.User': { - 'is_authenticated': True if self.get_current_user() else False + 'user': { + 'is_authenticated': True if user else False } } @@ -249,6 +230,9 @@ def log_exception(self, typ, value, tb): log_exception() is added in Tornado v3.1. """ rv = super(SentryMixin, self).log_exception(typ, value, tb) + # Do not capture tornado.web.HTTPErrors outside the 500 range. + if isinstance(value, WebHTTPError) and (value.status_code < 500 or value.status_code > 599): + return rv self.captureException(exc_info=(typ, value, tb)) return rv @@ -260,5 +244,6 @@ def send_error(self, status_code=500, **kwargs): return super(SentryMixin, self).send_error(status_code, **kwargs) else: rv = super(SentryMixin, self).send_error(status_code, **kwargs) - self.captureException(exc_info=kwargs.get('exc_info')) + if 500 <= status_code <= 599: + self.captureException(exc_info=kwargs.get('exc_info')) return rv diff --git a/raven/contrib/transports/__init__.py b/raven/contrib/transports/__init__.py deleted file mode 100644 index 467d50c3c..000000000 --- a/raven/contrib/transports/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -raven.contrib.transports -~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. -:license: BSD, see LICENSE for more details. -""" -from __future__ import absolute_import diff --git a/raven/contrib/transports/zeromq/__init__.py b/raven/contrib/transports/zeromq/__init__.py deleted file mode 100644 index c5b23d948..000000000 --- a/raven/contrib/transports/zeromq/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -raven.contrib.transports.zeromq -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details -:license: BSD, see LICENSE for more details -""" -from __future__ import absolute_import - -from raven.contrib.transports.zeromq.raven_zmq import ZmqPubTransport # NOQA diff --git a/raven/contrib/transports/zeromq/raven_zmq.py b/raven/contrib/transports/zeromq/raven_zmq.py deleted file mode 100644 index 1c27c12cc..000000000 --- a/raven/contrib/transports/zeromq/raven_zmq.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -raven.contrib.transports.zeromq.raven_zmq -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details -:license: BSD, see LICENSE for more details -""" -from __future__ import absolute_import - -from raven.transport import Transport - -import json - - -class MissingLibrary(Exception): - pass - -try: - import zmq - ZMQ_CONTEXT = zmq.Context() -except: - ZMQ_CONTEXT = None - msg = "You need to have pyzmq installed" - raise MissingLibrary(msg) - - -class ZmqPubTransport(Transport): - """ - This provides a zeromq publisher transport. - - This transport does *not* do initial handshaking so it it - susceptible to initially dropped messages due to the "slow joiner" - problem. - - Note that the scheme here indicates a pub socket over tcp - """ - scheme = ['zmq+tcp'] - - def __init__(self, parsed_url): - self.check_scheme(parsed_url) - self._parsed_url = parsed_url - - self._zmq_url = parsed_url.geturl().replace("zmq+", '') - - self._sock = ZMQ_CONTEXT.socket(zmq.PUB) - self._sock.setsockopt(zmq.LINGER, 0) - self._sock.connect(self._zmq_url) - - def send(self, data, headers): - """ - Just push a message out as JSON and include everything - """ - self._sock.send(json.dumps({'data': data, 'headers': - headers})) - - def compute_scope(self, url, scope): - scope.update({ - 'SENTRY_SERVERS': [url.geturl()], - 'SENTRY_PROJECT': '', - 'SENTRY_PUBLIC_KEY': 'fake_user', - 'SENTRY_SECRET_KEY': 'fake_password', - }) - return scope diff --git a/raven/contrib/webpy/__init__.py b/raven/contrib/webpy/__init__.py new file mode 100644 index 000000000..2e5387f2c --- /dev/null +++ b/raven/contrib/webpy/__init__.py @@ -0,0 +1,78 @@ +""" +raven.contrib.webpy +~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2013 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import sys + +import web + +from raven.conf import setup_logging +from raven.handlers.logging import SentryHandler +from raven.contrib.webpy.utils import get_data_from_request + + +class SentryApplication(web.application): + """ + Web.py application for Sentry. + + >>> sentry = Sentry(client, mapping=urls, fvars=globals()) + + Automatically configure logging:: + + >>> sentry = Sentry(client, logging=True, mapping=urls, fvars=globals()) + + Capture an exception:: + + >>> try: + >>> 1 / 0 + >>> except ZeroDivisionError: + >>> sentry.captureException() + + Capture a message:: + + >>> sentry.captureMessage('hello, world!') + """ + + def __init__(self, client, logging=False, **kwargs): + self.client = client + self.logging = logging + if self.logging: + setup_logging(SentryHandler(self.client)) + web.application.__init__(self, **kwargs) + + def handle_exception(self, *args, **kwargs): + self.client.captureException( + exc_info=kwargs.get('exc_info'), + data=get_data_from_request(), + extra={ + 'app': self, + }, + ) + + def handle(self): + try: + return web.application.handle(self) + except Exception: + self.handle_exception(exc_info=sys.exc_info()) + raise + + def captureException(self, *args, **kwargs): + assert self.client, 'captureException called before application configured' + data = kwargs.get('data') + if data is None: + kwargs['data'] = get_data_from_request() + + return self.client.captureException(*args, **kwargs) + + def captureMessage(self, *args, **kwargs): + assert self.client, 'captureMessage called before application configured' + data = kwargs.get('data') + if data is None: + kwargs['data'] = get_data_from_request() + + return self.client.captureMessage(*args, **kwargs) diff --git a/raven/contrib/webpy/utils.py b/raven/contrib/webpy/utils.py new file mode 100644 index 000000000..bc4fb4b97 --- /dev/null +++ b/raven/contrib/webpy/utils.py @@ -0,0 +1,26 @@ +""" +raven.contrib.webpy.utils +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import web + +from raven.utils.wsgi import get_headers, get_environ + + +def get_data_from_request(): + """Returns request data extracted from web.ctx.""" + return { + 'request': { + 'url': '%s://%s%s' % (web.ctx['protocol'], web.ctx['host'], web.ctx['path']), + 'query_string': web.ctx.query, + 'method': web.ctx.method, + 'data': web.data(), + 'headers': dict(get_headers(web.ctx.environ)), + 'env': dict(get_environ(web.ctx.environ)), + } + } diff --git a/raven/contrib/zconfig/__init__.py b/raven/contrib/zconfig/__init__.py new file mode 100644 index 000000000..d5f555b44 --- /dev/null +++ b/raven/contrib/zconfig/__init__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +""" +raven.contrib.zconfig +~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2013 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +import logging +import ZConfig.components.logger.factory +import raven.handlers.logging + + +class Factory(ZConfig.components.logger.factory.Factory): + + def __init__(self, section): + ZConfig.components.logger.factory.Factory.__init__(self) + self.section = section + self.section.level = self.section.level or logging.ERROR + + def getLevel(self): + return self.section.level + + def create(self): + return raven.handlers.logging.SentryHandler( + dsn=self.section.dsn, + site=self.section.site, + name=self.section.name, + release=self.section.release, + environment=self.section.environment, + exclude_paths=self.section.exclude_paths, + include_paths=self.section.include_paths, + sample_rate=self.section.sample_rate, + list_max_length=self.section.list_max_length, + string_max_length=self.section.string_max_length, + auto_log_stacks=self.section.auto_log_stacks, + processors=self.section.processors, + level=self.section.level) diff --git a/raven/contrib/zconfig/component.xml b/raven/contrib/zconfig/component.xml new file mode 100644 index 000000000..716d71d59 --- /dev/null +++ b/raven/contrib/zconfig/component.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/raven/contrib/zerorpc/__init__.py b/raven/contrib/zerorpc/__init__.py index 4a540637b..c9362ad5b 100644 --- a/raven/contrib/zerorpc/__init__.py +++ b/raven/contrib/zerorpc/__init__.py @@ -22,26 +22,25 @@ class SentryMiddleware(object): Exceptions detected server-side in ZeroRPC will be submitted to Sentry (and propagated to the client as well). - """ def __init__(self, hide_zerorpc_frames=True, client=None, **kwargs): - """Create a middleware object that can be injected in a ZeroRPC server. + """ + Create a middleware object that can be injected in a ZeroRPC server. - hide_zerorpc_frames: modify the exception stacktrace to remove the internal zerorpc frames (True by default to make the stacktrace as readable as possible); - client: use an existing raven.Client object, otherwise one will be instantiated from the keyword arguments. - """ - self._sentry_client = client or Client(**kwargs) self._hide_zerorpc_frames = hide_zerorpc_frames def server_inspect_exception(self, req_event, rep_event, task_ctx, exc_info): - """Called when an exception has been raised in the code run by ZeroRPC""" - + """ + Called when an exception has been raised in the code run by ZeroRPC + """ # Hide the zerorpc internal frames for readability, for a REQ/REP or # REQ/STREAM server the frames to hide are: # - core.ServerBase._async_task diff --git a/raven/contrib/zope/__init__.py b/raven/contrib/zope/__init__.py index 126a62df9..b103c6be3 100644 --- a/raven/contrib/zope/__init__.py +++ b/raven/contrib/zope/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- """ raven.contrib.zope @@ -13,7 +12,7 @@ from raven.handlers.logging import SentryHandler from ZConfig.components.logger.factory import Factory import logging - +from AccessControl.users import nobody from raven.utils.stacks import iter_stack_frames logger = logging.getLogger(__name__) @@ -33,20 +32,26 @@ def __init__(self, section): class ZopeSentryHandler(SentryHandler): - ''' + """ Zope unfortunately eats the stack trace information. To get the stack trace information and other useful information from the request object, this class looks into the different stack frames when the emit method is invoked. - ''' + """ def __init__(self, *args, **kw): super(ZopeSentryHandler, self).__init__(*args, **kw) level = kw.get('level', logging.ERROR) self.setLevel(level) + def can_record(self, record): + return not ( + record.name == 'raven' + or record.name.startswith(('sentry.errors', 'raven.')) + ) + def emit(self, record): - if record.levelno <= logging.ERROR: + if record.levelno <= logging.ERROR and self.can_record(record): request = None exc_info = None for frame_info in getouterframes(currentframe()): @@ -86,17 +91,17 @@ def emit(self, record): http['headers']['User-Agent'] = \ http['headers']['HTTP_USER_AGENT'] if 'QUERY_STRING' in http['headers']: - http['query_string'] = http['headers' - ]['QUERY_STRING'] - setattr(record, 'sentry.interfaces.Http', http) + http['query_string'] = http['headers']['QUERY_STRING'] + setattr(record, 'request', http) user = request.get('AUTHENTICATED_USER', None) - if user is not None: - user_dict = dict(id=user.getId(), - is_authenticated=user.has_role('Authenticated'), - email=user.getProperty('email') or '') + if user is not None and user != nobody: + user_dict = { + 'id': user.getId(), + 'email': user.getProperty('email') or '', + } else: - user_dict = {'is_authenticated': False} - setattr(record, 'sentry.interfaces.User', user_dict) + user_dict = {} + setattr(record, 'user', user_dict) except (AttributeError, KeyError): logger.warning('Could not extract data from request', exc_info=True) return super(ZopeSentryHandler, self).emit(record) diff --git a/raven/contrib/zope/component.xml b/raven/contrib/zope/component.xml index c3d92f2ec..6bb1a204d 100644 --- a/raven/contrib/zope/component.xml +++ b/raven/contrib/zope/component.xml @@ -8,20 +8,18 @@ datatype="raven.contrib.zope.ZopeSentryHandlerFactory" implements="ZConfig.logger.handler" extends="ZConfig.logger.base-log-handler"> - - - - + + diff --git a/raven/data/cacert.pem b/raven/data/cacert.pem new file mode 100644 index 000000000..8f81e8ded --- /dev/null +++ b/raven/data/cacert.pem @@ -0,0 +1,5307 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Low-Value Services Root" +# Serial: 1 +# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc +# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d +# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw +MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD +VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul +CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n +tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl +dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch +PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC ++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O +BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk +ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X +7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz +43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl +pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA +WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Public Services Root" +# Serial: 1 +# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f +# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 +# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx +MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB +ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV +BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV +6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX +GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP +dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH +1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF +62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW +BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL +MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU +cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv +b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 +IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ +iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh +4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm +XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Qualified Certificates Root" +# Serial: 1 +# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb +# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf +# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 +MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK +EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh +BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq +xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G +87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i +2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U +WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 +0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G +A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr +pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL +ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm +aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv +hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm +hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 +P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y +iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no +xqE= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: O=RSA Security Inc OU=RSA Security 2048 V3 +# Subject: O=RSA Security Inc OU=RSA Security 2048 V3 +# Label: "RSA Security 2048 v3" +# Serial: 13297492616345471454730593562152402946 +# MD5 Fingerprint: 77:0d:19:b1:21:fd:00:42:9c:3e:0c:a5:dd:0b:02:8e +# SHA1 Fingerprint: 25:01:90:19:cf:fb:d9:99:1c:b7:68:25:74:8d:94:5f:30:93:95:42 +# SHA256 Fingerprint: af:8b:67:62:a1:e5:28:22:81:61:a9:5d:5c:55:9e:e2:66:27:8f:75:d7:9e:83:01:89:a5:03:50:6a:bd:6b:4c +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6 +MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp +dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX +BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy +MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp +eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg +/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl +wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh +AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2 +PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu +AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR +MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc +HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/ +Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+ +f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO +rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch +6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3 +7CAFYd4= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Global CA 2" +# Serial: 1 +# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 +# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d +# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs +IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg +R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A +PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 +Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL +TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL +5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 +S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe +2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap +EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td +EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv +/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN +A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 +abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF +I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz +4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Label: "Visa eCommerce Root" +# Serial: 25952180776285836048024890241505565794 +# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02 +# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62 +# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22 +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr +MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl +cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw +CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h +dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l +cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h +2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E +lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV +ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq +299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t +vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL +dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF +AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR +zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 +LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd +7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw +++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum CA O=Unizeto Sp. z o.o. +# Subject: CN=Certum CA O=Unizeto Sp. z o.o. +# Label: "Certum Root CA" +# Serial: 65568 +# MD5 Fingerprint: 2c:8f:9f:66:1d:18:90:b1:47:26:9d:8e:86:82:8c:a9 +# SHA1 Fingerprint: 62:52:dc:40:f7:11:43:a2:2f:de:9e:f7:34:8e:06:42:51:b1:81:18 +# SHA256 Fingerprint: d8:e0:fe:bc:1d:b2:e3:8d:00:94:0f:37:d2:7d:41:34:4d:99:3e:73:4b:99:d5:65:6d:97:78:d4:d8:14:36:24 +-----BEGIN CERTIFICATE----- +MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM +MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD +QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E +jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo +ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI +ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu +Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg +AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7 +HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA +uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa +TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg +xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q +CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x +O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs +6GAqm4VKQPNriiTsBhYscw== +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=Secure Certificate Services O=Comodo CA Limited +# Subject: CN=Secure Certificate Services O=Comodo CA Limited +# Label: "Comodo Secure Services root" +# Serial: 1 +# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd +# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 +# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp +ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow +fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV +BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM +cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S +HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 +CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk +3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz +6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV +HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv +Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw +Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww +DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 +5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI +gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ +aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl +izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= +-----END CERTIFICATE----- + +# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited +# Subject: CN=Trusted Certificate Services O=Comodo CA Limited +# Label: "Comodo Trusted Services root" +# Serial: 1 +# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 +# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd +# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 +aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla +MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD +VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW +fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt +TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL +fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW +1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 +kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G +A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v +ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo +dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu +Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ +HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS +jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ +xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn +dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 +# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 +# Label: "Security Communication Root CA" +# Serial: 0 +# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a +# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 +# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c +-----BEGIN CERTIFICATE----- +MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY +MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t +dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 +WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD +VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 +9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ +DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 +Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N +QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ +xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G +A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG +kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr +Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 +Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU +JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot +RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== +-----END CERTIFICATE----- + +# Issuer: CN=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----END CERTIFICATE----- + +# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Label: "UTN USERFirst Hardware Root CA" +# Serial: 91374294542884704022267039221184531197 +# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 +# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 +# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Subject: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Label: "Camerfirma Chambers of Commerce Root" +# Serial: 0 +# MD5 Fingerprint: b0:01:ee:14:d9:af:29:18:94:76:8e:f1:69:33:2a:84 +# SHA1 Fingerprint: 6e:3a:55:a4:19:0c:19:5c:93:84:3c:c0:db:72:2e:31:30:61:f0:b1 +# SHA256 Fingerprint: 0c:25:8a:12:a5:67:4a:ef:25:f2:8b:a7:dc:fa:ec:ee:a3:48:e5:41:e6:f5:cc:4e:e6:3b:71:b3:61:60:6a:c3 +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg +b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa +MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB +ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw +IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B +AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb +unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d +BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq +7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3 +0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX +roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG +A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j +aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p +26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA +BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud +EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN +BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz +aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB +AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd +p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi +1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc +XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0 +eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu +tGWaIZDgqtCYvDi1czyL+Nw= +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Subject: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org +# Label: "Camerfirma Global Chambersign Root" +# Serial: 0 +# MD5 Fingerprint: c5:e6:7b:bf:06:d0:4f:43:ed:c4:7a:65:8a:fb:6b:19 +# SHA1 Fingerprint: 33:9b:6b:14:50:24:9b:55:7a:01:87:72:84:d9:e0:2f:c3:d2:d8:e9 +# SHA256 Fingerprint: ef:3c:b4:17:fc:8e:bf:6f:97:87:6c:9e:4e:ce:39:de:1e:a5:fe:64:91:41:d1:02:8b:7d:11:c0:b2:29:8c:ed +-----BEGIN CERTIFICATE----- +MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn +MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL +ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo +YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9 +MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy +NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G +A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA +A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0 +Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s +QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV +eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795 +B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh +z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T +AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i +ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w +TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH +MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD +VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE +VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh +bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B +AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM +bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi +ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG +VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c +ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/ +AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A== +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 1 +# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 +# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f +# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j +ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js +LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM +BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy +dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh +cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh +YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg +dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp +bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ +YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT +TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ +9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 +jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW +FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz +ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 +ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L +EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu +L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC +O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V +um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh +NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= +-----END CERTIFICATE----- + +# Issuer: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root CA 1" +# Serial: 122348795730808398873664200247279986742 +# MD5 Fingerprint: f8:38:7c:77:88:df:2c:16:68:2e:c2:e2:52:4b:b8:f9 +# SHA1 Fingerprint: 5f:3a:fc:0a:8b:64:f6:86:67:34:74:df:7e:a9:a2:fe:f9:fa:7a:51 +# SHA256 Fingerprint: 21:db:20:12:36:60:bb:2e:d4:18:20:5d:a1:1e:e7:a8:5a:65:e2:bc:6e:55:b5:af:7e:78:99:c8:a2:66:d9:2e +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk +MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg +Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT +AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp +Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9 +m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih +FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/ +TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F +EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco +kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu +HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF +vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo +19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC +L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW +bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX +JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw +FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j +BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc +K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf +ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik +Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB +sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e +3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR +ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip +mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH +b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf +rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms +hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y +zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6 +MBr1mmz0DlP5OlvRHA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=Class 2 Primary CA O=Certplus +# Subject: CN=Class 2 Primary CA O=Certplus +# Label: "Certplus Class 2 Primary CA" +# Serial: 177770208045934040241468760488327595043 +# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b +# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb +# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw +PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz +cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 +MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz +IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ +ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR +VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL +kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd +EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas +H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 +HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud +DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 +QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu +Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ +AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 +yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR +FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA +ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB +kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----END CERTIFICATE----- + +# Issuer: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES +# Subject: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES +# Label: "DST ACES CA X6" +# Serial: 17771143917277623872238992636097467865 +# MD5 Fingerprint: 21:d8:4c:82:2b:99:09:33:a2:eb:14:24:8d:8e:5f:e8 +# SHA1 Fingerprint: 40:54:da:6f:1c:3f:40:74:ac:ed:0f:ec:cd:db:79:d1:53:fb:90:1d +# SHA256 Fingerprint: 76:7c:95:5a:76:41:2c:89:af:68:8e:90:a1:c7:0f:55:6c:fd:6b:60:25:db:ea:10:41:6d:7e:b6:83:1f:8c:40 +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx +ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w +MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD +VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx +FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu +ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7 +gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH +fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a +ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT +ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk +c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto +dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt +aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI +hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk +QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/ +h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq +nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR +rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 +9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln +biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF +MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT +d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 +76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ +bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c +6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE +emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd +MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt +MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y +MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y +FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi +aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM +gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB +qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 +lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn +8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov +L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 +45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO +UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 +O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC +bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv +GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a +77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC +hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 +92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp +Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w +ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt +Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE +BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu +IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow +RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY +U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv +Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br +YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF +nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH +6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt +eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ +c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ +MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH +HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf +jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 +5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB +rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c +wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 +cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB +AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp +WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 +xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ +2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ +IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 +aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X +em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR +dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ +OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ +hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy +tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----END CERTIFICATE----- + +# Issuer: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA +# Subject: CN=WellsSecure Public Root Certificate Authority O=Wells Fargo WellsSecure OU=Wells Fargo Bank NA +# Label: "WellsSecure Public Root Certificate Authority" +# Serial: 1 +# MD5 Fingerprint: 15:ac:a5:c2:92:2d:79:bc:e8:7f:cb:67:ed:02:cf:36 +# SHA1 Fingerprint: e7:b4:f6:9d:61:ec:90:69:db:7e:90:a7:40:1a:3c:f4:7d:4f:e8:ee +# SHA256 Fingerprint: a7:12:72:ae:aa:a3:cf:e8:72:7f:7f:b3:9f:0f:b3:d1:e5:42:6e:90:60:b0:6e:e6:f1:3e:9a:3c:58:33:cd:43 +-----BEGIN CERTIFICATE----- +MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx +IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs +cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v +dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0 +MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl +bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD +DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r +WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU +Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs +HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj +z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf +SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl +AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG +KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P +AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j +BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC +VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX +ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg +Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB +ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd +/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB +A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn +k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9 +iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv +2G0xffX8oRAHh84vWdw+WNs= +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=IGC/A O=PM/SGDN OU=DCSSI +# Subject: CN=IGC/A O=PM/SGDN OU=DCSSI +# Label: "IGC/A" +# Serial: 245102874772 +# MD5 Fingerprint: 0c:7f:dd:6a:f4:2a:b9:c8:9b:bd:20:7e:a9:db:5c:37 +# SHA1 Fingerprint: 60:d6:89:74:b5:c2:65:9e:8a:0f:c1:88:7c:88:d2:46:69:1b:18:2c +# SHA256 Fingerprint: b9:be:a7:86:0a:96:2e:a3:61:1d:ab:97:ab:6d:a3:e2:1c:10:68:b9:7d:55:57:5e:d0:e1:12:79:c1:1c:89:32 +-----BEGIN CERTIFICATE----- +MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT +AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ +TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG +9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw +MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM +BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO +MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 +LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI +s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 +xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 +u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b +F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx +Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd +PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV +HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx +NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF +AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ +L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY +YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg +Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a +NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R +0982gaEbeC9xs/FZTEYYKKuF0mBWWg== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1 +# Label: "Security Communication EV RootCA1" +# Serial: 0 +# MD5 Fingerprint: 22:2d:a6:01:ea:7c:0a:f7:f0:6c:56:43:3f:77:76:d3 +# SHA1 Fingerprint: fe:b8:c4:32:dc:f9:76:9a:ce:ae:3d:d8:90:8f:fd:28:86:65:64:7d +# SHA256 Fingerprint: a2:2d:ba:68:1e:97:37:6e:2d:39:7d:72:8a:ae:3a:9b:62:96:b9:fd:ba:60:bc:2e:11:f6:47:f2:c6:75:fb:37 +-----BEGIN CERTIFICATE----- +MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz +MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N +IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11 +bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE +RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO +zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5 +bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF +MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1 +VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC +OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW +tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ +q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb +EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+ +Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O +VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490 +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA +# Subject: CN=Microsec e-Szigno Root CA O=Microsec Ltd. OU=e-Szigno CA +# Label: "Microsec e-Szigno Root CA" +# Serial: 272122594155480254301341951808045322001 +# MD5 Fingerprint: f0:96:b6:2f:c5:10:d5:67:8e:83:25:32:e8:5e:2e:e5 +# SHA1 Fingerprint: 23:88:c9:d3:71:cc:9e:96:3d:ff:7d:3c:a7:ce:fc:d6:25:ec:19:0d +# SHA256 Fingerprint: 32:7a:3d:76:1a:ba:de:a0:34:eb:99:84:06:27:5c:b1:a4:77:6e:fd:ae:2f:df:6d:01:68:ea:1c:4f:55:67:d0 +-----BEGIN CERTIFICATE----- +MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw +cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy +b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z +ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4 +NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN +TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p +Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u +uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+ +LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA +vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770 +Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx +62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB +AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw +LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP +BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB +AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov +MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5 +ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn +AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT +AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh +ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo +AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa +AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln +bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p +Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP +PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv +Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB +EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu +w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj +cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV +HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI +VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS +BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS +b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS +8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds +ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl +7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a +86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR +hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/ +MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Label: "Deutsche Telekom Root CA 2" +# Serial: 38 +# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 +# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf +# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc +MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj +IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB +IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE +RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl +U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 +IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU +ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC +QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr +rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S +NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc +QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH +txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP +BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp +tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa +IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl +6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ +xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi +# Subject: CN=TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3 O=Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK OU=Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE/Kamu Sertifikasyon Merkezi +# Label: "T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3" +# Serial: 17 +# MD5 Fingerprint: ed:41:f5:8c:50:c5:2b:9c:73:e6:ee:6c:eb:c2:a8:26 +# SHA1 Fingerprint: 1b:4b:39:61:26:27:6b:64:91:a2:68:6d:d7:02:43:21:2d:1f:1d:96 +# SHA256 Fingerprint: e4:c7:34:30:d7:a5:b5:09:25:df:43:37:0a:0d:21:6e:9a:79:b9:d6:db:83:73:a0:c6:9e:b1:cc:31:c7:c5:2a +-----BEGIN CERTIFICATE----- +MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS +MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp +bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw +VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy +YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy +dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2 +ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe +Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx +GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls +aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU +QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh +xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0 +aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr +IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h +gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK +O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO +fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw +lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL +hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID +AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP +NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t +wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM +7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh +gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n +oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs +yZyQ2uypQjyttgI= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 CA 1 O=Buypass AS-983163327 +# Label: "Buypass Class 2 CA 1" +# Serial: 1 +# MD5 Fingerprint: b8:08:9a:f0:03:cc:1b:0d:c8:6c:0b:76:a1:75:64:23 +# SHA1 Fingerprint: a0:a1:ab:90:c9:fc:84:7b:3b:12:61:e8:97:7d:5f:d3:22:61:d3:cc +# SHA256 Fingerprint: 0f:4e:9c:dd:26:4b:02:55:50:d1:70:80:63:40:21:4f:e9:44:34:c9:b0:2f:69:7e:c7:10:fc:5f:ea:fb:5e:38 +-----BEGIN CERTIFICATE----- +MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg +Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL +MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD +VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0 +ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX +l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB +HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B +5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3 +WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD +AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP +gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+ +DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu +BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs +h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk +LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=CNNIC ROOT O=CNNIC +# Subject: CN=CNNIC ROOT O=CNNIC +# Label: "CNNIC ROOT" +# Serial: 1228079105 +# MD5 Fingerprint: 21:bc:82:ab:49:c4:13:3b:4b:b2:2b:5c:6b:90:9c:19 +# SHA1 Fingerprint: 8b:af:4c:9b:1d:f0:2a:92:f7:da:12:8e:b9:1b:ac:f4:98:60:4b:6f +# SHA256 Fingerprint: e2:83:93:77:3d:a8:45:a6:79:f2:08:0c:c7:fb:44:a3:b7:a1:c3:79:2c:b7:eb:77:29:fd:cb:6a:8d:99:ae:a7 +-----BEGIN CERTIFICATE----- +MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD +TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2 +MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF +Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh +IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6 +dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO +V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC +GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN +v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB +AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB +Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO +76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK +OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH +ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi +yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL +buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj +2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE= +-----END CERTIFICATE----- + +# Issuer: O=Japanese Government OU=ApplicationCA +# Subject: O=Japanese Government OU=ApplicationCA +# Label: "ApplicationCA - Japanese Government" +# Serial: 49 +# MD5 Fingerprint: 7e:23:4e:5b:a7:a5:b4:25:e9:00:07:74:11:62:ae:d6 +# SHA1 Fingerprint: 7f:8a:b0:cf:d0:51:87:6a:66:f3:36:0f:47:c8:8d:8c:d3:35:fc:74 +# SHA256 Fingerprint: 2d:47:43:7d:e1:79:51:21:5a:12:f3:c5:8e:51:c7:29:a5:80:26:ef:1f:cc:0a:5f:b3:d9:dc:01:2f:60:0d:19 +-----BEGIN CERTIFICATE----- +MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc +MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp +b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT +AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs +aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H +j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K +f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55 +IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw +FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht +QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm +/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ +k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ +MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC +seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ +hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+ +eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U +DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj +B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL +rosot4LKGAfmt1t06SAZf7IbiVQ= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) Főtanúsítvány O=NetLock Kft. OU=Tanúsítványkiadók (Certification Services) +# Label: "NetLock Arany (Class Gold) Főtanúsítvány" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post +# Label: "Hongkong Post Root CA 1" +# Serial: 1000 +# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca +# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 +# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 +-----BEGIN CERTIFICATE----- +MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx +FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg +Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG +A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr +b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ +jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn +PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh +ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 +nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h +q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED +MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC +mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 +7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB +oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs +EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO +fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi +AmvZWg== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. +# Label: "SecureSign RootCA11" +# Serial: 1 +# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 +# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 +# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr +MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG +A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 +MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp +Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD +QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz +i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 +h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV +MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 +UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni +8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC +h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD +VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB +AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm +KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ +X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr +QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 +pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN +QSdJQO7e5iNEOdyhIta6A/I= +-----END CERTIFICATE----- + +# Issuer: CN=ACEDICOM Root O=EDICOM OU=PKI +# Subject: CN=ACEDICOM Root O=EDICOM OU=PKI +# Label: "ACEDICOM Root" +# Serial: 7029493972724711941 +# MD5 Fingerprint: 42:81:a0:e2:1c:e3:55:10:de:55:89:42:65:96:22:e6 +# SHA1 Fingerprint: e0:b4:32:2e:b2:f6:a5:68:b6:54:53:84:48:18:4a:50:36:87:43:84 +# SHA256 Fingerprint: 03:95:0f:b4:9a:53:1f:3e:19:91:94:23:98:df:a9:e0:ea:32:d7:ba:1c:dd:9b:c8:5d:b5:7e:d9:40:0b:43:4a +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE +AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x +CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW +MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF +RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7 +09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7 +XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P +Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK +t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb +X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28 +MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU +fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI +2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH +K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae +ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP +BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ +MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw +RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv +bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm +fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3 +gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe +I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i +5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi +ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn +MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ +o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6 +zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN +GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt +r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK +Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 6047274297262753887 +# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 +# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa +# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy +MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD +VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp +cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv +ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl +AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF +661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 +am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 +ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 +PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS +3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k +SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF +3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM +ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g +StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz +Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB +jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 +MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 +ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD +VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j +b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq +scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO +xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H +LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX +uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD +yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ +JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q +rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN +BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L +hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB +QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ +HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu +Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg +QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB +BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx +MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA +A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb +laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 +awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo +JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw +LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT +VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk +LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb +UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ +QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ +naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls +QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== +-----END CERTIFICATE----- + +# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Autorité Racine O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Autorité Racine" +# Serial: 1 +# MD5 Fingerprint: 7f:30:78:8c:03:e3:ca:c9:0a:e2:c9:ea:1e:aa:55:1a +# SHA1 Fingerprint: 2e:14:da:ec:28:f0:fa:1e:8e:38:9a:4e:ab:eb:26:c0:0a:d3:83:c3 +# SHA256 Fingerprint: fc:bf:e2:88:62:06:f7:2b:27:59:3c:8b:07:02:97:e1:2d:76:9e:d1:0e:d7:93:07:05:a8:09:8e:ff:c1:4d:17 +-----BEGIN CERTIFICATE----- +MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk +BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4 +Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl +cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0 +aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY +F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N +8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe +rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K +/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu +7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC +28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6 +lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E +nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB +0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09 +5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj +WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN +jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ +KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s +ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM +OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q +619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn +2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj +o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v +nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG +5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq +pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb +dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0 +BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5 +-----END CERTIFICATE----- + +# Issuer: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA +# Subject: CN=Root CA Generalitat Valenciana O=Generalitat Valenciana OU=PKIGVA +# Label: "Root CA Generalitat Valenciana" +# Serial: 994436456 +# MD5 Fingerprint: 2c:8c:17:5e:b1:54:ab:93:17:b5:36:5a:db:d1:c6:f2 +# SHA1 Fingerprint: a0:73:e5:c5:bd:43:61:0d:86:4c:21:13:0a:85:58:57:cc:9c:ea:46 +# SHA256 Fingerprint: 8c:4e:df:d0:43:48:f3:22:96:9e:7e:29:a4:cd:4d:ca:00:46:55:06:1c:16:e1:b0:76:42:2e:f3:42:ad:63:0e +-----BEGIN CERTIFICATE----- +MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF +UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ +R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN +MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G +A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw +JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+ +WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj +SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl +u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy +A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk +Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7 +MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr +aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC +IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A +cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA +YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA +bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA +bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA +aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA +aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA +ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA +YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA +ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA +LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6 +Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y +eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw +CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G +A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu +Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn +lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt +b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg +9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF +ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC +IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 45 +# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 +# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 +# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC +ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w +ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk +aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 +YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg +c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 +d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG +CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF +wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS +Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst +0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc +pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl +CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF +P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK +1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm +KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ +8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm +fyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Label: "StartCom Certification Authority G2" +# Serial: 59 +# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 +# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 +# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 +OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG +A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ +JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD +vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo +D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ +Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW +RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK +HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN +nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM +0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i +UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 +Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg +TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL +BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX +UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl +6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK +9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ +HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI +wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY +XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l +IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo +hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr +so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Aralık 2007 +# Label: "TURKTRUST Certificate Services Provider Root 2007" +# Serial: 1 +# MD5 Fingerprint: 2b:70:20:56:86:82:a0:18:c8:07:53:12:28:70:21:72 +# SHA1 Fingerprint: f1:7f:6f:b6:31:dc:99:e3:a3:c8:7f:fe:1c:f1:81:10:88:d9:60:33 +# SHA256 Fingerprint: 97:8c:d9:66:f2:fa:a0:7b:a7:aa:95:00:d9:c0:2e:9d:77:f2:cd:ad:a6:ad:6b:a7:4a:f4:b9:1c:66:59:3c:50 +-----BEGIN CERTIFICATE----- +MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc +UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx +c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS +S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg +SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx +OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry +b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC +VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE +sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F +ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY +KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG ++7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG +HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P +IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M +733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk +Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW +AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I +aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5 +mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa +XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ +qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Raiz del Estado Venezolano O=Sistema Nacional de Certificacion Electronica OU=Superintendencia de Servicios de Certificacion Electronica +# Subject: CN=PSCProcert O=Sistema Nacional de Certificacion Electronica OU=Proveedor de Certificados PROCERT +# Label: "PSCProcert" +# Serial: 11 +# MD5 Fingerprint: e6:24:e9:12:01:ae:0c:de:8e:85:c4:ce:a3:12:dd:ec +# SHA1 Fingerprint: 70:c1:8d:74:b4:28:81:0a:e4:fd:a5:75:d7:01:9f:99:b0:3d:50:74 +# SHA256 Fingerprint: 3c:fc:3c:14:d1:f6:84:ff:17:e3:8c:43:ca:44:0c:00:b9:67:ec:93:3e:8b:fe:06:4c:a1:d7:2c:90:f2:ad:b0 +-----BEGIN CERTIFICATE----- +MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1 +dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s +YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz +dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0 +aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh +IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ +KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw +MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy +b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx +KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG +A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u +aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9 +7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74 +BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G +ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9 +JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0 +PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2 +0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH +0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/ +6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m +v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7 +K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev +bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw +MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w +MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD +gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0 +b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh +bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0 +cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp +ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg +ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq +hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD +AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w +MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag +RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t +UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl +cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v +Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG +AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN +AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS +1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB +3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv +Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh +HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm +pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz +sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE +qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb +mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9 +opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H +YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km +-----END CERTIFICATE----- + +# Issuer: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center +# Subject: CN=China Internet Network Information Center EV Certificates Root O=China Internet Network Information Center +# Label: "China Internet Network Information Center EV Certificates Root" +# Serial: 1218379777 +# MD5 Fingerprint: 55:5d:63:00:97:bd:6a:97:f5:67:ab:4b:fb:6e:63:15 +# SHA1 Fingerprint: 4f:99:aa:93:fb:2b:d1:37:26:a1:99:4a:ce:7f:f0:05:f2:93:5d:1e +# SHA256 Fingerprint: 1c:01:c6:f4:db:b2:fe:fc:22:55:8b:2b:ca:32:56:3f:49:84:4a:cf:c3:2b:7b:e4:b0:ff:59:9f:9e:8c:7a:f7 +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC +Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g +Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0 +aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa +Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg +SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo +aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp +ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z +7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA// +DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx +zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8 +hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs +4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u +gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY +NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3 +j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG +52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB +echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws +ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI +zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy +wy39FCqQmbkHzJ8= +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root CA 2 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root CA 2" +# Serial: 40698052477090394928831521023204026294 +# MD5 Fingerprint: 5b:04:69:ec:a5:83:94:63:18:a7:86:d0:e4:f2:6e:19 +# SHA1 Fingerprint: 77:47:4f:c6:30:e4:0f:4c:47:64:3f:84:ba:b8:c6:95:4a:8a:41:ec +# SHA256 Fingerprint: f0:9b:12:2c:71:14:f4:a0:9b:d4:ea:4f:4a:99:d5:58:b4:6e:4c:25:cd:81:14:0d:29:c0:56:13:91:4c:38:41 +-----BEGIN CERTIFICATE----- +MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk +MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 +YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg +Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT +AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp +Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr +jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r +0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f +2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP +ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF +y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA +tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL +6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0 +uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL +acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh +k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q +VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw +FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O +BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh +b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R +fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv +/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI +REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx +srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv +aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT +woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n +Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W +t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N +8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2 +9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5 +wSsSnqaeG8XmDtkx2Q== +-----END CERTIFICATE----- + +# Issuer: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services +# Subject: CN=Swisscom Root EV CA 2 O=Swisscom OU=Digital Certificate Services +# Label: "Swisscom Root EV CA 2" +# Serial: 322973295377129385374608406479535262296 +# MD5 Fingerprint: 7b:30:34:9f:dd:0a:4b:6b:35:ca:31:51:28:5d:ae:ec +# SHA1 Fingerprint: e7:a1:90:29:d3:d5:52:dc:0d:0f:c6:92:d3:ea:88:0d:15:2e:1a:6b +# SHA256 Fingerprint: d9:5f:ea:3c:a4:ee:dc:e7:4c:d7:6e:75:fc:6d:1f:f6:2c:44:1f:0f:a8:bc:77:f0:34:b1:9e:5d:b2:58:01:5d +-----BEGIN CERTIFICATE----- +MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw +ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp +dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290 +IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD +VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy +dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg +MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx +UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD +1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH +oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR +HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/ +5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv +idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL +OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC +NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f +46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB +UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth +7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G +A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED +MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB +bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x +XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T +PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0 +Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70 +WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL +Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm +7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S +nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN +vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB +WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI +fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb +I+2ksx0WckNLIOFZfsLorSa/ovc= +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R1 O=Disig a.s. +# Subject: CN=CA Disig Root R1 O=Disig a.s. +# Label: "CA Disig Root R1" +# Serial: 14052245610670616104 +# MD5 Fingerprint: be:ec:11:93:9a:f5:69:21:bc:d7:c1:c0:67:89:cc:2a +# SHA1 Fingerprint: 8e:1c:74:f8:a6:20:b9:e5:8a:f4:61:fa:ec:2b:47:56:51:1a:52:c6 +# SHA256 Fingerprint: f9:6f:23:f4:c3:e7:9c:07:7a:46:98:8d:5a:f5:90:06:76:a0:f0:39:cb:64:5d:d1:75:49:b2:16:c8:24:40:ce +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy +MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk +D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o +OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A +fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe +IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n +oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK +/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj +rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD +3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE +7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC +yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd +qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI +hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR +xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA +SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo +HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB +emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC +AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb +7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x +DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk +F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF +a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT +Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited +# Label: "WoSign" +# Serial: 125491772294754854453622855443212256657 +# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d +# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb +# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV +BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw +MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX +b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN +rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U +fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc +f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 +ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M +x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR +aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch +zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar +uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K +mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA +Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv +HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H +EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ +MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e +JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN +g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp +dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab +R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ +PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce +xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ +J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl +OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT +ee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited +# Subject: CN=CA 沃通根证书 O=WoSign CA Limited +# Label: "WoSign China" +# Serial: 106921963437422998931660691310149453965 +# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 +# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 +# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV +BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw +MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl +ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r +D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 +9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf +v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk +UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L +NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb ++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V +qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K +yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G +AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK +J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 +WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj +/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 +jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 +ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX +X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n +FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D +u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l +O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le +ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 +2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5" +# Serial: 156233699172481 +# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e +# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb +# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78 +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE +BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn +aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg +QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0 +MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD +VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 +dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom +/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR +Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3 +4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z +5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0 +hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID +AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX +SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l +VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf +peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF +Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW ++qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6" +# Serial: 138134509972618 +# MD5 Fingerprint: f8:c5:ee:2a:6b:be:95:8d:08:f7:25:4a:ea:71:3e:46 +# SHA1 Fingerprint: 8a:5c:8c:ee:a5:03:e6:05:56:ba:d8:1b:d4:f6:c9:b0:ed:e5:2f:e0 +# SHA256 Fingerprint: 8d:e7:86:55:e1:be:7f:78:47:80:0b:93:f6:94:d2:1d:36:8c:c0:6e:03:3e:7f:ab:04:bb:5e:b9:9d:a6:b7:00 +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG +EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp +IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB +LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI +aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx +NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV +BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2 +ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs +ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x +eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9 ++bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA +z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p +u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p +lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB +AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq +FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC +QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy +o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID +gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm +9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG +tAuYSyher4hYyw== +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign G2 O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign G2 O=WoSign CA Limited +# Label: "Certification Authority of WoSign G2" +# Serial: 142423943073812161787490648904721057092 +# MD5 Fingerprint: c8:1c:7d:19:aa:cb:71:93:f2:50:f8:52:a8:1e:ba:60 +# SHA1 Fingerprint: fb:ed:dc:90:65:b7:27:20:37:bc:55:0c:9c:56:de:bb:f2:78:94:e1 +# SHA256 Fingerprint: d4:87:a5:6f:83:b0:74:82:e8:5e:96:33:94:c1:ec:c2:c9:e5:1d:09:03:ee:94:6b:02:c3:01:58:1e:d9:9e:16 +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBY +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNV +BAMTJENlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDEx +MDgwMDU4NThaFw00NDExMDgwMDU4NThaMFgxCzAJBgNVBAYTAkNOMRowGAYDVQQK +ExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsGA1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8jzrvZjtFB5sg+OPX +JYY1kBaiXW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzmDuDfBpgO +gHzKtB0TiGsOqCR3A9DuW/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg +5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg95k4ot+vElbGs/V6r+kHLXZ1L3PR8du9n +fwB6jdKgGlxNIuG12t12s9R23164i5jIFFTMaxeSt+BKv0mUYQs4kI9dJGwlezt5 +2eJ+na2fmKEG/HgUYFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJ +KoZIhvcNAQELBQADggEBAFfDejaCnI2Y4qtAqkePx6db7XznPWZaOzG73/MWM5H8 +fHulwqZm46qwtyeYP0nXYGdnPzZPSsvxFPpahygc7Y9BMsaV+X3avXtbwrAh449G +3CE4Q3RM+zD4F3LBMvzIkRfEzFg3TgvMWvchNSiDbGAtROtSjFA9tWwS1/oJu2yy +SrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu+sif/a+RZQp4OBXllxcU3fng +LDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS+7Q9LGOHSJDy7 +XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg= +-----END CERTIFICATE----- + +# Issuer: CN=CA WoSign ECC Root O=WoSign CA Limited +# Subject: CN=CA WoSign ECC Root O=WoSign CA Limited +# Label: "CA WoSign ECC Root" +# Serial: 138625735294506723296996289575837012112 +# MD5 Fingerprint: 80:c6:53:ee:61:82:28:72:f0:ff:21:b9:17:ca:b2:20 +# SHA1 Fingerprint: d2:7a:d2:be:ed:94:c0:a1:3c:c7:25:21:ea:5d:71:be:81:19:f3:2b +# SHA256 Fingerprint: 8b:45:da:1c:06:f7:91:eb:0c:ab:f2:6b:e5:88:f5:fb:23:16:5c:2e:61:4b:f8:85:56:2d:0d:ce:50:b2:9b:02 +-----BEGIN CERTIFICATE----- +MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQsw +CQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMT +EkNBIFdvU2lnbiBFQ0MgUm9vdDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4 +NThaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEb +MBkGA1UEAxMSQ0EgV29TaWduIEVDQyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACID +YgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/Srj57ywvr1FQPEd1bPiUt5v8 +KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU0NGKSMyCBSah1VES +1ns2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB +1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3 +aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=Certplus Root CA G1 O=Certplus +# Subject: CN=Certplus Root CA G1 O=Certplus +# Label: "Certplus Root CA G1" +# Serial: 1491911565779898356709731176965615564637713 +# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42 +# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66 +# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a +iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt +6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP +0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f +6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE +EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN +1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc +h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT +mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV +4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO +WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd +Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq +hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh +66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7 +/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS +S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j +2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R +Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr +RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy +6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV +V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5 +g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl +++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo= +-----END CERTIFICATE----- + +# Issuer: CN=Certplus Root CA G2 O=Certplus +# Subject: CN=Certplus Root CA G2 O=Certplus +# Label: "Certplus Root CA G2" +# Serial: 1492087096131536844209563509228951875861589 +# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31 +# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a +# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17 +-----BEGIN CERTIFICATE----- +MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat +93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x +Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj +FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG +SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch +p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal +U5ORGpOucGpnutee5WEaXw== +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust +# Subject: CN=OpenTrust Root CA G1 O=OpenTrust +# Label: "OpenTrust Root CA G1" +# Serial: 1492036577811947013770400127034825178844775 +# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da +# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e +# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b +wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX +/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0 +77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP +uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx +p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx +Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2 +TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W +G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw +vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY +EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1 +2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw +DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E +PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf +gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS +FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0 +V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P +XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I +i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t +TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91 +09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky +Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ +AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj +1oxx +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust +# Subject: CN=OpenTrust Root CA G2 O=OpenTrust +# Label: "OpenTrust Root CA G2" +# Serial: 1492012448042702096986875987676935573415441 +# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb +# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b +# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh +/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e +CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6 +1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE +FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS +gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X +G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy +YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH +vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4 +t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/ +gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3 +5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w +DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz +Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0 +nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT +RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT +wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2 +t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa +TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2 +o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU +3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA +iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f +WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM +S1IK +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust +# Subject: CN=OpenTrust Root CA G3 O=OpenTrust +# Label: "OpenTrust Root CA G3" +# Serial: 1492104908271485653071219941864171170455615 +# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24 +# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6 +# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92 +-----BEGIN CERTIFICATE----- +MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx +CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U +cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow +QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl +blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm +3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d +oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5 +DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK +BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q +j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx +4nxp5V2a+EEfOzmTk51V6s2N8fvB +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/raven/events.py b/raven/events.py index 76795c589..775a5804c 100644 --- a/raven/events.py +++ b/raven/events.py @@ -4,6 +4,7 @@ :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. + """ from __future__ import absolute_import @@ -32,6 +33,41 @@ def transform(self, value): return self.client.transform(value) +# The __suppress_context__ attribute was added in Python 3.3. +# See PEP 415 for details: +# https://www.python.org/dev/peps/pep-0415/ +if hasattr(Exception, '__suppress_context__'): + def _chained_exceptions(exc_info): + """ + Return a generator iterator over an exception's chain. + + The exceptions are yielded from outermost to innermost (i.e. last to + first when viewing a stack trace). + + """ + yield exc_info + exc_type, exc, exc_traceback = exc_info + + context = set() + context.add(exc) + while True: + if exc.__suppress_context__: + # Then __cause__ should be used instead. + exc = exc.__cause__ + else: + exc = exc.__context__ + if exc in context: + break + context.add(exc) + if exc is None: + break + yield type(exc), exc, exc.__traceback__ +else: + # Then we do not support reporting exception chains. + def _chained_exceptions(exc_info): + yield exc_info + + class Exception(BaseEvent): """ Exceptions store the following metadata: @@ -40,14 +76,39 @@ class Exception(BaseEvent): - type: 'ClassName' - module '__builtin__' (i.e. __builtin__.TypeError) - frames: a list of serialized frames (see _get_traceback_frames) + """ + name = 'exception' + def to_string(self, data): - exc = data['sentry.interfaces.Exception'] + exc = data[self.name]['values'][-1] if exc['value']: return '%s: %s' % (exc['type'], exc['value']) return exc['type'] + def _get_value(self, exc_type, exc_value, exc_traceback): + """ + Convert exception info to a value for the values list. + """ + stack_info = get_stack_info( + iter_traceback_frames(exc_traceback), + transformer=self.transform, + capture_locals=self.client.capture_locals, + ) + + exc_module = getattr(exc_type, '__module__', None) + if exc_module: + exc_module = str(exc_module) + exc_type = getattr(exc_type, '__name__', '') + + return { + 'value': to_unicode(exc_value), + 'type': str(exc_type), + 'module': to_unicode(exc_module), + 'stacktrace': stack_info, + } + def capture(self, exc_info=None, **kwargs): if not exc_info or exc_info is True: exc_info = sys.exc_info() @@ -55,34 +116,15 @@ def capture(self, exc_info=None, **kwargs): if not exc_info: raise ValueError('No exception found') - exc_type, exc_value, exc_traceback = exc_info - - try: - frames = get_stack_info( - iter_traceback_frames(exc_traceback), - transformer=self.transform) - - exc_module = getattr(exc_type, '__module__', None) - if exc_module: - exc_module = str(exc_module) - exc_type = getattr(exc_type, '__name__', '') - - return { - 'level': kwargs.get('level', logging.ERROR), - 'sentry.interfaces.Exception': { - 'value': to_unicode(exc_value), - 'type': str(exc_type), - 'module': to_unicode(exc_module), - }, - 'sentry.interfaces.Stacktrace': { - 'frames': frames - }, - } - finally: - try: - del exc_type, exc_value, exc_traceback - except Exception as e: - self.logger.exception(e) + values = [] + for exc_info in _chained_exceptions(exc_info): + value = self._get_value(*exc_info) + values.insert(0, value) + + return { + 'level': kwargs.get('level', logging.ERROR), + self.name: {'values': values}, + } class Message(BaseEvent): @@ -92,12 +134,19 @@ class Message(BaseEvent): - message: 'My message from %s about %s' - params: ('foo', 'bar') """ + + name = 'sentry.interfaces.Message' + + def to_string(self, data): + return data[self.name]['message'] + def capture(self, message, params=(), formatted=None, **kwargs): message = to_unicode(message) data = { - 'sentry.interfaces.Message': { + self.name: { 'message': message, 'params': self.transform(params), + 'formatted': formatted, }, } if 'message' not in data: @@ -112,13 +161,16 @@ class Query(BaseEvent): - query: 'SELECT * FROM table' - engine: 'postgesql_psycopg2' """ + + name = 'sentry.interfaces.Query' + def to_string(self, data): - sql = data['sentry.interfaces.Query'] + sql = data[self.name] return sql['query'] def capture(self, query, engine, **kwargs): return { - 'sentry.interfaces.Query': { + self.name: { 'query': to_unicode(query), 'engine': str(engine), } diff --git a/raven/exceptions.py b/raven/exceptions.py new file mode 100644 index 000000000..505c23e8b --- /dev/null +++ b/raven/exceptions.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import + +from raven.utils.compat import text_type + + +class APIError(Exception): + def __init__(self, message, code=0): + self.code = code + self.message = message + + def __unicode__(self): + return text_type("%s: %s" % (self.message, self.code)) + + +class RateLimited(APIError): + def __init__(self, message, retry_after=0): + self.retry_after = retry_after + super(RateLimited, self).__init__(message, 429) + + +class InvalidGitRepository(Exception): + pass + + +class ConfigurationError(ValueError): + pass + + +class InvalidDsn(ConfigurationError): + pass diff --git a/raven/handlers/logbook.py b/raven/handlers/logbook.py index 7f812b384..cd9ae94f2 100644 --- a/raven/handlers/logbook.py +++ b/raven/handlers/logbook.py @@ -7,22 +7,23 @@ """ from __future__ import absolute_import +from __future__ import print_function import logbook import sys import traceback +from raven.utils.compat import string_types from raven.base import Client from raven.utils.encoding import to_string -from raven.utils import six class SentryHandler(logbook.Handler): def __init__(self, *args, **kwargs): if len(args) == 1: arg = args[0] - if isinstance(arg, six.string_types): - self.client = kwargs.pop('client_cls', Client)(dsn=arg) + if isinstance(arg, string_types): + self.client = kwargs.pop('client_cls', Client)(dsn=arg, **kwargs) elif isinstance(arg, Client): self.client = arg else: @@ -42,14 +43,16 @@ def emit(self, record): try: # Avoid typical config issues by overriding loggers behavior if record.channel.startswith(('sentry.errors', 'raven')): - print >> sys.stderr, to_string(self.format(record)) + print(to_string(self.format(record)), file=sys.stderr) return return self._emit(record) except Exception: - print >> sys.stderr, "Top level Sentry exception caught - failed creating log record" - print >> sys.stderr, to_string(record.msg) - print >> sys.stderr, to_string(traceback.format_exc()) + if self.client.raise_send_errors: + raise + print("Top level Sentry exception caught - failed creating log record", file=sys.stderr) + print(to_string(record.msg), file=sys.stderr) + print(to_string(traceback.format_exc())) try: self.client.captureException() @@ -80,7 +83,7 @@ def _emit(self, record): data.update(handler.capture(**handler_kwargs)) event_type = 'raven.events.Exception' - handler_kwargs = {'exc_info': record.exc_info} + handler_kwargs['exc_info'] = record.exc_info extra = { 'lineno': record.lineno, diff --git a/raven/handlers/logging.py b/raven/handlers/logging.py index f1ab2b858..fac7bb553 100644 --- a/raven/handlers/logging.py +++ b/raven/handlers/logging.py @@ -14,12 +14,47 @@ import sys import traceback +from raven.utils.compat import string_types, iteritems, text_type from raven.base import Client -from raven.utils import six from raven.utils.encoding import to_string -from raven.utils.stacks import iter_stack_frames, label_from_frame +from raven.utils.stacks import iter_stack_frames -RESERVED = ('stack', 'name', 'module', 'funcName', 'args', 'msg', 'levelno', 'exc_text', 'exc_info', 'data', 'created', 'levelname', 'msecs', 'relativeCreated', 'tags') +RESERVED = frozenset(( + 'stack', 'name', 'module', 'funcName', 'args', 'msg', 'levelno', + 'exc_text', 'exc_info', 'data', 'created', 'levelname', 'msecs', + 'relativeCreated', 'tags', 'message', +)) + + +CONTEXTUAL = frozenset(( + 'user', 'culprit', 'server_name', 'fingerprint' +)) + + +def extract_extra(record, reserved=RESERVED, contextual=CONTEXTUAL): + data = {} + + extra = getattr(record, 'data', None) + if not isinstance(extra, dict): + if extra: + extra = {'data': extra} + else: + extra = {} + else: + # record.data may be something we don't want to mutate to not cause unexpected side effects + extra = dict(extra) + + for k, v in iteritems(vars(record)): + if k in reserved: + continue + if k.startswith('_'): + continue + if '.' not in k and k not in contextual: + extra[k] = v + else: + data[k] = v + + return data, extra class SentryHandler(logging.Handler, object): @@ -27,42 +62,48 @@ def __init__(self, *args, **kwargs): client = kwargs.get('client_cls', Client) if len(args) == 1: arg = args[0] - if isinstance(arg, six.string_types): - self.client = client(dsn=arg) + if isinstance(arg, string_types): + self.client = client(dsn=arg, **kwargs) elif isinstance(arg, Client): self.client = arg else: - raise ValueError('The first argument to %s must be either a Client instance or a DSN, got %r instead.' % ( - self.__class__.__name__, - arg, - )) + raise ValueError('The first argument to %s must be either a ' + 'Client instance or a DSN, got %r instead.' % + (self.__class__.__name__, arg,)) elif 'client' in kwargs: self.client = kwargs['client'] - elif len(args) == 2 and not kwargs: - servers, key = args - self.client = client(servers=servers, key=key) else: self.client = client(*args, **kwargs) + self.tags = kwargs.pop('tags', None) + logging.Handler.__init__(self, level=kwargs.get('level', logging.NOTSET)) + def can_record(self, record): + return not ( + record.name == 'raven' + or record.name.startswith(('sentry.errors', 'raven.')) + ) + def emit(self, record): try: # Beware to python3 bug (see #10805) if exc_info is (None, None, None) self.format(record) - # Avoid typical config issues by overriding loggers behavior - if record.name.startswith(('sentry.errors', 'raven')) or record.module.startswith('raven'): - print(to_string(record.message), sys.stderr) + if not self.can_record(record): + print(to_string(record.message), file=sys.stderr) return return self._emit(record) except Exception: - print("Top level Sentry exception caught - failed creating log record", sys.stderr) - print(to_string(record.msg), sys.stderr) - print(to_string(traceback.format_exc()), sys.stderr) - - def _get_targetted_stack(self, stack): + if self.client.raise_send_errors: + raise + print("Top level Sentry exception caught - failed " + "creating log record", file=sys.stderr) + print(to_string(record.msg), file=sys.stderr) + print(to_string(traceback.format_exc()), file=sys.stderr) + + def _get_targetted_stack(self, stack, record): # we might need to traverse this multiple times, so coerce it to a list stack = list(stack) frames = [] @@ -78,8 +119,10 @@ def _get_targetted_stack(self, stack): if not started: f_globals = getattr(frame, 'f_globals', {}) module_name = f_globals.get('__name__', '') - if ((last_mod and last_mod.startswith('logging')) - and not module_name.startswith('logging')): + if ( + last_mod and last_mod.startswith('logging') + and not module_name.startswith('logging') + ): started = True else: last_mod = module_name @@ -94,31 +137,14 @@ def _get_targetted_stack(self, stack): return frames def _emit(self, record, **kwargs): - data = {} - - extra = getattr(record, 'data', None) - if not isinstance(extra, dict): - if extra: - extra = {'data': extra} - else: - extra = {} - - for k, v in six.iteritems(vars(record)): - if k in RESERVED: - continue - if k.startswith('_'): - continue - if '.' not in k and k not in ('culprit',): - extra[k] = v - else: - data[k] = v + data, extra = extract_extra(record) stack = getattr(record, 'stack', None) if stack is True: stack = iter_stack_frames() if stack: - stack = self._get_targetted_stack(stack) + stack = self._get_targetted_stack(stack, record) date = datetime.datetime.utcfromtimestamp(record.created) event_type = 'raven.events.Message' @@ -126,13 +152,13 @@ def _emit(self, record, **kwargs): 'params': record.args, } try: - handler_kwargs['message'] = six.text_type(record.msg) + handler_kwargs['message'] = text_type(record.msg) except UnicodeDecodeError: # Handle binary strings where it should be unicode... handler_kwargs['message'] = repr(record.msg)[1:-1] try: - handler_kwargs['formatted'] = six.text_type(record.message) + handler_kwargs['formatted'] = text_type(record.message) except UnicodeDecodeError: # Handle binary strings where it should be unicode... handler_kwargs['formatted'] = repr(record.message)[1:-1] @@ -149,20 +175,18 @@ def _emit(self, record, **kwargs): event_type = 'raven.events.Exception' handler_kwargs = {'exc_info': record.exc_info} - # HACK: discover a culprit when we normally couldn't - elif not (data.get('sentry.interfaces.Stacktrace') or data.get('culprit')) and (record.name or record.funcName): - culprit = label_from_frame({'module': record.name, 'function': record.funcName}) - if culprit: - data['culprit'] = culprit - data['level'] = record.levelno data['logger'] = record.name - if hasattr(record, 'tags'): - kwargs['tags'] = record.tags + kwargs['tags'] = tags = {} + if self.tags: + tags.update(self.tags) + tags.update(getattr(record, 'tags', {})) kwargs.update(handler_kwargs) + sample_rate = extra.pop('sample_rate', None) return self.client.capture( event_type, stack=stack, data=data, - extra=extra, date=date, **kwargs) + extra=extra, date=date, sample_rate=sample_rate, + **kwargs) diff --git a/raven/middleware.py b/raven/middleware.py index 3f1e420e6..def5896af 100644 --- a/raven/middleware.py +++ b/raven/middleware.py @@ -7,10 +7,75 @@ """ from __future__ import absolute_import +from contextlib import contextmanager + +from raven.utils.compat import Iterator, next from raven.utils.wsgi import ( get_current_url, get_headers, get_environ) +@contextmanager +def common_exception_handling(environ, client): + try: + yield + except (StopIteration, GeneratorExit): + # Make sure we do this explicitly here. At least GeneratorExit + # is handled implicitly by the rest of the logic but we want + # to make sure this does not regress + raise + except Exception: + client.handle_exception(environ) + raise + except KeyboardInterrupt: + client.handle_exception(environ) + raise + except SystemExit as e: + if e.code != 0: + client.handle_exception(environ) + raise + + +class ClosingIterator(Iterator): + """ + An iterator that is implements a ``close`` method as-per + WSGI recommendation. + """ + + def __init__(self, sentry, iterable, environ): + self.sentry = sentry + self.environ = environ + self._close = getattr(iterable, 'close', None) + self.iterable = iter(iterable) + self.closed = False + + def __iter__(self): + return self + + def __next__(self): + try: + with common_exception_handling(self.environ, self.sentry): + return next(self.iterable) + except StopIteration: + # We auto close here if we reach the end because some WSGI + # middleware does not really like to close things. To avoid + # massive leaks we just close automatically at the end of + # iteration. + self.close() + raise + + def close(self): + if self.closed: + return + try: + if self._close is not None: + with common_exception_handling(self.environ, self.sentry): + self._close() + finally: + self.sentry.client.context.clear() + self.sentry.client.transaction.clear() + self.closed = True + + class Sentry(object): """ A WSGI middleware which will attempt to capture any @@ -19,6 +84,7 @@ class Sentry(object): >>> from raven.base import Client >>> application = Sentry(application, Client()) """ + def __init__(self, application, client=None): self.application = application if client is None: @@ -27,39 +93,23 @@ def __init__(self, application, client=None): self.client = client def __call__(self, environ, start_response): - try: + # TODO(dcramer): ideally this is lazy, but the context helpers must + # support callbacks first + self.client.http_context(self.get_http_context(environ)) + with common_exception_handling(environ, self): iterable = self.application(environ, start_response) - except Exception: - self.handle_exception(environ) - raise + return ClosingIterator(self, iterable, environ) - try: - for event in iterable: - yield event - except Exception: - self.handle_exception(environ) - raise - finally: - # wsgi spec requires iterable to call close if it exists - # see http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html - if iterable and hasattr(iterable, 'close') and callable(iterable.close): - try: - iterable.close() - except Exception: - self.handle_exception(environ) - - def handle_exception(self, environ): - event_id = self.client.captureException( - data={ - 'sentry.interfaces.Http': { - 'method': environ.get('REQUEST_METHOD'), - 'url': get_current_url(environ, strip_querystring=True), - 'query_string': environ.get('QUERY_STRING'), - # TODO - # 'data': environ.get('wsgi.input'), - 'headers': dict(get_headers(environ)), - 'env': dict(get_environ(environ)), - } - }, - ) - return event_id + def get_http_context(self, environ): + return { + 'method': environ.get('REQUEST_METHOD'), + 'url': get_current_url(environ, strip_querystring=True), + 'query_string': environ.get('QUERY_STRING'), + # TODO + # 'data': environ.get('wsgi.input'), + 'headers': dict(get_headers(environ)), + 'env': dict(get_environ(environ)), + } + + def handle_exception(self, environ=None): + return self.client.captureException() diff --git a/raven/processors.py b/raven/processors.py index 224dc1d47..a5509b6c9 100644 --- a/raven/processors.py +++ b/raven/processors.py @@ -8,9 +8,10 @@ from __future__ import absolute_import import re +import warnings +from raven.utils.compat import string_types, text_type, PY3 from raven.utils import varmap -from raven.utils import six class Processor(object): @@ -24,62 +25,83 @@ def process(self, data, **kwargs): resp = self.get_data(data, **kwargs) if resp: data = resp + + if 'exception' in data: + if 'values' in data['exception']: + for value in data['exception'].get('values', []): + if 'stacktrace' in value: + self.filter_stacktrace(value['stacktrace']) + + if 'request' in data: + self.filter_http(data['request']) + + if 'extra' in data: + data['extra'] = self.filter_extra(data['extra']) + return data + def filter_stacktrace(self, data): + pass -class RemovePostDataProcessor(Processor): - """ - Removes HTTP post data. - """ - def process(self, data, **kwargs): - if 'sentry.interfaces.Http' in data: - data['sentry.interfaces.Http'].pop('data', None) + def filter_http(self, data): + pass + def filter_extra(self, data): return data +class RemovePostDataProcessor(Processor): + """Removes HTTP post data.""" + + def filter_http(self, data, **kwargs): + data.pop('data', None) + + class RemoveStackLocalsProcessor(Processor): - """ - Removes local context variables from stacktraces. - """ - def process(self, data, **kwargs): - if 'sentry.interfaces.Stacktrace' in data: - for frame in data['sentry.interfaces.Stacktrace'].get('frames', []): - frame.pop('vars', None) + """Removes local context variables from stacktraces.""" - return data + def filter_stacktrace(self, data, **kwargs): + for frame in data.get('frames', []): + frame.pop('vars', None) -class SanitizePasswordsProcessor(Processor): +class SanitizeKeysProcessor(Processor): """ - Asterisk out things that look like passwords and credit - card numbers in frames, http, and basic extra data. + Asterisk out things that correspond to a configurable set of keys. """ + MASK = '*' * 8 - FIELDS = frozenset(['password', 'secret', 'passwd', 'authorization']) - VALUES_RE = re.compile(r'^(?:\d[ -]*?){13,16}$') - def sanitize(self, key, value): + @property + def sanitize_keys(self): + keys = getattr(self.client, 'sanitize_keys') + if keys is None: + raise ValueError('The sanitize_keys setting must be present to use SanitizeKeysProcessor') + return keys + + def sanitize(self, item, value): if value is None: return - if isinstance(value, six.string_types) and self.VALUES_RE.match(value): - return self.MASK - - if not key: # key can be a NoneType + if not item: # key can be a NoneType return value - key = key.lower() - for field in self.FIELDS: - if field in key: + # Just in case we have bytes here, we want to make them into text + # properly without failing so we can perform our check. + if isinstance(item, bytes): + item = item.decode('utf-8', 'replace') + else: + item = text_type(item) + + item = item.lower() + for key in self.sanitize_keys: + if key in item: # store mask as a fixed length for security return self.MASK return value def filter_stacktrace(self, data): - if 'frames' not in data: - return - for frame in data['frames']: + for frame in data.get('frames', []): if 'vars' not in frame: continue frame['vars'] = varmap(self.sanitize, frame['vars']) @@ -89,25 +111,75 @@ def filter_http(self, data): if n not in data: continue - if isinstance(data[n], six.string_types) and '=' in data[n]: + # data could be provided as bytes + if PY3 and isinstance(data[n], bytes): + data[n] = data[n].decode('utf-8', 'replace') + + if isinstance(data[n], string_types) and '=' in data[n]: # at this point we've assumed it's a standard HTTP query - querybits = [] - for bit in data[n].split('&'): - chunk = bit.split('=') - if len(chunk) == 2: - querybits.append((chunk[0], self.sanitize(*chunk))) - else: - querybits.append(chunk) - - data[n] = '&'.join('='.join(k) for k in querybits) + # or cookie + if n == 'cookies': + delimiter = ';' + else: + delimiter = '&' + + data[n] = self._sanitize_keyvals(data[n], delimiter) else: data[n] = varmap(self.sanitize, data[n]) + if n == 'headers' and 'Cookie' in data[n]: + data[n]['Cookie'] = self._sanitize_keyvals( + data[n]['Cookie'], ';' + ) + + def filter_extra(self, data): + return varmap(self.sanitize, data) + + def _sanitize_keyvals(self, keyvals, delimiter): + sanitized_keyvals = [] + for keyval in keyvals.split(delimiter): + keyval = keyval.split('=') + if len(keyval) == 2: + sanitized_keyvals.append((keyval[0], self.sanitize(*keyval))) + else: + sanitized_keyvals.append(keyval) - def process(self, data, **kwargs): - if 'sentry.interfaces.Stacktrace' in data: - self.filter_stacktrace(data['sentry.interfaces.Stacktrace']) + return delimiter.join('='.join(keyval) for keyval in sanitized_keyvals) - if 'sentry.interfaces.Http' in data: - self.filter_http(data['sentry.interfaces.Http']) - return data +class SanitizePasswordsProcessor(SanitizeKeysProcessor): + """ + Asterisk out things that look like passwords, credit card numbers, + and API keys in frames, http, and basic extra data. + """ + + KEYS = frozenset([ + 'password', + 'secret', + 'passwd', + 'authorization', + 'api_key', + 'apikey', + 'sentry_dsn', + 'access_token', + ]) + VALUES_RE = re.compile(r'^(?:\d[ -]*?){13,16}$') + + @property + def sanitize_keys(self): + return self.KEYS + + @property + def FIELDS(self): + warnings.warn( + "`SanitizePasswordsProcessor.Fields` has been deprecated. Use " + "`SanitizePasswordsProcessor.KEYS` or `SanitizePasswordsProcessor.sanitize_keys` " + "instead", + DeprecationWarning, + ) + return self.KEYS + + def sanitize(self, item, value): + value = super(SanitizePasswordsProcessor, self).sanitize(item, value) + if isinstance(value, string_types) and self.VALUES_RE.match(value): + return self.MASK + return value diff --git a/raven/scripts/runner.py b/raven/scripts/runner.py index 959cc94a6..9442a8654 100644 --- a/raven/scripts/runner.py +++ b/raven/scripts/runner.py @@ -12,6 +12,8 @@ import logging import os import sys +import time + from optparse import OptionParser from raven import Client, get_version @@ -38,35 +40,40 @@ def get_uid(): import pwd except ImportError: return None - return pwd.getpwuid(os.geteuid())[0] + try: + return pwd.getpwuid(os.geteuid())[0] + except KeyError: # Sometimes fails in containers + return None def send_test_message(client, options): - print("Client configuration:") - for k in ('servers', 'project', 'public_key', 'secret_key'): - print(' %-15s: %s' % (k, getattr(client, k))) - print() - - if not all([client.servers, client.project, client.public_key, client.secret_key]): - print("Error: All values must be set!") + sys.stdout.write("Client configuration:\n") + for k in ('base_url', 'project', 'public_key', 'secret_key'): + sys.stdout.write(' %-15s: %s\n' % (k, getattr(client.remote, k))) + sys.stdout.write('\n') + + remote_config = client.remote + if not remote_config.is_active(): + sys.stdout.write("Error: DSN configuration is not valid!\n") sys.exit(1) if not client.is_enabled(): - print('Error: Client reports as being disabled!') + sys.stdout.write('Error: Client reports as being disabled!\n') sys.exit(1) data = options.get('data', { 'culprit': 'raven.scripts.runner', 'logger': 'raven.test', - 'sentry.interfaces.Http': { + 'request': { 'method': 'GET', 'url': 'http://example.com', } }) - print('Sending a test message...',) + sys.stdout.write('Sending a test message... ') + sys.stdout.flush() - ident = client.get_ident(client.captureMessage( + ident = client.captureMessage( message='This is a test message generated using ``raven test``', data=data, level=logging.INFO, @@ -76,20 +83,16 @@ def send_test_message(client, options): 'user': get_uid(), 'loadavg': get_loadavg(), }, - )) - - if client.state.did_fail(): - print('error!') - return False + ) - print('success!') - print('Event ID was %r' % (ident,)) + sys.stdout.write('Event ID was %r\n' % (ident,)) def main(): root = logging.getLogger('sentry.errors') root.setLevel(logging.DEBUG) - root.addHandler(logging.StreamHandler()) + # if len(root.handlers) == 0: + # root.addHandler(logging.StreamHandler()) parser = OptionParser(version=get_version()) parser.add_option("--data", action="callback", callback=store_json, @@ -109,4 +112,13 @@ def main(): print() client = Client(dsn, include_paths=['raven']) + send_test_message(client, opts.__dict__) + + # TODO(dcramer): correctly support async models + time.sleep(3) + if client.state.did_fail(): + sys.stdout.write('error!\n') + sys.exit(1) + + sys.stdout.write('success!\n') diff --git a/raven/transport/__init__.py b/raven/transport/__init__.py index 3a42fe569..3549c7b6a 100644 --- a/raven/transport/__init__.py +++ b/raven/transport/__init__.py @@ -5,9 +5,17 @@ :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ +# TODO: deprecate this namespace and force non-default (sync + threaded) to +# manually import/register transports somehow from __future__ import absolute_import -from raven.transport.base import (Transport, AsyncTransport, HTTPTransport, GeventedHTTPTransport, TwistedHTTPTransport, # NOQA - TornadoHTTPTransport, UDPTransport, EventletHTTPTransport) # NOQA -from raven.transport.exceptions import InvalidScheme, DuplicateScheme # NOQA -from raven.transport.registry import TransportRegistry, default_transports # NOQA +from raven.transport.base import * # NOQA +from raven.transport.eventlet import * # NOQA +from raven.transport.exceptions import * # NOQA +from raven.transport.gevent import * # NOQA +from raven.transport.http import * # NOQA +from raven.transport.requests import * # NOQA +from raven.transport.registry import * # NOQA +from raven.transport.twisted import * # NOQA +from raven.transport.threaded import * # NOQA +from raven.transport.tornado import * # NOQA diff --git a/raven/transport/base.py b/raven/transport/base.py index 31cf0d68d..37045246d 100644 --- a/raven/transport/base.py +++ b/raven/transport/base.py @@ -1,58 +1,15 @@ """ -raven.transport.builtins -~~~~~~~~~~~~~~~~~~~~~~~~ +raven.transport.base +~~~~~~~~~~~~~~~~~~~~ :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ from __future__ import absolute_import -import logging -import sys -from raven.utils import compat -try: - # Google App Engine blacklists parts of the socket module, this will prevent - # it from blowing up. - from socket import socket, AF_INET, SOCK_DGRAM, error as socket_error - has_socket = True -except: - has_socket = False - -try: - import gevent - # gevent 1.0bN renamed coros to lock - try: - from gevent.lock import Semaphore - except ImportError: - from gevent.coros import Semaphore # NOQA - has_gevent = True -except: - has_gevent = None - -try: - import twisted.web.client - import twisted.internet.protocol - has_twisted = True -except: - has_twisted = False - -try: - from tornado import ioloop - from tornado.httpclient import AsyncHTTPClient, HTTPClient - has_tornado = True -except: - has_tornado = False - -try: - import eventlet - from eventlet.green import urllib2 as eventlet_urllib2 - has_eventlet = True -except: - has_eventlet = False - -from raven.conf import defaults -from raven.transport.exceptions import InvalidScheme +# Helper for external transports +has_newstyle_transports = True class Transport(object): @@ -60,289 +17,34 @@ class Transport(object): All transport implementations need to subclass this class You must implement a send method (or an async_send method if - sub-classing AsyncTransport) and the compute_scope method. - - Please see the HTTPTransport class for an example of a - compute_scope implementation. + sub-classing AsyncTransport). """ - async = False - - def check_scheme(self, url): - if url.scheme not in self.scheme: - raise InvalidScheme() + is_async = False + scheme = [] - def send(self, data, headers): + def send(self, url, data, headers): """ You need to override this to do something with the actual data. Usually - this is sending to a server """ raise NotImplementedError - def compute_scope(self, url, scope): - """ - You need to override this to compute the SENTRY specific - additions to the variable scope. See the HTTPTransport for an - example. - """ - raise NotImplementedError - class AsyncTransport(Transport): """ All asynchronous transport implementations should subclass this class. - You must implement a async_send method (and the compute_scope - method as describe on the base Transport class). + You must implement a async_send method. """ - async = True + is_async = True - def async_send(self, data, headers, success_cb, error_cb): + def async_send(self, url, data, headers, success_cb, error_cb): """ Override this method for asynchronous transports. Call `success_cb()` if the send succeeds or `error_cb(exception)` if the send fails. """ raise NotImplementedError - - -class BaseUDPTransport(Transport): - def __init__(self, parsed_url): - super(BaseUDPTransport, self).__init__() - self.check_scheme(parsed_url) - self._parsed_url = parsed_url - - def send(self, data, headers): - auth_header = headers.get('X-Sentry-Auth') - - if auth_header is None: - # silently ignore attempts to send messages without an auth header - return - - host, port = self._parsed_url.netloc.split(':') - self._send_data(auth_header + '\n\n' + data, (host, int(port))) - - def compute_scope(self, url, scope): - path_bits = url.path.rsplit('/', 1) - if len(path_bits) > 1: - path = path_bits[0] - else: - path = '' - project = path_bits[-1] - - if not all([url.port, project, url.username, url.password]): - raise ValueError('Invalid Sentry DSN: %r' % url.geturl()) - - netloc = url.hostname - netloc += ':%s' % url.port - - server = '%s://%s%s/api/%s/store/' % ( - url.scheme, netloc, path, project) - scope.update({ - 'SENTRY_SERVERS': [server], - 'SENTRY_PROJECT': project, - 'SENTRY_PUBLIC_KEY': url.username, - 'SENTRY_SECRET_KEY': url.password, - }) - return scope - - -class UDPTransport(BaseUDPTransport): - scheme = ['udp'] - - def __init__(self, parsed_url): - super(UDPTransport, self).__init__(parsed_url) - if not has_socket: - raise ImportError('UDPTransport requires the socket module') - - def _send_data(self, data, addr): - udp_socket = None - try: - udp_socket = socket(AF_INET, SOCK_DGRAM) - udp_socket.setblocking(False) - udp_socket.sendto(data, addr) - except socket_error: - # as far as I understand things this simply can't happen, - # but still, it can't hurt - pass - finally: - # Always close up the socket when we're done - if udp_socket is not None: - udp_socket.close() - udp_socket = None - - -class HTTPTransport(Transport): - - scheme = ['http', 'https'] - - def __init__(self, parsed_url, timeout=defaults.TIMEOUT): - self.check_scheme(parsed_url) - - self._parsed_url = parsed_url - self._url = parsed_url.geturl() - self.timeout = timeout - - def send(self, data, headers): - """ - Sends a request to a remote webserver using HTTP POST. - """ - req = compat.Request(self._url, headers=headers) - - if sys.version_info < (2, 6): - response = compat.urlopen(req, data).read() - else: - response = compat.urlopen(req, data, self.timeout).read() - return response - - def compute_scope(self, url, scope): - netloc = url.hostname - if url.port and (url.scheme, url.port) not in \ - (('http', 80), ('https', 443)): - netloc += ':%s' % url.port - - path_bits = url.path.rsplit('/', 1) - if len(path_bits) > 1: - path = path_bits[0] - else: - path = '' - project = path_bits[-1] - - if not all([netloc, project, url.username, url.password]): - raise ValueError('Invalid Sentry DSN: %r' % url.geturl()) - - server = '%s://%s%s/api/%s/store/' % ( - url.scheme, netloc, path, project) - scope.update({ - 'SENTRY_SERVERS': [server], - 'SENTRY_PROJECT': project, - 'SENTRY_PUBLIC_KEY': url.username, - 'SENTRY_SECRET_KEY': url.password, - }) - return scope - - -class GeventedHTTPTransport(AsyncTransport, HTTPTransport): - - scheme = ['gevent+http', 'gevent+https'] - - def __init__(self, parsed_url, maximum_outstanding_requests=100): - if not has_gevent: - raise ImportError('GeventedHTTPTransport requires gevent.') - self._lock = Semaphore(maximum_outstanding_requests) - - super(GeventedHTTPTransport, self).__init__(parsed_url) - - # remove the gevent+ from the protocol, as it is not a real protocol - self._url = self._url.split('+', 1)[-1] - - def async_send(self, data, headers, success_cb, failure_cb): - """ - Spawn an async request to a remote webserver. - """ - # this can be optimized by making a custom self.send that does not - # read the response since we don't use it. - self._lock.acquire() - return gevent.spawn( - super(GeventedHTTPTransport, self).send, data, headers - ).link(lambda x: self._done(x, success_cb, failure_cb)) - - def _done(self, greenlet, success_cb, failure_cb, *args): - self._lock.release() - if greenlet.successful(): - success_cb() - else: - failure_cb(greenlet.value) - - -class TwistedHTTPTransport(AsyncTransport, HTTPTransport): - - scheme = ['twisted+http', 'twisted+https'] - - def __init__(self, parsed_url): - if not has_twisted: - raise ImportError('TwistedHTTPTransport requires twisted.web.') - - super(TwistedHTTPTransport, self).__init__(parsed_url) - self.logger = logging.getLogger('sentry.errors') - - # remove the twisted+ from the protocol, as it is not a real protocol - self._url = self._url.split('+', 1)[-1] - - def async_send(self, data, headers, success_cb, failure_cb): - d = twisted.web.client.getPage(self._url, method='POST', postdata=data, - headers=headers) - d.addCallback(lambda r: success_cb()) - d.addErrback(lambda f: failure_cb(f.value)) - - -class TwistedUDPTransport(BaseUDPTransport): - scheme = ['twisted+udp'] - - def __init__(self, parsed_url): - super(TwistedUDPTransport, self).__init__(parsed_url) - if not has_twisted: - raise ImportError('TwistedUDPTransport requires twisted.') - self.protocol = twisted.internet.protocol.DatagramProtocol() - twisted.internet.reactor.listenUDP(0, self.protocol) - - def _send_data(self, data, addr): - self.protocol.transport.write(data, addr) - - -class TornadoHTTPTransport(HTTPTransport): - - scheme = ['tornado+http'] - - def __init__(self, parsed_url): - if not has_tornado: - raise ImportError('TornadoHTTPTransport requires tornado.') - - super(TornadoHTTPTransport, self).__init__(parsed_url) - - # remove the tornado+ from the protocol, as it is not a real protocol - self._url = self._url.split('+', 1)[-1] - - def send(self, data, headers): - kwargs = dict(method='POST', headers=headers, body=data) - - # only use async if ioloop is running, otherwise it will never send - if ioloop.IOLoop.initialized(): - client = AsyncHTTPClient() - kwargs['callback'] = None - else: - client = HTTPClient() - - client.fetch(self._url, **kwargs) - - -class EventletHTTPTransport(HTTPTransport): - - scheme = ['eventlet+http', 'eventlet+https'] - - def __init__(self, parsed_url, pool_size=100): - if not has_eventlet: - raise ImportError('EventletHTTPTransport requires eventlet.') - super(EventletHTTPTransport, self).__init__(parsed_url) - # remove the eventlet+ from the protocol, as it is not a real protocol - self._url = self._url.split('+', 1)[-1] - - def _send_payload(self, payload): - req = eventlet_urllib2.Request(self._url, headers=payload[1]) - try: - if sys.version_info < (2, 6): - response = eventlet_urllib2.urlopen(req, payload[0]).read() - else: - response = eventlet_urllib2.urlopen(req, payload[0], - self.timeout).read() - return response - except Exception as err: - return err - - def send(self, data, headers): - """ - Spawn an async request to a remote webserver. - """ - eventlet.spawn(self._send_payload, (data, headers)) diff --git a/raven/transport/eventlet.py b/raven/transport/eventlet.py new file mode 100644 index 000000000..48502b932 --- /dev/null +++ b/raven/transport/eventlet.py @@ -0,0 +1,51 @@ +""" +raven.transport.eventlet +~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import sys + +from raven.transport.http import HTTPTransport + +try: + import eventlet + try: + from eventlet.green import urllib2 as eventlet_urllib2 + except ImportError: + from eventlet.green.urllib import request as eventlet_urllib2 + has_eventlet = True +except ImportError: + has_eventlet = False + + +class EventletHTTPTransport(HTTPTransport): + + scheme = ['eventlet+http', 'eventlet+https'] + + def __init__(self, pool_size=100, **kwargs): + if not has_eventlet: + raise ImportError('EventletHTTPTransport requires eventlet.') + super(EventletHTTPTransport, self).__init__(**kwargs) + + def _send_payload(self, payload): + url, data, headers = payload + req = eventlet_urllib2.Request(url, headers=headers) + try: + if sys.version_info < (2, 6): + response = eventlet_urllib2.urlopen(req, data).read() + else: + response = eventlet_urllib2.urlopen(req, data, + self.timeout).read() + return response + except Exception as err: + return err + + def send(self, url, data, headers): + """ + Spawn an async request to a remote webserver. + """ + eventlet.spawn(self._send_payload, (url, data, headers)) diff --git a/raven/transport/gevent.py b/raven/transport/gevent.py new file mode 100644 index 000000000..e871545c6 --- /dev/null +++ b/raven/transport/gevent.py @@ -0,0 +1,53 @@ +""" +raven.transport.gevent +~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +from raven.transport.base import AsyncTransport +from raven.transport.http import HTTPTransport + +try: + import gevent + # gevent 1.0bN renamed coros to lock + try: + from gevent.lock import Semaphore + except ImportError: + from gevent.coros import Semaphore # NOQA + has_gevent = True +except ImportError: + has_gevent = None + + +class GeventedHTTPTransport(AsyncTransport, HTTPTransport): + + scheme = ['gevent+http', 'gevent+https'] + + def __init__(self, maximum_outstanding_requests=100, *args, **kwargs): + if not has_gevent: + raise ImportError('GeventedHTTPTransport requires gevent.') + + self._lock = Semaphore(maximum_outstanding_requests) + + super(GeventedHTTPTransport, self).__init__(*args, **kwargs) + + def async_send(self, url, data, headers, success_cb, failure_cb): + """ + Spawn an async request to a remote webserver. + """ + # this can be optimized by making a custom self.send that does not + # read the response since we don't use it. + self._lock.acquire() + return gevent.spawn( + super(GeventedHTTPTransport, self).send, url, data, headers + ).link(lambda x: self._done(x, success_cb, failure_cb)) + + def _done(self, greenlet, success_cb, failure_cb, *args): + self._lock.release() + if greenlet.successful(): + success_cb() + else: + failure_cb(greenlet.exception) diff --git a/raven/transport/http.py b/raven/transport/http.py new file mode 100644 index 000000000..a5a08046a --- /dev/null +++ b/raven/transport/http.py @@ -0,0 +1,58 @@ +""" +raven.transport.http +~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +from raven.utils.compat import string_types, urllib2 +from raven.conf import defaults +from raven.exceptions import APIError, RateLimited +from raven.transport.base import Transport +from raven.utils.http import urlopen + + +class HTTPTransport(Transport): + scheme = ['sync+http', 'sync+https'] + + def __init__(self, timeout=defaults.TIMEOUT, verify_ssl=True, + ca_certs=defaults.CA_BUNDLE): + if isinstance(timeout, string_types): + timeout = int(timeout) + if isinstance(verify_ssl, string_types): + verify_ssl = bool(int(verify_ssl)) + + self.timeout = timeout + self.verify_ssl = verify_ssl + self.ca_certs = ca_certs + + def send(self, url, data, headers): + """ + Sends a request to a remote webserver using HTTP POST. + """ + req = urllib2.Request(url, headers=headers) + + try: + response = urlopen( + url=req, + data=data, + timeout=self.timeout, + verify_ssl=self.verify_ssl, + ca_certs=self.ca_certs, + ) + except urllib2.HTTPError as exc: + msg = exc.headers.get('x-sentry-error') + code = exc.getcode() + if code == 429: + try: + retry_after = int(exc.headers.get('retry-after')) + except (ValueError, TypeError): + retry_after = 0 + raise RateLimited(msg, retry_after) + elif msg: + raise APIError(msg, code) + else: + raise + return response diff --git a/raven/transport/registry.py b/raven/transport/registry.py index 5b59058e6..9f5d3c576 100644 --- a/raven/transport/registry.py +++ b/raven/transport/registry.py @@ -7,11 +7,16 @@ """ from __future__ import absolute_import -from raven.transport.base import ( - HTTPTransport, GeventedHTTPTransport, TwistedHTTPTransport, - TornadoHTTPTransport, UDPTransport, EventletHTTPTransport) +# TODO(dcramer): we really should need to import all of these by default +from raven.transport.eventlet import EventletHTTPTransport from raven.transport.exceptions import DuplicateScheme +from raven.transport.http import HTTPTransport +from raven.transport.gevent import GeventedHTTPTransport +from raven.transport.requests import RequestsHTTPTransport from raven.transport.threaded import ThreadedHTTPTransport +from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport +from raven.transport.twisted import TwistedHTTPTransport +from raven.transport.tornado import TornadoHTTPTransport from raven.utils import urlparse @@ -46,25 +51,16 @@ def register_scheme(self, scheme, cls): def supported_scheme(self, scheme): return scheme in self._schemes - def get_transport(self, parsed_url): + def get_transport(self, parsed_url, **options): full_url = parsed_url.geturl() if full_url not in self._transports: - # Grab options from the querystring to pass to the transport - # e.g. ?timeout=30 - if parsed_url.query: - options = dict(q.split('=', 1) for q in parsed_url.query.split('&')) - else: - options = dict() + # Remove the options from the parsed_url + parsed_url = urlparse.urlparse(full_url.split('?')[0]) self._transports[full_url] = self._schemes[parsed_url.scheme](parsed_url, **options) return self._transports[full_url] - def compute_scope(self, url, scope): - """ - Compute a scope dictionary. This may be overridden by custom - transports - """ - transport = self._schemes[url.scheme](url) - return transport.compute_scope(url, scope) + def get_transport_cls(self, scheme): + return self._schemes[scheme] default_transports = [ @@ -72,7 +68,8 @@ def compute_scope(self, url, scope): ThreadedHTTPTransport, GeventedHTTPTransport, TwistedHTTPTransport, + RequestsHTTPTransport, + ThreadedRequestsHTTPTransport, TornadoHTTPTransport, - UDPTransport, EventletHTTPTransport, ] diff --git a/raven/transport/requests.py b/raven/transport/requests.py new file mode 100644 index 000000000..8310ef380 --- /dev/null +++ b/raven/transport/requests.py @@ -0,0 +1,35 @@ +""" +raven.transport.requests +~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +from raven.transport.http import HTTPTransport + +try: + import requests + has_requests = True +except ImportError: + has_requests = False + + +class RequestsHTTPTransport(HTTPTransport): + + scheme = ['requests+http', 'requests+https'] + + def __init__(self, *args, **kwargs): + if not has_requests: + raise ImportError('RequestsHTTPTransport requires requests.') + + super(RequestsHTTPTransport, self).__init__(*args, **kwargs) + + def send(self, url, data, headers): + if self.verify_ssl: + # If SSL verification is enabled use the provided CA bundle to + # perform the verification. + self.verify_ssl = self.ca_certs + requests.post(url, data=data, headers=headers, + verify=self.verify_ssl, timeout=self.timeout) diff --git a/raven/transport/threaded.py b/raven/transport/threaded.py index 55473b577..16b275de2 100644 --- a/raven/transport/threaded.py +++ b/raven/transport/threaded.py @@ -9,12 +9,14 @@ import atexit import logging -import time import threading import os -from raven.utils.compat import Queue -from raven.transport.base import HTTPTransport, AsyncTransport +from time import sleep, time + +from raven.transport.base import AsyncTransport +from raven.transport.http import HTTPTransport +from raven.utils.compat import Queue, check_threads DEFAULT_TIMEOUT = 10 @@ -25,25 +27,82 @@ class AsyncWorker(object): _terminator = object() def __init__(self, shutdown_timeout=DEFAULT_TIMEOUT): + check_threads() self._queue = Queue(-1) self._lock = threading.Lock() self._thread = None + self._thread_for_pid = None self.options = { 'shutdown_timeout': shutdown_timeout, } self.start() + def is_alive(self): + if self._thread_for_pid != os.getpid(): + return False + return self._thread and self._thread.is_alive() + + def _ensure_thread(self): + if self.is_alive(): + return + self.start() + def main_thread_terminated(self): - size = self._queue.qsize() - if size: + with self._lock: + if not self.is_alive(): + # thread not started or already stopped - nothing to do + return + + # wake the processing thread up + self._queue.put_nowait(self._terminator) + timeout = self.options['shutdown_timeout'] - print("Sentry is attempting to send %s pending error messages" % size) - print("Waiting up to %s seconds" % timeout) - if os.name == 'nt': - print("Press Ctrl-Break to quit") - else: - print("Press Ctrl-C to quit") - self.stop(timeout=timeout) + + # wait briefly, initially + initial_timeout = min(0.1, timeout) + + if not self._timed_queue_join(initial_timeout): + # if that didn't work, wait a bit longer + # NB that size is an approximation, because other threads may + # add or remove items + size = self._queue.qsize() + + print("Sentry is attempting to send %i pending error messages" + % size) + print("Waiting up to %s seconds" % timeout) + + if os.name == 'nt': + print("Press Ctrl-Break to quit") + else: + print("Press Ctrl-C to quit") + + self._timed_queue_join(timeout - initial_timeout) + + self._thread = None + + def _timed_queue_join(self, timeout): + """ + implementation of Queue.join which takes a 'timeout' argument + + returns true on success, false on timeout + """ + deadline = time() + timeout + queue = self._queue + + queue.all_tasks_done.acquire() + try: + while queue.unfinished_tasks: + delay = deadline - time() + if delay <= 0: + # timed out + return False + + queue.all_tasks_done.wait(timeout=delay) + + return True + + finally: + queue.all_tasks_done.release() def start(self): """ @@ -51,10 +110,11 @@ def start(self): """ self._lock.acquire() try: - if not self._thread: - self._thread = threading.Thread(target=self._target) + if not self.is_alive(): + self._thread = threading.Thread(target=self._target, name="raven.AsyncWorker") self._thread.setDaemon(True) self._thread.start() + self._thread_for_pid = os.getpid() finally: self._lock.release() atexit.register(self.main_thread_terminated) @@ -63,55 +123,51 @@ def stop(self, timeout=None): """ Stops the task thread. Synchronous! """ - self._lock.acquire() - try: + with self._lock: if self._thread: self._queue.put_nowait(self._terminator) self._thread.join(timeout=timeout) self._thread = None - finally: - self._lock.release() + self._thread_for_pid = None def queue(self, callback, *args, **kwargs): + self._ensure_thread() self._queue.put_nowait((callback, args, kwargs)) def _target(self): - while 1: + while True: record = self._queue.get() - if record is self._terminator: - break - callback, args, kwargs = record try: - callback(*args, **kwargs) - except Exception: - logger.error('Failed processing job', exc_info=True) + if record is self._terminator: + break + callback, args, kwargs = record + try: + callback(*args, **kwargs) + except Exception: + logger.error('Failed processing job', exc_info=True) + finally: + self._queue.task_done() - time.sleep(0) + sleep(0) class ThreadedHTTPTransport(AsyncTransport, HTTPTransport): - scheme = ['threaded+http', 'threaded+https'] - - def __init__(self, parsed_url): - super(ThreadedHTTPTransport, self).__init__(parsed_url) - - # remove the threaded+ from the protocol, as it is not a real protocol - self._url = self._url.split('+', 1)[-1] + scheme = ['http', 'https', 'threaded+http', 'threaded+https'] def get_worker(self): - if not hasattr(self, '_worker'): + if not hasattr(self, '_worker') or not self._worker.is_alive(): self._worker = AsyncWorker() return self._worker - def send_sync(self, data, headers, success_cb, failure_cb): + def send_sync(self, url, data, headers, success_cb, failure_cb): try: - super(ThreadedHTTPTransport, self).send(data, headers) + super(ThreadedHTTPTransport, self).send(url, data, headers) except Exception as e: failure_cb(e) else: success_cb() - def async_send(self, data, headers, success_cb, failure_cb): - self.get_worker().queue(self.send_sync, data, headers, success_cb, - failure_cb) + def async_send(self, url, data, headers, success_cb, failure_cb): + self.get_worker().queue( + self.send_sync, url, data, headers, success_cb, failure_cb) diff --git a/raven/transport/threaded_requests.py b/raven/transport/threaded_requests.py new file mode 100644 index 000000000..cdc05a34d --- /dev/null +++ b/raven/transport/threaded_requests.py @@ -0,0 +1,34 @@ +""" +raven.transport.threaded_requests +~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +from raven.transport.base import AsyncTransport +from raven.transport import RequestsHTTPTransport +from raven.transport.threaded import AsyncWorker + + +class ThreadedRequestsHTTPTransport(AsyncTransport, RequestsHTTPTransport): + + scheme = ['threaded+requests+http', 'threaded+requests+https'] + + def get_worker(self): + if not hasattr(self, '_worker'): + self._worker = AsyncWorker() + return self._worker + + def send_sync(self, url, data, headers, success_cb, failure_cb): + try: + super(ThreadedRequestsHTTPTransport, self).send(url, data, headers) + except Exception as e: + failure_cb(e) + else: + success_cb() + + def async_send(self, url, data, headers, success_cb, failure_cb): + self.get_worker().queue( + self.send_sync, url, data, headers, success_cb, failure_cb) diff --git a/raven/transport/tornado.py b/raven/transport/tornado.py new file mode 100644 index 000000000..b1a1dec0c --- /dev/null +++ b/raven/transport/tornado.py @@ -0,0 +1,60 @@ +""" +raven.transport.tornado +~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +from functools import partial + +from raven.transport.base import AsyncTransport +from raven.transport.http import HTTPTransport + +try: + from tornado import ioloop + from tornado.httpclient import AsyncHTTPClient, HTTPClient + has_tornado = True +except ImportError: + has_tornado = False + + +class TornadoHTTPTransport(AsyncTransport, HTTPTransport): + + scheme = ['tornado+http', 'tornado+https'] + + def __init__(self, *args, **kwargs): + if not has_tornado: + raise ImportError('TornadoHTTPTransport requires tornado.') + + super(TornadoHTTPTransport, self).__init__(*args, **kwargs) + + def async_send(self, url, data, headers, success_cb, failure_cb): + kwargs = dict(method='POST', headers=headers, body=data) + kwargs["validate_cert"] = self.verify_ssl + kwargs["connect_timeout"] = self.timeout + kwargs["ca_certs"] = self.ca_certs + + # only use async if ioloop is running, otherwise it will never send + if ioloop.IOLoop.initialized(): + client = AsyncHTTPClient() + kwargs['callback'] = None + + future = client.fetch(url, **kwargs) + ioloop.IOLoop.current().add_future(future, partial(self.handler, success_cb, failure_cb)) + else: + client = HTTPClient() + try: + client.fetch(url, **kwargs) + success_cb() + except Exception as e: + failure_cb(e) + + @staticmethod + def handler(success, error, future): + try: + future.result() + success() + except Exception as e: + error(e) diff --git a/raven/transport/twisted.py b/raven/transport/twisted.py new file mode 100644 index 000000000..e3f19c539 --- /dev/null +++ b/raven/transport/twisted.py @@ -0,0 +1,75 @@ +""" +raven.transport.twisted +~~~~~~~~~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + + +from raven.utils.compat import BytesIO +from raven.transport.base import AsyncTransport +from raven.transport.http import HTTPTransport + +try: + from twisted.web.client import ( + Agent, FileBodyProducer, HTTPConnectionPool, ResponseNeverReceived, + readBody, + ) + from twisted.web.http_headers import Headers + has_twisted = True +except ImportError: + has_twisted = False + + +class TwistedHTTPTransport(AsyncTransport, HTTPTransport): + scheme = ['twisted+http', 'twisted+https'] + + def __init__(self, *args, **kwargs): + if not has_twisted: + raise ImportError('TwistedHTTPTransport requires twisted.web.') + + super(TwistedHTTPTransport, self).__init__(*args, **kwargs) + + # Import reactor as late as possible. + from twisted.internet import reactor + + # Use a persistent connection pool. + self._agent = Agent(reactor, pool=HTTPConnectionPool(reactor)) + + def async_send(self, url, data, headers, success_cb, failure_cb): + d = self._agent.request( + b"POST", url, + bodyProducer=FileBodyProducer(BytesIO(data)), + headers=Headers(dict((k, [v]) for k, v in headers.items())) + ) + + def on_failure(failure): + ex = failure.check(ResponseNeverReceived) + if ex: + # ResponseNeverReceived wraps the actual error(s). + failure_cb([f.value for f in failure.value.reasons]) + else: + failure_cb(failure.value) + + def on_success(response): + """ + Success only means that the request succeeded, *not* that the + actual submission was successful. + """ + if response.code == 200: + success_cb() + else: + def on_error_body(body): + failure_cb(Exception(response.code, response.phrase, body)) + + return readBody(response).addCallback( + on_error_body, + ) + + d.addCallback( + on_success, + ).addErrback( + on_failure, + ) diff --git a/raven/utils/__init__.py b/raven/utils/__init__.py index d624ebc71..8d7c540d1 100644 --- a/raven/utils/__init__.py +++ b/raven/utils/__init__.py @@ -7,37 +7,18 @@ """ from __future__ import absolute_import -from raven.utils import six import logging -try: - import pkg_resources -except ImportError: - pkg_resources = None # NOQA import sys -logger = logging.getLogger('raven.errors') +# Using "NOQA" to preserve export compatibility +from raven.utils.compat import iteritems, string_types # NOQA +from raven.utils.basic import ( # NOQA + merge_dicts, varmap, memoize, once, is_namedtuple +) -def varmap(func, var, context=None, name=None): - """ - Executes ``func(key_name, value)`` on all values - recurisively discovering dict and list scoped - values. - """ - if context is None: - context = {} - objid = id(var) - if objid in context: - return func(name, '<...>') - context[objid] = 1 - if isinstance(var, dict): - ret = dict((k, varmap(func, v, context, k)) for k, v in six.iteritems(var)) - elif isinstance(var, (list, tuple)): - ret = [varmap(func, f, context, name) for f in var] - else: - ret = func(name, var) - del context[objid] - return ret +logger = logging.getLogger('raven.errors') + # We store a cache of module_name->version string to avoid # continuous imports and lookups of modules @@ -45,27 +26,44 @@ def varmap(func, var, context=None, name=None): def get_version_from_app(module_name, app): + version = None + + # Try to pull version from pkg_resources first + # as it is able to detect version tagged with egg_info -b + try: + # Importing pkg_resources can be slow, so only import it + # if we need it. + import pkg_resources + except ImportError: + # pkg_resource is not available on Google App Engine + pass + else: + # pull version from pkg_resources if distro exists + try: + return pkg_resources.get_distribution(module_name).version + except Exception: + pass + if hasattr(app, 'get_version'): - get_version = app.get_version - if callable(get_version): - version = get_version() - else: - version = get_version - elif hasattr(app, 'VERSION'): - version = app.VERSION + version = app.get_version elif hasattr(app, '__version__'): version = app.__version__ - elif pkg_resources: - # pull version from pkg_resources if distro exists - try: - version = pkg_resources.get_distribution(module_name).version - except pkg_resources.DistributionNotFound: - return None - else: + elif hasattr(app, 'VERSION'): + version = app.VERSION + elif hasattr(app, 'version'): + version = app.version + + if callable(version): + version = version() + + if not isinstance(version, (string_types, list, tuple)): + version = None + + if version is None: return None if isinstance(version, (list, tuple)): - version = '.'.join(str(o) for o in version) + version = '.'.join(map(str, version)) return str(version) @@ -77,7 +75,8 @@ def get_versions(module_list=None): ext_module_list = set() for m in module_list: parts = m.split('.') - ext_module_list.update('.'.join(parts[:idx]) for idx in range(1, len(parts) + 1)) + ext_module_list.update('.'.join(parts[:idx]) + for idx in range(1, len(parts) + 1)) versions = {} for module_name in ext_module_list: @@ -101,13 +100,13 @@ def get_versions(module_list=None): _VERSION_CACHE[module_name] = version else: version = _VERSION_CACHE[module_name] - if version is None: - continue - versions[module_name] = version + if version is not None: + versions[module_name] = version return versions -def get_auth_header(protocol, timestamp, client, api_key, api_secret=None, **kwargs): +def get_auth_header(protocol, timestamp, client, api_key, + api_secret=None, **kwargs): header = [ ('sentry_timestamp', timestamp), ('sentry_client', client), @@ -118,29 +117,3 @@ def get_auth_header(protocol, timestamp, client, api_key, api_secret=None, **kwa header.append(('sentry_secret', api_secret)) return 'Sentry %s' % ', '.join('%s=%s' % (k, v) for k, v in header) - - -class memoize(object): - """ - Memoize the result of a property call. - - >>> class A(object): - >>> @memoize - >>> def func(self): - >>> return 'foo' - """ - - def __init__(self, func): - self.__name__ = func.__name__ - self.__module__ = func.__module__ - self.__doc__ = func.__doc__ - self.func = func - - def __get__(self, obj, type=None): - if obj is None: - return self - d, n = vars(obj), self.__name__ - if n not in d: - value = self.func(obj) - d[n] = value - return value diff --git a/raven/utils/basic.py b/raven/utils/basic.py new file mode 100644 index 000000000..3342c1ba0 --- /dev/null +++ b/raven/utils/basic.py @@ -0,0 +1,102 @@ +from __future__ import absolute_import + +try: + from collections.abc import Mapping +except ImportError: + # Python < 3.3 + from collections import Mapping + +from functools import update_wrapper +import threading + +from raven.utils.compat import iteritems + + +def merge_dicts(*dicts): + out = {} + for d in dicts: + if not d: + continue + + for k, v in iteritems(d): + out[k] = v + return out + + +def varmap(func, var, context=None, name=None): + """ + Executes ``func(key_name, value)`` on all values + recurisively discovering dict and list scoped + values. + """ + if context is None: + context = {} + objid = id(var) + if objid in context: + return func(name, '<...>') + context[objid] = 1 + + if isinstance(var, (list, tuple)) and not is_namedtuple(var): + ret = [varmap(func, f, context, name) for f in var] + else: + ret = func(name, var) + if isinstance(ret, Mapping): + ret = dict((k, varmap(func, v, context, k)) + for k, v in iteritems(var)) + del context[objid] + return ret + + +class memoize(object): + """ + Memoize the result of a property call. + + >>> class A(object): + >>> @memoize + >>> def func(self): + >>> return 'foo' + """ + + def __init__(self, func): + self.__name__ = func.__name__ + self.__module__ = func.__module__ + self.__doc__ = func.__doc__ + self.func = func + + def __get__(self, obj, type=None): + if obj is None: + return self + d, n = vars(obj), self.__name__ + if n not in d: + d[n] = self.func(obj) + return d[n] + + +def once(func): + """Runs a thing once and once only.""" + lock = threading.Lock() + + def new_func(*args, **kwargs): + if new_func.called: + return + with lock: + if new_func.called: + return + rv = func(*args, **kwargs) + new_func.called = True + return rv + + new_func = update_wrapper(new_func, func) + new_func.called = False + return new_func + + +def is_namedtuple(value): + # https://stackoverflow.com/a/2166841/1843746 + # But modified to handle subclasses of namedtuples. + if not isinstance(value, tuple): + return False + f = getattr(type(value), '_fields', None) + if not isinstance(f, tuple): + return False + return all(type(n) == str for n in f) diff --git a/raven/utils/compat.py b/raven/utils/compat.py index f2f1e6c9d..9c4f8e366 100644 --- a/raven/utils/compat.py +++ b/raven/utils/compat.py @@ -1,44 +1,219 @@ """ raven.utils.compat -~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~ -:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:copyright: (c) 2010-2016 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. + +Utilities for writing code that runs on Python 2 and 3 """ +# flake8: noqa + +# Copyright (c) 2010-2013 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. from __future__ import absolute_import +import operator +import sys +import types -try: - from urllib.error import HTTPError -except ImportError: - from urllib2 import HTTPError # NOQA +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 -try: - from urllib.request import Request, urlopen -except ImportError: - from urllib2 import Request, urlopen # NOQA + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str try: - from urllib import quote as urllib_quote -except ImportError: - from urllib.parse import quote as urllib_quote # NOQA + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + +if PY3: + Iterator = object +else: + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + import StringIO + StringIO = BytesIO = StringIO.StringIO + + +if PY3: + exec_ = getattr(__import__('builtins'), 'exec') + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + try: + if from_value is None: + raise value + raise value from from_value + finally: + value = None +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +if PY3: + from urllib.error import HTTPError + from http import client as httplib + import urllib.request as urllib2 from queue import Queue -except ImportError: - from Queue import Queue # NOQA + from urllib.parse import quote as urllib_quote + from urllib import parse as urlparse +else: + from urllib2 import HTTPError + import httplib + import urllib2 + from Queue import Queue + from urllib import quote as urllib_quote + import urlparse -try: - import urlparse as _urlparse -except ImportError: - from urllib import parse as _urlparse # NOQA +def get_code(func): + rv = getattr(func, '__code__', getattr(func, 'func_code', None)) + if rv is None: + raise TypeError('Could not get code from %r' % type(func).__name__) + return rv -try: - from unittest2 import TestCase -except ImportError: - from unittest import TestCase # NOQA +def check_threads(): + try: + from uwsgi import opt + except ImportError: + return + + # When `threads` is passed in as a uwsgi option, + # `enable-threads` is implied on. + if 'threads' in opt: + return + + if str(opt.get('enable-threads', '0')).lower() in ('false', 'off', 'no', '0'): + from warnings import warn + warn(Warning('We detected the use of uwsgi with disabled threads. ' + 'This will cause issues with the transport you are ' + 'trying to use. Please enable threading for uwsgi. ' + '(Enable the "enable-threads" flag).')) diff --git a/raven/utils/conf.py b/raven/utils/conf.py new file mode 100644 index 000000000..0483b9219 --- /dev/null +++ b/raven/utils/conf.py @@ -0,0 +1,64 @@ +from __future__ import absolute_import + +import copy +import os + +from raven.utils.compat import string_types +from raven.utils.imports import import_string + + +def convert_options(settings, defaults=None): + """ + Convert a settings object (or dictionary) to parameters which may be passed + to a new ``Client()`` instance. + """ + if defaults is None: + defaults = {} + + if isinstance(settings, dict): + def getopt(key, default=None): + return settings.get( + 'SENTRY_%s' % key.upper(), + defaults.get(key, default) + ) + + options = copy.copy( + settings.get('SENTRY_CONFIG') + or settings.get('RAVEN_CONFIG') + or {} + ) + else: + def getopt(key, default=None): + return getattr(settings, 'SENTRY_%s' % key.upper(), defaults.get(key, default)) + + options = copy.copy( + getattr(settings, 'SENTRY_CONFIG', None) + or getattr(settings, 'RAVEN_CONFIG', None) + or {} + ) + + options.setdefault('include_paths', getopt('include_paths', [])) + options.setdefault('exclude_paths', getopt('exclude_paths', [])) + options.setdefault('timeout', getopt('timeout')) + options.setdefault('name', getopt('name')) + options.setdefault('auto_log_stacks', getopt('auto_log_stacks')) + options.setdefault('string_max_length', getopt('string_max_length')) + options.setdefault('list_max_length', getopt('list_max_length')) + options.setdefault('site', getopt('site')) + options.setdefault('processors', getopt('processors')) + options.setdefault('sanitize_keys', getopt('sanitize_keys')) + options.setdefault('dsn', getopt('dsn', os.environ.get('SENTRY_DSN'))) + options.setdefault('context', getopt('context')) + options.setdefault('tags', getopt('tags')) + options.setdefault('release', getopt('release')) + options.setdefault('repos', getopt('repos')) + options.setdefault('environment', getopt('environment')) + options.setdefault('ignore_exceptions', getopt('ignore_exceptions')) + options.setdefault('sample_rate', getopt('sample_rate')) + + transport = getopt('transport') or options.get('transport') + if isinstance(transport, string_types): + transport = import_string(transport) + options['transport'] = transport + + return options diff --git a/raven/utils/encoding.py b/raven/utils/encoding.py index b9c63e0a8..997a94347 100644 --- a/raven/utils/encoding.py +++ b/raven/utils/encoding.py @@ -8,7 +8,9 @@ from __future__ import absolute_import, unicode_literals import warnings -from raven.utils import six + +from raven.utils.compat import integer_types, text_type, binary_type, \ + string_types, PY2 def is_protected_type(obj): @@ -19,7 +21,7 @@ def is_protected_type(obj): """ import Decimal import datetime - return isinstance(obj, six.integer_types + (type(None), float, Decimal, + return isinstance(obj, integer_types + (type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time)) @@ -31,31 +33,31 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): If strings_only is True, don't convert (some) non-string-like objects. """ # Handle the common case first, saves 30-40% when s is an instance of - # six.text_type. This function gets called often in that setting. - if isinstance(s, six.text_type): + # text_type. This function gets called often in that setting. + if isinstance(s, text_type): return s if strings_only and is_protected_type(s): return s try: - if not isinstance(s, six.string_types): + if not isinstance(s, string_types): if hasattr(s, '__unicode__'): s = s.__unicode__() else: - if six.PY3: + if not PY2: if isinstance(s, bytes): - s = six.text_type(s, encoding, errors) + s = text_type(s, encoding, errors) else: - s = six.text_type(s) + s = text_type(s) else: - s = six.text_type(bytes(s), encoding, errors) + s = text_type(bytes(s), encoding, errors) else: - # Note: We use .decode() here, instead of six.text_type(s, encoding, + # Note: We use .decode() here, instead of text_type(s, encoding, # errors), so that if s is a SafeBytes, it ends up being a # SafeText at the end. s = s.decode(encoding, errors) except UnicodeDecodeError as e: if not isinstance(s, Exception): - raise UnicodeDecodeError(s, *e.args) + raise UnicodeDecodeError(*e.args) else: # If we get to here, the caller has passed in an Exception # subclass populated with non-ASCII bytestring data without a @@ -69,19 +71,20 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'): def transform(value): from raven.utils.serializer import transform - warnings.warn('You should switch to raven.utils.serializer.transform', DeprecationWarning) + warnings.warn('You should switch to raven.utils.serializer.' + 'transform', DeprecationWarning) return transform(value) def to_unicode(value): try: - value = six.text_type(force_text(value)) + value = text_type(force_text(value)) except (UnicodeEncodeError, UnicodeDecodeError): value = '(Error decoding value)' except Exception: # in some cases we get a different exception try: - value = six.binary_type(repr(type(value))) + value = text_type(force_text(repr(type(value)))) except Exception: value = '(Error decoding value)' return value @@ -89,6 +92,6 @@ def to_unicode(value): def to_string(value): try: - return six.binary_type(value.decode('utf-8').encode('utf-8')) - except: + return binary_type(value.decode('utf-8').encode('utf-8')) + except Exception: return to_unicode(value).encode('utf-8') diff --git a/raven/utils/http.py b/raven/utils/http.py new file mode 100644 index 000000000..f0d14a0c4 --- /dev/null +++ b/raven/utils/http.py @@ -0,0 +1,66 @@ +""" +raven.utils.http +~~~~~~~~~~~~~~~~ + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +from __future__ import absolute_import + +import socket +import ssl +import sys + +from raven.conf import defaults +from raven.utils.compat import urllib2, httplib +from raven.utils.ssl_match_hostname import match_hostname + + +def urlopen(url, data=None, timeout=defaults.TIMEOUT, ca_certs=None, + verify_ssl=False, assert_hostname=None): + + class ValidHTTPSConnection(httplib.HTTPConnection): + default_port = httplib.HTTPS_PORT + + def __init__(self, *args, **kwargs): + httplib.HTTPConnection.__init__(self, *args, **kwargs) + + def connect(self): + sock = socket.create_connection( + address=(self.host, self.port), + timeout=self.timeout, + ) + if self._tunnel_host: + self.sock = sock + self._tunnel() + + self.sock = ssl.wrap_socket( + sock, ca_certs=ca_certs, cert_reqs=ssl.CERT_REQUIRED) + + if assert_hostname is not None: + match_hostname(self.sock.getpeercert(), + self.assert_hostname or self.host) + + class ValidHTTPSHandler(urllib2.HTTPSHandler): + def https_open(self, req): + return self.do_open(ValidHTTPSConnection, req) + + if verify_ssl: + handlers = [ValidHTTPSHandler] + else: + try: + handlers = [urllib2.HTTPSHandler( + context=ssl._create_unverified_context())] + except AttributeError: + handlers = [] + + opener = urllib2.build_opener(*handlers) + + if sys.version_info < (2, 6): + default_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + return opener.open(url, data) + finally: + socket.setdefaulttimeout(default_timeout) + return opener.open(url, data, timeout) diff --git a/raven/utils/imports.py b/raven/utils/imports.py new file mode 100644 index 000000000..fc0dce987 --- /dev/null +++ b/raven/utils/imports.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import + +from raven.utils.compat import PY2 + + +def import_string(key): + # HACK(dcramer): Ensure a unicode key is still importable + if PY2: + key = str(key) + + if '.' not in key: + return __import__(key) + + module_name, class_name = key.rsplit('.', 1) + module = __import__(module_name, {}, {}, [class_name], 0) + return getattr(module, class_name) diff --git a/raven/utils/json.py b/raven/utils/json.py index 8599e246b..d1de66d6a 100644 --- a/raven/utils/json.py +++ b/raven/utils/json.py @@ -8,13 +8,15 @@ from __future__ import absolute_import +import codecs +import collections import datetime import uuid +import json + +from .basic import is_namedtuple +from .compat import PY2 -try: - import simplejson as json -except ImportError: - import json try: JSONDecodeError = json.JSONDecodeError @@ -23,14 +25,31 @@ class BetterJSONEncoder(json.JSONEncoder): + ENCODER_BY_TYPE = { + uuid.UUID: lambda o: o.hex, + datetime.datetime: lambda o: o.strftime('%Y-%m-%dT%H:%M:%SZ'), + set: list, + frozenset: list, + bytes: lambda o: o.decode('utf-8', errors='replace'), + collections.namedtuple: lambda o: o._asdict(), + } + def default(self, obj): - if isinstance(obj, uuid.UUID): - return obj.hex - elif isinstance(obj, datetime.datetime): - return obj.strftime('%Y-%m-%dT%H:%M:%SZ') - elif isinstance(obj, (set, frozenset)): - return list(obj) - return super(BetterJSONEncoder, self).default(obj) + obj_type = type(obj) + if obj_type not in self.ENCODER_BY_TYPE and is_namedtuple(obj): + obj_type = collections.namedtuple + + try: + encoder = self.ENCODER_BY_TYPE[obj_type] + except KeyError: + try: + return super(BetterJSONEncoder, self).default(obj) + except Exception: + try: + return repr(obj) + except Exception: + return object.__repr__(obj) + return encoder(obj) def better_decoder(data): @@ -38,8 +57,68 @@ def better_decoder(data): def dumps(value, **kwargs): + if PY2: + kwargs['encoding'] = 'safe-utf-8' + return json.dumps(value, cls=BetterJSONEncoder, **kwargs) def loads(value, **kwargs): return json.loads(value, object_hook=better_decoder) + + +_utf8_encoder = codecs.getencoder('utf-8') + + +def safe_encode(input, errors='backslashreplace'): + return _utf8_encoder(input, errors) + + +_utf8_decoder = codecs.getdecoder('utf-8') + + +def safe_decode(input, errors='replace'): + return _utf8_decoder(input, errors) + + +class Codec(codecs.Codec): + + def encode(self, input, errors='backslashreplace'): + return safe_encode(input, errors) + + def decode(self, input, errors='replace'): + return safe_decode(input, errors) + + +class IncrementalEncoder(codecs.IncrementalEncoder): + def encode(self, input, final=False): + return safe_encode(input, self.errors)[0] + + +class IncrementalDecoder(codecs.IncrementalDecoder): + def decode(self, input, final=False): + return safe_decode(input, self.errors)[0] + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def getregentry(name): + if name == 'safe-utf-8': + return codecs.CodecInfo( + name=name, + encode=safe_encode, + decode=safe_decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, + ) + + +codecs.register(getregentry) diff --git a/raven/utils/serializer/base.py b/raven/utils/serializer/base.py index ed798b2a9..465d0d1dd 100644 --- a/raven/utils/serializer/base.py +++ b/raven/utils/serializer/base.py @@ -8,11 +8,16 @@ """ from __future__ import absolute_import +import collections import itertools -from raven.utils import six +import types + +from raven.utils.compat import text_type, binary_type, string_types, iteritems, \ + class_types, PY2, PY3 from raven.utils.encoding import to_unicode -from raven.utils.serializer.manager import register -from uuid import UUID +from .manager import manager as serialization_manager +from raven.utils import is_namedtuple + __all__ = ('Serializer',) @@ -48,16 +53,41 @@ def recurse(self, value, max_depth=6, _depth=0, **kwargs): Given ``value``, recurse (using the parent serializer) to handle coercing of newly defined values. """ + string_max_length = kwargs.get('string_max_length', None) + _depth += 1 if _depth >= max_depth: try: - value = six.text_type(repr(value)) + value = text_type(repr(value))[:string_max_length] except Exception as e: import traceback traceback.print_exc() self.manager.logger.exception(e) - return type(value) - return self.manager.transform(value, max_depth=max_depth, _depth=_depth, **kwargs) + return text_type(type(value)) + return self.manager.transform(value, max_depth=max_depth, + _depth=_depth, **kwargs) + + +class NamedtupleSerializer(Serializer): + types = (collections.namedtuple,) + + def can(self, value): + """ + Given ``value``, return a boolean describing whether this + serializer can operate on the given type + """ + return is_namedtuple(value) + + def serialize(self, value, **kwargs): + list_max_length = kwargs.get('list_max_length') or float('inf') + less_than = lambda x: x[0] < list_max_length + items = value._asdict().items() + takewhile = itertools.takewhile + x = dict([ + (k, self.recurse(v, **kwargs)) + for n, (k, v) in takewhile(less_than, enumerate(items)) + ]) + return x class IterableSerializer(Serializer): @@ -68,22 +98,16 @@ def serialize(self, value, **kwargs): return tuple( self.recurse(o, **kwargs) for n, o - in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(value)) + in itertools.takewhile(lambda x: x[0] < list_max_length, + enumerate(value)) ) -class UUIDSerializer(Serializer): - types = (UUID,) - - def serialize(self, value, **kwargs): - return repr(value) - - class DictSerializer(Serializer): types = (dict,) def make_key(self, key): - if not isinstance(key, six.string_types): + if not isinstance(key, string_types): return to_unicode(key) return key @@ -92,34 +116,36 @@ def serialize(self, value, **kwargs): return dict( (self.make_key(self.recurse(k, **kwargs)), self.recurse(v, **kwargs)) for n, (k, v) - in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(six.iteritems(value))) + in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate( + iteritems(value))) ) class UnicodeSerializer(Serializer): - types = (six.text_type,) + types = (text_type,) def serialize(self, value, **kwargs): # try to return a reasonable string that can be decoded - # correctly by the server so it doesnt show up as \uXXX for each + # correctly by the server so it doesn't show up as \uXXX for each # unicode character # e.g. we want the output to be like: "u'רונית מגן'" string_max_length = kwargs.get('string_max_length', None) - return repr(six.text_type('%s')) % (value[:string_max_length],) + return repr(text_type('%s')) % (value[:string_max_length],) class StringSerializer(Serializer): - types = (six.binary_type,) + types = (binary_type,) def serialize(self, value, **kwargs): string_max_length = kwargs.get('string_max_length', None) - if six.PY3: + if PY3: return repr(value[:string_max_length]) try: # Python2 madness: let's try to recover from developer's issues # Try to process the string as if it was a unicode. - return "'" + value.decode('utf8')[:string_max_length].encode('utf8') + "'" + return "'" + value.decode('utf8')[:string_max_length] \ + .encode('utf8') + "'" except UnicodeDecodeError: pass @@ -127,10 +153,11 @@ def serialize(self, value, **kwargs): class TypeSerializer(Serializer): - types = six.class_types + types = class_types def can(self, value): - return not super(TypeSerializer, self).can(value) and has_sentry_metadata(value) + return not super(TypeSerializer, self).can(value) \ + and has_sentry_metadata(value) def serialize(self, value, **kwargs): return self.recurse(value.__sentry__(), **kwargs) @@ -140,39 +167,50 @@ class BooleanSerializer(Serializer): types = (bool,) def serialize(self, value, **kwargs): - return bool(value) + return repr(bool(value)) class FloatSerializer(Serializer): types = (float,) def serialize(self, value, **kwargs): - return float(value) + # Wrap with repr to convert inf/nan to string + return repr(float(value)) class IntegerSerializer(Serializer): types = (int,) def serialize(self, value, **kwargs): - return int(value) + return repr(int(value)) + + +class FunctionSerializer(Serializer): + types = (types.FunctionType,) + + def serialize(self, value, **kwargs): + return '' % ( + value.__name__, value.__module__, id(value)) -if not six.PY3: +if PY2: class LongSerializer(Serializer): types = (long,) # noqa def serialize(self, value, **kwargs): - return long(value) # noqa - - -register(IterableSerializer) -register(UUIDSerializer) -register(DictSerializer) -register(UnicodeSerializer) -register(StringSerializer) -register(TypeSerializer) -register(BooleanSerializer) -register(FloatSerializer) -register(IntegerSerializer) -if not six.PY3: - register(LongSerializer) + return repr(long(value)) # noqa + + +# register all serializers, order matters +serialization_manager.register(NamedtupleSerializer) +serialization_manager.register(IterableSerializer) +serialization_manager.register(DictSerializer) +serialization_manager.register(UnicodeSerializer) +serialization_manager.register(StringSerializer) +serialization_manager.register(TypeSerializer) +serialization_manager.register(BooleanSerializer) +serialization_manager.register(FloatSerializer) +serialization_manager.register(IntegerSerializer) +serialization_manager.register(FunctionSerializer) +if PY2: + serialization_manager.register(LongSerializer) diff --git a/raven/utils/serializer/manager.py b/raven/utils/serializer/manager.py index b5fa6c880..7477c662b 100644 --- a/raven/utils/serializer/manager.py +++ b/raven/utils/serializer/manager.py @@ -9,7 +9,7 @@ import logging from contextlib import closing -from raven.utils import six +from raven.utils.compat import text_type __all__ = ('register', 'transform') @@ -25,7 +25,7 @@ def __init__(self): @property def serializers(self): - # XXX: Would serializers ever need state that we shouldnt cache them? + # XXX: Would serializers ever need state that we shouldn't cache them? for serializer in self.__registry: yield serializer @@ -64,21 +64,23 @@ def transform(self, value, **kwargs): try: for serializer in self.serializers: - if serializer.can(value): - try: + try: + if serializer.can(value): return serializer.serialize(value, **kwargs) - except Exception as e: - logger.exception(e) - return six.text_type(type(value)) + except Exception as e: + logger.exception(e) + return text_type(type(value)) # if all else fails, lets use the repr of the object try: return repr(value) except Exception as e: logger.exception(e) - # It's common case that a model's __unicode__ definition may try to query the database - # which if it was not cleaned up correctly, would hit a transaction aborted exception - return six.text_type(type(value)) + # It's common case that a model's __unicode__ definition + # may try to query the database which if it was not + # cleaned up correctly, would hit a transaction aborted + # exception + return text_type(type(value)) finally: self.context.remove(objid) diff --git a/raven/utils/six.py b/raven/utils/six.py deleted file mode 100644 index 966d36fb4..000000000 --- a/raven/utils/six.py +++ /dev/null @@ -1,406 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" -# flake8: noqa - -# Copyright (c) 2010-2013 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -# the Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -from __future__ import absolute_import - -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.3.0" - - -# True if we are running on Python 3. -PY3 = sys.version_info[0] == 3 - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) - # This is a bit ugly, but it avoids running this again. - delattr(tp, self.name) - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _MovedItems(types.ModuleType): - """Lazy loading of moved objects""" - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("reload_module", "__builtin__", "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("winreg", "_winreg"), -] -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) -del attr - -moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" - - _iterkeys = "keys" - _itervalues = "values" - _iteritems = "items" - _iterlists = "lists" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - _iterkeys = "iterkeys" - _itervalues = "itervalues" - _iteritems = "iteritems" - _iterlists = "iterlists" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - -if PY3: - def get_unbound_function(unbound): - return unbound - - Iterator = object -else: - def get_unbound_function(unbound): # NOQA - return unbound.im_func - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -def iterkeys(d, **kw): - """Return an iterator over the keys of a dictionary.""" - return iter(getattr(d, _iterkeys)(**kw)) - - -def itervalues(d, **kw): - """Return an iterator over the values of a dictionary.""" - return iter(getattr(d, _itervalues)(**kw)) - - -def iteritems(d, **kw): - """Return an iterator over the (key, value) pairs of a dictionary.""" - return iter(getattr(d, _iteritems)(**kw)) - - -def iterlists(d, **kw): - """Return an iterator over the (key, [values]) pairs of a dictionary.""" - return iter(getattr(d, _iterlists)(**kw)) - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - if sys.version_info[1] <= 1: - def int2byte(i): - return bytes((i,)) - else: - # This is about 2x faster than the implementation above on 3.2+ - int2byte = operator.methodcaller("to_bytes", 1, "big") - import io - StringIO = io.StringIO - BytesIO = io.BytesIO -else: - def b(s): # NOQA - return s - - def u(s): # NOQA - return unicode(s, "unicode_escape") - int2byte = chr - import StringIO - StringIO = BytesIO = StringIO.StringIO -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -if PY3: - import builtins - exec_ = getattr(builtins, "exec") - - def reraise(tp, value, tb=None): - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - print_ = getattr(builtins, "print") - del builtins - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - def print_(*args, **kwargs): - """The new-style print function.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) - -_add_doc(reraise, """Reraise an exception.""") - - -def with_metaclass(meta, base=object): - """Create a base class with a metaclass.""" - return meta("NewBase", (base,), {}) diff --git a/raven/utils/ssl_match_hostname.py b/raven/utils/ssl_match_hostname.py new file mode 100644 index 000000000..7aa8c6223 --- /dev/null +++ b/raven/utils/ssl_match_hostname.py @@ -0,0 +1,104 @@ +"""The match_hostname() function from Python 3.2, essential when using SSL.""" + +from __future__ import absolute_import + +import re + +__version__ = '3.2.2' + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + parts = dn.split(r'.') + leftmost = parts[0] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in parts[1:]: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r doesn't match either of %s" % ( + hostname, ', '.join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError( + "hostname %r doesn't match %r" % (hostname, dnsnames[0]) + ) + else: + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/raven/utils/stacks.py b/raven/utils/stacks.py index 18fb2f461..487eae329 100644 --- a/raven/utils/stacks.py +++ b/raven/utils/stacks.py @@ -5,21 +5,23 @@ :copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. :license: BSD, see LICENSE for more details. """ -from __future__ import absolute_import +from __future__ import absolute_import, division import inspect +import linecache import re +import os import sys -import warnings from raven.utils.serializer import transform -from raven.utils import six +from raven.utils.compat import iteritems _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') -def get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): +def get_lines_from_file(filename, lineno, context_lines, + loader=None, module_name=None): """ Returns context_lines before and after lineno from file. Returns (pre_context_lineno, pre_context, context_line, post_context). @@ -28,7 +30,7 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam if loader is not None and hasattr(loader, "get_source"): try: source = loader.get_source(module_name) - except ImportError: + except (ImportError, IOError): # Traceback (most recent call last): # File "/Users/dcramer/Development/django-sentry/sentry/client/handlers.py", line 31, in emit # get_client().create_from_record(record, request=request) @@ -46,82 +48,44 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam source = None if source is not None: source = source.splitlines() + if source is None: try: - f = open(filename, 'rb') - try: - source = f.readlines() - finally: - f.close() + source = linecache.getlines(filename) except (OSError, IOError): - pass - - if source is None: return None, None, None - encoding = 'utf8' - for line in source[:2]: - # File coding may be specified. Match pattern from PEP-263 - # (http://www.python.org/dev/peps/pep-0263/) - match = _coding_re.search(line.decode('utf8')) # let's assume utf8 - if match: - encoding = match.group(1) - break - source = [six.text_type(sline, encoding, 'replace') for sline in source] + if not source: + return None, None, None lower_bound = max(0, lineno - context_lines) upper_bound = min(lineno + 1 + context_lines, len(source)) try: - pre_context = [line.strip('\r\n') for line in source[lower_bound:lineno]] + pre_context = [ + line.strip('\r\n') + for line in source[lower_bound:lineno] + ] context_line = source[lineno].strip('\r\n') - post_context = [line.strip('\r\n') for line in source[(lineno + 1):upper_bound]] + post_context = [ + line.strip('\r\n') + for line in source[(lineno + 1):upper_bound] + ] except IndexError: # the file may have changed since it was loaded into memory return None, None, None - return pre_context, context_line, post_context - - -def label_from_frame(frame): - module = frame.get('module') or '?' - function = frame.get('function') or '?' - if module == function == '?': - return '' - return '%s in %s' % (module, function) - - -def get_culprit(frames, *args, **kwargs): - # We iterate through each frame looking for a deterministic culprit - # When one is found, we mark it as last "best guess" (best_guess) and then - # check it against ``exclude_paths``. If it isnt listed, then we - # use this option. If nothing is found, we use the "best guess". - if args or kwargs: - warnings.warn('get_culprit no longer does application detection') - - best_guess = None - culprit = None - for frame in reversed(frames): - culprit = label_from_frame(frame) - if not culprit: - culprit = None - continue - - if frame.get('in_app'): - return culprit - elif not best_guess: - best_guess = culprit - elif best_guess: - break - - # Return either the best guess or the last frames call - return best_guess or culprit + return ( + slim_string(pre_context), + slim_string(context_line), + slim_string(post_context) + ) def _getitem_from_frame(f_locals, key, default=None): """ f_locals is not guaranteed to have .get(), but it will always - support __getitem__. Even if it doesnt, we return ``default``. + support __getitem__. Even if it doesn't, we return ``default``. """ try: return f_locals[key] @@ -150,7 +114,9 @@ def iter_traceback_frames(tb): frames that do not contain the ``__traceback_hide__`` local variable. """ - while tb: + # Some versions of celery have hacked traceback objects that might + # miss tb_frame. + while tb and hasattr(tb, 'tb_frame'): # support for __traceback_hide__ which is used by a few libraries # to hide internal frames. f_locals = getattr(tb.tb_frame, 'f_locals', {}) @@ -168,25 +134,108 @@ def iter_stack_frames(frames=None): if not frames: frames = inspect.stack()[1:] - for frame, lineno in ((f[0], f[2]) for f in frames): + for frame, lineno in ((f[0], f[2]) for f in reversed(frames)): f_locals = getattr(frame, 'f_locals', {}) - if _getitem_from_frame(f_locals, '__traceback_hide__'): - continue - yield frame, lineno + if not _getitem_from_frame(f_locals, '__traceback_hide__'): + yield frame, lineno + + +def get_frame_locals(frame, transformer=transform, max_var_size=4096): + f_locals = getattr(frame, 'f_locals', None) + if not f_locals: + return None + if not isinstance(f_locals, dict): + # XXX: Genshi (and maybe others) have broken implementations of + # f_locals that are not actually dictionaries + try: + f_locals = to_dict(f_locals) + except Exception: + return None + + f_vars = {} + f_size = 0 + for k, v in iteritems(f_locals): + v = transformer(v) + v_size = len(repr(v)) + if v_size + f_size < max_var_size: + f_vars[k] = v + f_size += v_size + return f_vars + + +def slim_frame_data(frames, frame_allowance=25): + """ + Removes various excess metadata from middle frames which go beyond + ``frame_allowance``. -def get_stack_info(frames, transformer=transform): + Returns ``frames``. + """ + frames_len = 0 + app_frames = [] + system_frames = [] + for frame in frames: + frames_len += 1 + if frame.get('in_app'): + app_frames.append(frame) + else: + system_frames.append(frame) + + if frames_len <= frame_allowance: + return frames + + remaining = frames_len - frame_allowance + app_count = len(app_frames) + system_allowance = max(frame_allowance - app_count, 0) + if system_allowance: + half_max = int(system_allowance / 2) + # prioritize trimming system frames + for frame in system_frames[half_max:-half_max]: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) + remaining -= 1 + + else: + for frame in system_frames: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) + remaining -= 1 + + if remaining: + app_allowance = app_count - remaining + half_max = int(app_allowance / 2) + + for frame in app_frames[half_max:-half_max]: + frame.pop('vars', None) + frame.pop('pre_context', None) + frame.pop('post_context', None) + + return frames + + +def slim_string(value, length=512): + if not value: + return value + if len(value) > length: + return value[:length - 3] + '...' + return value[:length] + + +def get_stack_info(frames, transformer=transform, capture_locals=True, + frame_allowance=25): """ Given a list of frames, returns a list of stack information dictionary objects that are JSON-ready. We have to be careful here as certain implementations of the - _Frame class do not contain the nescesary data to lookup all + _Frame class do not contain the necessary data to lookup all of the information we want. """ __traceback_hide__ = True # NOQA - results = [] + result = [] for frame_info in frames: # Old, terrible API if isinstance(frame_info, (list, tuple)): @@ -218,7 +267,8 @@ def get_stack_info(frames, transformer=transform): lineno -= 1 if lineno is not None and abs_path: - pre_context, context_line, post_context = get_lines_from_file(abs_path, lineno, 5, loader, module_name) + pre_context, context_line, post_context = \ + get_lines_from_file(abs_path, lineno, 5, loader, module_name) else: pre_context, context_line, post_context = None, None, None @@ -226,35 +276,36 @@ def get_stack_info(frames, transformer=transform): # This changes /foo/site-packages/baz/bar.py into baz/bar.py try: base_filename = sys.modules[module_name.split('.', 1)[0]].__file__ - filename = abs_path.split(base_filename.rsplit('/', 2)[0], 1)[-1][1:] - except: + filename = abs_path.split( + base_filename.rsplit(os.sep, 2)[0], 1)[-1].lstrip(os.sep) + except Exception: filename = abs_path if not filename: filename = abs_path - if f_locals is not None and not isinstance(f_locals, dict): - # XXX: Genshi (and maybe others) have broken implementations of - # f_locals that are not actually dictionaries - try: - f_locals = to_dict(f_locals) - except Exception: - f_locals = '' - frame_result = { 'abs_path': abs_path, 'filename': filename, 'module': module_name or None, 'function': function or '', 'lineno': lineno + 1, - 'vars': transformer(f_locals), } + if capture_locals: + f_vars = get_frame_locals(frame, transformer=transformer) + if f_vars: + frame_result['vars'] = f_vars + if context_line is not None: frame_result.update({ 'pre_context': pre_context, 'context_line': context_line, 'post_context': post_context, }) + result.append(frame_result) + + stackinfo = { + 'frames': slim_frame_data(result, frame_allowance=frame_allowance), + } - results.append(frame_result) - return results + return stackinfo diff --git a/raven/utils/testutils.py b/raven/utils/testutils.py index 6be28b5fb..4859f435b 100644 --- a/raven/utils/testutils.py +++ b/raven/utils/testutils.py @@ -7,10 +7,27 @@ """ from __future__ import absolute_import +import raven + from exam import Exam -from .compat import TestCase as BaseTestCase +try: + from unittest2 import TestCase as BaseTestCase +except ImportError: + from unittest import TestCase as BaseTestCase # NOQA class TestCase(Exam, BaseTestCase): pass + + +class InMemoryClient(raven.Client): + def __init__(self, **kwargs): + self.events = [] + super(InMemoryClient, self).__init__(**kwargs) + + def is_enabled(self): + return True + + def send(self, **kwargs): + self.events.append(kwargs) diff --git a/raven/utils/transaction.py b/raven/utils/transaction.py new file mode 100644 index 000000000..8dc6e064d --- /dev/null +++ b/raven/utils/transaction.py @@ -0,0 +1,51 @@ +from __future__ import absolute_import + +from threading import local + + +class TransactionContext(object): + def __init__(self, stack, context): + self.stack = stack + self.context = context + + def __enter__(self): + self.stack.push(self.context) + return self + + def __exit__(self, *exc_info): + self.stack.pop(self.context) + + +class TransactionStack(local): + def __init__(self): + self.stack = [] + + def __len__(self): + return len(self.stack) + + def __iter__(self): + return iter(self.stack) + + def __call__(self, context): + return TransactionContext(self, context) + + def clear(self): + self.stack = [] + + def peek(self): + try: + return self.stack[-1] + except IndexError: + return None + + def push(self, context): + self.stack.append(context) + return context + + def pop(self, context=None): + if context is None: + return self.stack.pop() + + while self.stack: + if self.stack.pop() is context: + return context diff --git a/raven/utils/urlparse.py b/raven/utils/urlparse.py index ac0ac3602..4be96c007 100644 --- a/raven/utils/urlparse.py +++ b/raven/utils/urlparse.py @@ -15,3 +15,4 @@ def register_scheme(scheme): urlparse = _urlparse.urlparse +parse_qsl = _urlparse.parse_qsl diff --git a/raven/utils/wsgi.py b/raven/utils/wsgi.py index ad83bde33..71f46792e 100644 --- a/raven/utils/wsgi.py +++ b/raven/utils/wsgi.py @@ -6,8 +6,7 @@ """ from __future__ import absolute_import -from raven.utils import six -from raven.utils.compat import urllib_quote +from raven.utils.compat import iteritems, urllib_quote # `get_headers` comes from `werkzeug.datastructures.EnvironHeaders` @@ -15,7 +14,7 @@ def get_headers(environ): """ Returns only proper HTTP headers. """ - for key, value in six.iteritems(environ): + for key, value in iteritems(environ): key = str(key) if key.startswith('HTTP_') and key not in \ ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'): @@ -93,3 +92,17 @@ def get_current_url(environ, root_only=False, strip_querystring=False, if qs: cat('?' + qs) return ''.join(tmp) + + +def get_client_ip(environ): + """ + Naively yank the first IP address in an X-Forwarded-For header + and assume this is correct. + + Note: Don't use this in security sensitive situations since this + value may be forged from a client. + """ + try: + return environ['HTTP_X_FORWARDED_FOR'].split(',')[0].strip() + except (KeyError, IndexError): + return environ.get('REMOTE_ADDR') diff --git a/raven/versioning.py b/raven/versioning.py new file mode 100644 index 000000000..8e1a262de --- /dev/null +++ b/raven/versioning.py @@ -0,0 +1,74 @@ +from __future__ import absolute_import + +import os.path + +from raven.utils.compat import text_type +from .exceptions import InvalidGitRepository + +__all__ = ('fetch_git_sha', 'fetch_package_version') + + +def fetch_git_sha(path, head=None): + """ + >>> fetch_git_sha(os.path.dirname(__file__)) + """ + if not head: + head_path = os.path.join(path, '.git', 'HEAD') + if not os.path.exists(head_path): + raise InvalidGitRepository( + 'Cannot identify HEAD for git repository at %s' % (path,)) + + with open(head_path, 'r') as fp: + head = text_type(fp.read()).strip() + + if head.startswith('ref: '): + head = head[5:] + revision_file = os.path.join( + path, '.git', *head.split('/') + ) + else: + return head + else: + revision_file = os.path.join(path, '.git', 'refs', 'heads', head) + + if not os.path.exists(revision_file): + if not os.path.exists(os.path.join(path, '.git')): + raise InvalidGitRepository( + '%s does not seem to be the root of a git repository' % (path,)) + + # Check for our .git/packed-refs' file since a `git gc` may have run + # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery + packed_file = os.path.join(path, '.git', 'packed-refs') + if os.path.exists(packed_file): + with open(packed_file) as fh: + for line in fh: + line = line.rstrip() + if line and line[:1] not in ('#', '^'): + try: + revision, ref = line.split(' ', 1) + except ValueError: + continue + if ref == head: + return text_type(revision) + + raise InvalidGitRepository( + 'Unable to find ref to head "%s" in repository' % (head,)) + + with open(revision_file) as fh: + return text_type(fh.read()).strip() + + +def fetch_package_version(dist_name): + """ + >>> fetch_package_version('sentry') + """ + try: + # Importing pkg_resources can be slow, so only import it + # if we need it. + import pkg_resources + except ImportError: + # pkg_resource is not available on Google App Engine + raise NotImplementedError('pkg_resources is not available ' + 'on this Python install') + dist = pkg_resources.get_distribution(dist_name) + return dist.version diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 000000000..5e6e19c3f --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eux + +SCRIPT_DIR="$( dirname "$0" )" +cd $SCRIPT_DIR/.. + +OLD_VERSION="${1}" +NEW_VERSION="${2}" + +echo "Current version: $OLD_VERSION" +echo "Bumping version: $NEW_VERSION" + +function replace() { + ! grep "$2" $3 + perl -i -pe "s/$1/$2/g" $3 + grep "$2" $3 # verify that replacement was successful +} + +replace "current_version = [0-9.]+" "current_version = $NEW_VERSION" ./.bumpversion.cfg +replace "VERSION = '[0-9.]+'" "VERSION = '$NEW_VERSION'" ./raven/__init__.py diff --git a/setup.cfg b/setup.cfg index 7c1bf61c4..8032dda0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,16 @@ -[pytest] +[tool:pytest] python_files=test*.py -addopts=--tb=short +addopts=--tb=native -p no:doctest -p no:logging --cov=raven +norecursedirs=raven build bin dist htmlcov hooks node_modules .* {args} +DJANGO_SETTINGS_MODULE = tests.contrib.django.settings +python_paths = tests +flake8-ignore = + tests/ ALL + +[flake8] +ignore = F999,E501,E128,E124,E402,W503,E731,F841,D100,D101,D102,D103,D104,D105,D107,D200,D201,D205,D400,D401,D402,D403,I100,I101, I201, I202 +max-line-length = 100 +exclude = .tox,.git,tests + +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py index 600756203..cbe5248b6 100755 --- a/setup.py +++ b/setup.py @@ -1,13 +1,13 @@ #!/usr/bin/env python """ Raven -====== +===== Raven is a Python client for `Sentry `_. It provides full out-of-the-box support for many of the popular frameworks, including `Django `_, `Flask `_, and `Pylons `_. Raven also includes drop-in support for any -`WSGI `_-compatible web application. +`WSGI `_-compatible web application. """ # Hack to prevent stupid "TypeError: 'NoneType' object is not callable" error @@ -22,17 +22,21 @@ from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand +import re import sys +import ast -setup_requires = [ - 'pytest', -] -dev_requires = [ - 'flake8>=1.6,<2.0', -] +_version_re = re.compile(r'VERSION\s+=\s+(.*)') + +with open('raven/__init__.py', 'rb') as f: + version = str(ast.literal_eval(_version_re.search( + f.read().decode('utf-8')).group(1))) + +install_requires = [] unittest2_requires = ['unittest2'] + flask_requires = [ 'Flask>=0.8', 'blinker>=1.1', @@ -42,64 +46,95 @@ 'Flask-Login>=0.2.0', ] -# If it's python3, remove flask & unittest2 +sanic_requires = [] +sanic_tests_requires = [] + +webpy_tests_requires = [ + 'paste', + 'web.py', +] + +# If it's python3, remove unittest2 & web.py. if sys.version_info[0] == 3: - flask_requires = [] - flask_tests_requires = [] unittest2_requires = [] + webpy_tests_requires = [] + +# If it's Python 3.5+, add Sanic packages. +if sys.version_info >= (3, 5): + sanic_requires = [ + 'blinker>=1.1', + 'sanic>=0.7.0', + ] + sanic_tests_requires = ['aiohttp', ] tests_require = [ 'bottle', 'celery>=2.5', - 'Django>=1.2', - 'django-celery>=2.5', + 'coverage<4', 'exam>=0.5.2', + 'flake8==3.5.0', 'logbook', 'mock', 'nose', - 'pep8', 'pytz', - 'pytest', - 'pytest-cov>=1.4', - 'pytest-django-lite', - 'python-coveralls', - 'tornado', + 'pytest>=3.2.0,<3.3.0', + 'pytest-timeout==1.2.1', + 'pytest-xdist==1.18.2', + 'pytest-pythonpath==0.7.2', + 'pytest-cov==2.5.1', + 'pytest-flake8==1.0.0', + 'requests', + 'tornado>=4.1,<5.0', + 'tox', 'webob', 'webtest', + 'wheel', 'anyjson', -] + flask_requires + flask_tests_requires + unittest2_requires + 'ZConfig', +] + ( + flask_requires + flask_tests_requires + + sanic_requires + sanic_tests_requires + + unittest2_requires + webpy_tests_requires +) class PyTest(TestCommand): + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = [] + def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] self.test_suite = True def run_tests(self): - #import here, cause outside the eggs aren't loaded + # import here, cause outside the eggs aren't loaded import pytest - errno = pytest.main(self.test_args) + errno = pytest.main(self.pytest_args) sys.exit(errno) setup( name='raven', - version='3.4.1', - author='David Cramer', - author_email='dcramer@gmail.com', - url='http://github.com/getsentry/raven-python', - description='Raven is a client for Sentry (https://www.getsentry.com)', + version=version, + author='Sentry', + author_email='hello@getsentry.com', + url='https://github.com/getsentry/raven-python', + description='Raven is a client for Sentry (https://getsentry.com)', long_description=__doc__, packages=find_packages(exclude=("tests", "tests.*",)), zip_safe=False, extras_require={ 'flask': flask_requires, + 'sanic': sanic_requires, 'tests': tests_require, - 'dev': dev_requires, + ':python_version<"3.2"': ['contextlib2'], }, license='BSD', tests_require=tests_require, + install_requires=install_requires, cmdclass={'test': PyTest}, include_package_data=True, entry_points={ @@ -114,11 +149,16 @@ def run_tests(self): 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Operating System :: OS Independent', - 'Topic :: Software Development', - 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python', + 'Topic :: Software Development', ], ) diff --git a/tests/base/tests.py b/tests/base/tests.py index 4d9953833..dfaf64581 100644 --- a/tests/base/tests.py +++ b/tests/base/tests.py @@ -3,21 +3,24 @@ import inspect import mock -import pytest import raven import time -from socket import socket, AF_INET, SOCK_DGRAM +import os +import sys + +from raven.utils.compat import PY2 from raven.base import Client, ClientState +from raven.exceptions import RateLimited from raven.transport import AsyncTransport +from raven.transport.http import HTTPTransport from raven.utils.stacks import iter_stack_frames -from raven.utils import six from raven.utils.testutils import TestCase class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -62,6 +65,22 @@ def test_set_success(self): self.assertEquals(state.last_check, None) self.assertEquals(state.retry_number, 0) + def test_should_try_retry_after(self): + state = ClientState() + state.status = state.ERROR + state.last_check = time.time() + state.retry_number = 1 + state.retry_after = 1 + self.assertFalse(state.should_try()) + + def test_should_try_retry_after_passed(self): + state = ClientState() + state.status = state.ERROR + state.last_check = time.time() - 1 + state.retry_number = 1 + state.retry_after = 1 + self.assertTrue(state.should_try()) + class ClientTest(TestCase): def setUp(self): @@ -77,29 +96,56 @@ def test_first_client_is_singleton(self): assert base.Raven is client assert client is not client2 - @mock.patch('raven.transport.base.HTTPTransport.send') + def test_client_picks_up_env_dsn(self): + DSN = 'sync+http://public:secret@example.com/1' + PUBLIC_DSN = '//public@example.com/1' + with mock.patch.dict(os.environ, {'SENTRY_DSN': DSN}): + client = Client() + assert client.remote.get_public_dsn() == PUBLIC_DSN + client = Client('') + assert client.remote.get_public_dsn() == PUBLIC_DSN + + @mock.patch('raven.transport.http.HTTPTransport.send') @mock.patch('raven.base.ClientState.should_try') def test_send_remote_failover(self, should_try, send): should_try.return_value = True client = Client( - servers=['http://example.com'], - public_key='public', - secret_key='secret', - project=1, + dsn='sync+http://public:secret@example.com/1' ) # test error send.side_effect = Exception() - client.send_remote('http://example.com/api/store', 'foo') + client.send_remote('sync+http://example.com/api/store', client.encode({})) self.assertEquals(client.state.status, client.state.ERROR) # test recovery send.side_effect = None - client.send_remote('http://example.com/api/store', 'foo') + client.send_remote('sync+http://example.com/api/store', client.encode({})) self.assertEquals(client.state.status, client.state.ONLINE) - @mock.patch('raven.base.Client._registry.get_transport') + @mock.patch('raven.transport.http.HTTPTransport.send') + @mock.patch('raven.base.ClientState.should_try') + def test_send_remote_failover_with_retry_after(self, should_try, send): + should_try.return_value = True + + client = Client( + dsn='sync+http://public:secret@example.com/1' + ) + + # test error + send.side_effect = RateLimited('foo', 5) + client.send_remote('sync+http://example.com/api/1/store/', client.encode({})) + self.assertEquals(client.state.status, client.state.ERROR) + self.assertEqual(client.state.retry_after, 5) + + # test recovery + send.side_effect = None + client.send_remote('sync+http://example.com/api/1/store/', client.encode({})) + self.assertEquals(client.state.status, client.state.ONLINE) + self.assertEqual(client.state.retry_after, 0) + + @mock.patch('raven.conf.remote.RemoteConfig.get_transport') @mock.patch('raven.base.ClientState.should_try') def test_async_send_remote_failover(self, should_try, get_transport): should_try.return_value = True @@ -108,26 +154,23 @@ def test_async_send_remote_failover(self, should_try, get_transport): get_transport.return_value = async_transport client = Client( - servers=['http://example.com'], - public_key='public', - secret_key='secret', - project=1, + dsn='http://public:secret@example.com/1', ) # test immediate raise of error async_send.side_effect = Exception() - client.send_remote('http://example.com/api/store', 'foo') + client.send_remote('http://example.com/api/1/store/', client.encode({})) self.assertEquals(client.state.status, client.state.ERROR) # test recovery - client.send_remote('http://example.com/api/store', 'foo') - success_cb = async_send.call_args[0][2] + client.send_remote('http://example.com/api/1/store/', client.encode({})) + success_cb = async_send.call_args[0][3] success_cb() self.assertEquals(client.state.status, client.state.ONLINE) # test delayed raise of error - client.send_remote('http://example.com/api/store', 'foo') - failure_cb = async_send.call_args[0][3] + client.send_remote('http://example.com/api/1/store/', client.encode({})) + failure_cb = async_send.call_args[0][4] failure_cb(Exception()) self.assertEquals(client.state.status, client.state.ERROR) @@ -136,23 +179,21 @@ def test_async_send_remote_failover(self, should_try, get_transport): def test_send(self, time, send_remote): time.return_value = 1328055286.51 client = Client( - servers=['http://example.com'], - public_key='public', - secret_key='secret', - project=1, + dsn='http://public:secret@example.com/1', ) client.send(**{ 'foo': 'bar', }) send_remote.assert_called_once_with( - url='http://example.com', - data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='), + url='http://example.com/api/1/store/', + data=client.encode({'foo': 'bar'}), headers={ 'User-Agent': 'raven-python/%s' % (raven.VERSION,), 'Content-Type': 'application/octet-stream', + 'Content-Encoding': client.get_content_encoding(), 'X-Sentry-Auth': ( 'Sentry sentry_timestamp=1328055286.51, ' - 'sentry_client=raven-python/%s, sentry_version=2.0, ' + 'sentry_client=raven-python/%s, sentry_version=6, ' 'sentry_key=public, ' 'sentry_secret=secret' % (raven.VERSION,)) }, @@ -163,61 +204,56 @@ def test_send(self, time, send_remote): def test_send_with_auth_header(self, time, send_remote): time.return_value = 1328055286.51 client = Client( - servers=['http://example.com'], - public_key='public', - secret_key='secret', - project=1, + dsn='http://public:secret@example.com/1', ) client.send(auth_header='foo', **{ 'foo': 'bar', }) send_remote.assert_called_once_with( - url='http://example.com', - data=six.b('eJyrVkrLz1eyUlBKSixSqgUAIJgEVA=='), + url='http://example.com/api/1/store/', + data=client.encode({'foo': 'bar'}), headers={ 'User-Agent': 'raven-python/%s' % (raven.VERSION,), 'Content-Type': 'application/octet-stream', - 'X-Sentry-Auth': 'foo' + 'Content-Encoding': client.get_content_encoding(), + 'X-Sentry-Auth': 'foo', }, ) + @mock.patch('raven.transport.http.HTTPTransport.send') + @mock.patch('raven.base.ClientState.should_try') + def test_raise_exception_on_send_error(self, should_try, _send_remote): + should_try.return_value = True + client = Client( + dsn='sync+http://public:secret@example.com/1', + ) + + # Test for the default behaviour in which a send error is handled by the client + _send_remote.side_effect = Exception() + client.capture('Message', data={}, date=None, time_spent=10, + extra={}, stack=None, tags=None, message='Test message') + assert client.state.status == client.state.ERROR + + # Test for the case in which a send error is raised to the calling frame. + client = Client( + dsn='sync+http://public:secret@example.com/1', + raise_send_errors=True, + ) + with self.assertRaises(Exception): + client.capture('Message', data={}, date=None, time_spent=10, + extra={}, stack=None, tags=None, message='Test message') + def test_encode_decode(self): data = {'foo': 'bar'} encoded = self.client.encode(data) self.assertTrue(type(encoded), str) self.assertEquals(data, self.client.decode(encoded)) - def test_dsn(self): - client = Client(dsn='http://public:secret@example.com/1') - self.assertEquals(client.servers, ['http://example.com/api/1/store/']) - self.assertEquals(client.project, '1') - self.assertEquals(client.public_key, 'public') - self.assertEquals(client.secret_key, 'secret') - - def test_dsn_as_first_arg(self): - client = Client('http://public:secret@example.com/1') - self.assertEquals(client.servers, ['http://example.com/api/1/store/']) - self.assertEquals(client.project, '1') - self.assertEquals(client.public_key, 'public') - self.assertEquals(client.secret_key, 'secret') - - def test_slug_in_dsn(self): - client = Client('http://public:secret@example.com/slug-name') - self.assertEquals(client.servers, ['http://example.com/api/slug-name/store/']) - self.assertEquals(client.project, 'slug-name') - self.assertEquals(client.public_key, 'public') - self.assertEquals(client.secret_key, 'secret') - def test_get_public_dsn(self): - client = Client('threaded+http://public:secret@example.com/1') + client = Client('http://public:secret@example.com/1') public_dsn = client.get_public_dsn() self.assertEquals(public_dsn, '//public@example.com/1') - def test_get_public_dsn_override_scheme(self): - client = Client('threaded+http://public:secret@example.com/1') - public_dsn = client.get_public_dsn('https') - self.assertEquals(public_dsn, 'https://public@example.com/1') - def test_explicit_message_on_message_event(self): self.client.captureMessage(message='test', data={ 'message': 'foo' @@ -227,10 +263,20 @@ def test_explicit_message_on_message_event(self): event = self.client.events.pop(0) self.assertEquals(event['message'], 'foo') + def test_message_from_kwargs(self): + try: + raise ValueError('foo') + except ValueError: + self.client.captureException(message='test', data={}) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'test') + def test_explicit_message_on_exception_event(self): try: raise ValueError('foo') - except: + except ValueError: self.client.captureException(data={'message': 'foobar'}) self.assertEquals(len(self.client.events), 1) @@ -240,65 +286,209 @@ def test_explicit_message_on_exception_event(self): def test_exception_event(self): try: raise ValueError('foo') - except: + except ValueError: self.client.captureException() self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'ValueError: foo') - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] + self.assertTrue('exception' in event) + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'foo') self.assertEquals(exc['module'], ValueError.__module__) # this differs in some Python versions - self.assertTrue('sentry.interfaces.Stacktrace' in event) - frames = event['sentry.interfaces.Stacktrace'] - self.assertEquals(len(frames['frames']), 1) - frame = frames['frames'][0] + assert 'stacktrace' not in event + stacktrace = exc['stacktrace'] + self.assertEquals(len(stacktrace['frames']), 1) + frame = stacktrace['frames'][0] self.assertEquals(frame['abs_path'], __file__.replace('.pyc', '.py')) self.assertEquals(frame['filename'], 'tests/base/tests.py') self.assertEquals(frame['module'], __name__) self.assertEquals(frame['function'], 'test_exception_event') self.assertTrue('timestamp' in event) + def test_exception_nan_in_vars(self): + try: + foo = float("nan") # noqa + raise ValueError("foo") + except ValueError: + self.client.captureException() + + event, = self.client.events + exc, = event['exception']['values'] + frame, = exc['stacktrace']['frames'] + assert frame['vars']['foo'] == "nan" + + def test_exception_event_true_exc_info(self): + try: + raise ValueError('foo') + except ValueError: + self.client.captureException(exc_info=True) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'ValueError: foo') + self.assertTrue('exception' in event) + exc = event['exception']['values'][-1] + stacktrace = exc['stacktrace'] + self.assertEquals(len(stacktrace['frames']), 1) + frame = stacktrace['frames'][0] + self.assertEquals(frame['abs_path'], __file__.replace('.pyc', '.py')) + self.assertEquals(frame['filename'], 'tests/base/tests.py') + self.assertEquals(frame['module'], __name__) + + def test_exception_event_ignore_string(self): + class Foo(Exception): + pass + + client = TempStoreClient(ignore_exceptions=['Foo']) + try: + raise Foo() + except Foo: + client.captureException() + + self.assertEquals(len(client.events), 0) + + def test_exception_event_ignore_class(self): + class Foo(Exception): + pass + + client = TempStoreClient(ignore_exceptions=[Foo]) + try: + raise Foo() + except Foo: + client.captureException() + + self.assertEquals(len(client.events), 0) + + def test_exception_event_ignore_child(self): + class Foo(Exception): + pass + + class Bar(Foo): + pass + + client = TempStoreClient(ignore_exceptions=[Foo]) + try: + raise Bar() + except Bar: + client.captureException() + + self.assertEquals(len(client.events), 0) + + def test_decorator_preserves_function(self): + @self.client.capture_exceptions + def test1(): + return 'foo' + + self.assertEquals(test1(), 'foo') + + class DecoratorTestException(Exception): + pass + + def test_decorator_functionality(self): + @self.client.capture_exceptions + def test2(): + raise self.DecoratorTestException() + + try: + test2() + except self.DecoratorTestException: + pass + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'DecoratorTestException') + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'DecoratorTestException') + self.assertEquals(exc['module'], self.DecoratorTestException.__module__) + stacktrace = exc['stacktrace'] + # this is a wrapped class object with __call__ so three frames are expected + self.assertEquals(len(stacktrace['frames']), 3) + frame = stacktrace['frames'][-1] + self.assertEquals(frame['module'], __name__) + self.assertEquals(frame['function'], 'test2') + + def test_decorator_filtering(self): + @self.client.capture_exceptions(self.DecoratorTestException) + def test3(): + raise Exception() + + try: + test3() + except Exception: + pass + + self.assertEquals(len(self.client.events), 0) + + def test_context_manager_functionality(self): + def test4(): + raise self.DecoratorTestException() + + try: + with self.client.capture_exceptions(): + test4() + except self.DecoratorTestException: + pass + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + self.assertEquals(event['message'], 'DecoratorTestException') + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'DecoratorTestException') + self.assertEquals(exc['module'], self.DecoratorTestException.__module__) + stacktrace = exc['stacktrace'] + # three frames are expected: test4, `with` block and context manager internals + self.assertEquals(len(stacktrace['frames']), 3) + frame = stacktrace['frames'][-1] + self.assertEquals(frame['module'], __name__) + self.assertEquals(frame['function'], 'test4') + + def test_content_manager_filtering(self): + def test5(): + raise Exception() + + try: + with self.client.capture_exceptions(self.DecoratorTestException): + test5() + except Exception: + pass + + self.assertEquals(len(self.client.events), 0) + def test_message_event(self): self.client.captureMessage(message='test') self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'test') - self.assertFalse('sentry.interfaces.Stacktrace' in event) + assert 'stacktrace' not in event self.assertTrue('timestamp' in event) - def test_exception_context_manager(self): - cm = self.client.context(tags={'foo': 'bar'}) + def test_fingerprint(self): + self.client.captureMessage( + message='test', + fingerprint=['{{ default }}', 'foobar'], + ) + + assert len(self.client.events) == 1 + event = self.client.events.pop(0) + assert event['fingerprint'] == ['{{ default }}', 'foobar'] + + def test_context(self): + self.client.context.merge({ + 'tags': {'foo': 'bar'}, + }) try: - with cm: - raise ValueError('foo') - except: - pass + raise ValueError('foo') + except ValueError: + self.client.captureException() else: self.fail('Exception should have been raised') - self.assertNotEquals(cm.result, None) - - self.assertEquals(len(self.client.events), 1) + assert len(self.client.events) == 1 event = self.client.events.pop(0) - self.assertEquals(event['message'], 'ValueError: foo') - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ValueError') - self.assertEquals(exc['value'], 'foo') - self.assertEquals(exc['module'], ValueError.__module__) # this differs in some Python versions - self.assertTrue('sentry.interfaces.Stacktrace' in event) - frames = event['sentry.interfaces.Stacktrace'] - self.assertEquals(len(frames['frames']), 1) - frame = frames['frames'][0] - self.assertEquals(frame['abs_path'], __file__.replace('.pyc', '.py')) - self.assertEquals(frame['filename'], 'tests/base/tests.py') - self.assertEquals(frame['module'], __name__) - self.assertEquals(frame['function'], 'test_exception_context_manager') - self.assertTrue('timestamp' in event) + assert event['tags'] == {'foo': 'bar'} def test_stack_explicit_frames(self): def bar(): @@ -311,9 +501,9 @@ def bar(): self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'test') - self.assertTrue('sentry.interfaces.Stacktrace' in event) - self.assertEquals(len(frames), len(event['sentry.interfaces.Stacktrace']['frames'])) - for frame, frame_i in zip(frames, event['sentry.interfaces.Stacktrace']['frames']): + assert 'stacktrace' in event + self.assertEquals(len(frames), len(event['stacktrace']['frames'])) + for frame, frame_i in zip(frames[::-1], event['stacktrace']['frames']): self.assertEquals(frame[0].f_code.co_filename, frame_i['abs_path']) self.assertEquals(frame[0].f_code.co_name, frame_i['function']) @@ -323,7 +513,7 @@ def test_stack_auto_frames(self): self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) self.assertEquals(event['message'], 'test') - self.assertTrue('sentry.interfaces.Stacktrace' in event) + self.assertTrue('stacktrace' in event) self.assertTrue('timestamp' in event) def test_site(self): @@ -367,28 +557,114 @@ def test_client_extra_context(self): self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) - if six.PY3: + if not PY2: expected = {'logger': "'test'", 'foo': "'bar'"} else: expected = {'logger': "u'test'", 'foo': "u'bar'"} self.assertEquals(event['extra'], expected) + def test_sample_rate(self): + self.client.sample_rate = 0.0 + self.client.captureMessage(message='test') + self.assertEquals(len(self.client.events), 0) -# TODO: Python 3 -@pytest.mark.skipif(str("six.PY3")) -class ClientUDPTest(TestCase): - def setUp(self): - self.server_socket = socket(AF_INET, SOCK_DGRAM) - self.server_socket.bind(('127.0.0.1', 0)) - self.client = Client(servers=["udp://%s:%s" % self.server_socket.getsockname()], key='BassOmatic') - - def test_delivery(self): - self.client.captureMessage('test') - data, address = self.server_socket.recvfrom(2 ** 16) - self.assertTrue("\n\n" in data) - header, payload = data.split("\n\n") - for substring in ("sentry_timestamp=", "sentry_client="): - self.assertTrue(substring in header) - - def tearDown(self): - self.server_socket.close() + def test_sample_rate_per_message(self): + self.client.sample_rate = 1 + self.client.captureMessage(message='test', sample_rate=0.0) + self.assertEquals(len(self.client.events), 0) + + self.client.sample_rate = 0 + self.client.captureMessage(message='test', sample_rate=1.0) + self.assertEquals(len(self.client.events), 1) + + def test_transport_registration(self): + client = Client('http://public:secret@example.com/1', + transport=HTTPTransport) + assert type(client.remote.get_transport()) is HTTPTransport + + client = Client('sync+http://public:secret@example.com/1') + assert type(client.remote.get_transport()) is HTTPTransport + + def test_marks_in_app_frames_for_stacktrace(self): + client = TempStoreClient( + include_paths=['foo'], + exclude_paths=['foo.bar'], + ) + client.captureMessage('hello', data={ + 'stacktrace': { + 'frames': [ + {'module': 'foo'}, + {'module': 'bar'}, + {'module': 'foo.bar'}, + {'module': 'foo.baz'}, + ] + } + }) + + event = client.events.pop(0) + frames = event['stacktrace']['frames'] + assert frames[0]['in_app'] + assert not frames[1]['in_app'] + assert not frames[2]['in_app'] + assert frames[3]['in_app'] + + def test_marks_in_app_frames_for_exception(self): + client = TempStoreClient( + include_paths=['foo'], + exclude_paths=['foo.bar'], + ) + client.captureMessage('hello', data={ + 'exception': { + 'values': [{ + 'stacktrace': { + 'frames': [ + {'module': 'foo'}, + {'module': 'bar'}, + {'module': 'foo.bar'}, + {'module': 'foo.baz'}, + ] + } + }] + } + }) + + event = client.events.pop(0) + frames = event['exception']['values'][-1]['stacktrace']['frames'] + assert frames[0]['in_app'] + assert not frames[1]['in_app'] + assert not frames[2]['in_app'] + assert frames[3]['in_app'] + + def test_captures_last_event_id(self): + client = TempStoreClient() + result = client.captureMessage('hello') + + assert result == client.last_event_id + + def test_no_sys_argv(self): + # if the python interpreter is started from C, sys.argv might not be available + # see https://github.com/getsentry/raven-python/issues/918 + argv = sys.argv + try: + del sys.argv + Client() + finally: + sys.argv = argv + + def test_repos_configuration(self): + client = Client(repos={ + '/foo/bar': { + 'name': 'repo', + }, + 'raven': { + 'name': 'getsentry/raven-python', + }, + }) + assert client.repos == { + '/foo/bar': { + 'name': 'repo', + }, + os.path.abspath(raven.__file__): { + 'name': 'getsentry/raven-python', + }, + } diff --git a/tests/breadcrumbs/__init__.py b/tests/breadcrumbs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/breadcrumbs/tests.py b/tests/breadcrumbs/tests.py new file mode 100644 index 000000000..923f47937 --- /dev/null +++ b/tests/breadcrumbs/tests.py @@ -0,0 +1,209 @@ +import sys +import logging + +from raven.utils.testutils import TestCase + +from raven.base import Client +from raven import breadcrumbs + +from io import StringIO + + +class DummyClass(object): + def dummy_method(self): + pass + + +class BreadcrumbTestCase(TestCase): + + def test_crumb_buffer(self): + for enable in 1, 0: + client = Client('http://foo:bar@example.com/0', + enable_breadcrumbs=enable) + with client.context: + breadcrumbs.record(type='foo', data={'bar': 'baz'}, + message='aha', category='huhu') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == enable + + def test_log_crumb_reporting(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s!', 'foo', blah='baz') + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['data'] == {'blah': 'baz'} + assert crumbs[0]['message'] == 'This is a message with foo!' + + def test_log_crumb_reporting_with_dict(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %(foo)s!', {'foo': 'bar'}, + extra={'blah': 'baz'}) + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['data'] == {'foo': 'bar', 'blah': 'baz'} + assert crumbs[0]['message'] == 'This is a message with bar!' + + def test_log_crumb_reporting_with_large_message(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('a' * 4096) + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['message'] == 'a' * 1024 + + def test_log_location(self): + out = StringIO() + logger = logging.getLogger(__name__) + logger.setLevel(logging.DEBUG) + handler = logging.StreamHandler(out) + handler.setFormatter(logging.Formatter( + u'%(name)s|%(filename)s|%(funcName)s|%(lineno)d|' + u'%(levelname)s|%(message)s')) + logger.addHandler(handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.info('Hello World!') + lineno = sys._getframe().f_lineno - 1 + + items = out.getvalue().strip().split('|') + assert items[0] == 'tests.breadcrumbs.tests' + assert items[1].rstrip('co') == 'tests.py' + assert items[2] == 'test_log_location' + assert int(items[3]) == lineno + assert items[4] == 'INFO' + assert items[5] == 'Hello World!' + + def test_broken_logging(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s. %s!', 42) + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 1 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['message'] == 'This is a message with %s. %s!' + + def test_dedup_logging(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + log = logging.getLogger('whatever.foo') + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 42) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 23) + log.info('This is a message with %s!', 42) + crumbs = client.context.breadcrumbs.get_buffer() + + assert len(crumbs) == 3 + assert crumbs[0]['type'] == 'default' + assert crumbs[0]['category'] == 'whatever.foo' + assert crumbs[0]['message'] == 'This is a message with 42!' + assert crumbs[1]['type'] == 'default' + assert crumbs[1]['category'] == 'whatever.foo' + assert crumbs[1]['message'] == 'This is a message with 23!' + assert crumbs[2]['type'] == 'default' + assert crumbs[2]['category'] == 'whatever.foo' + assert crumbs[2]['message'] == 'This is a message with 42!' + + def test_manual_record(self): + client = Client('http://foo:bar@example.com/0') + with client.context: + def processor(data): + assert data['message'] == 'whatever' + assert data['level'] == 'warning' + assert data['category'] == 'category' + assert data['type'] == 'the_type' + assert data['data'] == {'foo': 'bar'} + data['data']['extra'] = 'something' + + breadcrumbs.record(message='whatever', + level='warning', + category='category', + data={'foo': 'bar'}, + type='the_type', + processor=processor) + + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 1 + data = crumbs[0] + assert data['message'] == 'whatever' + assert data['level'] == 'warning' + assert data['category'] == 'category' + assert data['type'] == 'the_type' + assert data['data'] == {'foo': 'bar', 'extra': 'something'} + + def test_special_log_handlers(self): + name = __name__ + '.superspecial' + logger = logging.getLogger(name) + + def handler(logger, level, msg, args, kwargs): + assert logger.name == name + assert msg == 'aha!' + return True + + breadcrumbs.register_special_log_handler(name, handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.debug('aha!') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 0 + + def test_logging_handlers(self): + name = __name__ + '.superspecial2' + logger = logging.getLogger(name) + + def handler(logger, level, msg, args, kwargs): + if logger.name == name: + assert msg == 'aha!' + return True + + breadcrumbs.register_logging_handler(handler) + + client = Client('http://foo:bar@example.com/0') + with client.context: + logger.debug('aha!') + crumbs = client.context.breadcrumbs.get_buffer() + assert len(crumbs) == 0 + + def test_hook_libraries(self): + + @breadcrumbs.libraryhook('dummy') + def _install_func(): + old_func = DummyClass.dummy_method + + def new_func(self): + breadcrumbs.record(type='dummy', category='dummy', message="Dummy message") + old_func(self) + + DummyClass.dummy_method = new_func + + client = Client('http://foo:bar@example.com/0', hook_libraries=['requests']) + with client.context: + DummyClass().dummy_method() + crumbs = client.context.breadcrumbs.get_buffer() + assert 'dummy' not in set([i['type'] for i in crumbs]) + + client = Client('http://foo:bar@example.com/0', hook_libraries=['requests', 'dummy']) + with client.context: + DummyClass().dummy_method() + crumbs = client.context.breadcrumbs.get_buffer() + assert 'dummy' in set([i['type'] for i in crumbs]) + diff --git a/tests/conf/__init__.py b/tests/conf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conf/tests.py b/tests/conf/tests.py new file mode 100644 index 000000000..333ea55da --- /dev/null +++ b/tests/conf/tests.py @@ -0,0 +1,143 @@ +from __future__ import with_statement + +import logging +import mock + +from raven.conf import setup_logging +from raven.conf.remote import RemoteConfig +from raven.exceptions import InvalidDsn +from raven.utils import get_auth_header +from raven.utils.testutils import TestCase + + +class RemoteConfigTest(TestCase): + def test_path_strip(self): + dsn = 'https://foo:bar@sentry.local/app/1\n' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local/app' + assert res.store_endpoint == 'https://sentry.local/app/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_path(self): + dsn = 'https://foo:bar@sentry.local/app/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local/app' + assert res.store_endpoint == 'https://sentry.local/app/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_http(self): + dsn = 'http://foo:bar@sentry.local/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'http://sentry.local' + assert res.store_endpoint == 'http://sentry.local/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_http_with_port(self): + dsn = 'http://foo:bar@sentry.local:9000/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'http://sentry.local:9000' + assert res.store_endpoint == 'http://sentry.local:9000/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_https(self): + dsn = 'https://foo:bar@sentry.local/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local' + assert res.store_endpoint == 'https://sentry.local/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_https_with_port(self): + dsn = 'https://foo:bar@sentry.local:9000/app/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local:9000/app' + assert res.store_endpoint == 'https://sentry.local:9000/app/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {} + + def test_options(self): + dsn = 'http://foo:bar@sentry.local/1?timeout=1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'http://sentry.local' + assert res.store_endpoint == 'http://sentry.local/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key == 'bar' + assert res.options == {'timeout': '1'} + + def test_no_secret_key(self): + dsn = 'https://foo@sentry.local/1' + res = RemoteConfig.from_string(dsn) + assert res.project == '1' + assert res.base_url == 'https://sentry.local' + assert res.store_endpoint == 'https://sentry.local/api/1/store/' + assert res.public_key == 'foo' + assert res.secret_key is None + assert res.options == {} + assert res.is_active() + + assert get_auth_header(protocol=7, timestamp=42, + client='raven-python/1.0', + api_key=res.public_key) == ( + 'Sentry sentry_timestamp=42, sentry_client=raven-python/1.0, ' + 'sentry_version=7, sentry_key=foo') + + def test_missing_netloc(self): + dsn = 'https://foo:bar@/1' + self.assertRaises(InvalidDsn, RemoteConfig.from_string, dsn) + + def test_missing_project(self): + dsn = 'https://foo:bar@example.com' + self.assertRaises(InvalidDsn, RemoteConfig.from_string, dsn) + + def test_missing_public_key(self): + dsn = 'https://:bar@example.com' + self.assertRaises(InvalidDsn, RemoteConfig.from_string, dsn) + + def test_invalid_scheme(self): + dsn = 'ftp://foo:bar@sentry.local/1' + self.assertRaises(InvalidDsn, RemoteConfig.from_string, dsn) + + def test_get_public_dsn(self): + res = RemoteConfig( + base_url='http://example.com', + project='1', + public_key='public', + secret_key='secret', + ) + public_dsn = res.get_public_dsn() + assert public_dsn == '//public@example.com/1' + + +class SetupLoggingTest(TestCase): + def test_basic_not_configured(self): + with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: + logger = getLogger() + logger.handlers = [] + handler = mock.Mock() + result = setup_logging(handler) + self.assertTrue(result) + + def test_basic_already_configured(self): + with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: + handler = mock.Mock() + logger = getLogger() + logger.handlers = [handler] + result = setup_logging(handler) + self.assertFalse(result) diff --git a/tests/config/tests.py b/tests/config/tests.py deleted file mode 100644 index d60c36292..000000000 --- a/tests/config/tests.py +++ /dev/null @@ -1,143 +0,0 @@ -from __future__ import with_statement -import logging -import mock -from raven.conf import load, setup_logging -from raven.utils.testutils import TestCase - - -class LoadTest(TestCase): - def test_basic(self): - dsn = 'https://foo:bar@sentry.local/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_path(self): - dsn = 'https://foo:bar@sentry.local/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_port(self): - dsn = 'https://foo:bar@sentry.local:9000/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local:9000/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_scope_is_optional(self): - dsn = 'https://foo:bar@sentry.local/1' - res = load(dsn) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_http(self): - dsn = 'http://foo:bar@sentry.local/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['http://sentry.local/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_http_with_port(self): - dsn = 'http://foo:bar@sentry.local:9000/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['http://sentry.local:9000/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_https_port_443(self): - dsn = 'https://foo:bar@sentry.local:443/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_https_port_80(self): - dsn = 'https://foo:bar@sentry.local:80/app/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['https://sentry.local:80/app/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_udp(self): - dsn = 'udp://foo:bar@sentry.local:9001/1' - res = {} - load(dsn, res) - self.assertEquals(res, { - 'SENTRY_PROJECT': '1', - 'SENTRY_SERVERS': ['udp://sentry.local:9001/api/1/store/'], - 'SENTRY_PUBLIC_KEY': 'foo', - 'SENTRY_SECRET_KEY': 'bar', - }) - - def test_missing_netloc(self): - dsn = 'https://foo:bar@/1' - self.assertRaises(ValueError, load, dsn) - - def test_missing_project(self): - dsn = 'https://foo:bar@example.com' - self.assertRaises(ValueError, load, dsn) - - def test_missing_public_key(self): - dsn = 'https://:bar@example.com' - self.assertRaises(ValueError, load, dsn) - - def test_missing_secret_key(self): - dsn = 'https://bar@example.com' - self.assertRaises(ValueError, load, dsn) - - def test_invalid_scheme(self): - dsn = 'ftp://foo:bar@sentry.local/1' - self.assertRaises(ValueError, load, dsn) - - -class SetupLoggingTest(TestCase): - def test_basic_not_configured(self): - with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: - logger = getLogger() - logger.handlers = [] - handler = mock.Mock() - result = setup_logging(handler) - self.assertTrue(result) - - def test_basic_already_configured(self): - with mock.patch('logging.getLogger', spec=logging.getLogger) as getLogger: - handler = mock.Mock() - logger = getLogger() - logger.handlers = [handler] - result = setup_logging(handler) - self.assertFalse(result) diff --git a/tests/context/tests.py b/tests/context/tests.py index 04a1427f9..9332655cd 100644 --- a/tests/context/tests.py +++ b/tests/context/tests.py @@ -1,46 +1,72 @@ -import mock -import sys -from exam import fixture +import threading from raven.utils.testutils import TestCase +from raven.base import Client from raven.context import Context class ContextTest(TestCase): - @fixture - def client(self): - return mock.Mock() - - def context(self, **kwargs): - return Context(self.client, **kwargs) - - def test_capture_exception(self): - with self.context(tags={'foo': 'bar'}) as client: - result = client.captureException('exception') - self.assertEquals(result, self.client.captureException.return_value) - self.client.captureException.assert_called_once_with('exception', tags={ + def test_simple(self): + context = Context() + context.merge({'foo': 'bar'}) + context.merge({'biz': 'baz'}) + context.merge({'biz': 'boz'}) + assert context.get() == { + 'foo': 'bar', + 'biz': 'boz', + } + + def test_tags(self): + context = Context() + context.merge({'tags': {'foo': 'bar'}}) + context.merge({'tags': {'biz': 'baz'}}) + assert context.get() == { + 'tags': { 'foo': 'bar', - }) + 'biz': 'baz', + } + } - def test_capture_message(self): - with self.context(tags={'foo': 'bar'}) as client: - result = client.captureMessage('exception') - self.assertEquals(result, self.client.captureMessage.return_value) - self.client.captureMessage.assert_called_once_with('exception', tags={ + def test_extra(self): + context = Context() + context.merge({'extra': {'foo': 'bar'}}) + context.merge({'extra': {'biz': 'baz'}}) + assert context.get() == { + 'extra': { 'foo': 'bar', - }) - - def test_implicit_exception_handling(self): - try: - with self.context(tags={'foo': 'bar'}): - try: - 1 / 0 - except Exception: - exc_info = sys.exc_info() - raise - except Exception: - pass - - self.client.captureException.assert_called_once_with(exc_info, tags={ - 'foo': 'bar', - }) + 'biz': 'baz', + } + } + + def test_thread_binding(self): + client = Client() + called = [] + + class TestContext(Context): + + def activate(self): + Context.activate(self) + called.append('activate') + + def deactivate(self): + called.append('deactivate') + Context.deactivate(self) + + # The main thread activates the context but clear does not + # deactivate. + context = TestContext(client) + context.clear() + assert called == ['activate'] + + # But another thread does. + del called[:] + + def test_thread(): + # This activate is unnecessary as the first activate happens + # automatically + context.activate() + context.clear() + t = threading.Thread(target=test_thread) + t.start() + t.join() + assert called == ['activate', 'activate', 'deactivate'] diff --git a/tests/contrib/awslambda/conftest.py b/tests/contrib/awslambda/conftest.py new file mode 100644 index 000000000..c1a481215 --- /dev/null +++ b/tests/contrib/awslambda/conftest.py @@ -0,0 +1,106 @@ + +import pytest +from raven.contrib.awslambda import LambdaClient +import uuid +import time + + +class MockClient(LambdaClient): + def __init__(self, *args, **kwargs): + self.events = [] + super(MockClient, self).__init__(*args, **kwargs) + + def send(self, **kwargs): + self.events.append(kwargs) + + def is_enabled(self, **kwargs): + return True + + +class LambdaIndentityStub(object): + def __init__(self, id=1, pool_id=1): + self.cognito_identity_id = id + self.cognito_identity_pool_id = pool_id + + def __getitem__(self, item): + return getattr(self, item) + + def get(self, name, default=None): + return getattr(self, name, default) + + +class LambdaContextStub(object): + + def __init__(self, function_name, memory_limit_in_mb=128, timeout=300, function_version='$LATEST'): + self.function_name = function_name + self.memory_limit_in_mb = memory_limit_in_mb + self.timeout = timeout + self.function_version = function_version + self.timeout = timeout + self.invoked_function_arn = 'invoked_function_arn' + self.log_group_name = 'log_group_name' + self.log_stream_name = 'log_stream_name' + self.identity = LambdaIndentityStub(id=0, pool_id=0) + self.client_context = None + self.aws_request_id = str(uuid.uuid4()) + self.start_time = time.time() * 1000 + + def __getitem__(self, item, default): + return getattr(self, item, default) + + def get(self, name, default=None): + return getattr(self, name, default) + + def get_remaining_time_in_millis(self): + return max(self.timeout * 1000 - int((time.time() * 1000) - self.start_time), 0) + + +class LambdaEventStub(object): + def __init__(self, body=None, headers=None, http_method='GET', path='/test', query_string=None): + self.body = body + self.headers = headers + self.httpMethod = http_method + self.isBase64Encoded = False + self.path = path + self.queryStringParameters = query_string + self.resource = path + self.stageVariables = None + self.requestContext = { + 'accountId': '0000000', + 'apiId': 'AAAAAAAA', + 'httpMethod': http_method, + 'identity': LambdaIndentityStub(), + 'path': path, + 'requestId': 'test-request', + 'resourceId': 'bbzeyv', + 'resourcePath': '/test', + 'stage': 'test-stage' + } + + def __getitem__(self, name): + return getattr(self, name) + + def get(self, name, default=None): + return getattr(self, name, default) + + +@pytest.fixture +def lambda_env(monkeypatch): + monkeypatch.setenv('SENTRY_DSN', 'http://public:secret@example.com/1') + monkeypatch.setenv('AWS_LAMBDA_FUNCTION_NAME', 'test_func') + monkeypatch.setenv('AWS_LAMBDA_FUNCTION_VERSION', '$LATEST') + monkeypatch.setenv('SENTRY_RELEASE', '$LATEST') + monkeypatch.setenv('SENTRY_ENVIRONMENT', 'testing') + + +@pytest.fixture +def mock_client(): + return MockClient + +@pytest.fixture +def lambda_event(): + return LambdaEventStub + +@pytest.fixture +def lambda_context(): + return LambdaContextStub diff --git a/tests/contrib/awslambda/test_lambda.py b/tests/contrib/awslambda/test_lambda.py new file mode 100644 index 000000000..39cfc95ab --- /dev/null +++ b/tests/contrib/awslambda/test_lambda.py @@ -0,0 +1,69 @@ +import pytest +from raven.transport.http import HTTPTransport + + +class MyException(Exception): + pass + + +def test_decorator_exception(lambda_env, mock_client, lambda_event, lambda_context): + + client = mock_client() + + @client.capture_exceptions + def test_func(event, context): + raise MyException('There was an error.') + + with pytest.raises(MyException): + test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) + + assert client.events + assert isinstance(client.remote.get_transport(), HTTPTransport) + assert 'user' in client.events[0].keys() + assert 'request' in client.events[0].keys() + + +def test_decorator_with_args(lambda_env, mock_client, lambda_event, lambda_context): + client = mock_client() + + @client.capture_exceptions((MyException,)) + def test_func(event, context): + raise Exception + + with pytest.raises(Exception): + test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) + + assert not client.events + + @client.capture_exceptions((MyException,)) + def test_func(event, context): + raise MyException + + with pytest.raises(Exception): + test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) + + assert client.events + + +def test_decorator_without_exceptions(lambda_env, mock_client, lambda_event, lambda_context): + client = mock_client() + + @client.capture_exceptions((MyException,)) + def test_func(event, context): + return 0 + + assert test_func(event=lambda_event(), context=lambda_context(function_name='test_func')) == 0 + + +def test_decorator_without_kwargs(lambda_env, mock_client, lambda_event, lambda_context): + + client = mock_client() + + @client.capture_exceptions((MyException,)) + def test_func(event, context): + raise MyException + + with pytest.raises(Exception): + test_func(lambda_event(), lambda_context(function_name='test_func')) + + assert client.events diff --git a/tests/contrib/bottle/tests.py b/tests/contrib/bottle/tests.py index c08b191f5..bfa7ada00 100644 --- a/tests/contrib/bottle/tests.py +++ b/tests/contrib/bottle/tests.py @@ -1,8 +1,6 @@ -import logging - from exam import fixture -from webtest import TestApp +from webtest import TestApp as WebtestApp # prevent pytest-warning import bottle @@ -12,9 +10,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -22,13 +20,12 @@ def is_enabled(self): def send(self, **kwargs): self.events.append(kwargs) -def create_app(raven): - import os +def create_app(raven): app = bottle.app() app.catchall = False app = Sentry(app, client=raven) - tapp = TestApp(app) + tapp = WebtestApp(app) @bottle.route('/error/', ['GET', 'POST']) def an_error(): @@ -49,6 +46,7 @@ def capture_message(): return tapp + class BaseTest(TestCase): @fixture def app(self): @@ -59,20 +57,17 @@ def app(self): def client(self): return self.app + class BottleTest(BaseTest): def test_error(self): self.assertRaises(ValueError, self.client.get, '/error/?foo=bar') self.assertEquals(len(self.raven.events), 1) event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) + assert 'exception' in event - exc = event['sentry.interfaces.Exception'] + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') - self.assertEquals(exc['value'], 'hello world') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'ValueError: hello world') - self.assertEquals(event['culprit'], 'tests.contrib.bottle.tests in an_error') def test_captureException_captures_http(self): response = self.client.get('/capture/?foo=bar') @@ -82,8 +77,8 @@ def test_captureException_captures_http(self): event = self.raven.events.pop(0) assert event['message'] == 'ValueError: Boom' - assert 'sentry.interfaces.Http' in event - assert 'sentry.interfaces.Exception' in event + assert 'request' in event + assert 'exception' in event def test_captureMessage_captures_http(self): response = self.client.get('/message/?foo=bar') @@ -92,5 +87,5 @@ def test_captureMessage_captures_http(self): event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Message' in event) - self.assertTrue('sentry.interfaces.Http' in event) + assert 'sentry.interfaces.Message' in event + assert 'request' in event diff --git a/tests/contrib/celery/tests.py b/tests/contrib/celery/tests.py deleted file mode 100644 index cd405eaee..000000000 --- a/tests/contrib/celery/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -import mock -from raven.utils.testutils import TestCase -from celery.tests.utils import with_eager_tasks -from raven.contrib.celery import CeleryClient - - -class ClientTest(TestCase): - def setUp(self): - self.client = CeleryClient(servers=['http://example.com']) - - @mock.patch('raven.contrib.celery.CeleryClient.send_raw') - def test_send_encoded(self, send_raw): - self.client.send_encoded('foo') - - send_raw.delay.assert_called_once_with('foo') - - @mock.patch('raven.contrib.celery.CeleryClient.send_raw') - def test_without_eager(self, send_raw): - """ - Integration test to ensure it propagates all the way down - and calls delay on the task. - """ - self.client.captureMessage(message='test') - - self.assertEquals(send_raw.delay.call_count, 1) - - @with_eager_tasks - @mock.patch('raven.base.Client.send_encoded') - def test_with_eager(self, send_encoded): - """ - Integration test to ensure it propagates all the way down - and calls the parent client's send_encoded method. - """ - self.client.captureMessage(message='test') - - self.assertEquals(send_encoded.call_count, 1) diff --git a/tests/contrib/django/api.py b/tests/contrib/django/api.py new file mode 100644 index 000000000..2affeb6c8 --- /dev/null +++ b/tests/contrib/django/api.py @@ -0,0 +1,85 @@ +from __future__ import absolute_import + +from uuid import uuid4 +from tastypie.bundle import Bundle +from tastypie.resources import Resource + +from raven.contrib.django.models import client + + +class Item(object): + def __init__(self, pk=None, name=None): + self.pk = pk or uuid4().hex + self.name = name or '' + + +class ExampleResource(Resource): + class Meta: + resource_name = 'example' + object_class = Item + + def detail_uri_kwargs(self, bundle_or_obj): + kwargs = {} + + if isinstance(bundle_or_obj, Bundle): + kwargs['pk'] = bundle_or_obj.obj.pk + else: + kwargs['pk'] = bundle_or_obj.pk + + return kwargs + + def obj_get_list(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + return [] + + def obj_create(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + + bundle.obj = Item(**kwargs) + bundle = self.full_hydrate(bundle) + return bundle + + def obj_update(self, bundle, **kwargs): + return self.obj_create(bundle, **kwargs) + + +class AnotherExampleResource(Resource): + class Meta: + resource_name = 'another' + object_class = Item + + def detail_uri_kwargs(self, bundle_or_obj): + kwargs = {} + + if isinstance(bundle_or_obj, Bundle): + kwargs['pk'] = bundle_or_obj.obj.pk + else: + kwargs['pk'] = bundle_or_obj.pk + + return kwargs + + def obj_get_list(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + return [] + + def obj_create(self, bundle, **kwargs): + try: + raise Exception('oops') + except: + client.captureException() + + bundle.obj = Item(**kwargs) + bundle = self.full_hydrate(bundle) + return bundle + + def obj_update(self, bundle, **kwargs): + return self.obj_create(bundle, **kwargs) diff --git a/tests/contrib/django/conftest.py b/tests/contrib/django/conftest.py new file mode 100644 index 000000000..b6faff687 --- /dev/null +++ b/tests/contrib/django/conftest.py @@ -0,0 +1,39 @@ +import pytest + +import django + +from raven.contrib.django.resolver import RouteResolver + +try: + from django.conf.urls import url, include +except ImportError: + # for Django version less than 1.4 + from django.conf.urls.defaults import url, include + + +@pytest.fixture +def route_resolver(): + return RouteResolver() + + +@pytest.fixture +def urlconf(): + if django.VERSION < (1, 9): + included_url_conf = ( + url(r'^foo/bar/(?P[\w]+)', lambda x: ''), + ), '', '' + else: + included_url_conf = (( + url(r'^foo/bar/(?P[\w]+)', lambda x: ''), + ), '') + + if django.VERSION >= (2, 0): + from django.urls import path, re_path + + example_url_conf = ( + re_path(r'^api/(?P[\w_-]+)/store/$', lambda x: ''), + re_path(r'^report/', lambda x: ''), + re_path(r'^example/', include(included_url_conf)), + path('api/v2//store/', lambda x: '') + ) + return example_url_conf diff --git a/tests/contrib/django/management/__init__.py b/tests/contrib/django/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/django/management/commands/__init__.py b/tests/contrib/django/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/django/management/commands/test_raven.py b/tests/contrib/django/management/commands/test_raven.py new file mode 100644 index 000000000..b857dc9d4 --- /dev/null +++ b/tests/contrib/django/management/commands/test_raven.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import + +import django + +from django.core.management import call_command +from django.test import TestCase +from mock import patch + +from raven.contrib.django.models import client + +DJANGO_18 = django.VERSION >= (1, 8, 0) + + +class RavenCommandTest(TestCase): + @patch('raven.contrib.django.management.commands.raven.send_test_message') + def test_basic(self, mock_send_test_message): + call_command('raven', 'test') + + mock_send_test_message.assert_called_once_with( + client, { + 'tags': None, + 'data': None, + } + ) diff --git a/tests/contrib/django/middleware.py b/tests/contrib/django/middleware.py index 629a837f9..d3d9b5a63 100644 --- a/tests/contrib/django/middleware.py +++ b/tests/contrib/django/middleware.py @@ -1,13 +1,22 @@ -class BrokenRequestMiddleware(object): +try: + # Django >= 1.10 + from django.utils.deprecation import MiddlewareMixin +except ImportError: + # Not required for Django <= 1.9, see: + # https://docs.djangoproject.com/en/1.10/topics/http/middleware/#upgrading-pre-django-1-10-style-middleware + MiddlewareMixin = object + + +class BrokenRequestMiddleware(MiddlewareMixin): def process_request(self, request): raise ImportError('request') -class BrokenResponseMiddleware(object): +class BrokenResponseMiddleware(MiddlewareMixin): def process_response(self, request, response): raise ImportError('response') -class BrokenViewMiddleware(object): +class BrokenViewMiddleware(MiddlewareMixin): def process_view(self, request, func, args, kwargs): raise ImportError('view') diff --git a/tests/contrib/django/models.py b/tests/contrib/django/models.py index 06e03f226..d198cfde5 100644 --- a/tests/contrib/django/models.py +++ b/tests/contrib/django/models.py @@ -3,5 +3,5 @@ from django.db import models -class TestModel(models.Model): +class MyTestModel(models.Model): pass diff --git a/tests/contrib/django/settings.py b/tests/contrib/django/settings.py new file mode 100644 index 000000000..881e1b6f4 --- /dev/null +++ b/tests/contrib/django/settings.py @@ -0,0 +1,64 @@ + +import os + +# ---- SENTRY CONFIG ---- # +SENTRY_CLIENT = 'tests.contrib.django.tests.MockClient' +DISABLE_SENTRY_INSTRUMENTATION = True +SENTRY_ALLOW_ORIGIN = '*' + +# ---- GENERIC DJANGO SETTINGS ---- # +PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) + +SECRET_KEY = "Change this!" +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + + 'raven.contrib.django', + 'tests.contrib.django', +] + + +DATABASE_ENGINE='sqlite3' +DATABASES = { + 'default': { + 'NAME': ':memory:', + 'ENGINE': 'django.db.backends.sqlite3', + 'TEST_NAME': ':memory:', + }, +} +DATABASE_NAME = ':memory:' +TEST_DATABASE_NAME = ':memory:' +ROOT_URLCONF = 'tests.contrib.django.urls' +DEBUG = False +SITE_ID = 1 + +TEMPLATE_DEBUG = True +LANGUAGE_CODE = 'en' +LANGUAGES = (('en', 'English'),) +TEMPLATE_DIRS = [ + os.path.join(PROJECT_ROOT, 'templates'), +] +TEMPLATES = [{ + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'DIRS': TEMPLATE_DIRS, +}] +ALLOWED_HOSTS = ['*'] + +# ---- CELERY SETTINGS ---- # + +try: + import djcelery # NOQA + INSTALLED_APPS.append('djcelery') +except ImportError: + pass + +BROKER_HOST = "localhost" +BROKER_PORT = 5672 +BROKER_USER = "guest" +BROKER_PASSWORD = "guest" +BROKER_VHOST = "/" +CELERY_ALWAYS_EAGER = True diff --git a/tests/contrib/django/templates/error.html b/tests/contrib/django/templates/error.html index 7d36b9b21..9f601208a 100644 --- a/tests/contrib/django/templates/error.html +++ b/tests/contrib/django/templates/error.html @@ -1 +1,20 @@ -{% invalid template tag %} \ No newline at end of file +1 +2 +3 +4 +5 +6 +7 +8 +9 +{% invalid template tag %} +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/tests/contrib/django/test_resolver.py b/tests/contrib/django/test_resolver.py new file mode 100644 index 000000000..042715a2a --- /dev/null +++ b/tests/contrib/django/test_resolver.py @@ -0,0 +1,58 @@ +from __future__ import absolute_import + +import pytest +import django + +try: + from django.conf.urls import url, include +except ImportError: + # for Django version less than 1.4 + from django.conf.urls.defaults import url, include # NOQA + +from raven.contrib.django.resolver import RouteResolver + + +if django.VERSION < (1, 9): + included_url_conf = ( + url(r'^foo/bar/(?P[\w]+)', lambda x: ''), + ), '', '' +else: + included_url_conf = (( + url(r'^foo/bar/(?P[\w]+)', lambda x: ''), + ), '') + +example_url_conf = ( + url(r'^api/(?P[\w_-]+)/store/$', lambda x: ''), + url(r'^report/', lambda x: ''), + url(r'^example/', include(included_url_conf)), +) + + +def test_no_match(): + resolver = RouteResolver() + result = resolver.resolve('/foo/bar', example_url_conf) + assert result == '/foo/bar' + + +def test_simple_match(): # TODO: ash add matchedstring to make this test actually test something + resolver = RouteResolver() + result = resolver.resolve('/report/', example_url_conf) + assert result == '/report/' + + +def test_complex_match(): + resolver = RouteResolver() + result = resolver.resolve('/api/1234/store/', example_url_conf) + assert result == '/api/{project_id}/store/' + + +def test_included_match(): + resolver = RouteResolver() + result = resolver.resolve('/example/foo/bar/baz', example_url_conf) + assert result == '/example/foo/bar/{param}' + + +@pytest.mark.skipif(django.VERSION < (2, 0), reason="Requires Django > 2.0") +def test_newstyle_django20_urlconf(urlconf, route_resolver): + result = route_resolver.resolve('/api/v2/1234/store/', urlconf) + assert result == '/api/v2/{project_id}/store/' \ No newline at end of file diff --git a/tests/contrib/django/test_tastypie.py b/tests/contrib/django/test_tastypie.py new file mode 100644 index 000000000..a25fcf6d4 --- /dev/null +++ b/tests/contrib/django/test_tastypie.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import + +import django +import pytest + +from django.test import TestCase +from django.core.urlresolvers import get_resolver +from tastypie.test import ResourceTestCaseMixin + +from raven.contrib.django.models import client +from raven.contrib.django.resolver import RouteResolver + +DJANGO_19 = django.VERSION >= (1, 9, 0) and django.VERSION <= (1, 10, 0) + + +class TastypieTest(ResourceTestCaseMixin, TestCase): + def setUp(self): + super(TastypieTest, self).setUp() + self.path = '/api/v1/example/' + + def test_list_break(self): + self.api_client.get(self.path) + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/' + + def test_create_break(self): + self.api_client.post('/api/v1/example/') + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/' + + def test_update_break(self): + self.api_client.put('/api/v1/example/foo/', data={'name': 'bar'}) + + assert len(client.events) == 1 + event = client.events.pop(0) + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'oops' + assert 'request' in event + assert event['request']['url'] == 'http://testserver/api/v1/example/foo/' + + @pytest.mark.skipif(not DJANGO_19, reason='Django != 1.9') + def test_resolver(self): + resolver = get_resolver() + route_resolver = RouteResolver() + result = route_resolver._resolve(resolver, '/api/v1/example/') + assert result == '/api/{api_name}/{resource_name}/' diff --git a/tests/contrib/django/tests.py b/tests/contrib/django/tests.py index 7ff19ca6f..086bb4c8f 100644 --- a/tests/contrib/django/tests.py +++ b/tests/contrib/django/tests.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import with_statement -from __future__ import unicode_literals +from __future__ import absolute_import, print_function, with_statement import datetime import django @@ -10,36 +8,48 @@ import mock import pytest import re -import sys # NOQA -from exam import fixture -from celery.tests.utils import with_eager_tasks +import sys from django.conf import settings from django.core.exceptions import SuspiciousOperation -from django.core.urlresolvers import reverse from django.core.signals import got_request_exception from django.core.handlers.wsgi import WSGIRequest +from django.http import QueryDict from django.template import TemplateSyntaxError from django.test import TestCase +from django.test.client import Client as DjangoTestClient, ClientHandler as DjangoTestClientHandler +from django.utils.translation import gettext_lazy +from exam import fixture + +try: + from django.urls import reverse +except ImportError: + # For Django version less than 1.10. + from django.core.urlresolvers import reverse from raven.base import Client -from raven.contrib.django.client import DjangoClient +from raven.utils.compat import StringIO, iteritems, PY2, string_types, text_type +from raven.contrib.django.client import DjangoClient, record_sql from raven.contrib.django.celery import CeleryClient from raven.contrib.django.handlers import SentryHandler -from raven.contrib.django.models import client, get_client, sentry_exception_handler +from raven.contrib.django.models import ( + SentryDjangoHandler, client, get_client +) from raven.contrib.django.middleware.wsgi import Sentry from raven.contrib.django.templatetags.raven import sentry_public_dsn from raven.contrib.django.views import is_valid_origin +from raven.transport import HTTPTransport from raven.utils.serializer import transform -from raven.utils import six -from raven.utils.six import StringIO -from django.test.client import Client as TestClient, ClientHandler as TestClientHandler -from .models import TestModel +from .views import AppError -settings.SENTRY_CLIENT = 'tests.contrib.django.tests.TempStoreClient' +#from .models import MyTestModel DJANGO_15 = django.VERSION >= (1, 5, 0) +DJANGO_18 = django.VERSION >= (1, 8, 0) +DJANGO_110 = django.VERSION >= (1, 10, 0) + +MIDDLEWARE_ATTR = 'MIDDLEWARE' if DJANGO_110 else 'MIDDLEWARE_CLASSES' def make_request(): @@ -53,22 +63,22 @@ def make_request(): }) -class MockClientHandler(TestClientHandler): +class MockClientHandler(DjangoTestClientHandler): def __call__(self, environ, start_response=[]): - # this pretends doesnt require start_response + # this pretends doesn't require start_response return super(MockClientHandler, self).__call__(environ) class MockSentryMiddleware(Sentry): def __call__(self, environ, start_response=[]): - # this pretends doesnt require start_response + # this pretends doesn't require start_response return list(super(MockSentryMiddleware, self).__call__(environ, start_response)) -class TempStoreClient(DjangoClient): +class MockClient(DjangoClient): def __init__(self, *args, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(*args, **kwargs) + super(MockClient, self).__init__(*args, **kwargs) def send(self, **kwargs): self.events.append(kwargs) @@ -77,7 +87,7 @@ def is_enabled(self, **kwargs): return True -class DisabledTempStoreClient(TempStoreClient): +class DisabledMockClient(MockClient): def is_enabled(self, **kwargs): return False @@ -97,12 +107,12 @@ def __init__(self, **overrides): self._orig = {} def __enter__(self): - for k, v in six.iteritems(self.overrides): + for k, v in iteritems(self.overrides): self._orig[k] = getattr(settings, k, self.NotDefined) setattr(settings, k, v) def __exit__(self, exc_type, exc_value, traceback): - for k, v in six.iteritems(self._orig): + for k, v in iteritems(self._orig): if v is self.NotDefined: delattr(settings, k) else: @@ -111,230 +121,312 @@ def __exit__(self, exc_type, exc_value, traceback): class ClientProxyTest(TestCase): def test_proxy_responds_as_client(self): - self.assertEquals(get_client(), client) + assert get_client() == client - @mock.patch.object(TempStoreClient, 'captureMessage') + @mock.patch.object(MockClient, 'captureMessage') def test_basic(self, captureMessage): client.captureMessage(message='foo') captureMessage.assert_called_once_with(message='foo') +@pytest.mark.usefixtures("user_instance") class DjangoClientTest(TestCase): - ## Fixture setup/teardown + # Fixture setup/teardown urls = 'tests.contrib.django.urls' def setUp(self): self.raven = get_client() + self.handler = SentryDjangoHandler(self.raven) + self.handler.install() + self.addCleanup(self.handler.uninstall) def test_basic(self): self.raven.captureMessage(message='foo') - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Message' in event) + assert 'sentry.interfaces.Message' in event message = event['sentry.interfaces.Message'] - self.assertEquals(message['message'], 'foo') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'foo') - self.assertEquals(type(event['timestamp']), datetime.datetime) + assert message['message'] == 'foo' + assert event['level'] == logging.ERROR + assert event['message'] == 'foo' + assert isinstance(event['timestamp'], datetime.datetime) def test_signal_integration(self): try: - int('hello') - except: + int(None) + except Exception: got_request_exception.send(sender=self.__class__, request=None) else: self.fail('Expected an exception.') - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ValueError') - self.assertEquals(exc['value'], "invalid literal for int() with base 10: 'hello'") - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], "ValueError: invalid literal for int() with base 10: 'hello'") - self.assertEquals(event['culprit'], 'tests.contrib.django.tests in test_signal_integration') - + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'TypeError' + assert exc['value'], "int() argument must be a string or a number == not 'NoneType'" + assert event['level'] == logging.ERROR + assert event['message'], "TypeError: int() argument must be a string or a number == not 'NoneType'" + + @pytest.mark.skipif(sys.version_info[:2] == (2, 6), reason='Python 2.6') def test_view_exception(self): - self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) + path = reverse('sentry-raise-exc') + self.assertRaises(Exception, self.client.get, path) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'Exception') - self.assertEquals(exc['value'], 'view exception') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'Exception: view exception') - self.assertEquals(event['culprit'], 'tests.contrib.django.views in raise_exc') - - def test_user_info(self): - from django.contrib.auth.models import User - user = User(username='admin', email='admin@example.com') - user.set_password('admin') - user.save() + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'view exception' + assert event['level'] == logging.ERROR + assert event['message'] == 'Exception: view exception' + assert 'request' in event + assert event['request']['url'] == 'http://testserver{}'.format(path) + + def test_request_data_unavailable_if_request_is_read(self): + with Settings(**{MIDDLEWARE_ATTR: []}): + path = reverse('sentry-readrequest-raise-exc') + self.assertRaises( + AppError, + self.client.post, + path, + '{"a":"b"}', + content_type='application/json') + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert event['request']['data'] == '' + + def test_djangorestframeworkcompatmiddleware_fills_request_data(self): + with Settings(**{MIDDLEWARE_ATTR: [ + 'raven.contrib.django.middleware.DjangoRestFrameworkCompatMiddleware']}): + path = reverse('sentry-readrequest-raise-exc') + self.assertRaises( + AppError, + self.client.post, + path, + '{"a":"b"}', + content_type='application/json') + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert event['request']['data'] == '{"a":"b"}' - self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) + def test_capture_event_with_request_middleware(self): + path = reverse('sentry-trigger-event') + resp = self.client.get(path) + assert resp.status_code == 200 assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - assert 'sentry.interfaces.User' not in event + assert event['message'] == 'test' + assert 'request' in event + assert event['request']['url'] == 'http://testserver{}'.format(path) - assert self.client.login(username='admin', password='admin') + def test_user_info(self): + with Settings(**{MIDDLEWARE_ATTR: [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware']}): - self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) + self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) - self.assertEquals(len(self.raven.events), 1) - event = self.raven.events.pop(0) - assert 'sentry.interfaces.User' in event - user_info = event['sentry.interfaces.User'] + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert 'user' in event + + user_info = event['user'] + assert user_info == {'ip_address': '127.0.0.1'} + + assert self.client.login(username='admin', password='password') + + self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc')) + + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert 'user' in event + user_info = event['user'] + assert user_info == { + 'ip_address': '127.0.0.1', + 'username': self.user.username, + 'id': self.user.id, + 'email': self.user.email, + } + + @pytest.mark.skipif(not DJANGO_15, reason='< Django 1.5') + def test_get_user_info_abstract_user(self): + + from django.db import models + from django.http import HttpRequest + from django.contrib.auth.models import AbstractBaseUser + + class MyUser(AbstractBaseUser): + USERNAME_FIELD = 'username' + + username = models.CharField(max_length=32) + email = models.EmailField() + + user = MyUser( + username='admin', + email='admin@example.com', + id=1, + ) + + request = HttpRequest() + request.META['REMOTE_ADDR'] = '127.0.0.1' + request.user = user + user_info = self.raven.get_user_info(request) assert user_info == { + 'ip_address': '127.0.0.1', 'username': user.username, 'id': user.id, 'email': user.email, } - @pytest.mark.skipif(str('not DJANGO_15')) - def test_get_user_info_abstract_user(self): + request = HttpRequest() + request.META['REMOTE_ADDR'] = '127.0.0.1' + request.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1, 2.2.2.2' + request.user = user + user_info = self.raven.get_user_info(request) + assert user_info == { + 'ip_address': '1.1.1.1', + 'username': user.username, + 'id': user.id, + 'email': user.email, + } + + @pytest.mark.skipif(not DJANGO_110, reason='< Django 1.10') + def test_get_user_info_is_authenticated_property(self): from django.db import models + from django.http import HttpRequest from django.contrib.auth.models import AbstractBaseUser class MyUser(AbstractBaseUser): USERNAME_FIELD = 'username' - username = models.CharField(max_length=32) email = models.EmailField() + @property + def is_authenticated(self): + return True + user = MyUser( username='admin', email='admin@example.com', id=1, ) - user_info = self.raven.get_user_info(user) + + request = HttpRequest() + request.META['REMOTE_ADDR'] = '127.0.0.1' + request.user = user + user_info = self.raven.get_user_info(request) + assert user_info == { + 'ip_address': '127.0.0.1', + 'username': user.username, + 'id': user.id, + 'email': user.email, + } + + request = HttpRequest() + request.META['REMOTE_ADDR'] = '127.0.0.1' + request.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1, 2.2.2.2' + request.user = user + user_info = self.raven.get_user_info(request) assert user_info == { + 'ip_address': '1.1.1.1', 'username': user.username, 'id': user.id, 'email': user.email, } def test_request_middleware_exception(self): - with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenRequestMiddleware']): + with Settings(**{MIDDLEWARE_ATTR: ['tests.contrib.django.middleware.BrokenRequestMiddleware']}): self.assertRaises(ImportError, self.client.get, reverse('sentry-raise-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ImportError') - self.assertEquals(exc['value'], 'request') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'ImportError: request') - self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_request') + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'ImportError' + assert exc['value'] == 'request' + assert event['level'] == logging.ERROR + assert event['message'] == 'ImportError: request' def test_response_middlware_exception(self): if django.VERSION[:2] < (1, 3): return - with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenResponseMiddleware']): + with Settings(**{MIDDLEWARE_ATTR: ['tests.contrib.django.middleware.BrokenResponseMiddleware']}): self.assertRaises(ImportError, self.client.get, reverse('sentry-no-error')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ImportError') - self.assertEquals(exc['value'], 'response') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'ImportError: response') - self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_response') + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'ImportError' + assert exc['value'] == 'response' + assert event['level'] == logging.ERROR + assert event['message'] == 'ImportError: response' def test_broken_500_handler_with_middleware(self): with Settings(BREAK_THAT_500=True, INSTALLED_APPS=['raven.contrib.django']): - client = TestClient(REMOTE_ADDR='127.0.0.1') + client = DjangoTestClient(REMOTE_ADDR='127.0.0.1') client.handler = MockSentryMiddleware(MockClientHandler()) self.assertRaises(Exception, client.get, reverse('sentry-raise-exc')) - - assert len(self.raven.events) == 2 + assert len(self.raven.events) == 2 or 4 # TODO: ash remove duplicate client events event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'Exception') - self.assertEquals(exc['value'], 'view exception') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'Exception: view exception') - self.assertEquals(event['culprit'], 'tests.contrib.django.views in raise_exc') + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'Exception' + assert exc['value'] == 'view exception' + assert event['level'] == logging.ERROR + assert event['message'] == 'Exception: view exception' event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ValueError') - self.assertEquals(exc['value'], 'handler500') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'ValueError: handler500') - self.assertEquals(event['culprit'], 'tests.contrib.django.urls in handler500') + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'ValueError' + assert exc['value'] == 'handler500' + assert event['level'] == logging.ERROR + assert event['message'] == 'ValueError: handler500' def test_view_middleware_exception(self): - with Settings(MIDDLEWARE_CLASSES=['tests.contrib.django.middleware.BrokenViewMiddleware']): + with Settings(**{MIDDLEWARE_ATTR: ['tests.contrib.django.middleware.BrokenViewMiddleware']}): self.assertRaises(ImportError, self.client.get, reverse('sentry-raise-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] - self.assertEquals(exc['type'], 'ImportError') - self.assertEquals(exc['value'], 'view') - self.assertEquals(event['level'], logging.ERROR) - self.assertEquals(event['message'], 'ImportError: view') - self.assertEquals(event['culprit'], 'tests.contrib.django.middleware in process_view') - - def test_exclude_modules_view(self): - exclude_paths = self.raven.exclude_paths - self.raven.exclude_paths = ['tests.views'] - self.assertRaises(Exception, self.client.get, reverse('sentry-raise-exc-decor')) - - self.assertEquals(len(self.raven.events), 1) - event = self.raven.events.pop(0) - - self.assertEquals(event['culprit'], 'tests.contrib.django.views in raise_exc') - self.raven.exclude_paths = exclude_paths - - def test_include_modules(self): - include_paths = self.raven.include_paths - self.raven.include_paths = ['django.shortcuts'] - - self.assertRaises(Exception, self.client.get, reverse('sentry-django-exc')) - - self.assertEquals(len(self.raven.events), 1) - event = self.raven.events.pop(0) - - assert event['culprit'].startswith('django.shortcuts in ') - self.raven.include_paths = include_paths + assert 'exception' in event + exc = event['exception']['values'][-1] + assert exc['type'] == 'ImportError' + assert exc['value'] == 'view' + assert event['level'] == logging.ERROR + assert event['message'] == 'ImportError: view' + @pytest.mark.skipif(DJANGO_18, reason='Django 1.8+ not supported') def test_template_name_as_view(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertEquals(event['culprit'], 'error.html') + assert event['culprit'] == 'error.html' # def test_request_in_logging(self): # resp = self.client.get(reverse('sentry-log-request-exc')) - # self.assertEquals(resp.status_code, 200) + # assert resp.status_code == 200 - # self.assertEquals(len(self.raven.events), 1) + # assert len(self.raven.events) == 1 # event = self.raven.events.pop(0) - # self.assertEquals(event['culprit'], 'tests.contrib.django.views in logging_request_exc') - # self.assertEquals(event['data']['META']['REMOTE_ADDR'], '127.0.0.1') + # assert event['culprit'] == 'tests.contrib.django.views in logging_request_exc' + # assert event['data']['META']['REMOTE_ADDR'] == '127.0.0.1' # TODO: Python bug #10805 - @pytest.mark.skipif(str('six.PY3')) + @pytest.mark.skipif(not PY2, reason='Python 2') def test_record_none_exc_info(self): # sys.exc_info can return (None, None, None) if no exception is being # handled anywhere on the stack. See: @@ -351,63 +443,82 @@ def test_record_none_exc_info(self): handler = SentryHandler() handler.emit(record) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertEquals(event['message'], 'test') + assert event['message'] == 'test' def test_404_middleware(self): - with Settings(MIDDLEWARE_CLASSES=['raven.contrib.django.middleware.Sentry404CatchMiddleware']): - resp = self.client.get('/non-existant-page') - self.assertEquals(resp.status_code, 404) + with Settings(**{MIDDLEWARE_ATTR: ['raven.contrib.django.middleware.Sentry404CatchMiddleware']}): + resp = self.client.get('/non-existent-page') + assert resp.status_code == 404 - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1, [e['message'] for e in self.raven.events] event = self.raven.events.pop(0) - self.assertEquals(event['level'], logging.INFO) - self.assertEquals(event['logger'], 'http404') + assert event['level'] == logging.INFO + assert event['logger'] == 'http404' - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] - self.assertEquals(http['url'], 'http://testserver/non-existant-page') - self.assertEquals(http['method'], 'GET') - self.assertEquals(http['query_string'], '') - self.assertEquals(http['data'], None) + assert 'request' in event + http = event['request'] + assert http['url'] == 'http://testserver/non-existent-page' + assert http['method'] == 'GET' + assert http['query_string'] == '' + assert http['data'] is None def test_404_middleware_when_disabled(self): extra_settings = { - 'MIDDLEWARE_CLASSES': ['raven.contrib.django.middleware.Sentry404CatchMiddleware'], - 'SENTRY_CLIENT': 'tests.contrib.django.tests.DisabledTempStoreClient', + MIDDLEWARE_ATTR: ['raven.contrib.django.middleware.Sentry404CatchMiddleware'], + 'SENTRY_CLIENT': 'tests.contrib.django.tests.DisabledMockClient', } with Settings(**extra_settings): - resp = self.client.get('/non-existant-page') + resp = self.client.get('/non-existent-page') assert resp.status_code == 404 assert self.raven.events == [] + def test_invalid_client(self): + extra_settings = { + 'SENTRY_CLIENT': 'raven.contrib.django.DjangoClient', # default + } + # Should return fallback client (MockClient) + client = get_client('nonexistent.and.invalid') + + # client should be valid, and the same as with the next call. + assert client is get_client() + + with Settings(**extra_settings): + assert isinstance(get_client(), DjangoClient) + + def test_transport_specification(self): + extra_settings = { + 'SENTRY_TRANSPORT': 'raven.transport.HTTPTransport', + 'SENTRY_DSN': 'http://public:secret@example.com/1', + } + with Settings(**extra_settings): + client = get_client(reset=True) + assert type(client.remote.get_transport()) is HTTPTransport + def test_response_error_id_middleware(self): # TODO: test with 500s - with Settings(MIDDLEWARE_CLASSES=['raven.contrib.django.middleware.SentryResponseErrorIdMiddleware', - 'raven.contrib.django.middleware.Sentry404CatchMiddleware']): - resp = self.client.get('/non-existant-page') - self.assertEquals(resp.status_code, 404) + with Settings(**{MIDDLEWARE_ATTR: [ + 'raven.contrib.django.middleware.SentryResponseErrorIdMiddleware', + 'raven.contrib.django.middleware.Sentry404CatchMiddleware']}): + resp = self.client.get('/non-existent-page') + assert resp.status_code == 404 headers = dict(resp.items()) - self.assertTrue('X-Sentry-ID' in headers) - self.assertEquals(len(self.raven.events), 1) + assert 'X-Sentry-ID' in headers + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) assert event['event_id'] == headers['X-Sentry-ID'] def test_get_client(self): - self.assertEquals(get_client(), get_client()) - self.assertEquals(get_client('raven.base.Client').__class__, Client) - self.assertEquals(get_client(), self.raven) + assert get_client() == get_client() + assert get_client('raven.base.Client').__class__ == Client + assert get_client() == self.raven - self.assertEquals(get_client('%s.%s' % (self.raven.__class__.__module__, self.raven.__class__.__name__)), - self.raven) - self.assertEquals(get_client(), self.raven) + assert get_client('%s.%s' % (type(self.raven).__module__, type(self.raven).__name__)) == self.raven + assert get_client() == self.raven - # This test only applies to Django 1.3+ def test_raw_post_data_partial_read(self): - if django.VERSION[:2] < (1, 3): - return v = '{"foo": "bar"}' request = make_request() request.environ.update({ @@ -419,47 +530,59 @@ def test_raw_post_data_partial_read(self): self.raven.captureMessage(message='foo', request=request) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + + assert 'request' in event + http = event['request'] + assert http['method'] == 'POST' + assert http['data'] == '' + + def test_read_post_data(self): + request = make_request() + request.POST = QueryDict("foo=bar&ham=spam") + request.read(1) + + self.raven.captureMessage(message='foo', request=request) + + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] - self.assertEquals(http['method'], 'POST') - self.assertEquals(http['data'], '') + assert 'request' in event + http = event['request'] + assert http['method'] == 'POST' + assert http['data'] == {'foo': 'bar', 'ham': 'spam'} - # This test only applies to Django 1.3+ def test_request_capture(self): - if django.VERSION[:2] < (1, 3): - return request = make_request() request.read(1) self.raven.captureMessage(message='foo', request=request) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] - self.assertEquals(http['method'], 'POST') - self.assertEquals(http['data'], '') - self.assertTrue('headers' in http) + assert 'request' in event + http = event['request'] + assert http['method'] == 'POST' + assert http['data'] == '' + assert 'headers' in http headers = http['headers'] - self.assertTrue('Content-Type' in headers, headers.keys()) - self.assertEquals(headers['Content-Type'], 'text/html') + assert 'Content-Type' in headers, headers.keys() + assert headers['Content-Type'] == 'text/html' env = http['env'] - self.assertTrue('SERVER_NAME' in env, env.keys()) - self.assertEquals(env['SERVER_NAME'], 'testserver') - self.assertTrue('SERVER_PORT' in env, env.keys()) - self.assertEquals(env['SERVER_PORT'], '80') + assert 'SERVER_NAME' in env, env.keys() + assert env['SERVER_NAME'] == 'testserver' + assert 'SERVER_PORT' in env, env.keys() + assert env['SERVER_PORT'] == '80' def test_marks_django_frames_correctly(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - frames = event['sentry.interfaces.Stacktrace']['frames'] + frames = event['exception']['values'][-1]['stacktrace']['frames'] for frame in frames: if frame['module'].startswith('django.'): assert frame.get('in_app') is False @@ -467,7 +590,7 @@ def test_marks_django_frames_correctly(self): def test_adds_site_to_tags(self): self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) tags = event['tags'] @@ -475,10 +598,10 @@ def test_adds_site_to_tags(self): assert tags['site'] == 'example.com' def test_adds_site_to_tags_fallback(self): - with Settings(SITE_ID=12345): # nonexistant site, should fallback to SITE_ID + with Settings(SITE_ID=12345): # nonexistent site, should fallback to SITE_ID self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) tags = event['tags'] @@ -489,7 +612,7 @@ def test_settings_site_overrides_contrib(self): self.raven.site = 'FOO' self.assertRaises(TemplateSyntaxError, self.client.get, reverse('sentry-template-exc')) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) tags = event['tags'] @@ -503,8 +626,8 @@ def test_suspicious_operation_in_build_absolute_uri(self, build_absolute_uri): request.META['HTTP_HOST'] = 'example.com' result = self.raven.get_data_from_request(request) build_absolute_uri.assert_called_once_with() - assert 'sentry.interfaces.Http' in result - assert result['sentry.interfaces.Http']['url'] == 'http://example.com/' + assert 'request' in result + assert result['request']['url'] == 'http://example.com/' class DjangoTemplateTagTest(TestCase): @@ -542,19 +665,34 @@ def test_request_kwarg(self): }) }) - self.assertEquals(len(self.raven.events), 1) + assert len(self.raven.events) == 1 + event = self.raven.events.pop(0) + assert 'request' in event + http = event['request'] + assert http['method'] == 'POST' + + def test_tags(self): + tags = {'tag1': 'test'} + handler = SentryHandler(tags=tags) + + logger = self.logger + logger.handlers = [] + logger.addHandler(handler) + + logger.error('This is a test error') + + assert len(self.raven.events) == 1 event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] - self.assertEquals(http['method'], 'POST') + assert 'tags' in event + # event['tags'] also contains some other data, like 'site' + assert 'tag1' in event['tags'] + assert event['tags']['tag1'] == tags['tag1'] class CeleryIsolatedClientTest(TestCase): def setUp(self): self.client = CeleryClient( - servers=['http://example.com'], - public_key='public', - secret_key='secret', + dsn='sync+http://public:secret@example.com/1' ) @mock.patch('raven.contrib.django.celery.send_raw') @@ -571,145 +709,110 @@ def test_without_eager(self, send_raw): """ self.client.captureMessage(message='test') - self.assertEquals(send_raw.delay.call_count, 1) - - @with_eager_tasks - @mock.patch('raven.contrib.django.DjangoClient.send_encoded') - def test_with_eager(self, send_encoded): - """ - Integration test to ensure it propagates all the way down - and calls the parent client's send_encoded method. - """ - self.client.captureMessage(message='test') - - self.assertEquals(send_encoded.call_count, 1) - - -class CeleryIntegratedClientTest(TestCase): - def setUp(self): - self.client = CeleryClient() - - @mock.patch('raven.contrib.django.celery.send_raw_integrated') - def test_send_encoded(self, send_raw): - with Settings(INSTALLED_APPS=tuple(settings.INSTALLED_APPS) + ('sentry',)): - self.client.send_integrated('foo') - - send_raw.delay.assert_called_once_with('foo') - - @mock.patch('raven.contrib.django.celery.send_raw_integrated') - def test_without_eager(self, send_raw): - """ - Integration test to ensure it propagates all the way down - and calls delay on the task. - """ - with Settings(INSTALLED_APPS=tuple(settings.INSTALLED_APPS) + ('sentry',)): - self.client.captureMessage(message='test') - - self.assertEquals(send_raw.delay.call_count, 1) - - @with_eager_tasks - @mock.patch('raven.contrib.django.DjangoClient.send_encoded') - def test_with_eager(self, send_encoded): - """ - Integration test to ensure it propagates all the way down - and calls the parent client's send_encoded method. - """ - self.client.captureMessage(message='test') - - self.assertEquals(send_encoded.call_count, 1) + assert send_raw.delay.call_count == 1 class IsValidOriginTestCase(TestCase): def test_setting_empty(self): with Settings(SENTRY_ALLOW_ORIGIN=None): - self.assertFalse(is_valid_origin('http://example.com')) + assert not is_valid_origin('http://example.com') def test_setting_all(self): with Settings(SENTRY_ALLOW_ORIGIN='*'): - self.assertTrue(is_valid_origin('http://example.com')) + assert is_valid_origin('http://example.com') def test_setting_uri(self): with Settings(SENTRY_ALLOW_ORIGIN=['http://example.com']): - self.assertTrue(is_valid_origin('http://example.com')) + assert is_valid_origin('http://example.com') def test_setting_regexp(self): with Settings(SENTRY_ALLOW_ORIGIN=[re.compile('https?\://(.*\.)?example\.com')]): - self.assertTrue(is_valid_origin('http://example.com')) + assert is_valid_origin('http://example.com') class ReportViewTest(TestCase): urls = 'raven.contrib.django.urls' def setUp(self): - self.path = reverse('raven-report') + super(ReportViewTest, self).setUp() + self.path = reverse('raven-report', args=['1'], urlconf=self.urls) @mock.patch('raven.contrib.django.views.is_valid_origin') def test_calls_is_valid_origin_with_header(self, is_valid_origin): - self.client.post(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + self.client.post(self.path, HTTP_ORIGIN='http://example.com') is_valid_origin.assert_called_once_with('http://example.com') @mock.patch('raven.contrib.django.views.is_valid_origin') def test_calls_is_valid_origin_with_header_as_get(self, is_valid_origin): - self.client.get(self.path, HTTP_ORIGIN='http://example.com') + with self.settings(ROOT_URLCONF=self.urls): + self.client.get(self.path, HTTP_ORIGIN='http://example.com') is_valid_origin.assert_called_once_with('http://example.com') @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=False)) def test_fails_on_invalid_origin(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') - self.assertEquals(resp.status_code, 403) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') + assert resp.status_code == 403 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_options_call_sends_headers(self): - resp = self.client.options(self.path, HTTP_ORIGIN='http://example.com') - self.assertEquals(resp.status_code, 200) - self.assertEquals(resp['Access-Control-Allow-Origin'], 'http://example.com') - self.assertEquals(resp['Access-Control-Allow-Methods'], 'GET, POST, OPTIONS') + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.options(self.path, HTTP_ORIGIN='http://example.com') + assert resp.status_code == 200 + assert resp['Access-Control-Allow-Origin'] == 'http://example.com' + assert resp['Access-Control-Allow-Methods'], 'GET, POST == OPTIONS' @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_missing_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') - self.assertEquals(resp.status_code, 400) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com') + assert resp.status_code == 400 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_invalid_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - data='[1', content_type='application/octet-stream') - self.assertEquals(resp.status_code, 400) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + data='[1', content_type='application/octet-stream') + assert resp.status_code == 400 @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_data(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - data='{}', content_type='application/octet-stream') - self.assertEquals(resp.status_code, 200) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + data='{}', content_type='application/octet-stream') + assert resp.status_code == 200 event = client.events.pop(0) - self.assertEquals(event, {'auth_header': None}) + assert event == {'auth_header': None} @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_authorization_header(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - HTTP_AUTHORIZATION='Sentry foo/bar', data='{}', content_type='application/octet-stream') - self.assertEquals(resp.status_code, 200) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + HTTP_AUTHORIZATION='Sentry foo/bar', data='{}', content_type='application/octet-stream') + assert resp.status_code == 200 event = client.events.pop(0) - self.assertEquals(event, {'auth_header': 'Sentry foo/bar'}) + assert event == {'auth_header': 'Sentry foo/bar'} @mock.patch('raven.contrib.django.views.is_valid_origin', mock.Mock(return_value=True)) def test_sends_x_sentry_auth_header(self): - resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', - HTTP_X_SENTRY_AUTH='Sentry foo/bar', data='{}', - content_type='application/octet-stream') - self.assertEquals(resp.status_code, 200) + with self.settings(ROOT_URLCONF=self.urls): + resp = self.client.post(self.path, HTTP_ORIGIN='http://example.com', + HTTP_X_SENTRY_AUTH='Sentry foo/bar', data='{}', + content_type='application/octet-stream') + assert resp.status_code == 200 event = client.events.pop(0) - self.assertEquals(event, {'auth_header': 'Sentry foo/bar'}) + assert event == {'auth_header': 'Sentry foo/bar'} class PromiseSerializerTestCase(TestCase): def test_basic(self): from django.utils.functional import lazy - obj = lazy(lambda: 'bar', six.text_type)() + obj = lazy(lambda: 'bar', text_type)() res = transform(obj) - expected = "'bar'" if six.PY3 else "u'bar'" - self.assertEquals(res, expected) + expected = "'bar'" if not PY2 else "u'bar'" + assert res == expected def test_handles_gettext_lazy(self): from django.utils.functional import lazy @@ -717,31 +820,37 @@ def test_handles_gettext_lazy(self): def fake_gettext(to_translate): return 'Igpay Atinlay' - fake_gettext_lazy = lazy(fake_gettext, six.text_type) + fake_gettext_lazy = lazy(fake_gettext, text_type) result = transform(fake_gettext_lazy("something")) - self.assertTrue(isinstance(result, six.string_types)) - expected = "'Igpay Atinlay'" if six.PY3 else "u'Igpay Atinlay'" - self.assertEquals(result, expected) + assert isinstance(result, string_types) + expected = "'Igpay Atinlay'" if not PY2 else "u'Igpay Atinlay'" + assert result == expected + def test_real_gettext_lazy(self): + d = {text_type('lazy_translation'): gettext_lazy(text_type('testing'))} + key = "'lazy_translation'" if not PY2 else "u'lazy_translation'" + value = "'testing'" if not PY2 else "u'testing'" + assert transform(d) == {key: value} -class ModelInstanceSerializerTestCase(TestCase): - def test_basic(self): - instance = TestModel() + +class ModelInstanceSerializerTestCase(object): + def test_basic(self, mytest_model): + instance = mytest_model() result = transform(instance) - self.assertTrue(isinstance(result, six.string_types)) - self.assertEquals(result, '') + assert isinstance(result, string_types) + assert result == '' -class QuerySetSerializerTestCase(TestCase): - def test_basic(self): +class QuerySetSerializerTestCase(object): + def test_basic(self, mytest_model): from django.db.models.query import QuerySet - obj = QuerySet(model=TestModel) + obj = QuerySet(model=mytest_model) result = transform(obj) - self.assertTrue(isinstance(result, six.string_types)) - self.assertEquals(result, '') + assert isinstance(result, string_types) + assert result == '' class SentryExceptionHandlerTest(TestCase): @@ -753,47 +862,69 @@ def request(self): def exc_info(self): return (ValueError, ValueError('lol world'), None) - @mock.patch.object(TempStoreClient, 'captureException') + def setUp(self): + super(SentryExceptionHandlerTest, self).setUp() + self.client = get_client() + self.handler = SentryDjangoHandler(self.client) + + @mock.patch.object(MockClient, 'captureException') @mock.patch('sys.exc_info') def test_does_capture_exception(self, exc_info, captureException): exc_info.return_value = self.exc_info - sentry_exception_handler(request=self.request) + self.handler.exception_handler(request=self.request) captureException.assert_called_once_with(exc_info=self.exc_info, request=self.request) - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_does_exclude_filtered_types(self, get_option, exc_info, captureException): + def test_does_exclude_filtered_types(self, exc_info, mock_send): exc_info.return_value = self.exc_info - get_option.return_value = ['ValueError'] + try: + self.client.ignore_exceptions = set(['ValueError']) - sentry_exception_handler(request=self.request) + self.handler.exception_handler(request=self.request) + finally: + self.client.ignore_exceptions.clear() - assert not captureException.called + assert not mock_send.called - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_ignore_exceptions_with_expression_match(self, get_option, exc_info, captureException): + def test_ignore_exceptions_with_expression_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info - get_option.return_value = ['builtins.*'] - if not six.PY3: - get_option.return_value = ['exceptions.*'] - sentry_exception_handler(request=self.request) + try: + if not PY2: + self.client.ignore_exceptions = set(['builtins.*']) + else: + self.client.ignore_exceptions = set(['exceptions.*']) + self.handler.exception_handler(request=self.request) + finally: + self.client.ignore_exceptions.clear() - assert not captureException.called + assert not mock_send.called - @mock.patch.object(TempStoreClient, 'captureException') + @mock.patch.object(MockClient, 'send') @mock.patch('sys.exc_info') - @mock.patch('raven.contrib.django.models.get_option') - def test_ignore_exceptions_with_module_match(self, get_option, exc_info, captureException): + def test_ignore_exceptions_with_module_match(self, exc_info, mock_send): exc_info.return_value = self.exc_info - get_option.return_value = ['builtins.ValueError'] - if not six.PY3: - get_option.return_value = ['exceptions.ValueError'] - sentry_exception_handler(request=self.request) + try: + if not PY2: + self.client.ignore_exceptions = set(['builtins.ValueError']) + else: + self.client.ignore_exceptions = set(['exceptions.ValueError']) + self.handler.exception_handler(request=self.request) + finally: + self.client.ignore_exceptions.clear() + + assert not mock_send.called + - assert not captureException.called +class SQLHookTestCase(TestCase): + def test_wrong_params(self): + query = 'SELECT COUNT(*) FROM mytestmodel WHERE id = %s' + args = ['foobar', 42] + record_sql(None, None, None, None, query, args) + crumbs = get_client().context.breadcrumbs.get_buffer() + self.assertEqual(crumbs[-1]['message'], query) diff --git a/tests/contrib/django/urls.py b/tests/contrib/django/urls.py index 954084db5..900e824fd 100644 --- a/tests/contrib/django/urls.py +++ b/tests/contrib/django/urls.py @@ -2,31 +2,50 @@ from django.conf import settings try: - from django.conf.urls import url, patterns + from django.conf.urls import url, include except ImportError: - # for Django version less then 1.4 - from django.conf.urls.defaults import url, patterns # NOQA + # for Django version less than 1.4 + from django.conf.urls.defaults import url, include # NOQA from django.http import HttpResponse +from tests.contrib.django import views -def handler404(request): + +def handler404(request, exception=None): return HttpResponse('', status=404) -def handler500(request): +def handler500(request, exception=None): if getattr(settings, 'BREAK_THAT_500', False): raise ValueError('handler500') return HttpResponse('', status=500) -urlpatterns = patterns('', - url(r'^no-error$', 'tests.contrib.django.views.no_error', name='sentry-no-error'), - url(r'^fake-login$', 'tests.contrib.django.views.fake_login', name='sentry-fake-login'), - url(r'^trigger-500$', 'tests.contrib.django.views.raise_exc', name='sentry-raise-exc'), - url(r'^trigger-500-ioerror$', 'tests.contrib.django.views.raise_ioerror', name='sentry-raise-ioerror'), - url(r'^trigger-500-decorated$', 'tests.contrib.django.views.decorated_raise_exc', name='sentry-raise-exc-decor'), - url(r'^trigger-500-django$', 'tests.contrib.django.views.django_exc', name='sentry-django-exc'), - url(r'^trigger-500-template$', 'tests.contrib.django.views.template_exc', name='sentry-template-exc'), - url(r'^trigger-500-log-request$', 'tests.contrib.django.views.logging_request_exc', name='sentry-log-request-exc'), +urlpatterns = ( + url(r'^no-error$', views.no_error, name='sentry-no-error'), + url(r'^fake-login$', views.fake_login, name='sentry-fake-login'), + url(r'^trigger-500$', views.raise_exc, name='sentry-raise-exc'), + url(r'^trigger-500-readrequest$', views.read_request_and_raise_exc, name='sentry-readrequest-raise-exc'), + url(r'^trigger-500-ioerror$', views.raise_ioerror, name='sentry-raise-ioerror'), + url(r'^trigger-500-decorated$', views.decorated_raise_exc, name='sentry-raise-exc-decor'), + url(r'^trigger-500-django$', views.django_exc, name='sentry-django-exc'), + url(r'^trigger-500-template$', views.template_exc, name='sentry-template-exc'), + url(r'^trigger-500-log-request$', views.logging_request_exc, name='sentry-log-request-exc'), + url(r'^trigger-event$', views.capture_event, name='sentry-trigger-event'), ) + +try: + from tastypie.api import Api +except ImportError: + pass +else: + from tests.contrib.django.api import ExampleResource, AnotherExampleResource + + v1_api = Api(api_name='v1') + v1_api.register(ExampleResource()) + v1_api.register(AnotherExampleResource()) + + urlpatterns += ( + url(r'^api/', include(v1_api.urls)), + ) diff --git a/tests/contrib/django/views.py b/tests/contrib/django/views.py index 72a37af0e..28d0f8957 100644 --- a/tests/contrib/django/views.py +++ b/tests/contrib/django/views.py @@ -2,30 +2,48 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404, render_to_response +from raven.contrib.django.models import client import logging + +class AppError(Exception): + pass + + def no_error(request): return HttpResponse('') + def fake_login(request): return HttpResponse('') + def django_exc(request): return get_object_or_404(Exception, pk=1) + def raise_exc(request): raise Exception(request.GET.get('message', 'view exception')) + +def read_request_and_raise_exc(request): + request.read() + raise AppError() + + def raise_ioerror(request): raise IOError(request.GET.get('message', 'view exception')) + def decorated_raise_exc(request): return raise_exc(request) + def template_exc(request): return render_to_response('error.html') + def logging_request_exc(request): logger = logging.getLogger(__name__) try: @@ -33,3 +51,8 @@ def logging_request_exc(request): except Exception as e: logger.error(e, exc_info=True, extra={'request': request}) return HttpResponse('') + + +def capture_event(request): + client.captureMessage('test') + return HttpResponse('') diff --git a/tests/contrib/flask/tests.py b/tests/contrib/flask/tests.py index 16aa0d1c6..e7a46f2a6 100644 --- a/tests/contrib/flask/tests.py +++ b/tests/contrib/flask/tests.py @@ -1,49 +1,65 @@ import logging +import pytest from exam import before, fixture -from mock import patch +from flask import Flask, current_app, g +try: + from flask.ext.login import LoginManager, AnonymousUserMixin, login_user +except ImportError: + from flask_login import LoginManager, AnonymousUserMixin, login_user +from mock import patch, Mock -from flask import Flask, current_app -from flask.ext.login import LoginManager, AnonymousUserMixin, login_user - -from raven.base import Client -from raven.contrib.flask import Sentry -from raven.utils.testutils import TestCase - - -class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): - self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) - - def is_enabled(self): - return True - - def send(self, **kwargs): - self.events.append(kwargs) +from raven.contrib.flask import Sentry, logging_configured +from raven.handlers.logging import SentryHandler +from raven.utils.testutils import InMemoryClient, TestCase class User(AnonymousUserMixin): is_active = lambda x: True is_authenticated = lambda x: True get_id = lambda x: 1 + name = 'TestUser' + + def to_dict(self): + return { + 'id': self.get_id(), + 'name': self.name + } -def create_app(): +def create_app(ignore_exceptions=None, debug=False, **config): import os app = Flask(__name__) app.config['SECRET_KEY'] = os.urandom(40) + for key, value in config.items(): + app.config[key] = value + + app.debug = debug + + if ignore_exceptions: + app.config['RAVEN_IGNORE_EXCEPTIONS'] = ignore_exceptions @app.route('/an-error/', methods=['GET', 'POST']) def an_error(): raise ValueError('hello world') + @app.route('/log-an-error/', methods=['GET']) + def log_an_error(): + app.logger.error('Log an error') + return 'Hello' + + @app.route('/log-a-generic-error/', methods=['GET']) + def log_a_generic_error(): + logger = logging.getLogger('random-logger') + logger.error('Log an error') + return 'Hello' + @app.route('/capture/', methods=['GET', 'POST']) def capture_exception(): try: raise ValueError('Boom') - except: + except Exception: current_app.extensions['sentry'].captureException() return 'Hello' @@ -52,10 +68,10 @@ def capture_message(): current_app.extensions['sentry'].captureMessage('Interesting') return 'World' - @app.route('/an-error-logged-in/', methods=['GET', 'POST']) + @app.route('/login/', methods=['GET', 'POST']) def login(): login_user(User()) - raise ValueError('hello world') + return "hello world" return app @@ -81,9 +97,15 @@ def client(self): @before def bind_sentry(self): - self.raven = TempStoreClient() + self.raven = InMemoryClient() self.middleware = Sentry(self.app, client=self.raven) + def make_client_and_raven(self, logging=False, *args, **kwargs): + app = create_app(*args, **kwargs) + raven = InMemoryClient() + Sentry(app, logging=logging, client=raven) + return app.test_client(), raven, app + class FlaskTest(BaseTest): def test_does_add_to_extensions(self): @@ -97,13 +119,19 @@ def test_error_handler(self): event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] + assert 'exception' in event + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'hello world') self.assertEquals(event['level'], logging.ERROR) self.assertEquals(event['message'], 'ValueError: hello world') - self.assertEquals(event['culprit'], 'tests.contrib.flask.tests in an_error') + + def test_capture_plus_logging(self): + client, raven, app = self.make_client_and_raven(debug=False, logging=True) + client.get('/an-error/') + client.get('/log-an-error/') + client.get('/log-a-generic-error/') + assert len(raven.events) == 3 def test_get(self): response = self.client.get('/an-error/?foo=bar') @@ -112,18 +140,14 @@ def test_get(self): event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] + assert 'request' in event + http = event['request'] self.assertEquals(http['url'], 'http://localhost/an-error/') self.assertEquals(http['query_string'], 'foo=bar') self.assertEquals(http['method'], 'GET') self.assertEquals(http['data'], {}) self.assertTrue('headers' in http) headers = http['headers'] - self.assertTrue('Content-Length' in headers, headers.keys()) - self.assertEquals(headers['Content-Length'], '0') - self.assertTrue('Content-Type' in headers, headers.keys()) - self.assertEquals(headers['Content-Type'], '') self.assertTrue('Host' in headers, headers.keys()) self.assertEquals(headers['Host'], 'localhost') env = http['env'] @@ -139,8 +163,8 @@ def test_post(self): event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] + assert 'request' in event + http = event['request'] self.assertEquals(http['url'], 'http://localhost/an-error/') self.assertEquals(http['query_string'], 'biz=baz') self.assertEquals(http['method'], 'POST') @@ -165,10 +189,11 @@ def test_captureException_captures_http(self): self.assertEquals(len(self.raven.events), 1) event = self.raven.events.pop(0) + self.assertEquals(event['event_id'], response.headers['X-Sentry-ID']) assert event['message'] == 'ValueError: Boom' - assert 'sentry.interfaces.Http' in event - assert 'sentry.interfaces.Exception' in event + assert 'request' in event + assert 'exception' in event def test_captureMessage_captures_http(self): response = self.client.get('/message/?foo=bar') @@ -176,9 +201,10 @@ def test_captureMessage_captures_http(self): self.assertEquals(len(self.raven.events), 1) event = self.raven.events.pop(0) + self.assertEquals(event['event_id'], response.headers['X-Sentry-ID']) - self.assertTrue('sentry.interfaces.Message' in event) - self.assertTrue('sentry.interfaces.Http' in event) + assert 'sentry.interfaces.Message' in event + assert 'request' in event @patch('flask.wrappers.RequestBase._load_form_data') def test_get_data_handles_disconnected_client(self, lfd): @@ -188,19 +214,104 @@ def test_get_data_handles_disconnected_client(self, lfd): event = self.raven.events.pop(0) - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] + assert 'request' in event + http = event['request'] self.assertEqual({}, http.get('data')) + def test_wrap_wsgi_status(self): + _, _, app_debug = self.make_client_and_raven(debug=True) + self.assertFalse(app_debug.extensions['sentry'].wrap_wsgi) + + _, _, app_ndebug = self.make_client_and_raven(debug=False) + self.assertTrue(app_ndebug.extensions['sentry'].wrap_wsgi) + + def test_captureException_sets_last_event_id(self): + with self.app.test_request_context('/'): + try: + raise ValueError + except Exception: + self.middleware.captureException() + else: + self.fail() + + event_id = self.raven.events.pop(0)['event_id'] + assert self.middleware.last_event_id == event_id + assert g.sentry_event_id == event_id + + def test_captureMessage_sets_last_event_id(self): + with self.app.test_request_context('/'): + self.middleware.captureMessage('foo') + + event_id = self.raven.events.pop(0)['event_id'] + assert self.middleware.last_event_id == event_id + assert g.sentry_event_id == event_id + + + @pytest.mark.skip(reason="Fails with the current implementation if the logger is already configured") + def test_logging_setup_with_exclusion_list(self): + app = Flask(__name__) + raven = InMemoryClient() + Sentry(app, client=raven, logging=True, logging_exclusions=("excluded_logger",)) + + + excluded_logger = logging.getLogger("excluded_logger") + self.assertFalse(excluded_logger.propagate) + + some_other_logger = logging.getLogger("some_other_logger") + self.assertTrue(some_other_logger.propagate) + + def test_logging_setup_signal(self): + app = Flask(__name__) + + mock_handler = Mock() + + def receiver(sender, *args, **kwargs): + self.assertIn("exclude", kwargs) + mock_handler(*args, **kwargs) + + logging_configured.connect(receiver) + raven = InMemoryClient() + + Sentry( + app, client=raven, logging=True, + logging_exclusions=("excluded_logger",)) + + mock_handler.assert_called() + + def test_check_client_type(self): + self.assertRaises(TypeError, lambda _: Sentry(self.app, "oops, I'm putting my DSN instead")) + + def test_uses_dsn(self): + app = Flask(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.remote.base_url == 'http://example.com' + + def test_binds_default_include_paths(self): + app = Flask(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set([app.import_name]) + + def test_overrides_default_include_paths(self): + app = Flask(__name__) + app.config['SENTRY_CONFIG'] = {'include_paths': ['foo.bar']} + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set(['foo.bar']) + class FlaskLoginTest(BaseTest): + @fixture + def app(self): + return create_app(SENTRY_USER_ATTRS=['name']) + @before def setup_login(self): self.login_manager = init_login(self.app) def test_user(self): - self.client.get('/an-error-logged-in/') + self.client.get('/login/') + self.client.get('/an-error/', environ_overrides={'REMOTE_ADDR': '127.0.0.1'}) event = self.raven.events.pop(0) assert event['message'] == 'ValueError: hello world' - assert 'sentry.interfaces.Http' in event - assert 'sentry.interfaces.User' in event + assert 'request' in event + assert 'user' in event + self.assertDictEqual(event['user'], dict({'ip_address': '127.0.0.1'}, **User().to_dict())) diff --git a/tests/contrib/pylons/tests.py b/tests/contrib/pylons/tests.py index eb59869a7..29b659c47 100644 --- a/tests/contrib/pylons/tests.py +++ b/tests/contrib/pylons/tests.py @@ -12,8 +12,6 @@ def setUp(self): def test_init(self): config = { - 'sentry.servers': 'http://localhost/api/store', - 'sentry.public_key': 'p' * 32, - 'sentry.secret_key': 's' * 32, + 'sentry.dsn': 'http://public:secret@example.com/1', } middleware = Sentry(self.app, config) diff --git a/tests/contrib/sanic/__init__.py b/tests/contrib/sanic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/sanic/tests.py b/tests/contrib/sanic/tests.py new file mode 100644 index 000000000..52ef59847 --- /dev/null +++ b/tests/contrib/sanic/tests.py @@ -0,0 +1,240 @@ +import json +import logging +import pytest +import sys + +from exam import before, fixture +from mock import patch, Mock + +from raven.contrib.sanic import Sentry, logging_configured +from raven.handlers.logging import SentryHandler +from raven.utils.testutils import InMemoryClient, TestCase + + +if sys.version_info >= (3, 5): + from sanic import Sanic, response + +# When using test_client, Sanic will run the server at 127.0.0.1:42101. +# For ease of reading, let's establish that as a constant. +BASE_URL = '127.0.0.1:42101' + +def create_app(ignore_exceptions=None, debug=False, **config): + import os + app = Sanic(__name__) + for key, value in config.items(): + app.config[key] = value + + app.debug = debug + + if ignore_exceptions: + app.config['RAVEN_IGNORE_EXCEPTIONS'] = ignore_exceptions + + @app.route('/an-error/', methods=['GET', 'POST']) + def an_error(request): + raise ValueError('hello world') + + @app.route('/log-an-error/', methods=['GET']) + def log_an_error(request): + logger = logging.getLogger('random-logger') + logger.error('Log an error') + return response.text('Hello') + + @app.route('/capture/', methods=['GET', 'POST']) + def capture_exception(request): + try: + raise ValueError('Boom') + except Exception: + request.app.extensions['sentry'].captureException() + return response.text('Hello') + + @app.route('/message/', methods=['GET', 'POST']) + def capture_message(request): + request.app.extensions['sentry'].captureMessage('Interesting') + return response.text('World') + + return app + +@pytest.mark.skipif(sys.version_info < (3,5), reason="Requires Python 3.5+.") +class BaseTest(TestCase): + @fixture + def app(self): + return create_app() + + @fixture + def client(self): + return self.app.test_client + + @before + def bind_sentry(self): + self.raven = InMemoryClient() + self.middleware = Sentry(self.app, client=self.raven) + + def make_client_and_raven(self, logging=False, *args, **kwargs): + app = create_app(*args, **kwargs) + raven = InMemoryClient() + Sentry(app, logging=logging, client=raven) + return app.test_client, raven, app + +@pytest.mark.skipif(sys.version_info < (3,5), reason="Requires Python 3.5+.") +class SanicTest(BaseTest): + def test_does_add_to_extensions(self): + self.assertIn('sentry', self.app.extensions) + self.assertEquals(self.app.extensions['sentry'], self.middleware) + + def test_error_handler(self): + request, response = self.client.get('/an-error/') + self.assertEquals(response.status, 500) + self.assertEquals(len(self.raven.events), 1) + + event = self.raven.events.pop(0) + + assert 'exception' in event + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'ValueError') + self.assertEquals(exc['value'], 'hello world') + self.assertEquals(event['level'], logging.ERROR) + self.assertEquals(event['message'], 'ValueError: hello world') + + def test_capture_plus_logging(self): + client, raven, app = self.make_client_and_raven( + debug=False, logging=True) + client.get('/an-error/') + client.get('/log-an-error/') + assert len(raven.events) == 2 + + def test_get(self): + request, response = self.client.get('/an-error/?foo=bar') + self.assertEquals(response.status, 500) + self.assertEquals(len(self.raven.events), 1) + + event = self.raven.events.pop(0) + + assert 'request' in event + http = event['request'] + self.assertEquals(http['url'], 'http://{0}/an-error/'.format(BASE_URL)) + self.assertEquals(http['query_string'], 'foo=bar') + self.assertEquals(http['method'], 'GET') + self.assertEquals(http['data'], {}) + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('host' in headers, headers.keys()) + self.assertEqual(headers['host'], BASE_URL) + self.assertTrue('user-agent' in headers, headers.keys()) + self.assertTrue('aiohttp' in headers['user-agent']) + + def test_post_form(self): + request, response = self.client.post('/an-error/?biz=baz', data={'foo': 'bar'}) + self.assertEquals(response.status, 500) + self.assertEquals(len(self.raven.events), 1) + event = self.raven.events.pop(0) + + assert 'request' in event + http = event['request'] + self.assertEquals(http['url'], 'http://{0}/an-error/'.format(BASE_URL)) + self.assertEquals(http['query_string'], 'biz=baz') + self.assertEquals(http['method'], 'POST') + self.assertEquals(http['data'], {'foo': ['bar']}) + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('host' in headers, headers.keys()) + self.assertEqual(headers['host'], BASE_URL) + self.assertTrue('user-agent' in headers, headers.keys()) + self.assertTrue('aiohttp' in headers['user-agent']) + + def test_post_json(self): + request, response = self.client.post( + '/an-error/?biz=baz', data=json.dumps({'foo': 'bar'}), + headers={'content-type': 'application/json'}) + self.assertEquals(response.status, 500) + self.assertEquals(len(self.raven.events), 1) + event = self.raven.events.pop(0) + assert 'request' in event + http = event['request'] + self.assertEquals(http['url'], 'http://{0}/an-error/'.format(BASE_URL)) + self.assertEquals(http['query_string'], 'biz=baz') + self.assertEquals(http['method'], 'POST') + self.assertEquals(http['data'], {'foo': 'bar'}) + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('host' in headers, headers.keys()) + self.assertEqual(headers['host'], BASE_URL) + self.assertTrue('user-agent' in headers, headers.keys()) + self.assertTrue('aiohttp' in headers['user-agent']) + + def test_captureException_captures_http(self): + request, response = self.client.get('/capture/?foo=bar') + self.assertEquals(response.status, 200) + self.assertEquals(len(self.raven.events), 1) + + event = self.raven.events.pop(0) + self.assertEquals(event['event_id'], response.headers['X-Sentry-ID']) + + assert event['message'] == 'ValueError: Boom' + print(event) + assert 'request' in event + assert 'exception' in event + + def test_captureMessage_captures_http(self): + request, response = self.client.get('/message/?foo=bar') + self.assertEquals(response.status, 200) + self.assertEquals(len(self.raven.events), 1) + + event = self.raven.events.pop(0) + self.assertEquals(event['event_id'], response.headers['X-Sentry-ID']) + + assert 'sentry.interfaces.Message' in event + assert 'request' in event + + def test_captureException_sets_last_event_id(self): + try: + raise ValueError + except Exception: + self.middleware.captureException() + else: + self.fail() + + event_id = self.raven.events.pop(0)['event_id'] + assert self.middleware.last_event_id == event_id + + def test_captureMessage_sets_last_event_id(self): + self.middleware.captureMessage('foo') + + event_id = self.raven.events.pop(0)['event_id'] + assert self.middleware.last_event_id == event_id + + def test_logging_setup_signal(self): + app = Sanic(__name__) + + mock_handler = Mock() + + def receiver(sender, *args, **kwargs): + self.assertIn("exclude", kwargs) + mock_handler(*args, **kwargs) + + logging_configured.connect(receiver) + raven = InMemoryClient() + + Sentry( + app, client=raven, logging=True, + logging_exclusions=("excluded_logger",)) + + mock_handler.assert_called() + + def test_check_client_type(self): + self.assertRaises(TypeError, lambda _: Sentry(self.app, "oops, I'm putting my DSN instead")) + + def test_uses_dsn(self): + app = Sanic(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.remote.base_url == 'http://example.com' + + def test_binds_default_include_paths(self): + app = Sanic(__name__) + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set([app.name]) + + def test_overrides_default_include_paths(self): + app = Sanic(__name__) + app.config['SENTRY_CONFIG'] = {'include_paths': ['foo.bar']} + sentry = Sentry(app, dsn='http://public:secret@example.com/1') + assert sentry.client.include_paths == set(['foo.bar']) diff --git a/tests/contrib/test_celery.py b/tests/contrib/test_celery.py new file mode 100644 index 000000000..e5f0b91b8 --- /dev/null +++ b/tests/contrib/test_celery.py @@ -0,0 +1,162 @@ +from __future__ import absolute_import + +import celery +import logging + +from raven.contrib.celery import ( + SentryCeleryHandler, + register_logger_signal, + CeleryFilter +) +from raven.handlers.logging import SentryHandler +from raven.utils.testutils import InMemoryClient, TestCase + + +class CeleryTestCase(TestCase): + def setUp(self): + super(CeleryTestCase, self).setUp() + self.celery = celery.Celery(__name__) + self.celery.conf.CELERY_ALWAYS_EAGER = True + + self.client = InMemoryClient() + self.handler = SentryCeleryHandler(self.client, ignore_expected=True) + self.handler.install() + self.addCleanup(self.handler.uninstall) + + def test_simple(self): + @self.celery.task(name='dummy_task') + def dummy_task(x, y): + return x / y + + dummy_task.delay(1, 2) + dummy_task.delay(1, 0) + assert len(self.client.events) == 1 + event = self.client.events[0] + exception = event['exception']['values'][-1] + assert event['transaction'] == 'dummy_task' + assert exception['type'] == 'ZeroDivisionError' + + def test_ignore_expected(self): + @self.celery.task(name='dummy_task', throws=(ZeroDivisionError,)) + def dummy_task(x, y): + return x / y + + dummy_task.delay(1, 2) + dummy_task.delay(1, 0) + assert len(self.client.events) == 0 + + +class CeleryLoggingHandlerTestCase(TestCase): + def setUp(self): + super(CeleryLoggingHandlerTestCase, self).setUp() + + self.client = InMemoryClient() + + # register the logger signal + # and unregister the signal when the test is done + register_logger_signal(self.client) + receiver = celery.signals.after_setup_logger.receivers[0][1] + self.addCleanup(celery.signals.after_setup_logger.disconnect, receiver) + + # remove any existing handlers and restore + # them when complete + self.root = logging.getLogger() + for handler in self.root.handlers: + self.root.removeHandler(handler) + self.addCleanup(self.root.addHandler, handler) + + def test_handler_added(self): + # Given: there are no handlers configured + assert not self.root.handlers + + # When: the after_setup_logger signal is sent + celery.signals.after_setup_logger.send( + sender=None, logger=self.root, + loglevel=logging.WARNING, logfile=None, + format=u'', colorize=False, + ) + + # Then: there is 1 new handler + assert len(self.root.handlers) == 1 + + # Then: the new handler is an instance of + # `raven.handlers.logging.SentryHandler` + handler = self.root.handlers[0] + assert isinstance(handler, SentryHandler) + + # Then: the handler has 1 filter + assert len(handler.filters) == 1 + + # Then: the filter is a CeleryFilter + _filter = handler.filters[0] + assert isinstance(_filter, CeleryFilter) + + # set up the handler to be removed once the test is done + self.addCleanup(self.root.removeHandler, handler) + + def test_handler_updated(self): + + # Given: there is 1 preconfigured SentryHandler + # with no filters + handler = SentryHandler(self.client) + assert not handler.filters + self.root.addHandler(handler) + # set up the handler to be removed once the test is done + self.addCleanup(self.root.removeHandler, handler) + + # When: the after_setup_logger signal is sent + celery.signals.after_setup_logger.send( + sender=None, logger=self.root, + loglevel=logging.WARNING, logfile=None, + format=u'', colorize=False, + ) + + # Then: there is still just 1 handler + assert len(self.root.handlers) == 1 + + # Then: the existing handler is an instance of + # `raven.handlers.logging.SentryHandler` + handler = self.root.handlers[0] + assert isinstance(handler, SentryHandler) + + # Then: the existing handler has 1 filter + assert len(handler.filters) == 1 + + # Then: the filter is a CeleryFilter + _filter = handler.filters[0] + assert isinstance(_filter, CeleryFilter) + + def test_subclassed_handler_updated(self): + + # Given: there is 1 preconfigured CustomHandler + # with no filters + class CustomHandler(SentryHandler): + pass + + handler = CustomHandler(self.client) + assert not handler.filters + self.root.addHandler(handler) + # set up the handler to be removed once the test is done + self.addCleanup(self.root.removeHandler, handler) + + # When: the after_setup_logger signal is sent + celery.signals.after_setup_logger.send( + sender=None, logger=self.root, + loglevel=logging.WARNING, logfile=None, + format=u'', colorize=False, + ) + + # Then: there is still just 1 handler + assert len(self.root.handlers) == 1 + + # Then: the existing handler is an instance of + # `CustomHandler` + handler = self.root.handlers[0] + assert isinstance(handler, CustomHandler) + + # Then: the existing handler has 1 filter + assert len(handler.filters) == 1 + + # Then: the filter is a CeleryFilter + _filter = handler.filters[0] + assert isinstance(_filter, CeleryFilter) diff --git a/tests/contrib/tornado/tests.py b/tests/contrib/tornado/tests.py index 1b19e167d..e2e50b21d 100644 --- a/tests/contrib/tornado/tests.py +++ b/tests/contrib/tornado/tests.py @@ -1,16 +1,12 @@ # -*- coding: utf-8 -*- -""" - tests - - Test the tornado Async Client -""" from __future__ import unicode_literals -import unittest from mock import patch from tornado import web, gen, testing +from tornado.concurrent import Future +from tornado.httpclient import HTTPError from raven.contrib.tornado import SentryMixin, AsyncSentryClient -from raven.utils import six +from raven.utils.compat import PY2 class AnErrorProneHandler(SentryMixin, web.RequestHandler): @@ -66,6 +62,11 @@ def get_current_user(self): } +class HTTPErrorHandler(SentryMixin, web.RequestHandler): + def get(self): + raise web.HTTPError(int(self.get_query_argument('code', 500)), "Oops") + + class TornadoAsyncClientTestCase(testing.AsyncHTTPTestCase): def get_app(self): app = web.Application([ @@ -75,6 +76,7 @@ def get_app(self): web.url(r'/send-error-async', SendErrorAsyncHandler), web.url(r'/an-error-with-custom-non-dict-data', AnErrorWithCustomNonDictData), web.url(r'/an-error-with-custom-dict-data', AnErrorWithCustomDictData), + web.url(r'/http-error', HTTPErrorHandler) ]) app.sentry_client = AsyncSentryClient( 'http://public_key:secret_key@host:9000/project' @@ -88,18 +90,17 @@ def test_error_handler(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Stacktrace' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Exception' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) @patch('raven.contrib.tornado.AsyncSentryClient.send') @@ -109,23 +110,22 @@ def test_error_with_custom_non_dict_data_handler(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Stacktrace' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Exception' in kwargs)) - self.assertTrue(('extra' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs + assert 'extra' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) assert 'extra_data' in kwargs['extra'] - if six.PY3: + if not PY2: expected = "'extra custom non-dict data'" else: expected = "u'extra custom non-dict data'" @@ -138,23 +138,22 @@ def test_error_with_custom_dict_data_handler(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Stacktrace' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Exception' in kwargs)) - self.assertTrue(('extra' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs + assert 'extra' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) assert 'extra_data' in kwargs['extra'] - if six.PY3: + if not PY2: expected = "'extra custom dict data'" else: expected = "u'extra custom dict data'" @@ -169,17 +168,17 @@ def test_message_handler(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Message' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'sentry.interfaces.Message' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], True) @patch('raven.contrib.tornado.AsyncSentryClient.send') @@ -189,18 +188,17 @@ def test_send_error_handler(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Stacktrace' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Exception' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) @patch('raven.contrib.tornado.AsyncSentryClient.send') @@ -210,20 +208,64 @@ def test_send_error_handler_async(self, send): self.assertEqual(send.call_count, 1) args, kwargs = send.call_args - self.assertTrue(('sentry.interfaces.User' in kwargs)) - self.assertTrue(('sentry.interfaces.Stacktrace' in kwargs)) - self.assertTrue(('sentry.interfaces.Http' in kwargs)) - self.assertTrue(('sentry.interfaces.Exception' in kwargs)) + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs - http_data = kwargs['sentry.interfaces.Http'] + http_data = kwargs['request'] self.assertEqual(http_data['cookies'], None) self.assertEqual(http_data['url'], response.effective_url) self.assertEqual(http_data['query_string'], 'qs=qs') self.assertEqual(http_data['method'], 'GET') - user_data = kwargs['sentry.interfaces.User'] + user_data = kwargs['user'] self.assertEqual(user_data['is_authenticated'], False) + @testing.gen_test + def test_sending_to_unresponsive_sentry_server_logs_error(self): + client = self.get_app().sentry_client + with patch.object(client, '_failed_send') as mock_failed: + client.send() + + yield gen.sleep(0.01) # we need to run after the async send + assert mock_failed.called + + @testing.gen_test + def test_non_successful_responses_marks_client_as_failed(self): + client = self.get_app().sentry_client + with patch.object(client, '_failed_send') as mock_failed: + with patch.object(client, '_send_remote') as mock_send: -if __name__ == '__main__': - unittest.main() + f = Future() + f.set_exception(HTTPError(499, 'error')) + mock_send.return_value = f + client.send() + + yield gen.sleep(0.01) # we need to run after the async send + assert mock_failed.called + + @patch('raven.contrib.tornado.AsyncSentryClient.send') + def test_http_error_500(self, send): + response = self.fetch('/http-error?code=500') + self.assertEqual(response.code, 500) + self.assertEqual(send.call_count, 1) + args, kwargs = send.call_args + + assert 'user' in kwargs + assert 'request' in kwargs + assert 'exception' in kwargs + + http_data = kwargs['request'] + self.assertEqual(http_data['cookies'], None) + self.assertEqual(http_data['url'], response.effective_url) + self.assertEqual(http_data['query_string'], 'code=500') + self.assertEqual(http_data['method'], 'GET') + + user_data = kwargs['user'] + self.assertEqual(user_data['is_authenticated'], False) + + @patch('raven.contrib.tornado.AsyncSentryClient.send') + def test_http_error_400(self, send): + response = self.fetch('/http-error?code=400') + self.assertEqual(response.code, 400) + self.assertEqual(send.call_count, 0) diff --git a/tests/contrib/webpy/__init__.py b/tests/contrib/webpy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/webpy/tests.py b/tests/contrib/webpy/tests.py new file mode 100644 index 000000000..9b13e71e7 --- /dev/null +++ b/tests/contrib/webpy/tests.py @@ -0,0 +1,84 @@ +from exam import fixture +from paste.fixture import TestApp as PasteTestApp # prevent pytest-warning + +from raven.base import Client +from raven.contrib.webpy import SentryApplication +from raven.utils.testutils import TestCase + + +class TempStoreClient(Client): + def __init__(self, **kwargs): + self.events = [] + super(TempStoreClient, self).__init__(**kwargs) + + def is_enabled(self): + return True + + def send(self, **kwargs): + self.events.append(kwargs) + + +class TestEndpoint(object): + def GET(self): + raise ValueError('That\'s what she said') + + def POST(self): + raise TypeError('Potato') + + +urls = ( + '/test', TestEndpoint +) + + +def create_app(client): + return SentryApplication(client=client, mapping=urls) + + +class WebPyTest(TestCase): + @fixture + def app(self): + self.store = TempStoreClient() + return create_app(self.store) + + @fixture + def client(self): + return PasteTestApp(self.app.wsgifunc()) + + def test_get(self): + resp = self.client.get('/test', expect_errors=True) + + self.assertEquals(resp.status, 500) + self.assertEquals(len(self.store.events), 1) + + event = self.store.events.pop() + assert 'exception' in event + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'ValueError') + self.assertEquals(exc['value'], 'That\'s what she said') + self.assertEquals(event['message'], 'ValueError: That\'s what she said') + + def test_post(self): + response = self.client.post('/test?biz=baz', params={'foo': 'bar'}, expect_errors=True) + self.assertEquals(response.status, 500) + self.assertEquals(len(self.store.events), 1) + + event = self.store.events.pop() + + assert 'request' in event + http = event['request'] + self.assertEquals(http['url'], 'http://localhost/test') + self.assertEquals(http['query_string'], '?biz=baz') + self.assertEquals(http['method'], 'POST') + self.assertEquals(http['data'], 'foo=bar') + self.assertTrue('headers' in http) + headers = http['headers'] + self.assertTrue('Content-Length' in headers, headers.keys()) + self.assertEquals(headers['Content-Length'], '7') + self.assertTrue('Content-Type' in headers, headers.keys()) + self.assertEquals(headers['Content-Type'], 'application/x-www-form-urlencoded') + self.assertTrue('Host' in headers, headers.keys()) + self.assertEquals(headers['Host'], 'localhost') + env = http['env'] + self.assertTrue('SERVER_NAME' in env, env.keys()) + self.assertEquals(env['SERVER_NAME'], 'localhost') diff --git a/tests/contrib/zconfig/__init__.py b/tests/contrib/zconfig/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/contrib/zconfig/tests.py b/tests/contrib/zconfig/tests.py new file mode 100644 index 000000000..9626a734b --- /dev/null +++ b/tests/contrib/zconfig/tests.py @@ -0,0 +1,76 @@ +from __future__ import absolute_import +import logging +import mock +import unittest +from ZConfig import configureLoggers + + +class TestConfig(unittest.TestCase): + + @mock.patch("raven.handlers.logging.Client") + def test_minimal(self, Client): + configureLoggers(""" + + %import raven.contrib.zconfig + + dsn https://abc:def@example.com/42 + + + """) + handler = logging.getLogger().handlers[-1] + logging.getLogger().handlers.remove(handler) + self.assertEqual(handler.level, logging.ERROR) + Client.assert_called_with( + dsn='https://abc:def@example.com/42', + site=None, + name=None, + release=None, + environment=None, + exclude_paths=None, + include_paths=None, + sample_rate=1.0, + list_max_length=None, + string_max_length=None, + auto_log_stacks=None, + processors=None, + level=logging.ERROR) + + @mock.patch("raven.handlers.logging.Client") + def test_many(self, Client): + configureLoggers(""" + + %import raven.contrib.zconfig + + level WARNING + dsn https://abc:def@example.com/42 + site test-site + name test + release 42.0 + environment testing + exclude_paths /a /b + include_paths /c /d + sample_rate 0.5 + list_max_length 9 + string_max_length 99 + auto_log_stacks true + processors x y z + + + """) + handler = logging.getLogger().handlers[-1] + logging.getLogger().handlers.remove(handler) + self.assertEqual(handler.level, logging.WARNING) + Client.assert_called_with( + dsn='https://abc:def@example.com/42', + site='test-site', + name='test', + release='42.0', + environment='testing', + exclude_paths=['/a', '/b'], + include_paths=['/c', '/d'], + sample_rate=0.5, + list_max_length=9, + string_max_length=99, + auto_log_stacks=True, + processors=['x', 'y', 'z'], + level=logging.WARNING) diff --git a/tests/contrib/zerorpc/tests.py b/tests/contrib/zerorpc/tests.py index 6184158b5..1df7c285f 100644 --- a/tests/contrib/zerorpc/tests.py +++ b/tests/contrib/zerorpc/tests.py @@ -13,9 +13,9 @@ class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -27,14 +27,12 @@ def send(self, **kwargs): class ZeroRPCTest(TestCase): def setUp(self): self._socket_dir = tempfile.mkdtemp(prefix='ravenzerorpcunittest') - self._server_endpoint = 'ipc://{0}'.format(os.path.join( - self._socket_dir, 'random_zeroserver' - )) + self._server_endpoint = 'ipc://{0}'.format( + os.path.join(self._socket_dir, 'random_zeroserver')) self._sentry = TempStoreClient() - zerorpc.Context.get_instance().register_middleware(SentryMiddleware( - client=self._sentry - )) + zerorpc.Context.get_instance().register_middleware( + SentryMiddleware(client=self._sentry)) def test_zerorpc_middleware_with_reqrep(self): self._server = zerorpc.Server(random) @@ -49,14 +47,13 @@ def test_zerorpc_middleware_with_reqrep(self): except zerorpc.exceptions.RemoteError as ex: self.assertEqual(ex.name, 'IndexError') self.assertEqual(len(self._sentry.events), 1) - exc = self._sentry.events[0]['sentry.interfaces.Exception'] + exc = self._sentry.events[0]['exception'] self.assertEqual(exc['type'], 'IndexError') - frames = self._sentry.events[0]['sentry.interfaces.Stacktrace']['frames'] + frames = exc['stacktrace']['frames'] self.assertEqual(frames[0]['function'], 'choice') self.assertEqual(frames[0]['module'], 'random') - return - - self.fail('An IndexError exception should have been raised an catched') + else: + self.fail('An IndexError exception should have been raised an catched') def test_zerorpc_middleware_with_pushpull(self): self._server = zerorpc.Puller(random) @@ -68,12 +65,12 @@ def test_zerorpc_middleware_with_pushpull(self): self._client.choice([]) - for attempt in xrange(0, 10): + for attempt in range(0, 10): gevent.sleep(0.1) if len(self._sentry.events): - exc = self._sentry.events[0]['sentry.interfaces.Exception'] + exc = self._sentry.events[0]['exception'] self.assertEqual(exc['type'], 'IndexError') - frames = self._sentry.events[0]['sentry.interfaces.Stacktrace']['frames'] + frames = exc['stacktrace']['frames'] self.assertEqual(frames[0]['function'], 'choice') self.assertEqual(frames[0]['module'], 'random') return diff --git a/tests/events/tests.py b/tests/events/tests.py new file mode 100644 index 000000000..e9372cec5 --- /dev/null +++ b/tests/events/tests.py @@ -0,0 +1,100 @@ +from __future__ import absolute_import + +import pytest + +from raven.base import Client +from raven.events import Exception as ExceptionEvent +from raven.utils.testutils import TestCase +from raven.utils.compat import raise_from + + +class ExceptionTest(TestCase): + + # Handle compatibility. + if hasattr(Exception, '__suppress_context__'): + # Then exception chains are supported. + def transform_expected(self, expected): + return expected + else: + # Otherwise, we only report the first element. + def transform_expected(self, expected): + return expected[-1:] + + def check_capture(self, expected): + """ + Check the return value of capture(). + + Args: + expected: the expected "type" values. + """ + c = Client() + event = ExceptionEvent(c) + result = event.capture() + info = result['exception'] + values = info['values'] + + type_names = [value['type'] for value in values] + expected = self.transform_expected(expected) + + self.assertEqual(type_names, expected) + + def test_simple(self): + try: + raise ValueError() + except Exception: + self.check_capture(['ValueError']) + + def test_nested(self): + try: + raise ValueError() + except Exception: + try: + raise KeyError() + except Exception: + self.check_capture(['ValueError', 'KeyError']) + + def test_raise_from(self): + try: + raise ValueError() + except Exception as exc: + try: + raise_from(KeyError(), exc) + except Exception: + self.check_capture(['ValueError', 'KeyError']) + + def test_raise_from_different(self): + try: + raise ValueError() + except Exception as exc: + try: + raise_from(KeyError(), TypeError()) + except Exception: + self.check_capture(['TypeError', 'KeyError']) + + def test_handles_self_referencing(self): + try: + raise ValueError() + except Exception as exc: + try: + raise_from(exc, exc) + except Exception: + self.check_capture(['ValueError']) + else: + pytest.fail() + else: + pytest.fail() + + try: + raise ValueError() + except Exception as exc: + try: + raise_from(KeyError(), exc) + except KeyError as exc2: + try: + raise_from(exc, exc2) + except Exception: + self.check_capture(['KeyError', 'ValueError']) + else: + pytest.fail() + else: + pytest.fail() diff --git a/tests/functional/tests.py b/tests/functional/tests.py index 80996b611..7255414b4 100644 --- a/tests/functional/tests.py +++ b/tests/functional/tests.py @@ -2,7 +2,7 @@ import os from subprocess import call -from raven.utils.compat import TestCase +from raven.utils.testutils import BaseTestCase as TestCase ROOT = os.path.normpath( diff --git a/tests/handlers/logbook/tests.py b/tests/handlers/logbook/tests.py index 9801785f5..ef408a23f 100644 --- a/tests/handlers/logbook/tests.py +++ b/tests/handlers/logbook/tests.py @@ -2,16 +2,16 @@ from __future__ import unicode_literals import logbook +from raven.utils.compat import PY2 from raven.utils.testutils import TestCase -from raven.utils import six from raven.base import Client from raven.handlers.logbook import SentryHandler class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -37,8 +37,7 @@ def test_logger(self): self.assertEquals(event['logger'], __name__) self.assertEquals(event['level'], 'error') self.assertEquals(event['message'], 'This is a test error') - self.assertFalse('sentry.interfaces.Stacktrace' in event) - self.assertFalse('sentry.interfaces.Exception' in event) + self.assertFalse('exception' in event) self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEquals(msg['message'], 'This is a test error') @@ -50,8 +49,7 @@ def test_logger(self): self.assertEquals(event['logger'], __name__) self.assertEquals(event['level'], 'warning') self.assertEquals(event['message'], 'This is a test warning') - self.assertFalse('sentry.interfaces.Stacktrace' in event) - self.assertFalse('sentry.interfaces.Exception' in event) + self.assertFalse('exception' in event) self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEquals(msg['message'], 'This is a test warning') @@ -62,13 +60,12 @@ def test_logger(self): )) self.assertEquals(len(client.events), 1) event = client.events.pop(0) - if six.PY3: + if not PY2: expected = "'http://example.com'" else: expected = "u'http://example.com'" self.assertEquals(event['extra']['url'], expected) - self.assertFalse('sentry.interfaces.Stacktrace' in event) - self.assertFalse('sentry.interfaces.Exception' in event) + self.assertFalse('exception' in event) self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEquals(msg['message'], 'This is a test info with a url') @@ -83,9 +80,8 @@ def test_logger(self): event = client.events.pop(0) self.assertEquals(event['message'], 'This is a test info with an exception') - self.assertTrue('sentry.interfaces.Stacktrace' in event) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] + assert 'exception' in event + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'This is a test ValueError') self.assertTrue('sentry.interfaces.Message' in event) @@ -98,12 +94,11 @@ def test_logger(self): self.assertEquals(len(client.events), 1) event = client.events.pop(0) self.assertEquals(event['message'], 'This is a test of args') - self.assertFalse('sentry.interfaces.Stacktrace' in event) - self.assertFalse('sentry.interfaces.Exception' in event) + assert 'exception' not in event self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEquals(msg['message'], 'This is a test of {0}') - expected = ("'args'",) if six.PY3 else ("u'args'",) + expected = ("'args'",) if not PY2 else ("u'args'",) self.assertEquals(msg['params'], expected) def test_client_arg(self): diff --git a/tests/handlers/logging/tests.py b/tests/handlers/logging/tests.py index 248cdcc69..f29102193 100644 --- a/tests/handlers/logging/tests.py +++ b/tests/handlers/logging/tests.py @@ -2,17 +2,19 @@ import logging import sys -from raven.utils.testutils import TestCase -from raven.utils import six +import mock + +from raven.utils.compat import iteritems, PY2 from raven.base import Client from raven.handlers.logging import SentryHandler from raven.utils.stacks import iter_stack_frames +from raven.utils.testutils import TestCase class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -26,10 +28,10 @@ def setUp(self): self.client = TempStoreClient(include_paths=['tests', 'raven']) self.handler = SentryHandler(self.client) - def make_record(self, msg, args=(), level=logging.INFO, extra=None, exc_info=None): - record = logging.LogRecord('root', level, __file__, 27, msg, args, exc_info, 'make_record') + def make_record(self, msg, args=(), level=logging.INFO, extra=None, exc_info=None, name='root', pathname=__file__): + record = logging.LogRecord(name, level, pathname, 27, msg, args, exc_info, 'make_record') if extra: - for key, value in six.iteritems(extra): + for key, value in iteritems(extra): record.__dict__[key] = value return record @@ -42,13 +44,67 @@ def test_logger_basic(self): self.assertEqual(event['logger'], 'root') self.assertEqual(event['level'], logging.INFO) self.assertEqual(event['message'], 'This is a test error') - self.assertFalse('sentry.interfaces.Stacktrace' in event) - self.assertFalse('sentry.interfaces.Exception' in event) + assert 'exception' not in event self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEqual(msg['message'], 'This is a test error') self.assertEqual(msg['params'], ()) + def test_logger_ignore_exception(self): + class Foo(Exception): + pass + old = self.client.ignore_exceptions + self.client.ignore_exceptions = set(['Foo']) + try: + try: + raise Foo() + except Exception: + exc_info = sys.exc_info() + record = self.make_record('This is a test error', + exc_info=exc_info) + self.handler.emit(record) + self.assertEqual(len(self.client.events), 0) + finally: + self.client.ignore_exceptions = old + + def test_can_record(self): + tests = [ + ("raven", False), + ("raven.foo", False), + ("sentry.errors", False), + ("sentry.errors.foo", False), + ("raven_utils", True), + ] + + for test in tests: + record = self.make_record("Test", name=test[0]) + self.assertEqual(self.handler.can_record(record), test[1]) + + @mock.patch('raven.transport.http.HTTPTransport.send') + @mock.patch('raven.base.ClientState.should_try') + def test_exception_on_emit(self, should_try, _send_remote): + should_try.return_value = True + # Test for the default behaviour in which an exception is handled by the client or handler + client = Client( + dsn='sync+http://public:secret@example.com/1', + ) + handler = SentryHandler(client) + _send_remote.side_effect = Exception() + record = self.make_record('This is a test error') + handler.emit(record) + self.assertEquals(handler.client.state.status, handler.client.state.ERROR) + + # Test for the case in which a send error is raised to the calling frame. + client = Client( + dsn='sync+http://public:secret@example.com/1', + raise_send_errors=True, + ) + handler = SentryHandler(client) + _send_remote.side_effect = Exception() + with self.assertRaises(Exception): + record = self.make_record('This is a test error') + handler.emit(record) + def test_logger_extra_data(self): record = self.make_record('This is a test error', extra={'data': { 'url': 'http://example.com', @@ -57,12 +113,20 @@ def test_logger_extra_data(self): self.assertEqual(len(self.client.events), 1) event = self.client.events.pop(0) - if six.PY3: + if not PY2: expected = "'http://example.com'" else: expected = "u'http://example.com'" self.assertEqual(event['extra']['url'], expected) + def test_extra_data_dict_is_not_mutated(self): + # The code used to modify the dictionary included in extra arguments under the 'data' key. + # This is unexpected behavior, let's make sure it doesn't happen anymore. + data = {'data_key': 'data_value'} + record = self.make_record('irrelevant', extra={'data': data}) + self.handler.emit(record) + self.assertEqual(data, {'data_key': 'data_value'}) + def test_logger_exc_info(self): try: raise ValueError('This is a test ValueError') @@ -77,9 +141,8 @@ def test_logger_exc_info(self): event = self.client.events.pop(0) self.assertEqual(event['message'], 'This is a test info with an exception') - self.assertTrue('sentry.interfaces.Stacktrace' in event) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] + assert 'exception' in event + exc = event['exception']['values'][-1] self.assertEqual(exc['type'], 'ValueError') self.assertEqual(exc['value'], 'This is a test ValueError') self.assertTrue('sentry.interfaces.Message' in event) @@ -96,7 +159,7 @@ def test_message_params(self): self.assertEqual(event['message'], 'This is a test of args') msg = event['sentry.interfaces.Message'] self.assertEqual(msg['message'], 'This is a test of %s') - expected = ("'args'",) if six.PY3 else ("u'args'",) + expected = ("'args'",) if not PY2 else ("u'args'",) self.assertEqual(msg['params'], expected) def test_record_stack(self): @@ -105,14 +168,13 @@ def test_record_stack(self): self.assertEqual(len(self.client.events), 1) event = self.client.events.pop(0) - self.assertTrue('sentry.interfaces.Stacktrace' in event) - frames = event['sentry.interfaces.Stacktrace']['frames'] + self.assertTrue('stacktrace' in event) + frames = event['stacktrace']['frames'] self.assertNotEquals(len(frames), 1) - frame = frames[0] + frame = frames[-1] self.assertEqual(frame['module'], 'raven.handlers.logging') - self.assertFalse('sentry.interfaces.Exception' in event) + assert 'exception' not in event self.assertTrue('sentry.interfaces.Message' in event) - self.assertEqual(event['culprit'], 'root in make_record') self.assertEqual(event['message'], 'This is a test of stacks') def test_no_record_stack(self): @@ -130,12 +192,10 @@ def test_explicit_stack(self): self.assertEqual(len(self.client.events), 1) event = self.client.events.pop(0) - assert 'sentry.interfaces.Stacktrace' in event - assert 'culprit' in event - assert event['culprit'] == 'root in make_record' + assert 'stacktrace' in event self.assertTrue('message' in event, event) self.assertEqual(event['message'], 'This is a test of stacks') - self.assertFalse('sentry.interfaces.Exception' in event) + assert 'exception' not in event self.assertTrue('sentry.interfaces.Message' in event) msg = event['sentry.interfaces.Message'] self.assertEqual(msg['message'], 'This is a test of stacks') @@ -155,7 +215,7 @@ def test_extra_data_as_string(self): self.assertEqual(len(self.client.events), 1) event = self.client.events.pop(0) - expected = "'foo'" if six.PY3 else "u'foo'" + expected = "'foo'" if not PY2 else "u'foo'" self.assertEqual(event['extra']['data'], expected) def test_tags(self): @@ -177,6 +237,50 @@ def test_tags_on_error(self): event = self.client.events.pop(0) assert event['tags'] == {'foo': 'bar'} + def test_tags_merge(self): + handler = SentryHandler(self.client, tags={'foo': 'bar', 'biz': 'baz'}) + record = self.make_record('Message', extra={'tags': {'foo': 'faz'}}) + handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['tags'] == {'foo': 'faz', 'biz': 'baz'} + + def test_fingerprint_on_event(self): + record = self.make_record('Message', extra={'fingerprint': ['foo']}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['fingerprint'] == ['foo'] + + def test_culprit_on_event(self): + record = self.make_record('Message', extra={'culprit': 'foo'}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['culprit'] == 'foo' + + def test_server_name_on_event(self): + record = self.make_record('Message', extra={'server_name': 'foo'}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + event = self.client.events.pop(0) + assert event['server_name'] == 'foo' + + def test_sample_rate(self): + record = self.make_record('Message', extra={'sample_rate': 0.0}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 0) + + record = self.make_record('Message', extra={'sample_rate': 1.0}) + self.handler.emit(record) + + self.assertEqual(len(self.client.events), 1) + class LoggingHandlerTest(TestCase): def test_client_arg(self): @@ -189,10 +293,6 @@ def test_client_kwarg(self): handler = SentryHandler(client=client) self.assertEqual(handler.client, client) - def test_args_as_servers_and_key(self): - handler = SentryHandler(['http://sentry.local/api/store/'], 'KEY') - self.assertTrue(isinstance(handler.client, Client)) - def test_first_arg_as_dsn(self): handler = SentryHandler('http://public:secret@example.com/1') self.assertTrue(isinstance(handler.client, Client)) diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 50f90f50f..2c1702b51 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -3,16 +3,17 @@ import logging import webob from exam import fixture -from raven.utils.testutils import TestCase from raven.base import Client from raven.middleware import Sentry +from raven.utils.compat import Iterator +from raven.utils.testutils import TestCase class TempStoreClient(Client): - def __init__(self, servers=None, **kwargs): + def __init__(self, **kwargs): self.events = [] - super(TempStoreClient, self).__init__(servers=servers, **kwargs) + super(TempStoreClient, self).__init__(**kwargs) def is_enabled(self): return True @@ -21,17 +22,46 @@ def send(self, **kwargs): self.events.append(kwargs) -class ErroringIterable(object): +class ErroringIterable(Iterator): def __init__(self): self.closed = False def __iter__(self): + return self + + def __next__(self): raise ValueError('hello world') def close(self): self.closed = True +class ExitingIterable(ErroringIterable): + def __init__(self, exc_func): + self._exc_func = exc_func + + def __iter__(self): + return self + + def __next__(self): + raise self._exc_func() + + +class SimpleIteratable(Iterator): + def __init__(self): + self.closed = False + self._iter = iter(['a']) + + def __iter__(self): + return self + + def __next__(self): + return next(self._iter) + + def close(self): + self.closed = True + + class ExampleApp(object): def __init__(self, iterable): self.iterable = iterable @@ -59,21 +89,18 @@ def test_captures_error_in_iteration(self): with self.assertRaises(ValueError): response = list(response) - # TODO: this should be a separate test - self.assertTrue(iterable.closed, True) - self.assertEquals(len(self.client.events), 1) event = self.client.events.pop(0) - self.assertTrue('sentry.interfaces.Exception' in event) - exc = event['sentry.interfaces.Exception'] + assert 'exception' in event + exc = event['exception']['values'][-1] self.assertEquals(exc['type'], 'ValueError') self.assertEquals(exc['value'], 'hello world') self.assertEquals(event['level'], logging.ERROR) self.assertEquals(event['message'], 'ValueError: hello world') - self.assertTrue('sentry.interfaces.Http' in event) - http = event['sentry.interfaces.Http'] + assert 'request' in event + http = event['request'] self.assertEquals(http['url'], 'http://localhost/an-error') self.assertEquals(http['query_string'], 'foo=bar') self.assertEquals(http['method'], 'GET') @@ -86,3 +113,64 @@ def test_captures_error_in_iteration(self): self.assertEquals(env['SERVER_NAME'], 'localhost') self.assertTrue('SERVER_PORT' in env, env.keys()) self.assertEquals(env['SERVER_PORT'], '80') + + def test_systemexit_0_is_ignored(self): + iterable = ExitingIterable(lambda: SystemExit(0)) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(SystemExit): + response = list(response) + + self.assertEquals(len(self.client.events), 0) + + def test_systemexit_is_captured(self): + iterable = ExitingIterable(lambda: SystemExit(1)) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(SystemExit): + response = list(response) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + + assert 'exception' in event + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'SystemExit') + self.assertEquals(exc['value'], '1') + self.assertEquals(event['level'], logging.ERROR) + self.assertEquals(event['message'], 'SystemExit: 1') + + def test_keyboard_interrupt_is_captured(self): + iterable = ExitingIterable(lambda: KeyboardInterrupt()) + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + + with self.assertRaises(KeyboardInterrupt): + response = list(response) + + self.assertEquals(len(self.client.events), 1) + event = self.client.events.pop(0) + + assert 'exception' in event + exc = event['exception']['values'][-1] + self.assertEquals(exc['type'], 'KeyboardInterrupt') + self.assertEquals(exc['value'], '') + self.assertEquals(event['level'], logging.ERROR) + + def test_close(self): + iterable = SimpleIteratable() + app = ExampleApp(iterable) + middleware = Sentry(app, client=self.client) + + response = middleware(self.request.environ, lambda *args: None) + list(response) # exhaust iterator + response.close() + self.assertTrue(iterable.closed, True) diff --git a/tests/processors/tests.py b/tests/processors/tests.py index 2f3da8a17..dca07e44d 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -1,122 +1,361 @@ # -*- coding: utf-8 -*- from mock import Mock + +import raven from raven.utils.testutils import TestCase -from raven.processors import SanitizePasswordsProcessor, RemovePostDataProcessor, \ - RemoveStackLocalsProcessor - - -class SantizePasswordsProcessorTest(TestCase): - def test_stacktrace(self): - data = { - 'sentry.interfaces.Stacktrace': { - 'frames': [ - { - 'vars': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - } - ] - } - } +from raven.processors import SanitizeKeysProcessor, \ + SanitizePasswordsProcessor, RemovePostDataProcessor, RemoveStackLocalsProcessor - proc = SanitizePasswordsProcessor(Mock()) - result = proc.process(data) - self.assertTrue('sentry.interfaces.Stacktrace' in result) - stack = result['sentry.interfaces.Stacktrace'] +VARS = { + 'foo': 'bar', + 'vars_dict': { + 42: 'bar', + ('foo', 'bar'): 'hello', + 'password': 'hello', + }, + 'password': 'hello', + 'the_secret': 'hello', + 'a_password_here': 'hello', + 'api_key': 'secret_key', + 'apiKey': 'secret_key', + 'access_token': 'oauth2 access token', + 'custom_key1': 'you should not see this', + 'custom_key2': 'you should not see this', + 'custom_key3': {'mask': 'This entire dict'} +} + + +def get_stack_trace_data_real(exception_class=TypeError, **kwargs): + def _will_throw_type_error(foo, **kwargs): + vars_dict = VARS['vars_dict'] + password = "you should not see this" # NOQA F841 + the_secret = "nor this" # NOQA F841 + a_password_here = "Don't look at me!" # NOQA F841 + api_key = "I'm hideous!" # NOQA F841 + apiKey = "4567000012345678" # NOQA F841 + access_token = "secret stuff!" # NOQA F841 + custom_key1 = "you shouldn't see this" # NOQA F841 + custom_key2 = "you shouldn't see this" # NOQA F841 + custom_key3 = "you shouldn't see this" # NOQA F841 + + # TypeError: unsupported operand type(s) for /: 'str' and 'str' + raise exception_class() + + client = raven.Client('http://public:secret@sentry.local/1') + try: + _will_throw_type_error('bar') + except exception_class: + data = client.build_msg('raven.events.Exception') + + return data + + +def get_http_data(): + """ + This is not so real as the data retrieved + from the `get_stack_trace_data_real()` + because we're still injecting HTTP data. + Otherwise, we have to hard code the structure of a traceback, and this goes + out of date when the format of data returned by + ``raven.base.Client/build_msg`` changes. In that case, the tests pass, but + the data is still dirty. This is a dangerous situation to be in. + """ + data = get_stack_trace_data_real() + + data['request'] = { + 'cookies': VARS, + 'data': VARS, + 'env': VARS, + 'headers': VARS, + 'method': 'GET', + 'query_string': '', + 'url': 'http://localhost/', + } + return data + + +def get_extra_data(): + data = get_stack_trace_data_real() + + data['extra'] = VARS + return data + + +class SanitizeKeysProcessorTest(TestCase): + + def setUp(self): + client = Mock( + sanitize_keys=['custom_key1', 'custom_key2', 'custom_key3'] + ) + self.proc = SanitizeKeysProcessor(client) + + def _check_vars_sanitized(self, vars, MASK): + """ + Helper to check that keys have been sanitized. + """ + self.assertTrue('custom_key1' in vars) + self.assertEquals(vars['custom_key1'], MASK) + self.assertTrue('custom_key2' in vars) + self.assertEquals(vars['custom_key2'], MASK) + self.assertTrue('custom_key3' in vars) + self.assertEquals(vars['custom_key3'], MASK) + + def test_stacktrace(self, *args, **kwargs): + data = get_stack_trace_data_real() + result = self.proc.process(data) + + self.assertTrue('exception' in result) + exception = result['exception'] + self.assertTrue('values' in exception) + values = exception['values'] + stack = values[-1]['stacktrace'] self.assertTrue('frames' in stack) - self.assertEquals(len(stack['frames']), 1) - frame = stack['frames'][0] + self.assertEquals(len(stack['frames']), 2) + frame = stack['frames'][1] # frame of will_throw_type_error() self.assertTrue('vars' in frame) - vars = frame['vars'] + self._check_vars_sanitized(frame['vars'], self.proc.MASK) + + def test_http(self): + data = get_http_data() + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + for n in ('data', 'env', 'headers', 'cookies'): + self.assertTrue(n in http) + self._check_vars_sanitized(http[n], self.proc.MASK) + + def test_extra(self): + data = get_extra_data() + result = self.proc.process(data) + + self.assertTrue('extra' in result) + extra = result['extra'] + self._check_vars_sanitized(extra, self.proc.MASK) + + def test_querystring_as_string(self): + data = get_http_data() + data['request']['query_string'] = 'foo=bar&custom_key1=nope&custom_key2=nope&custom_key3=%7B%27key2%27%3A+%27nope%27%2C+%27key1%27%3A+%27nope%27%7D' + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['query_string'], + "foo=bar&custom_key1=%(m)s&custom_key2=%(m)s&custom_key3=%(m)s" % {'m': self.proc.MASK} + ) + + def test_querystring_as_string_with_partials(self): + data = get_http_data() + data['request']['query_string'] = 'foo=bar&custom_key1&baz=bar' + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['query_string'], + 'foo=bar&custom_key1&baz=bar' % {'m': self.proc.MASK} + ) + + def test_cookie_as_string(self): + data = get_http_data() + + data['request']['cookies'] = \ + 'foo=bar;custom_key1=nope;custom_key2=nope;' + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['cookies'], + 'foo=bar;custom_key1=%(m)s;custom_key2=%(m)s;' % {'m': self.proc.MASK}) + + def test_cookie_as_string_with_partials(self): + data = get_http_data() + data['request']['cookies'] = 'foo=bar;custom_key1;baz=bar' + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['cookies'], + 'foo=bar;custom_key1;baz=bar' % dict(m=self.proc.MASK) + ) + + def test_cookie_header(self): + data = get_http_data() + data['request']['headers']['Cookie'] = 'foo=bar;custom_key1=nope;custom_key2=nope;' + result = self.proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['headers']['Cookie'], + 'foo=bar;custom_key1=%(m)s;custom_key2=%(m)s;' % {'m': self.proc.MASK}) + + def test_sanitize_non_ascii(self): + result = self.proc.sanitize('__repr__: жили-были', '42') + self.assertEquals(result, '42') + + +class SanitizePasswordsProcessorTest(TestCase): + + def _check_vars_sanitized(self, vars, proc): + """ + Helper to check that keys have been sanitized. + """ self.assertTrue('foo' in vars) - self.assertEquals(vars['foo'], 'bar') + self.assertIn(vars['foo'], ( + VARS['foo'], "'%s'" % VARS['foo'], '"%s"' % VARS['foo']) + ) + self.assertTrue('vars_dict' in vars) + vars_dict = vars['vars_dict'] + ref_dict = VARS['vars_dict'].copy() + ref_dict['password'] = proc.MASK + self.assertTrue(42 in vars_dict or '42' in vars_dict) + if 42 in vars_dict: + # Extra data - dictionary keys are not changed. + self.assertDictEqual(vars_dict, ref_dict) + else: + # Stack trace - dictionary keys are converted to strings. + self.assertTrue('42' in vars_dict) + self.assertIn(vars_dict['42'], "'%s'" % ref_dict[42], '"%s"' % ref_dict[42]) + self.assertTrue('("\'foo\'", "\'bar\'")' in vars_dict or "('\"foo\"', '\"bar\"')" in vars_dict) + self.assertTrue('"password"' in vars_dict or "'password'" in vars_dict) + if "'password'" in vars_dict: + self.assertEqual(vars_dict["'password'"], proc.MASK) + else: + self.assertEqual(vars_dict['"password"'], proc.MASK) self.assertTrue('password' in vars) self.assertEquals(vars['password'], proc.MASK) self.assertTrue('the_secret' in vars) self.assertEquals(vars['the_secret'], proc.MASK) self.assertTrue('a_password_here' in vars) self.assertEquals(vars['a_password_here'], proc.MASK) + self.assertTrue('api_key' in vars) + self.assertEquals(vars['api_key'], proc.MASK) + self.assertTrue('apiKey' in vars) + self.assertEquals(vars['apiKey'], proc.MASK) + self.assertTrue('access_token' in vars) + self.assertEquals(vars['access_token'], proc.MASK) + + def test_stacktrace(self, *args, **kwargs): + """ + Check whether sensitive variables are properly stripped from stack-trace + messages. + """ + data = get_stack_trace_data_real() + proc = SanitizePasswordsProcessor(Mock()) + result = proc.process(data) + + # data['exception']['values'][-1]['stacktrace']['frames'][0]['vars'] + self.assertTrue('exception' in result) + exception = result['exception'] + self.assertTrue('values' in exception) + values = exception['values'] + stack = values[-1]['stacktrace'] + self.assertTrue('frames' in stack) + + self.assertEquals(len(stack['frames']), 2) + frame = stack['frames'][1] # frame of will_throw_type_error() + self.assertTrue('vars' in frame) + self._check_vars_sanitized(frame['vars'], proc) def test_http(self): - data = { - 'sentry.interfaces.Http': { - 'data': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - 'env': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - 'headers': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - 'cookies': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - } - } + data = get_http_data() proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) - self.assertTrue('sentry.interfaces.Http' in result) - http = result['sentry.interfaces.Http'] + self.assertTrue('request' in result) + http = result['request'] + for n in ('data', 'env', 'headers', 'cookies'): self.assertTrue(n in http) - vars = http[n] - self.assertTrue('foo' in vars) - self.assertEquals(vars['foo'], 'bar') - self.assertTrue('password' in vars) - self.assertEquals(vars['password'], proc.MASK) - self.assertTrue('the_secret' in vars) - self.assertEquals(vars['the_secret'], proc.MASK) - self.assertTrue('a_password_here' in vars) - self.assertEquals(vars['a_password_here'], proc.MASK) + self._check_vars_sanitized(http[n], proc) + + def test_extra(self): + data = get_extra_data() + + proc = SanitizePasswordsProcessor(Mock()) + result = proc.process(data) + + self.assertTrue('extra' in result) + extra = result['extra'] + + self._check_vars_sanitized(extra, proc) def test_querystring_as_string(self): - data = { - 'sentry.interfaces.Http': { - 'query_string': 'foo=bar&password=hello&the_secret=hello&a_password_here=hello', - } - } + data = get_http_data() + data['request']['query_string'] = 'foo=bar&password=hello&the_secret=hello'\ + '&a_password_here=hello&api_key=secret_key' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) - self.assertTrue('sentry.interfaces.Http' in result) - http = result['sentry.interfaces.Http'] - self.assertEquals(http['query_string'], 'foo=bar&password=%(m)s&the_secret=%(m)s&a_password_here=%(m)s' % dict(m=proc.MASK)) + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['query_string'], + 'foo=bar&password=%(m)s&the_secret=%(m)s' + '&a_password_here=%(m)s&api_key=%(m)s' % dict(m=proc.MASK)) def test_querystring_as_string_with_partials(self): - data = { - 'sentry.interfaces.Http': { - 'query_string': 'foo=bar&password&baz=bar', - } - } + data = get_http_data() + data['request']['query_string'] = 'foo=bar&password&baz=bar' proc = SanitizePasswordsProcessor(Mock()) result = proc.process(data) - self.assertTrue('sentry.interfaces.Http' in result) - http = result['sentry.interfaces.Http'] + self.assertTrue('request' in result) + http = result['request'] self.assertEquals(http['query_string'], 'foo=bar&password&baz=bar' % dict(m=proc.MASK)) + def test_cookie_as_string(self): + data = get_http_data() + data['request']['cookies'] = 'foo=bar;password=hello;the_secret=hello'\ + ';a_password_here=hello;api_key=secret_key' + + proc = SanitizePasswordsProcessor(Mock()) + result = proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['cookies'], + 'foo=bar;password=%(m)s;the_secret=%(m)s' + ';a_password_here=%(m)s;api_key=%(m)s' % dict(m=proc.MASK)) + + def test_cookie_as_string_with_partials(self): + data = get_http_data() + data['request']['cookies'] = 'foo=bar;password;baz=bar' + + proc = SanitizePasswordsProcessor(Mock()) + result = proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals(http['cookies'], 'foo=bar;password;baz=bar' % dict(m=proc.MASK)) + + def test_cookie_header(self): + data = get_http_data() + data['request']['headers']['Cookie'] = 'foo=bar;password=hello'\ + ';the_secret=hello;a_password_here=hello;api_key=secret_key'\ + ';access_token=at' + + proc = SanitizePasswordsProcessor(Mock()) + result = proc.process(data) + + self.assertTrue('request' in result) + http = result['request'] + self.assertEquals( + http['headers']['Cookie'], + 'foo=bar;password=%(m)s' + ';the_secret=%(m)s;a_password_here=%(m)s;api_key=%(m)s' + ';access_token=%(m)s' % dict(m=proc.MASK)) + def test_sanitize_credit_card(self): proc = SanitizePasswordsProcessor(Mock()) result = proc.sanitize('foo', '4242424242424242') @@ -128,43 +367,37 @@ def test_sanitize_credit_card_amex(self): result = proc.sanitize('foo', '424242424242424') self.assertEquals(result, proc.MASK) + def test_sanitize_non_ascii(self): + proc = SanitizePasswordsProcessor(Mock()) + result = proc.sanitize('__repr__: жили-были', '42') + self.assertEquals(result, '42') + + def test_sanitize_bytes(self): + proc = SanitizePasswordsProcessor(Mock()) + data = {'data': b'password=1234'} + result = proc.filter_http(data) + self.assertIn(data['data'], 'password=%s' % proc.MASK) + class RemovePostDataProcessorTest(TestCase): def test_does_remove_data(self): - data = { - 'sentry.interfaces.Http': { - 'data': 'foo', - } - } + data = get_http_data() + data['request']['data'] = 'foo' proc = RemovePostDataProcessor(Mock()) result = proc.process(data) - self.assertTrue('sentry.interfaces.Http' in result) - http = result['sentry.interfaces.Http'] + self.assertTrue('request' in result) + http = result['request'] self.assertFalse('data' in http) class RemoveStackLocalsProcessorTest(TestCase): def test_does_remove_data(self): - data = { - 'sentry.interfaces.Stacktrace': { - 'frames': [ - { - 'vars': { - 'foo': 'bar', - 'password': 'hello', - 'the_secret': 'hello', - 'a_password_here': 'hello', - }, - } - ] - } - } + data = get_stack_trace_data_real() proc = RemoveStackLocalsProcessor(Mock()) result = proc.process(data) - self.assertTrue('sentry.interfaces.Stacktrace' in result) - stack = result['sentry.interfaces.Stacktrace'] - for frame in stack['frames']: - self.assertFalse('vars' in frame) + for value in result['exception']['values']: + for frame in value['stacktrace']['frames']: + self.assertFalse('vars' in frame) diff --git a/tests/transport/gevent/.tests.py.swo b/tests/transport/gevent/.tests.py.swo deleted file mode 100644 index 6fc80e93c..000000000 Binary files a/tests/transport/gevent/.tests.py.swo and /dev/null differ diff --git a/tests/transport/gevent/tests.py b/tests/transport/gevent/tests.py index 0d58d7402..b31efe660 100644 --- a/tests/transport/gevent/tests.py +++ b/tests/transport/gevent/tests.py @@ -1,28 +1,32 @@ +from __future__ import absolute_import + import mock import time import socket import gevent.monkey +try: + from importlib import reload +except ImportError: + from imp import reload + from raven.utils.testutils import TestCase from raven.base import Client +from raven.transport.gevent import GeventedHTTPTransport class GeventTransportTest(TestCase): - def setUp(self): gevent.monkey.patch_socket() + self.addCleanup(reload, socket) gevent.monkey.patch_time() + self.addCleanup(reload, time) self.client = Client( dsn="gevent+http://some_username:some_password@localhost:8143/1", ) - def tearDown(self): - # Undo gevent monkey patching - reload(socket) - reload(time) - - @mock.patch('raven.transport.base.GeventedHTTPTransport._done') - @mock.patch('raven.transport.base.HTTPTransport.send') + @mock.patch.object(GeventedHTTPTransport, '_done') + @mock.patch('raven.transport.http.HTTPTransport.send') def test_does_send(self, send, done): self.client.captureMessage(message='foo') time.sleep(0) diff --git a/tests/transport/requests/__init__.py b/tests/transport/requests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/transport/requests/test_threaded_requests.py b/tests/transport/requests/test_threaded_requests.py new file mode 100644 index 000000000..2147962ba --- /dev/null +++ b/tests/transport/requests/test_threaded_requests.py @@ -0,0 +1,52 @@ +import mock +import time +from raven.utils.testutils import TestCase + +from raven.base import Client +from raven.transport.threaded_requests import ThreadedRequestsHTTPTransport +from raven.utils.urlparse import urlparse + + +class DummyThreadedScheme(ThreadedRequestsHTTPTransport): + def __init__(self, *args, **kwargs): + super(ThreadedRequestsHTTPTransport, self).__init__(*args, **kwargs) + self.events = [] + self.send_delay = 0 + + def send_sync(self, url, data, headers, success_cb, failure_cb): + # delay sending the message, to allow us to test that the shutdown + # hook waits correctly + time.sleep(self.send_delay) + + self.events.append((url, data, headers, success_cb, failure_cb)) + + +class ThreadedTransportTest(TestCase): + def setUp(self): + self.url = "threaded+requests+http://some_username:some_password@localhost:8143/1" + self.client = Client(dsn=self.url) + + @mock.patch('raven.transport.requests.post') + def test_does_send(self, send): + self.client.captureMessage(message='foo') + + time.sleep(0.1) + + self.assertEqual(send.call_count, 1) + expected_url = 'http://localhost:8143/api/1/store/' + self.assertEqual(expected_url, send.call_args[0][0]) + + def test_shutdown_waits_for_send(self): + url = urlparse(self.url) + transport = DummyThreadedScheme() + transport.send_delay = 0.5 + + data = self.client.build_msg('raven.events.Message', message='foo') + transport.async_send(url, data, None, None, None) + + time.sleep(0.1) + + # this should wait for the message to get sent + transport.get_worker().main_thread_terminated() + + self.assertEqual(len(transport.events), 1) diff --git a/tests/transport/requests/tests.py b/tests/transport/requests/tests.py new file mode 100644 index 000000000..97bc48314 --- /dev/null +++ b/tests/transport/requests/tests.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +import mock + +from raven.utils.testutils import TestCase +from raven.base import Client + + +class RequestsTransportTest(TestCase): + def setUp(self): + self.client = Client( + dsn="requests+http://some_username:some_password@localhost:8143/1", + ) + + @mock.patch('raven.transport.requests.post') + def test_does_send(self, post): + self.client.captureMessage(message='foo') + self.assertEqual(post.call_count, 1) + expected_url = 'http://localhost:8143/api/1/store/' + self.assertEqual(expected_url, post.call_args[0][0]) diff --git a/tests/transport/tests.py b/tests/transport/tests.py index 344d9c816..ecec09ae2 100644 --- a/tests/transport/tests.py +++ b/tests/transport/tests.py @@ -6,74 +6,71 @@ # Some internal stuff to extend the transport layer from raven.transport import Transport +from raven.transport.exceptions import DuplicateScheme + +# Simplify comparing dicts with primitive values: +from raven.utils import json import datetime import calendar import pytz +import zlib class DummyScheme(Transport): scheme = ['mock'] - def __init__(self, parsed_url): - self.check_scheme(parsed_url) - self._parsed_url = parsed_url + def __init__(self, timeout=5): + self.timeout = timeout - def send(self, data, headers): + def send(self, url, data, headers): """ Sends a request to a remote webserver """ + self._url = url self._data = data self._headers = headers - def compute_scope(self, url, scope): - netloc = url.hostname - netloc += ':%s' % url.port - - path_bits = url.path.rsplit('/', 1) - if len(path_bits) > 1: - path = path_bits[0] - else: - path = '' - project = path_bits[-1] - - if not all([netloc, project, url.username, url.password]): - raise ValueError('Invalid Sentry DSN: %r' % url.geturl()) - - server = '%s://%s%s/api/store/' % (url.scheme, netloc, path) - - # Note that these variables in the scope are not actually used - # for anything w.r.t the DummyTransport - scope.update({ - 'SENTRY_SERVERS': [server], - 'SENTRY_PROJECT': project, - 'SENTRY_PUBLIC_KEY': url.username, - 'SENTRY_SECRET_KEY': url.password, - }) - return scope - class TransportTest(TestCase): def setUp(self): try: Client.register_scheme('mock', DummyScheme) - except: + except DuplicateScheme: pass + def test_basic_config(self): + c = Client( + dsn="mock://some_username:some_password@localhost:8143/1?timeout=1", + name="test_server" + ) + assert c.remote.options == { + 'timeout': '1', + } + def test_custom_transport(self): c = Client(dsn="mock://some_username:some_password@localhost:8143/1") data = dict(a=42, b=55, c=list(range(50))) c.send(**data) - expected_message = c.encode(data) - self.assertIn('mock://localhost:8143/api/store/', Client._registry._transports) - mock_cls = Client._registry._transports['mock://localhost:8143/api/store/'] - assert mock_cls._data == expected_message + mock_cls = c._transport_cache['mock://some_username:some_password@localhost:8143/1'].get_transport() + print(mock_cls.__dict__) + + expected_message = zlib.decompress(c.encode(data)) + actual_message = zlib.decompress(mock_cls._data) + + # These loads()/dumps() pairs order the dict keys before comparing the string. + # See GH504 + self.assertEqual( + json.dumps(json.loads(expected_message.decode('utf-8')), sort_keys=True), + json.dumps(json.loads(actual_message.decode('utf-8')), sort_keys=True) + ) def test_build_then_send(self): - c = Client(dsn="mock://some_username:some_password@localhost:8143/1", + c = Client( + dsn="mock://some_username:some_password@localhost:8143/1", name="test_server") mydate = datetime.datetime(2012, 5, 4, tzinfo=pytz.utc) @@ -81,10 +78,13 @@ def test_build_then_send(self): msg = c.build_msg('raven.events.Message', message='foo', date=d) expected = { 'project': '1', - 'sentry.interfaces.Message': {'message': 'foo', 'params': ()}, + 'sentry.interfaces.Message': { + 'message': 'foo', + 'params': (), + 'formatted': None, + }, 'server_name': 'test_server', 'level': 40, - 'modules': {}, 'tags': {}, 'time_spent': None, 'timestamp': 1336089600, diff --git a/tests/transport/threaded/tests.py b/tests/transport/threaded/tests.py index c78eff135..e07e0b073 100644 --- a/tests/transport/threaded/tests.py +++ b/tests/transport/threaded/tests.py @@ -1,30 +1,45 @@ import mock +import os import time +from tempfile import mkstemp + from raven.utils.testutils import TestCase from raven.base import Client from raven.transport.threaded import ThreadedHTTPTransport +from raven.utils.urlparse import urlparse class DummyThreadedScheme(ThreadedHTTPTransport): - - scheme = ['threaded+mock'] - def __init__(self, *args, **kwargs): super(ThreadedHTTPTransport, self).__init__(*args, **kwargs) self.events = [] + self.send_delay = 0 + + def send_sync(self, url, data, headers, success_cb, failure_cb): + # delay sending the message, to allow us to test that the shutdown + # hook waits correctly + time.sleep(self.send_delay) + + self.events.append((url, data, headers, success_cb, failure_cb)) + + +class LoggingThreadedScheme(ThreadedHTTPTransport): + def __init__(self, filename, *args, **kwargs): + super(LoggingThreadedScheme, self).__init__(*args, **kwargs) + self.filename = filename - def send_sync(self, data, headers): - self.events.append((data, headers)) + def send_sync(self, url, data, headers, success_cb, failure_cb): + with open(self.filename, 'a') as log: + log.write("{0} {1}\n".format(os.getpid(), data['message'])) class ThreadedTransportTest(TestCase): def setUp(self): - self.client = Client( - dsn="threaded+http://some_username:some_password@localhost:8143/1", - ) + self.url = "threaded+http://some_username:some_password@localhost:8143/1" + self.client = Client(dsn=self.url) - @mock.patch('raven.transport.base.HTTPTransport.send') + @mock.patch('raven.transport.http.HTTPTransport.send') def test_does_send(self, send): self.client.captureMessage(message='foo') @@ -33,3 +48,83 @@ def test_does_send(self, send): # TODO: This test could be more precise by ensuring it's sending the same params that are sent # to the ThreadedHTTPTransport.send() method self.assertEqual(send.call_count, 1) + + def test_shutdown_waits_for_send(self): + url = urlparse(self.url) + transport = DummyThreadedScheme() + transport.send_delay = 0.5 + + data = self.client.build_msg('raven.events.Message', message='foo') + transport.async_send(url, data, None, None, None) + + time.sleep(0.1) + + # this should wait for the message to get sent + transport.get_worker().main_thread_terminated() + + self.assertEqual(len(transport.events), 1) + + def test_fork_spawns_anew(self): + url = urlparse(self.url) + transport = DummyThreadedScheme() + transport.send_delay = 0.5 + + data = self.client.build_msg('raven.events.Message', message='foo') + + pid = os.fork() + if pid == 0: + time.sleep(0.1) + + transport.async_send(url, data, None, None, None) + + # this should wait for the message to get sent + transport.get_worker().main_thread_terminated() + + self.assertEqual(len(transport.events), 1) + # Use os._exit here so that py.test gets not confused about + # what the hell we're doing here. + os._exit(0) + else: + os.waitpid(pid, 0) + + def test_fork_with_active_worker(self): + # Test threaded transport when forking with an active worker. + # Forking a process doesn't clone the worker thread - make sure + # logging from both processes still works. + event1 = self.client.build_msg('raven.events.Message', message='parent') + event2 = self.client.build_msg('raven.events.Message', message='child') + url = urlparse(self.url) + fd, filename = mkstemp() + try: + os.close(fd) + transport = LoggingThreadedScheme(filename) + + # Log from the parent process - starts the worker thread + transport.async_send(url, event1, None, None, None) + childpid = os.fork() + + if childpid == 0: + # Log from the child process + transport.async_send(url, event2, None, None, None) + + # Ensure threaded worker has finished + transport.get_worker().stop() + os._exit(0) + + # Wait for the child process to finish + os.waitpid(childpid, 0) + assert os.path.isfile(filename) + + # Ensure threaded worker has finished + transport.get_worker().stop() + + with open(filename, 'r') as logfile: + events = dict(x.strip().split() for x in logfile.readlines()) + + # Check parent and child both logged successfully + assert events == { + str(os.getpid()): 'parent', + str(childpid): 'child', + } + finally: + os.remove(filename) diff --git a/tests/transport/tornado/__init__.py b/tests/transport/tornado/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/transport/tornado/tests.py b/tests/transport/tornado/tests.py new file mode 100644 index 000000000..351b4d4a4 --- /dev/null +++ b/tests/transport/tornado/tests.py @@ -0,0 +1,63 @@ +from __future__ import absolute_import + +import mock + +from raven.base import Client +from tornado import gen, testing, httpclient + + +class TornadoTransportTests(testing.AsyncTestCase): + + def get_new_ioloop(self): + io_loop = super(TornadoTransportTests, self).get_new_ioloop() + io_loop.make_current() + return io_loop + + @mock.patch("raven.transport.tornado.HTTPClient") + def test_send(self, fake_client): + url = "https://user:pass@host:1234/1" + timeout = 1 + verify_ssl = 1 + ca_certs = "/some/path/somefile" + + fake = fake_client.return_value + raven_client = Client( + dsn="tornado+{0}?timeout={1}&verify_ssl={2}&ca_certs={3}". + format(url, timeout, verify_ssl, ca_certs)) + + raven_client.captureMessage(message="test") + + # make sure an instance of HTTPClient was created, since we are not in + # an IOLoop + fake_client.assert_called_once_with() + fake_fetch = fake.fetch + + # make sure we called fetch() which does the sending + self.assertEqual(fake_fetch.call_count, 1) + # only verify the special kwargs that we should be passing through, + # no need to verify the urls and whatnot + args, kwargs = fake_fetch.call_args + self.assertEqual(kwargs["connect_timeout"], timeout) + self.assertEqual(kwargs["validate_cert"], bool(verify_ssl)) + self.assertEqual(kwargs["ca_certs"], ca_certs) + + @testing.gen_test + def test__sending_with_error_calls_error_callback(self): + c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') + + with mock.patch.object(Client, '_failed_send') as mock_failed: + c.captureMessage(message='test') + yield gen.sleep(0.01) # we need to run after the async send + + assert mock_failed.called + + @testing.gen_test + def test__sending_successfully_calls_success_callback(self): + c = Client(dsn='tornado+http://uver:pass@localhost:46754/1') + with mock.patch.object(Client, '_successful_send') as mock_successful: + with mock.patch.object(httpclient.AsyncHTTPClient, 'fetch') as mock_fetch: + mock_fetch.return_value = gen.maybe_future(True) + c.captureMessage(message='test') + yield gen.sleep(0.01) # we need to run after the async send + + assert mock_successful.called diff --git a/tests/utils/encoding/tests.py b/tests/utils/encoding/tests.py index 21ec2f527..08f9387ac 100644 --- a/tests/utils/encoding/tests.py +++ b/tests/utils/encoding/tests.py @@ -2,83 +2,81 @@ import pytest import uuid -from raven.utils import six +from raven.utils import compat +from raven.utils import json from raven.utils.testutils import TestCase from raven.utils.serializer import transform class TransformTest(TestCase): - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_incorrect_unicode(self): - x = six.b('רונית מגן') + x = compat.b('רונית מגן') result = transform(x) - assert result == six.b("'רונית מגן'") + assert result == compat.b("'רונית מגן'") - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_truncating_unicode(self): # 'רונית מגן' - x = six.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') + x = compat.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') result = transform(x, string_max_length=5) - assert result == six.u("u'\u05e8\u05d5\u05e0\u05d9\u05ea'") + assert result == compat.u("u'\u05e8\u05d5\u05e0\u05d9\u05ea'") - @pytest.mark.skipif('not six.PY3') + @pytest.mark.skipif('not compat.PY3') def test_unicode_in_python3(self): # 'רונית מגן' - x = six.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') + x = compat.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') result = transform(x) - assert result == six.u("'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'") + assert result == compat.u("'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'") - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_unicode_in_python2(self): # 'רונית מגן' - x = six.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') + x = compat.u('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df') result = transform(x) - assert result == six.u("u'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'") + assert result == compat.u("u'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'") - @pytest.mark.skipif('not six.PY3') + @pytest.mark.skipif('not compat.PY3') def test_string_in_python3(self): # 'רונית מגן' - x = six.b('hello world') + x = compat.b('hello world') result = transform(x) assert result == "b'hello world'" - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_string_in_python2(self): # 'רונית מגן' - x = six.b('hello world') + x = compat.b('hello world') result = transform(x) assert result == "'hello world'" - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_bad_string(self): - x = six.b('The following character causes problems: \xd4') + x = compat.b('The following character causes problems: \xd4') result = transform(x) - assert result == six.b("'The following character causes problems: \\xd4'") + assert result == compat.b("'The following character causes problems: \\xd4'") def test_float(self): result = transform(13.0) - self.assertEqual(type(result), float) - self.assertEqual(result, 13.0) + self.assertEqual(result, "13.0") def test_bool(self): result = transform(True) - self.assertEqual(type(result), bool) - self.assertEqual(result, True) + self.assertEqual(result, 'True') def test_int_subclass(self): class X(int): pass result = transform(X()) - self.assertEqual(type(result), int) - self.assertEqual(result, 0) + self.assertEqual(result, '0') def test_dict_keys(self): x = {'foo': 'bar'} @@ -90,7 +88,7 @@ def test_dict_keys(self): self.assertTrue(type(keys[0]), str) self.assertEqual(keys[0], "'foo'") - @pytest.mark.skipif('six.PY3') + @pytest.mark.skipif('compat.PY3') def test_dict_keys_utf8_as_str(self): x = {'רונית מגן': 'bar'} @@ -102,14 +100,14 @@ def test_dict_keys_utf8_as_str(self): def test_dict_keys_utf8_as_unicode(self): x = { - six.text_type('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'): 'bar' + compat.text_type('\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'): 'bar' } result = transform(x) assert type(result) is dict keys = list(result.keys()) assert len(keys) == 1 - if six.PY3: + if compat.PY3: expected = "'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'" else: expected = "u'\u05e8\u05d5\u05e0\u05d9\u05ea \u05de\u05d2\u05df'" @@ -120,6 +118,16 @@ def test_uuid(self): result = transform(x) assert result == repr(x) + @pytest.mark.skipif('compat.PY3') + def test_recurse_exception(self): + class NonAsciiRepr(object): + def __repr__(self): + return compat.b('中文') + + x = [NonAsciiRepr()] + result = transform(x, max_depth=1) + self.assertEqual(json.dumps(result), compat.b('[""]')) + def test_recursive(self): x = [] x.append(x) @@ -130,12 +138,12 @@ def test_recursive(self): def test_custom_repr(self): class Foo(object): def __sentry__(self): - return six.u('example') + return compat.u('example') x = Foo() result = transform(x) - if six.PY3: + if compat.PY3: expected = "'example'" else: expected = "u'example'" @@ -156,7 +164,7 @@ def __repr__(self): def test_recursion_max_depth(self): x = [[[[1]]]] result = transform(x, max_depth=3) - if six.PY3: + if compat.PY3: expected = ((("'[1]'",),),) else: expected = ((("u'[1]'",),),) @@ -165,7 +173,7 @@ def test_recursion_max_depth(self): def test_list_max_length(self): x = list(range(10)) result = transform(x, list_max_length=3) - self.assertEqual(result, (0, 1, 2)) + self.assertEqual(result, ('0', '1', '2')) def test_dict_max_length(self): x = dict((x, x) for x in range(10)) @@ -174,15 +182,15 @@ def test_dict_max_length(self): self.assertEqual(len(result), 3) def test_string_max_length(self): - x = six.u('1234') + x = compat.u('1234') result = transform(x, string_max_length=3) - expected = "'123'" if six.PY3 else "u'123'" + expected = "'123'" if compat.PY3 else "u'123'" self.assertEqual(result, expected) def test_bytes_max_length(self): - x = six.b('\xd7\xd7\xd7\xd7\xd7\xd7') + x = compat.b('\xd7\xd7\xd7\xd7\xd7\xd7') result = transform(x, string_max_length=1) - if six.PY3: + if compat.PY3: assert result == "b'\\xd7'" else: assert result == "'\\xd7'" diff --git a/tests/utils/json/tests.py b/tests/utils/json/tests.py index 971503355..d85bc6286 100644 --- a/tests/utils/json/tests.py +++ b/tests/utils/json/tests.py @@ -2,6 +2,7 @@ import datetime import uuid +from decimal import Decimal from raven.utils.testutils import TestCase from raven.utils import json @@ -23,3 +24,27 @@ def test_set(self): def test_frozenset(self): res = frozenset(['foo', 'bar']) assert json.dumps(res) in ('["foo", "bar"]', '["bar", "foo"]') + + def test_unknown_type(self): + + class Unknown(object): + def __repr__(self): + return 'Unknown object' + + obj = Unknown() + assert json.dumps(obj) == '"Unknown object"' + + def test_unknown_type_with_repr_error(self): + + class Unknown(object): + def __repr__(self): + raise Exception + + obj = Unknown() + s = json.dumps(obj) + assert isinstance(s, str) + assert 'Unknown object at 0x' in s + + def test_decimal(self): + d = {'decimal': Decimal('123.45')} + assert json.dumps(d) == '{"decimal": "Decimal(\'123.45\')"}' diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index 122f6520a..9ab9d4e44 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import os.path + from mock import Mock +from raven.utils.compat import iterkeys, PY3 from raven.utils.testutils import TestCase -from raven.utils import six - -from raven.utils.stacks import get_culprit, get_stack_info, get_lines_from_file +from raven.utils.stacks import get_stack_info, get_lines_from_file class Context(object): @@ -14,34 +15,7 @@ def __init__(self, dict): __getitem__ = lambda s, *a: s.dict.__getitem__(*a) __setitem__ = lambda s, *a: s.dict.__setitem__(*a) - iterkeys = lambda s, *a: six.iterkeys(s.dict, *a) - - -class GetCulpritTest(TestCase): - def test_empty_module(self): - culprit = get_culprit([{ - 'module': None, - 'function': 'foo', - }]) - assert culprit == '? in foo' - - def test_empty_function(self): - culprit = get_culprit([{ - 'module': 'foo', - 'function': None, - }]) - assert culprit == 'foo in ?' - - def test_no_module_or_function(self): - culprit = get_culprit([{}]) - assert culprit is None - - def test_all_params(self): - culprit = get_culprit([{ - 'module': 'package.name', - 'function': 'foo', - }]) - assert culprit == 'package.name in foo' + iterkeys = lambda s, *a: iterkeys(s.dict, *a) class GetStackInfoTest(TestCase): @@ -58,27 +32,78 @@ def test_bad_locals_in_frame(self): frames = [(frame, 1)] results = get_stack_info(frames) - self.assertEquals(len(results), 1) - result = results[0] + assert len(results['frames']) == 1 + result = results['frames'][0] assert 'vars' in result - if six.PY3: + if PY3: expected = { - "'foo'": "'bar'", - "'biz'": "'baz'", + "foo": "'bar'", + "biz": "'baz'", } else: expected = { - "u'foo'": "u'bar'", - "u'biz'": "u'baz'", + "foo": "u'bar'", + "biz": "u'baz'", } assert result['vars'] == expected + def test_frame_allowance(self): + frames = [] + for x in range(10): + frame = Mock() + frame.f_locals = {'k': 'v'} + frame.f_lineno = None + frame.f_globals = {} + frame.f_code.co_filename = str(x) + frame.f_code.co_name = __name__ + frames.append((frame, 1)) + + results = get_stack_info(frames, frame_allowance=4) + assert len(results['frames']) == 10 + assert results['frames'][0]['filename'] == '0' + assert results['frames'][1]['filename'] == '1' + for idx, frame in enumerate(results['frames'][2:8]): + assert frame['filename'] == str(idx + 2) + assert 'vars' not in frame + assert results['frames'][8]['filename'] == '8' + assert results['frames'][9]['filename'] == '9' + + +class FailLoader(): + ''' + Recreating the built-in loaders from a fake stack trace was brittle. + This method ensures its testing the path where the loader is defined + but fails with known exceptions. + ''' + def get_source(self, module_name): + if '.py' in module_name: + raise ImportError('Cannot load .py files') + elif '.zip' in module_name: + raise IOError('Cannot load .zip files') + else: + raise ValueError('Invalid file extension') + class GetLineFromFileTest(TestCase): + def setUp(self): + self.loader = FailLoader() def test_non_ascii_file(self): - import os.path filename = os.path.join(os.path.dirname(__file__), 'utf8_file.txt') self.assertEqual( get_lines_from_file(filename, 3, 1), (['Some code here'], '', ['lorem ipsum'])) + + def test_missing_zip_get_source(self): + filename = 'does_not_exist.zip' + module = 'not.zip.loadable' + self.assertEqual( + get_lines_from_file(filename, 3, 1, self.loader, module), + (None, None, None)) + + def test_missing_get_source(self): + filename = 'does_not_exist.py' + module = 'not.py.loadable' + self.assertEqual( + get_lines_from_file(filename, 3, 1, self.loader, module), + (None, None, None)) diff --git a/tests/utils/test_conf.py b/tests/utils/test_conf.py new file mode 100644 index 000000000..0a9221e0a --- /dev/null +++ b/tests/utils/test_conf.py @@ -0,0 +1,20 @@ +from __future__ import absolute_import + +from raven.utils.conf import convert_options + + +def test_convert_options_parses_dict(): + options = convert_options({ + 'SENTRY_FOO': 'foo', + 'FOO': 'bar', + 'SENTRY_RELEASE': 'a', + 'SENTRY_IGNORE_EXCEPTIONS': [ + 'b', + ] + }, defaults={'environment': 'production'}) + + assert options['release'] == 'a' + assert options['ignore_exceptions'] == [ + 'b', + ] + assert options['environment'] == 'production' diff --git a/tests/utils/test_imports.py b/tests/utils/test_imports.py new file mode 100644 index 000000000..59f93087f --- /dev/null +++ b/tests/utils/test_imports.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import + +import raven + +from raven.utils.compat import text_type +from raven.utils.imports import import_string + + +def test_import_string(): + new_raven = import_string('raven') + assert new_raven is raven + + # this will test unicode on python2 + new_raven = import_string(text_type('raven')) + assert new_raven is raven + + new_client = import_string('raven.Client') + assert new_client is raven.Client + + # this will test unicode on python2 + new_client = import_string(text_type('raven.Client')) + assert new_client is raven.Client diff --git a/tests/utils/test_transaction.py b/tests/utils/test_transaction.py new file mode 100644 index 000000000..3ffe0cdb3 --- /dev/null +++ b/tests/utils/test_transaction.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import + +from raven.utils.transaction import TransactionStack + + +def test_simple(): + stack = TransactionStack() + + stack.push('foo') + + assert len(stack) == 1 + assert stack.peek() == 'foo' + + bar = stack.push(['bar']) + + assert len(stack) == 2 + assert stack.peek() == ['bar'] + + stack.push({'baz': True}) + + assert len(stack) == 3 + assert stack.peek() == {'baz': True} + + stack.pop(bar) + + assert len(stack) == 1 + assert stack.peek() == 'foo' + + stack.pop() + + assert len(stack) == 0 + assert stack.peek() == None + + +def test_context_manager(): + stack = TransactionStack() + + with stack('foo'): + assert stack.peek() == 'foo' + + assert stack.peek() is None diff --git a/tests/utils/wsgi/tests.py b/tests/utils/wsgi/tests.py index 5b2ffdc24..79022ae38 100644 --- a/tests/utils/wsgi/tests.py +++ b/tests/utils/wsgi/tests.py @@ -1,5 +1,5 @@ from raven.utils.testutils import TestCase -from raven.utils.wsgi import get_headers, get_host, get_environ +from raven.utils.wsgi import get_headers, get_host, get_environ, get_client_ip class GetHeadersTest(TestCase): @@ -84,3 +84,13 @@ def test_http_nonstandard_port(self): 'SERVER_PORT': '81', }) self.assertEquals(result, 'example.com:81') + + +class GetClientIpTest(TestCase): + def test_has_remote_addr(self): + result = get_client_ip({'REMOTE_ADDR': '127.0.0.1'}) + self.assertEquals(result, '127.0.0.1') + + def test_xff(self): + result = get_client_ip({'HTTP_X_FORWARDED_FOR': '1.1.1.1, 127.0.0.1'}) + self.assertEquals(result, '1.1.1.1') diff --git a/tests/versioning/__init__.py b/tests/versioning/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/versioning/tests.py b/tests/versioning/tests.py new file mode 100644 index 000000000..0878ef30c --- /dev/null +++ b/tests/versioning/tests.py @@ -0,0 +1,34 @@ +from __future__ import absolute_import + +import os.path +import pytest +import subprocess + +from raven.utils.compat import string_types +from raven.versioning import fetch_git_sha, fetch_package_version + + +# Python 2.6 does not contain subprocess.check_output +def check_output(cmd, **kwargs): + return subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + **kwargs + ).communicate()[0] + + +@pytest.mark.has_git_requirements +def test_fetch_git_sha(project_root): + result = fetch_git_sha(project_root) + assert result is not None + assert len(result) == 40 + assert isinstance(result, string_types) + assert result == check_output( + 'git rev-parse --verify HEAD', shell=True, cwd=project_root + ).decode('latin1').strip() + + +def test_fetch_package_version(): + result = fetch_package_version('raven') + assert result is not None + assert isinstance(result, string_types) diff --git a/tox.ini b/tox.ini index df54ed964..1358c870a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,157 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py30, py31, py32, py33, pypy +envlist = + # core + py{27,34,35,36,37} + pypy + flake8 + # contrib + {py35,py36,py37}-django-{200} + {py27,py35}-django-111 + {py27,py34,py35,py36}-django-{18,19,110} + {py27,py34,py35,py36}-django-18 + {py27,py34}-django-17 + py27-django-16 + {py27,py35,py36,py37}-flask-{10,11} + py37-flask-12 + py37-flask-dev + py27-celery-{3,4} + py{27,37}-lambda + py{35,36,37}-sanic-07 [testenv] -commands = python setup.py test +deps = + py27: gevent + django-{16,17,18}: pytest-django<3.0 + django-{19,110,110,111,200,dev}: pytest-django>=3.0,<3.3 + django-{18,19,110}: django-tastypie==0.14 + django-16: Django>=1.6,<1.7 + django-17: Django>=1.7,<1.8 + django-18: Django>=1.8,<1.9 + django-19: Django>=1.9,<1.10 + django-110: Django>=1.10,<1.11 + django-111: Django>=1.11,<1.12 + django-200: Django>=2.0,<2.1 + flask-10: Flask>=0.10,<0.11 + flask-11: Flask>=0.11,<0.12 + flask-12: Flask>=0.12,<0.13 + flask-dev: git+https://github.com/pallets/flask.git#egg=flask + flask-dev: flask-login + celery-3: Celery>=3.1,<3.2 + celery-4: Celery>=4.0,<4.1 + sanic-07: sanic>=0.7,<0.8 + sanic-07: aiohttp + # fix: git+https://github.com/pytest-dev/pytest-django.git#egg=pytest_django +setenv = + PYTHONDONTWRITEBYTECODE=1 + TESTPATH=tests + DJANGO_SETTINGS_MODULE=tests.contrib.django.settings + django: TESTPATH=tests/contrib/django + flask: TESTPATH=tests/contrib/flask + sanic: TESTPATH=tests/contrib/sanic + celery: TESTPATH=tests/contrib/test_celery.py + lambda: TESTPATH=tests/contrib/awslambda +usedevelop = true +extras = tests + +basepython = + py27: python2.7 + py34: python3.4 + py35: python3.5 + py36: python3.6 + py37: python3.7 + pypy: pypy + +commands = + py.test {env:TESTPATH} {posargs} --max-slave-restart=4 + +[testenv:pypy] +commands: + pypy: py.test {env:TESTPATH} {posargs} -n0 + +# Linters + + + +[testenv:flake8] +basepython = python3.7 +skip_install = true +deps = + flake8 + flake8-import-order>=0.9 +commands = + flake8 raven/ setup.py + +[testenv:pylint] +basepython = python3.6 +skip_install = true +deps = + pyflakes + pylint +commands = + pylint raven/ setup.py + + +[testenv:bandit] +basepython = python3.6 +skip_install = true +deps = + bandit +commands = + bandit -r raven/ -c .bandit.yml + +[testenv:linters] +basepython = python3.6 +skip_install = true +deps = + {[testenv:flake8]deps} + {[testenv:pylint]deps} + {[testenv:readme]deps} + {[testenv:bandit]deps} +commands = + {[testenv:flake8]commands} + {[testenv:pylint]commands} + {[testenv:readme]commands} + {[testenv:bandit]commands} + +[testenv:readme] +basepython = python3.6 +deps = + readme_renderer +commands = + python setup.py check -r -s + +# Release tooling +[testenv:build] +basepython = python3.6 +skip_install = true +deps = + wheel + setuptools +commands = + python setup.py -q sdist bdist_wheel + +[testenv:release] +basepython = python3.6 +skip_install = true +deps = + bumpversion +commands = + bumpversion --tag --commit {posargs} release + +[testenv:minor] +basepython = python3.6 +skip_install = true +deps = + bumpversion +commands = + bumpversion --commit {posargs} minor + +[testenv:dev] +basepython = python3.6 +skip_install = true +deps = + bumpversion +commands = + bumpversion {posargs} --commit {posargs} patch