diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/community/crypto | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/community/crypto')
338 files changed, 54029 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/README.md b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/README.md new file mode 100644 index 00000000..385e70ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/README.md @@ -0,0 +1,3 @@ +## Azure Pipelines Configuration + +Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information. diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml new file mode 100644 index 00000000..70f31cd9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml @@ -0,0 +1,270 @@ +trigger: + batch: true + branches: + include: + - main + - stable-* + +pr: + autoCancel: true + branches: + include: + - main + - stable-* + +schedules: + - cron: 0 9 * * * + displayName: Nightly + always: true + branches: + include: + - main + - stable-* + +variables: + - name: checkoutPath + value: ansible_collections/community/crypto + - name: coverageBranches + value: main + - name: pipelinesCoverage + value: coverage + - name: entryPoint + value: tests/utils/shippable/shippable.sh + - name: fetchDepth + value: 0 + +resources: + containers: + - container: default + image: quay.io/ansible/azure-pipelines-test-container:1.7.1 + +pool: Standard + +stages: +### Sanity & units + - stage: Ansible_devel + displayName: Sanity & Units devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: 'devel/sanity/1' + - name: Sanity Extra # Only on devel + test: 'devel/sanity/extra' + - name: Units + test: 'devel/units/1' + - stage: Ansible_2_10 + displayName: Sanity & Units 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.10/sanity/1' + - name: Units + test: '2.10/units/1' + - stage: Ansible_2_9 + displayName: Sanity & Units 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + targets: + - name: Sanity + test: '2.9/sanity/1' + - name: Units + test: '2.9/units/1' +### Docker + - stage: Docker_devel + displayName: Docker devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: devel/linux/{0}/1 + targets: + - name: CentOS 6 + test: centos6 + - name: CentOS 7 + test: centos7 + - name: CentOS 8 + test: centos8 + - name: Fedora 30 + test: fedora30 + - name: Fedora 31 + test: fedora31 + - name: Fedora 32 + test: fedora32 + - name: openSUSE 15 py2 + test: opensuse15py2 + - name: openSUSE 15 py3 + test: opensuse15 + - name: Ubuntu 16.04 + test: ubuntu1604 + - name: Ubuntu 18.04 + test: ubuntu1804 + - name: Ubuntu 20.04 + test: ubuntu2004 + - stage: Docker_2_10 + displayName: Docker 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.10/linux/{0}/1 + targets: + - name: CentOS 6 + test: centos6 + - name: CentOS 7 + test: centos7 + - name: CentOS 8 + test: centos8 + - name: Fedora 30 + test: fedora30 + - name: Fedora 31 + test: fedora31 + - name: Fedora 32 + test: fedora32 + - name: openSUSE 15 py2 + test: opensuse15py2 + - name: openSUSE 15 py3 + test: opensuse15 + - name: Ubuntu 16.04 + test: ubuntu1604 + - name: Ubuntu 18.04 + test: ubuntu1804 + - stage: Docker_2_9 + displayName: Docker 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.9/linux/{0}/1 + targets: + - name: CentOS 6 + test: centos6 + - name: CentOS 7 + test: centos7 + - name: CentOS 8 + test: centos8 + - name: Fedora 30 + test: fedora30 + - name: Fedora 31 + test: fedora31 + # fedora32 doesn't exist in 2.9 +# - name: Fedora 32 +# test: fedora32 + - name: openSUSE 15 py2 + test: opensuse15py2 + - name: openSUSE 15 py3 + test: opensuse15 + - name: Ubuntu 16.04 + test: ubuntu1604 + - name: Ubuntu 18.04 + test: ubuntu1804 + +### Remote + - stage: Remote_devel + displayName: Remote devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: devel/{0}/1 + targets: + - name: OS X 10.11 + test: osx/10.11 + - name: macOS 10.15 + test: macos/10.15 + - name: macOS 11.1 + test: macos/11.1 + - name: RHEL 7.8 + test: rhel/7.8 + - name: RHEL 8.2 + test: rhel/8.2 + - name: FreeBSD 11.4 + test: freebsd/11.4 + - name: FreeBSD 12.2 + test: freebsd/12.2 + - stage: Remote_2_10 + displayName: Remote 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.10/{0}/1 + targets: + - name: RHEL 7.8 + test: rhel/7.8 + - name: FreeBSD 12.1 + test: freebsd/12.1 + - stage: Remote_2_9 + displayName: Remote 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + testFormat: 2.9/{0}/1 + targets: + - name: 'RHEL 7.8' + test: 'rhel/7.8' +### cloud + - stage: Cloud_devel + displayName: Cloud devel + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + nameFormat: Python {0} + testFormat: devel/cloud/{0}/1 + targets: + - test: 2.6 + - test: 2.7 + - test: 3.5 + - test: 3.6 + - test: 3.7 + - test: 3.8 + - test: 3.9 + - stage: Cloud_2_10 + displayName: Cloud 2.10 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + nameFormat: Python {0} + testFormat: 2.10/cloud/{0}/1 + targets: + - test: 3.6 + - stage: Cloud_2_9 + displayName: Cloud 2.9 + dependsOn: [] + jobs: + - template: templates/matrix.yml + parameters: + nameFormat: Python {0} + testFormat: 2.9/cloud/{0}/1 + targets: + - test: 3.5 + + ## Finally + + - stage: Summary + condition: succeededOrFailed() + dependsOn: + - Ansible_devel + - Ansible_2_10 + - Ansible_2_9 + - Remote_devel + - Docker_devel + - Cloud_devel + - Remote_2_10 + - Docker_2_10 + - Cloud_2_10 + - Remote_2_9 + - Docker_2_9 + - Cloud_2_9 + jobs: + - template: templates/coverage.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/aggregate-coverage.sh b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/aggregate-coverage.sh new file mode 100755 index 00000000..f3113dd0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/aggregate-coverage.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Aggregate code coverage results for later processing. + +set -o pipefail -eu + +agent_temp_directory="$1" + +PATH="${PWD}/bin:${PATH}" + +mkdir "${agent_temp_directory}/coverage/" + +options=(--venv --venv-system-site-packages --color -v) + +ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}" + +if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then + # Only analyze coverage if the installed version of ansible-test supports it. + # Doing so allows this script to work unmodified for multiple Ansible versions. + ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}" +fi diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/combine-coverage.py b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/combine-coverage.py new file mode 100755 index 00000000..506ade64 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/combine-coverage.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +""" +Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job. +Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}" +The recommended coverage artifact name format is: Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName) +Keep in mind that Azure Pipelines does not enforce unique job display names (only names). +It is up to pipeline authors to avoid name collisions when deviating from the recommended format. +""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import re +import shutil +import sys + + +def main(): + """Main program entry point.""" + source_directory = sys.argv[1] + + if '/ansible_collections/' in os.getcwd(): + output_path = "tests/output" + else: + output_path = "test/results" + + destination_directory = os.path.join(output_path, 'coverage') + + if not os.path.exists(destination_directory): + os.makedirs(destination_directory) + + jobs = {} + count = 0 + + for name in os.listdir(source_directory): + match = re.search('^Coverage (?P<attempt>[0-9]+) (?P<label>.+)$', name) + label = match.group('label') + attempt = int(match.group('attempt')) + jobs[label] = max(attempt, jobs.get(label, 0)) + + for label, attempt in jobs.items(): + name = 'Coverage {attempt} {label}'.format(label=label, attempt=attempt) + source = os.path.join(source_directory, name) + source_files = os.listdir(source) + + for source_file in source_files: + source_path = os.path.join(source, source_file) + destination_path = os.path.join(destination_directory, source_file + '.' + label) + print('"%s" -> "%s"' % (source_path, destination_path)) + shutil.copyfile(source_path, destination_path) + count += 1 + + print('Coverage file count: %d' % count) + print('##vso[task.setVariable variable=coverageFileCount]%d' % count) + print('##vso[task.setVariable variable=outputPath]%s' % output_path) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/process-results.sh b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/process-results.sh new file mode 100755 index 00000000..f3f1d1ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/process-results.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Check the test results and set variables for use in later steps. + +set -o pipefail -eu + +if [[ "$PWD" =~ /ansible_collections/ ]]; then + output_path="tests/output" +else + output_path="test/results" +fi + +echo "##vso[task.setVariable variable=outputPath]${output_path}" + +if compgen -G "${output_path}"'/junit/*.xml' > /dev/null; then + echo "##vso[task.setVariable variable=haveTestResults]true" +fi + +if compgen -G "${output_path}"'/bot/ansible-test-*' > /dev/null; then + echo "##vso[task.setVariable variable=haveBotResults]true" +fi + +if compgen -G "${output_path}"'/coverage/*' > /dev/null; then + echo "##vso[task.setVariable variable=haveCoverageData]true" +fi diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/publish-codecov.sh b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/publish-codecov.sh new file mode 100755 index 00000000..7aeabda0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/publish-codecov.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Upload code coverage reports to codecov.io. +# Multiple coverage files from multiple languages are accepted and aggregated after upload. +# Python coverage, as well as PowerShell and Python stubs can all be uploaded. + +set -o pipefail -eu + +output_path="$1" + +curl --silent --show-error https://codecov.io/bash > codecov.sh + +for file in "${output_path}"/reports/coverage*.xml; do + name="${file}" + name="${name##*/}" # remove path + name="${name##coverage=}" # remove 'coverage=' prefix if present + name="${name%.xml}" # remove '.xml' suffix + + bash codecov.sh \ + -f "${file}" \ + -n "${name}" \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" +done diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/report-coverage.sh b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/report-coverage.sh new file mode 100755 index 00000000..1bd91bdc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/report-coverage.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Generate code coverage reports for uploading to Azure Pipelines and codecov.io. + +set -o pipefail -eu + +PATH="${PWD}/bin:${PATH}" + +if ! ansible-test --help >/dev/null 2>&1; then + # Install the devel version of ansible-test for generating code coverage reports. + # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs). + # Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used. + pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +fi + +ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/run-tests.sh b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/run-tests.sh new file mode 100755 index 00000000..a947fdf0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/run-tests.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Configure the test environment and run the tests. + +set -o pipefail -eu + +entry_point="$1" +test="$2" +read -r -a coverage_branches <<< "$3" # space separated list of branches to run code coverage on for scheduled builds + +export COMMIT_MESSAGE +export COMPLETE +export COVERAGE +export IS_PULL_REQUEST + +if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then + IS_PULL_REQUEST=true + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2) +else + IS_PULL_REQUEST= + COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD) +fi + +COMPLETE= +COVERAGE= + +if [ "${BUILD_REASON}" = "Schedule" ]; then + COMPLETE=yes + + if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then + COVERAGE=yes + fi +fi + +"${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py" diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/time-command.py b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/time-command.py new file mode 100755 index 00000000..5e8eb8d4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/scripts/time-command.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +"""Prepends a relative timestamp to each input line from stdin and writes it to stdout.""" + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import time + + +def main(): + """Main program entry point.""" + start = time.time() + + sys.stdin.reconfigure(errors='surrogateescape') + sys.stdout.reconfigure(errors='surrogateescape') + + for line in sys.stdin: + seconds = time.time() - start + sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line)) + sys.stdout.flush() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/coverage.yml b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/coverage.yml new file mode 100644 index 00000000..1864e444 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/coverage.yml @@ -0,0 +1,39 @@ +# This template adds a job for processing code coverage data. +# It will upload results to Azure Pipelines and codecov.io. +# Use it from a job stage that completes after all other jobs have completed. +# This can be done by placing it in a separate summary stage that runs after the test stage(s) have completed. + +jobs: + - job: Coverage + displayName: Code Coverage + container: default + workspace: + clean: all + steps: + - checkout: self + fetchDepth: $(fetchDepth) + path: $(checkoutPath) + - task: DownloadPipelineArtifact@2 + displayName: Download Coverage Data + inputs: + path: coverage/ + patterns: "Coverage */*=coverage.combined" + - bash: .azure-pipelines/scripts/combine-coverage.py coverage/ + displayName: Combine Coverage Data + - bash: .azure-pipelines/scripts/report-coverage.sh + displayName: Generate Coverage Report + condition: gt(variables.coverageFileCount, 0) + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: Cobertura + # Azure Pipelines only accepts a single coverage data file. + # That means only Python or PowerShell coverage can be uploaded, but not both. + # Set the "pipelinesCoverage" variable to determine which type is uploaded. + # Use "coverage" for Python and "coverage-powershell" for PowerShell. + summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml" + displayName: Publish to Azure Pipelines + condition: gt(variables.coverageFileCount, 0) + - bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)" + displayName: Publish to codecov.io + condition: gt(variables.coverageFileCount, 0) + continueOnError: true diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/matrix.yml b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/matrix.yml new file mode 100644 index 00000000..4e9555dd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/matrix.yml @@ -0,0 +1,55 @@ +# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template. +# If this matrix template does not provide the required functionality, consider using the test template directly instead. + +parameters: + # A required list of dictionaries, one per test target. + # Each item in the list must contain a "test" or "name" key. + # Both may be provided. If one is omitted, the other will be used. + - name: targets + type: object + + # An optional list of values which will be used to multiply the targets list into a matrix. + # Values can be strings or numbers. + - name: groups + type: object + default: [] + + # An optional format string used to generate the job name. + # - {0} is the name of an item in the targets list. + - name: nameFormat + type: string + default: "{0}" + + # An optional format string used to generate the test name. + # - {0} is the name of an item in the targets list. + - name: testFormat + type: string + default: "{0}" + + # An optional format string used to add the group to the job name. + # {0} is the formatted name of an item in the targets list. + # {{1}} is the group -- be sure to include the double "{{" and "}}". + - name: nameGroupFormat + type: string + default: "{0} - {{1}}" + + # An optional format string used to add the group to the test name. + # {0} is the formatted test of an item in the targets list. + # {{1}} is the group -- be sure to include the double "{{" and "}}". + - name: testGroupFormat + type: string + default: "{0}/{{1}}" + +jobs: + - template: test.yml + parameters: + jobs: + - ${{ if eq(length(parameters.groups), 0) }}: + - ${{ each target in parameters.targets }}: + - name: ${{ format(parameters.nameFormat, coalesce(target.name, target.test)) }} + test: ${{ format(parameters.testFormat, coalesce(target.test, target.name)) }} + - ${{ if not(eq(length(parameters.groups), 0)) }}: + - ${{ each group in parameters.groups }}: + - ${{ each target in parameters.targets }}: + - name: ${{ format(format(parameters.nameGroupFormat, parameters.nameFormat), coalesce(target.name, target.test), group) }} + test: ${{ format(format(parameters.testGroupFormat, parameters.testFormat), coalesce(target.test, target.name), group) }} diff --git a/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/test.yml b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/test.yml new file mode 100644 index 00000000..5250ed80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/.azure-pipelines/templates/test.yml @@ -0,0 +1,45 @@ +# This template uses the provided list of jobs to create test one or more test jobs. +# It can be used directly if needed, or through the matrix template. + +parameters: + # A required list of dictionaries, one per test job. + # Each item in the list must contain a "job" and "name" key. + - name: jobs + type: object + +jobs: + - ${{ each job in parameters.jobs }}: + - job: test_${{ replace(replace(replace(job.test, '/', '_'), '.', '_'), '-', '_') }} + displayName: ${{ job.name }} + container: default + workspace: + clean: all + steps: + - checkout: self + fetchDepth: $(fetchDepth) + path: $(checkoutPath) + - bash: .azure-pipelines/scripts/run-tests.sh "$(entryPoint)" "${{ job.test }}" "$(coverageBranches)" + displayName: Run Tests + - bash: .azure-pipelines/scripts/process-results.sh + condition: succeededOrFailed() + displayName: Process Results + - bash: .azure-pipelines/scripts/aggregate-coverage.sh "$(Agent.TempDirectory)" + condition: eq(variables.haveCoverageData, 'true') + displayName: Aggregate Coverage Data + - task: PublishTestResults@2 + condition: eq(variables.haveTestResults, 'true') + inputs: + testResultsFiles: "$(outputPath)/junit/*.xml" + displayName: Publish Test Results + - task: PublishPipelineArtifact@1 + condition: eq(variables.haveBotResults, 'true') + displayName: Publish Bot Results + inputs: + targetPath: "$(outputPath)/bot/" + artifactName: "Bot $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)" + - task: PublishPipelineArtifact@1 + condition: eq(variables.haveCoverageData, 'true') + displayName: Publish Coverage Data + inputs: + targetPath: "$(Agent.TempDirectory)/coverage/" + artifactName: "Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)" diff --git a/collections-debian-merged/ansible_collections/community/crypto/CHANGELOG.rst b/collections-debian-merged/ansible_collections/community/crypto/CHANGELOG.rst new file mode 100644 index 00000000..1361ae8a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/CHANGELOG.rst @@ -0,0 +1,218 @@ +============================== +Community Crypto Release Notes +============================== + +.. contents:: Topics + + +v1.4.0 +====== + +Release Summary +--------------- + +Release with several new features and bugfixes. + +Minor Changes +------------- + +- The ACME module_utils has been relicensed back from the Simplified BSD License (https://opensource.org/licenses/BSD-2-Clause) to the GPLv3+ (same license used by most other code in this collection). This undoes a licensing change when the original GPLv3+ licensed code was moved to module_utils in https://github.com/ansible/ansible/pull/40697 (https://github.com/ansible-collections/community.crypto/pull/165). +- The ``crypto/identify.py`` module_utils has been renamed to ``crypto/pem.py`` (https://github.com/ansible-collections/community.crypto/pull/166). +- luks_device - ``new_keyfile``, ``new_passphrase``, ``remove_keyfile`` and ``remove_passphrase`` are now idempotent (https://github.com/ansible-collections/community.crypto/issues/19, https://github.com/ansible-collections/community.crypto/pull/168). +- luks_device - allow to configure PBKDF (https://github.com/ansible-collections/community.crypto/pull/163). +- openssl_csr, openssl_csr_pipe - allow to specify CRL distribution endpoints with ``crl_distribution_points`` (https://github.com/ansible-collections/community.crypto/issues/147, https://github.com/ansible-collections/community.crypto/pull/167). +- openssl_pkcs12 - allow to specify certificate bundles in ``other_certificates`` by using new option ``other_certificates_parse_all`` (https://github.com/ansible-collections/community.crypto/issues/149, https://github.com/ansible-collections/community.crypto/pull/166). + +Bugfixes +-------- + +- acme_certificate - error when requested challenge type is not found for non-valid challenges, instead of hanging on step 2 (https://github.com/ansible-collections/community.crypto/issues/171, https://github.com/ansible-collections/community.crypto/pull/173). + +v1.3.0 +====== + +Release Summary +--------------- + +Contains new modules ``openssl_privatekey_pipe``, ``openssl_csr_pipe`` and ``x509_certificate_pipe`` which allow to create or update private keys, CSRs and X.509 certificates without having to write them to disk. + + +Minor Changes +------------- + +- openssh_cert - add module parameter ``use_agent`` to enable using signing keys stored in ssh-agent (https://github.com/ansible-collections/community.crypto/issues/116). +- openssl_csr - refactor module to allow code re-use by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123). +- openssl_privatekey - refactor module to allow code re-use by openssl_privatekey_pipe (https://github.com/ansible-collections/community.crypto/pull/119). +- openssl_privatekey - the elliptic curve ``secp192r1`` now triggers a security warning. Elliptic curves of at least 224 bits should be used for new keys; see `here <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#elliptic-curves>`_ (https://github.com/ansible-collections/community.crypto/pull/132). +- x509_certificate - for the ``selfsigned`` provider, a CSR is not required anymore. If no CSR is provided, the module behaves as if a minimal CSR which only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, https://github.com/ansible-collections/community.crypto/pull/129). +- x509_certificate - refactor module to allow code re-use by x509_certificate_pipe (https://github.com/ansible-collections/community.crypto/pull/135). + +Bugfixes +-------- + +- openssl_pkcs12 - report the correct state when ``action`` is ``parse`` (https://github.com/ansible-collections/community.crypto/issues/143). +- support code - improve handling of certificate and certificate signing request (CSR) loading with the ``cryptography`` backend when errors occur (https://github.com/ansible-collections/community.crypto/issues/138, https://github.com/ansible-collections/community.crypto/pull/139). +- x509_certificate - fix ``entrust`` provider, which was broken since community.crypto 0.1.0 due to a feature added before the collection move (https://github.com/ansible-collections/community.crypto/pull/135). + +New Modules +----------- + +- openssl_csr_pipe - Generate OpenSSL Certificate Signing Request (CSR) +- openssl_privatekey_pipe - Generate OpenSSL private keys without disk access +- x509_certificate_pipe - Generate and/or check OpenSSL certificates + +v1.2.0 +====== + +Release Summary +--------------- + +Please note that this release fixes a security issue (CVE-2020-25646). + +Minor Changes +------------- + +- acme_certificate - allow to pass CSR file as content with new option ``csr_content`` (https://github.com/ansible-collections/community.crypto/pull/115). +- x509_certificate_info - add ``fingerprints`` return value which returns certificate fingerprints (https://github.com/ansible-collections/community.crypto/pull/121). + +Security Fixes +-------------- + +- openssl_csr - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). +- openssl_privatekey_info - the option ``content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). +- openssl_publickey - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). +- openssl_signature - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). +- x509_certificate - the options ``privatekey_content`` and ``ownca_privatekey_content`` were not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). +- x509_crl - the option ``privatekey_content`` was not marked as ``no_log``, resulting in it being dumped into the system log by default, and returned in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + +Bugfixes +-------- + +- openssl_pkcs12 - do not crash when reading PKCS#12 file which has no private key and/or no main certificate (https://github.com/ansible-collections/community.crypto/issues/103). + +v1.1.1 +====== + +Release Summary +--------------- + +Bugfixes for Ansible 2.10.0. + +Bugfixes +-------- + +- meta/runtime.yml - convert Ansible version numbers for old names of modules to collection version numbers (https://github.com/ansible-collections/community.crypto/pull/108). +- openssl_csr - improve handling of IDNA errors (https://github.com/ansible-collections/community.crypto/issues/105). + +v1.1.0 +====== + +Release Summary +--------------- + +Release for Ansible 2.10.0. + + +Minor Changes +------------- + +- acme_account - add ``external_account_binding`` option to allow creation of ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89). +- acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain`` parameter (https://github.com/ansible-collections/community.crypto/pull/102). +- cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39). +- get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69). +- luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97). +- openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95). +- openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness for serial numbers, instead of a random number between 1000 and 99999. Please note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76). +- openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46). +- openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46). + +Bugfixes +-------- + +- acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86). +- get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used (https://github.com/ansible-collections/community.crypto/pull/84). +- openssl_*, x509_* modules - fix handling of general names which refer to IP networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92). + +New Modules +----------- + +- openssl_signature - Sign data with openssl +- openssl_signature_info - Verify signatures with openssl + +v1.0.0 +====== + +Release Summary +--------------- + +This is the first proper release of the ``community.crypto`` collection. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0. + + +Minor Changes +------------- + +- luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``. +- luks_device - add ``keysize`` parameter to set key size at LUKS container creation +- luks_device - added support to use UUIDs, and labels with LUKS2 containers +- luks_device - added the ``type`` option that allows user explicit define the LUKS container format version +- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``. +- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys. +- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names. +- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be neccessary.) +- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0 +- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options. +- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``. +- openssl_certificate_info - allow to provide certificate content via ``content`` option (https://github.com/ansible/ansible/issues/64776). +- openssl_csr - Add support for specifying the SAN ``otherName`` value in the OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``. +- openssl_csr - allow to provide private key content via ``private_key_content`` option. +- openssl_csr - allow to return the existing/generated CSR directly as ``csr`` by setting ``return_content`` to ``yes``. +- openssl_csr_info - allow to provide CSR content via ``content`` option. +- openssl_dhparam - allow to return the existing/generated DH params directly as ``dhparams`` by setting ``return_content`` to ``yes``. +- openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection can be overwritten with the ``select_crypto_backend`` option. +- openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as ``pkcs12`` by setting ``return_content`` to ``yes``. +- openssl_privatekey - add ``format`` and ``format_mismatch`` options. +- openssl_privatekey - allow to return the existing/generated private key directly as ``privatekey`` by setting ``return_content`` to ``yes``. +- openssl_privatekey - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys. +- openssl_privatekey_info - allow to provide private key content via ``content`` option. +- openssl_publickey - allow to provide private key content via ``private_key_content`` option. +- openssl_publickey - allow to return the existing/generated public key directly as ``publickey`` by setting ``return_content`` to ``yes``. + +Deprecated Features +------------------- + +- openssl_csr - all values for the ``version`` option except ``1`` are deprecated. The value 1 denotes the current only standardized CSR version. + +Removed Features (previously deprecated) +---------------------------------------- + +- The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead. + +Bugfixes +-------- + +- ACME modules: fix bug in ACME v1 account update code +- ACME modules: make sure some connection errors are handled properly +- ACME modules: support Buypass' ACME v1 endpoint +- acme_certificate - fix crash when module is used with Python 2.x. +- acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account`` set to ``false``. +- ecs_certificate - Always specify header ``connection: keep-alive`` for ECS API connections. +- ecs_certificate - Fix formatting of contents of ``full_chain_path``. +- get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900) +- openssh_keypair - add logic to avoid breaking password protected keys. +- openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969). +- openssh_keypair - public key's file attributes (permissions, owner, group, etc.) are now set to the same values as the private key. +- openssl_* modules - prevent crash on fingerprint determination in FIPS mode (https://github.com/ansible/ansible/issues/67213). +- openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive`` header for ECS API connections. +- openssl_certificate - ``provider`` option was documented as required, but it was not checked whether it was provided. It is now only required when ``state`` is ``present``. +- openssl_certificate - fix ``assertonly`` provider certificate verification, causing 'private key mismatch' and 'subject mismatch' errors. +- openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support for ``cryptography`` backend. This probably needs at least cryptography 2.8, since older versions have problems with signing certificates or CSRs with such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984) +- openssl_csr - a warning is issued if an unsupported value for ``version`` is used for the ``cryptography`` backend. +- openssl_csr - the module will now enforce that ``privatekey_path`` is specified when ``state=present``. +- openssl_publickey - fix a module crash caused when pyOpenSSL is not installed (https://github.com/ansible/ansible/issues/67035). + +New Modules +----------- + +- ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API +- x509_crl - Generate Certificate Revocation Lists (CRLs) +- x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs) diff --git a/collections-debian-merged/ansible_collections/community/crypto/COPYING b/collections-debian-merged/ansible_collections/community/crypto/COPYING new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/collections-debian-merged/ansible_collections/community/crypto/FILES.json b/collections-debian-merged/ansible_collections/community/crypto/FILES.json new file mode 100644 index 00000000..a4ccc896 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/FILES.json @@ -0,0 +1,3603 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/templates", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/templates/matrix.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4fb0d3ffb2125d5806c7597e4f9d4b2af69cf8c337e9d57803081eddd4a6b081", + "format": 1 + }, + { + "name": ".azure-pipelines/templates/coverage.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "daf1930264760d47b54588f05c6339fd69ca2d239c77c44bc4cee3c4e9f76447", + "format": 1 + }, + { + "name": ".azure-pipelines/templates/test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cfa1271f94c71f05ffa0b1f763d8946394b5636e14579cda8ee14bb38bbcf1c", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/report-coverage.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f6a373322759ccc2736fb25d25d8c402dfe16b5d9a57cfccb1ca8cb136e09663", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/run-tests.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cb08a3ec5715b00d476ae6d63ca22e11a9ad8887239439937d2a7ea342e5a623", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/time-command.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0232f415efeb583ddff907c058986963b775441eaf129d7162aee0acb0d36834", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/process-results.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c03d7273fe58882a439b6723e92ab89f1e127772b5ce35aa67c546dd62659741", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/publish-codecov.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2662ee5039b851fd66c069ff6704024951ced29fa04bf1e2df5b75f18fc2a32b", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/aggregate-coverage.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "820353ffde6fd3ad655118772547549d84ccf0a7ba951e8fb1325f912ef640a0", + "format": 1 + }, + { + "name": ".azure-pipelines/scripts/combine-coverage.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e34d4e863a65b9f53c4ca8ae37655858969898a949e050e9cb3cb0d5f02342d0", + "format": 1 + }, + { + "name": ".azure-pipelines/azure-pipelines.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28ce5e1fda2614db4f4c46195b214e98710ba8395f6cdd08935e325e4c088e7b", + "format": 1 + }, + { + "name": ".azure-pipelines/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "61f20decd3c8fb34ac2cc6ff79f598fc5136e642130a7ba065ccc5aa37960cd2", + "format": 1 + }, + { + "name": "changelogs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/changelog.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ea4ee23a5a8611005f107955025269baa930cb28129602164ef439447f2a80b8", + "format": 1 + }, + { + "name": "changelogs/config.yaml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b42f3b49a4e0bb3a5d81c1b05fb59df1d6265515d0d2af1c0691c2c971365410", + "format": 1 + }, + { + "name": "changelogs/fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "changelogs/fragments/.keep", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "COPYING", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986", + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.10.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c104be4d4528e067a83cc811a6c1ac8f46a0eb8ff25b65e4778e8941bbf7227", + "format": 1 + }, + { + "name": "tests/sanity/extra", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/sanity/extra/no-unwanted-files.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a3d3b17f699b042958c7cd845a9d685bc935d83062e0bcf077f2c7200e2c0bac", + "format": 1 + }, + { + "name": "tests/sanity/extra/no-unwanted-files.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f1468e7b22ba353d18fcf2f5b18607873f792de629f887798f081eb6e2cd54fc", + "format": 1 + }, + { + "name": "tests/sanity/extra/update-bundled.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f4300d0a33586251c48b819bb0fedd56f6ce7d530e02d5f1083466997ced1b65", + "format": 1 + }, + { + "name": "tests/sanity/extra/update-bundled.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c15842e2db94a656d72c78597ebdf79c55a397be2077c99ec5ff69f7e5b5036a", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.11.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4c104be4d4528e067a83cc811a6c1ac8f46a0eb8ff25b65e4778e8941bbf7227", + "format": 1 + }, + { + "name": "tests/sanity/ignore-2.9.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7367a19f8827b44795c9e7096ed5d32ff31d157802258406ea024aafb9e71df", + "format": 1 + }, + { + "name": "tests/integration", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cd3076e371e4349b6aa86b42af5e25136d4f9e496958cadadba3b4957c0e2fc", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_pipe/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "babf77aa101e399e75c329f441bc8cc673d31be0be32b0cdb8e1336076b0280e", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7d57ce61e6260a2323345c4a0d31313443d983e2c8c53f6b2f54cf4f3c6a3acf", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e8d632f9db7209967c5b2f6d734bede09841acc7b898dafc19f31c72cee9929", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e0cbb87523e9519e438cb93fa0a3b02465dd13565f25e12ed18f3eb789f2ed68", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/tasks/default.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73b2bfc5c2044f9d484d1f50a15908f4a66bbe5a01c0ae9b5a556eda6f6fc691", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_http_tests/vars/httptester.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ee7232593b60212e0e400015c9d479c5f5193f14c8868b044ceffceb7a54196a", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ee2bf2265d6c72b5bc0cfd302a7009d114b2e839694b2d76f4fe262be92a9160", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "84184eb304081aaf488cc579d70ac19356e64761fcb055ac5c22904d5c2c7a83", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93d35711506514580f06f58e3ed81fa60841e66f19aaf419b4ee8776c462a06c", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1be359d1fd2f8c5a8a1307b4ee9bc146205234705730d134f23b44cefe350dbe", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_certificate/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b1538c45c090406a1439139622bc726008ed5434212f0cf9b66fa410bd954f61", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bfaec5c7ef6ff16330977c61fc10eb82f5fc3dad800e2d3e86730b87ec0a6e3c", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_pipe/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d6ae0d7f4ba8bb3d4498a7677ea2e26c11e85740562ab9780caa84bf12e5ee39", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/files/cert1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e356f2747f331e35160a278a2d90e619e095cb6beba645692dfd6ed75640d426", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b0cf9b7c3d7a3adfdf7fdbaa954beea937f6df4613e92b3d976ed5b1bb0981dc", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "571ec856252620dc915e6984d75fc0763faf1e42cb8289916da873fbf0959382", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/test_plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate_info/test_plugins/jinja_compatibility.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad199784d27da441772f5c34381a393c5e528ecad78c2084ab6438c1cbc28da2", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b2bb1da7629d19af4eb1a19a878b27539bfa5c839da26f13a47d532ca539781f", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9637dec54b4e05789f47d4f41c91cf96e2c3a9387600dd7465c1a3e3647d11b9", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e73687003033fa29373cb78c5a4cb9d583068c2e0b9fbb1c6ee6b888cc954934", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f58b51dfe010e31eb0981f2e4d1d6afac3e909ec904b06afde5f6c80bcd17ce1", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8a3d7f33334c3c548ed0514d91b0a55e03eb9a766ba938f77e373c7a4e8da26a", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af0e5d4d92979ae07405853dbca4b5f33f9135afa425023fe4a7e7bc979750c3", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a53e90c6533de007d23d11496adcd2c74ee5a06ec1082d9dac977a2968c2af1", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae816995bad20b4c09762e88d447ddf2793e1df8f6d61c0fdc03a7432a06e75a", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a1e551e31f14d8d62e4848d3e0ca435bb2833ffb1034e1f6daa3127096c40559", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/vars/Suse.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c4e51f770b44194812f3da135f735da345082c1c68c83ddd518f188a5dbf5bb8", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/vars/RedHat.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bd748444001a9ad832da4650986fc6ee0eeccb0fa32f11ff4d329df1c2d7eeea", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_keygen/vars/Debian.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "902791fa9219824fa208dcedf4be15381b2ec22fac510d0156b3dd25a7b46588", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "03ab8f892078ec47f1d02e6428354d8125f01963e693d96ac59567f491ce9626", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_pipe/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cd0371af325634fbb201f4d2b406072f01a03290d895e11e4405865bfbd1dbb", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/tasks/obtain-cert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7feaf51b6672ed5e55e660625246a91fb2d9726e36523a10e940aba778ba8112", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_challenge_cert_helper/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd1a07498f1bfd20e916f0b08c6ff0ef90c31358a43b7acbe0d1895c996a8825", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f778c33f123ddbcdfe9d360af3f56d0d588050fd6466d55f6c9797af53ae3104", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c2fda066a7f78fc317675cfa601d291e555734b74db399471b9190154e42c339", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_inspect/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e73687003033fa29373cb78c5a4cb9d583068c2e0b9fbb1c6ee6b888cc954934", + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_tests/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/prepare_tests/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "97604b7531ca5da4b5edb58a8ed04b960f4c8c092e0b05edec9e9683579feeff", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/files/keyfile2", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/files/keyfile1", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests/key-management.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7c631c33cd421598f3b81a635e2f5148fb252b118184895feac85f9eb9eab26b", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests/passphrase.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8b5b59cd9216c538d1616436e55f99452def18ecab9f358533a5ae3610e7477d", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests/device-check.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5c4d4361de9c80d2e5f8f89b3f86b7d884674525061569350ce73af96f1ef03", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests/create-destroy.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8e9e34cab47b90cb5cfbe74c12ce6c03200eb4811fa76559a5b031becf0dc572", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/tests/options.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d021071bbbd8140f9867b14503815b1cabb69882cc39beba461953e5c39f630", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e57eca89f66d6961d4663307980b64741a119d4f475d44f9541e8dd041c47ba", + "format": 1 + }, + { + "name": "tests/integration/targets/luks_device/tasks/run-test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "124445b804a5d99e5f7c9edd6a57fdf3fc333b01d125f1e023534a105f2f4c62", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "edd8a1449dca1cf2ff7cade76d428a198a21d20a8b7b989fc4fe8ce3b0a93f3e", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e79e461decab572671593a346e987e8449f9adef39e4e0724c3e5b30f771602c", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/vars/Suse.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "127487d908dc190aa206b7e82b15caca68bd528f503a29f9c80c232127fe25f9", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/vars/FreeBSD.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77d83e8a3aa2c95439323dcecc945cd28a0686b76fd58f7dd2e0165e7dc42c21", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/vars/RedHat.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc76328480f82e44f2fb1ef2b257a15d139c08b398eb85f224367a2b229d8fdb", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pyopenssl/vars/Debian.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b9c9ce1f4a02bae168e4640830ec8fd35d10d8f5c6af4ba8cb89eadb560f921b", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d632bfb7c1e77ea33fb91d58cf15ff181470e8b3b04babe958bf4880441f7bca", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9bbb518d906e6e1d13088771c73b99de0887f40abf070180def177c4ddcc275", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "feab481dfc19c02d42dc2f2931b513d2f240071f68ac19dddbd253c12d9c4f4c", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da4eb022803120de7881d8ecdf4b6df8298420ca44316424f077f1eaccde0c18", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_crl/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2ca61dc28ab45314f54dcab00ed4c2dfc7e3507bb393f4bddd63c9b75157c441", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4b743231f14d92fb483ca99a7fff71624fc3a78096c9f50360f2d4c3ce57fede", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "215be3eb5d768b75a9f91a37c7bb654e68e50584cf72bce5e0b0ba9d5928ca43", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/test_plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr_info/test_plugins/jinja_compatibility.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad199784d27da441772f5c34381a393c5e528ecad78c2084ab6438c1cbc28da2", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4d744302772253c0ee66b09d36e674a568a2a953a5d3bda0c7dcff5edcd5cf59", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "765a4813bc037c1ae48a597063ef2af58869d02529347d0b457ca68ec3f2dce6", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7feaf51b6672ed5e55e660625246a91fb2d9726e36523a10e940aba778ba8112", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate_revoke/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e73687003033fa29373cb78c5a4cb9d583068c2e0b9fbb1c6ee6b888cc954934", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "edd8a1449dca1cf2ff7cade76d428a198a21d20a8b7b989fc4fe8ce3b0a93f3e", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ca0aebb8c1008bcd11d53297321bba7d91f6e7644fce9307a493e404dcf185f0", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars/Suse.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5de668ca53965cd4168aa0b52f5b3f14213d42169b0f8c45d90ead0cb9aed76", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars/FreeBSD.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3ecf3df5718ca24e94a4b2834bcb5193d1f0e70d670bc899abc6d117e8bdeea", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ac89b4e47c45ae4ee2c10d80cb0c4da086258f08210533662fbf887303d0e55f", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars/RedHat.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5de668ca53965cd4168aa0b52f5b3f14213d42169b0f8c45d90ead0cb9aed76", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_openssl/vars/Debian.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5de668ca53965cd4168aa0b52f5b3f14213d42169b0f8c45d90ead0cb9aed76", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_agent", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_agent/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_agent/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6ad9fd73cc91febe8f377568e4eceb36bf78f788c7c3c57244fdc59996e16a20", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_agent/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_ssh_agent/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "18fa7e5ca20175d408603831e2be2b44afb3ee0a87296b8d54f1af485c898a2d", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0b3eaf445038f112f320ddbf80856103a2dcbb14cac965c51bc7a345a3bba8f8", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6ad9fd73cc91febe8f377568e4eceb36bf78f788c7c3c57244fdc59996e16a20", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "68986293140c1660b866b5688f8772c17130a722783f592356077159909f8e7f", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_keypair/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a53e90c6533de007d23d11496adcd2c74ee5a06ec1082d9dac977a2968c2af1", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5a78296364240dbb7253fecd5ad91d148b3a73dbb1ad9dceff14854ee5fd5ed9", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "129c53753206c9e7d97d272104563e80c5d0dc968444d5a1cee061b738f7e555", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_account/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e73687003033fa29373cb78c5a4cb9d583068c2e0b9fbb1c6ee6b888cc954934", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00c92593325fe7846f1e7466ecb4f86f20fb167b17a8dd70ac768641e611dd6b", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/tasks/loop.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "223dbe37c3eaa397bc4b10d6bbc1428f7e4f1a47a8b3bab1fa37f6157562145f", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_signature/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "42b1d54086df4987a4f77129a352265f5c1d02b8ea108ce414d635de83aa911b", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a6613c3573e4518f252151470f059313cbed3ba61d8846551bceb61545c96bc1", + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssh_cert/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "287167f21388d235a57ad2dfa7820abe28485be14dc1970fcc0808e6dc10ee44", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4166fc81b0455cc5ce34c15073e55c274711ea9a048e8bfa54ccc31506b597ef", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3e88068882c4db6a95e5f2c948793352922e39770db8dbd714122e455e537bbb", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_csr/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "653ac1a0b7c29cf0533eec1829659b6b7dd43c3e087aa36d6ec51e92cd8e317f", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ee2bf2265d6c72b5bc0cfd302a7009d114b2e839694b2d76f4fe262be92a9160", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/defaults/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e8517c3998c6fa2a2d1e06dc8fdfcf57bbc16cdbc0a6240f233c185a80accb22", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dbce11dc51ead2d8cd98e7cd7edda44554a696dc2aa59b16a4f59573c27d2c07", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b63e1f9aa29cb3a173d297959443dda5ea374588c66e0dc2ab8291db9fbec7c0", + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/ecs_domain/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d6b5237cf50dfd17f2c791e9c23c117f4d5f236ab00140aa0a53fdb4614c2a8c", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b34f7e77b6117dd66a49957c17656b22e5dfa444d8f93af2b4e1d7f1450a5a3d", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e8d632f9db7209967c5b2f6d734bede09841acc7b898dafc19f31c72cee9929", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_constraints/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b625673c8e7b2a5dced6117b01aec6f25452246117746bd23450dcf389a61883", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tests/validate_ownca.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae7ae7119d2bbe091dd629ef593d2175d41db16b89abd4879908d27944b6f6d8", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ba6fa8b43a82257842534d77700cee268fcf7eb60988408306128f3ee6d9ed73", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/removal.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2c80d4cb4ebb0343b156be10e870a684a4fe6ca6da03c50c050ac66a4eb7868c", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/expired.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e8276ea7616a3999806f427e979cd13c6f9971b12a4dc29690b533e3654f030", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cc459df3476cf8db2107ca5cc93fa5638455db8600712050711e5e79ff130ee7", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "35fdcca35b7a82d6cdd8577a3ff3c2433541f606771f456c5b5e0c5770674c59", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/assertonly.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2dd5d2599e571fa541074876f94607b119dd82ede30dc036a587a05a6ca6e688", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/ownca.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "68d0fa80090da7336203f443dc69943c4e5cad14c841e5968299ed4c26b282cd", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate/tasks/selfsigned.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cf357b96e4996e5864ccc2d31fc730c017b05c4378492d83aa4931eb991d4d0d", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d46e27a5d2ee81993195037d80c4605eb7eaef57f616f168853eebce4b971b62", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_privatekey_info/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6fe2cb8aa68244d5c675ae08e439261f69e0cad3aa8956189862bf28d5ffd9a6", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "811322fad85866ac31f9501f8da0c7072809065397d53c06712447946b746b6b", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2-altchain.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e231300b2b023d34f4972a5b9bba2c189a91cbfc7f80ba8629d2918d77ef1480", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2-chain.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e446c5e9dbef9d09ac9f7027c034602492437a05ff6c40011d7235fca639c79a", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "97546a3c7cd92e96e6bd4e1f282102b0c3ae99cc75330f78d4c04633c080d74c", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2-fullchain.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b89e9ea8612096f642b644fddbefecc54830bbc1a321facfee7a2b888873a435", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e356f2747f331e35160a278a2d90e619e095cb6beba645692dfd6ed75640d426", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2-altroot.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "22b557a27055b33606b6559f37703928d3e4ad79f110b407d04986e1843543d1", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c0c90383dfe56da48ec157bd7e1b026f855a199e22fa090beadc77c931d456f2", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert1-fullchain.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e8e80e82ce39faeb2961f050f57efd13b648b3269f235592f2f8bcfecb576e69", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert1-root.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d69f7b57250536f57ffba92cffe82a8bbcb16e03a9a2607ec967f362ce83f9ce", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert1-chain.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "077752b681576a96a949c94c83cb53c3ad5e6045f30ca7a9228ddba13e080ac5", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots/COMODO_Certification_Authority.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e273097c7c57cb7cbb908057991ae1774d4e1e8c6a062fb6be9e6645b32fb431", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots/COMODO_ECC_Certification_Authority.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d69f7b57250536f57ffba92cffe82a8bbcb16e03a9a2607ec967f362ce83f9ce", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots/DST_Root_CA_X3.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "139a5e4a4e0fa505378c72c5f700934ce8333f4e6b1b508886c4b0eb14f4be99", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots/ISRG_Root_X1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "22b557a27055b33606b6559f37703928d3e4ad79f110b407d04986e1843543d1", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/roots/COMODO_RSA_Certification_Authority.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24b0d4292dacb02efc38542838e378bc35f040dcd21bebfddbc82dc7feb2876d", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/files/cert2-root.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "139a5e4a4e0fa505378c72c5f700934ce8333f4e6b1b508886c4b0eb14f4be99", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e8d632f9db7209967c5b2f6d734bede09841acc7b898dafc19f31c72cee9929", + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/certificate_complete_chain/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a97765ca9cc6444de31d9534fe32be27e5f3a2dfbfccf0c200c65d67f991a25a", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "62983df582bbab1e493023a306182c4f93ab72cde65423a316f68331d72f79d6", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d146f6f2baa7b038051d7f9ae7fc886d9f0ac6115ff7b00c44968447d4da8900", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/files", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/files/process_certs.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f7b59f3f69bb4347b940ec26825913858ac51012450c070a50c59b28b51fdd01", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/files/bogus_ca.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "95086bf225346d8ef0bf730f1a52393c22a4322ef51de37a9451ad4fb8b10ae4", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d48523a564617b603dac6b897713070288e4f3150dad7501f59f5a3c9207269", + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/get_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0144f46751339dbc2ee5b90a10b5a614dd2b9f1b3b651f3ed0e76c92ef4c78a3", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27b2610a0e3cb26b278c99cf45646ac1e9aeadd48b199b8bdfbb9730a0e3dba7", + "format": 1 + }, + { + "name": "tests/integration/targets/x509_certificate-acme/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a39304e6e04d03045ce95305eaa8e7cbe9e72ee4b26a42da29a48c89e150fba2", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "72d143bdcba55919d3518a9f01b761c75888372cb31eef3a855b5db552f5ad90", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e273324ab90d72180a971d99b9ab69f08689c8be2e6adb991154fc294cf1056e", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bcb3221a68dc87c7eae0c7ea50c0c0e81380932bf2e21a3dfdee1acc2266c3f3", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2441ac1753320d2cd3bea299c160540e6ae31739ed235923ca478284d1fcfe09", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pkg_mgr", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pkg_mgr/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_pkg_mgr/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6669fe0311bc7feccffc744b96ef0d42b04720e22f634f087dff55e5dc2ff00e", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4750a67f583ffc359cc3104794a3f24b273dd512ed465ef41da70a9cc7c9e631", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7a9b48e7d1c3262082edbaade44b5064417ffa7fd1be87f2fe7412a4c613a3e4", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b5acb999c2bbed265cf1b3a2bd051dc0f739e9585a8cc9044dc8105aa7f8f03", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5f0aad5598c9bd6d205dd161a48773b895bf8b3dbde352ce1bf5ab43a3617a9", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tasks/obtain-cert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7feaf51b6672ed5e55e660625246a91fb2d9726e36523a10e940aba778ba8112", + "format": 1 + }, + { + "name": "tests/integration/targets/acme_certificate/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6ba2dff0cb500f68d9361832934e5032509b56392432db09720f164fa4699c78", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3a33264788c41b8a0d945df63cf9f6952caeafcf00c6b57a4ab893de47bca15b", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/tasks/obtain-cert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7feaf51b6672ed5e55e660625246a91fb2d9726e36523a10e940aba778ba8112", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b51f3c5ca3913f4dcb6545c91dd5b05f42c37fc68accb5081d544c046cfeb3ec", + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/vars", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/setup_acme/vars/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ac89b4e47c45ae4ee2c10d80cb0c4da086258f08210533662fbf887303d0e55f", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e84a15b474af5bafd9f4c8fbe0e0c53cc3994f0acc1dc92716d24f78374adee5", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "feab481dfc19c02d42dc2f2931b513d2f240071f68ac19dddbd253c12d9c4f4c", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d325d80cd3119b686aa47e7ce3f5bed0237e3518bcdb35c08b0b9e60e2a5bdb8", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_dhparam/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "156f0976ba4551938f4ffa66d6fd4b3d42003422d3115786624d57a4e17e111e", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8044b2976d13a192f3cbee951e8d9694b050aa4c50fb97b0fe1172b2ed32e2a5", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52af9ea6c06521a7eb31e57e0cf9cdfab3fc5c9f60b11616fb996a1961a4b731", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_pkcs12/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "465c2411453ad0ca0e595c52fd88be9a83ddee54b054ff7e901d23718a875151", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/aliases", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08a5ac67dbbde608b42f081135f93a1f3462537e953a9725662c7a7a368c5536", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/tests/validate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "acda0f6e06afa17f47ea654b7440fa6adbf9a884b9e45ba9659ba1ee4071b173", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/meta/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d645588f055f394382745decafc66434aaa497c28798c7781411921c0b1bef31", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/tasks", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/tasks/impl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e8c34517643ac0a50493a785c32d9385106041abc8931dffb25375179c48746f", + "format": 1 + }, + { + "name": "tests/integration/targets/openssl_publickey/tasks/main.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cc600c09aa705f89dfbc863dff7b84cede58cbfd16cdb606ef99e1df60686dfa", + "format": 1 + }, + { + "name": "tests/utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/constraints.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5beb3383ef5546038c2c00ea8e5f438e607d91828ce2259594fd8fbaea003ec9", + "format": 1 + }, + { + "name": "tests/utils/shippable", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/utils/shippable/units.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "00169bf628f2336b63e274821cd691569feeaaecd6d29afe7b0a3fa483a44157", + "format": 1 + }, + { + "name": "tests/utils/shippable/remote.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/macos.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/check_matrix.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "66e62942a4ea5439654a0d916588ee1aa74b72ab260f81c9c9d1b69d5e910efe", + "format": 1 + }, + { + "name": "tests/utils/shippable/sanity.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a01d92ca36ea457c0e7032ece03a0b485377eef8c8598d8f7c04a185fba279ed", + "format": 1 + }, + { + "name": "tests/utils/shippable/osx.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/timing.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3f3cc03a997cdba719b0542fe668fc612451841cbe840ab36865f30aa54a1bd", + "format": 1 + }, + { + "name": "tests/utils/shippable/freebsd.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/rhel.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a140a1cea2fbf3b291009694bcfcf0f2877e92ec01c7e929e787f5b4cdd6d92", + "format": 1 + }, + { + "name": "tests/utils/shippable/shippable.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "949845f72210abf714307b6f4401aa1ab53776f666ece7de9e247ed093ec2334", + "format": 1 + }, + { + "name": "tests/utils/shippable/linux.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07aa5e07a0b732a671bf9fdadfe073dd310b81857b897328ce2fa829e2c76315", + "format": 1 + }, + { + "name": "tests/utils/shippable/timing.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ebb7d3553349747ad41d80899ed353e13cf32fcbecbb6566cf36e9d2bc33703e", + "format": 1 + }, + { + "name": "tests/utils/shippable/cloud.sh", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "47f7b254b1a75d3258d85c1f011527491025a77ddbc7053ed75f9cdc1fd79189", + "format": 1 + }, + { + "name": "tests/.gitignore", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600", + "format": 1 + }, + { + "name": "tests/unit", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/mock", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/mock/loader.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c45064a0beb765cd0b6cfbb74ca0cd491ceed2f4c2d22808f60a57071d9712cc", + "format": 1 + }, + { + "name": "tests/unit/mock/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/mock/path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "481013d06022b9bc447fedd2d61208ad25a636f1c0bf43b3c96c7894d6a0584a", + "format": 1 + }, + { + "name": "tests/unit/mock/procenv.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e92b9873d5732bb40a0661b574762f1b7a7ba875aab2b5dfa3f0dc140881f1b9", + "format": 1 + }, + { + "name": "tests/unit/mock/yaml_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "88718a93edc6059b93ff526fb9b432cabf84628e0341a0469e82f288a6581c1f", + "format": 1 + }, + { + "name": "tests/unit/mock/vault_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4535613601c419f7d20f0c21e638dabccf69b4a7fac99d5f6f9b81d1519dafd6", + "format": 1 + }, + { + "name": "tests/unit/plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/csr_2.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fb040b8c9d50143e6fb3540bda21d14defeeb0d2ab3354a7d9483a5e025d4b42", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "671b19bfc3a726ca1ff99873ac38a9369a36a10b5732322d8626e9ecd94ea2b0", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/csr_2.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "981c99ecb74a45f86f2c6cd2f81a558da472c611b162f86c26638678cec515c4", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1d49d28cca5b377cd5e2fa4495455e7c4b616ed5a672bd88bbfef6df573b45a6", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/csr_1.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4444cf7d12202eeffd5b6cc04574b24f61dc302c8ce6c5f81537872d30db233a", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "233f93521a93860e5c511399a44355e35d9d2c88f9eac8fcf8f3ecb1dd0d265f", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_1.pem", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "97d36de524ffebd29969c7a1d07353b799f71bbf75fa370c30f37650b1fd2a64", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/acme/test_acme.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f44f952e186eed5bbfa628ee299eadc87eea4671f502078127e5e042a31c0588", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/crypto", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/crypto/test_asn1.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6afe00692916eba05bd92c94fa0273752f88bb9544d4d66540b35a9bf96bed5a", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/crypto/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/crypto/test_cryptography_support.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d49bfd02af5986acffc51fbab2412ceb927aaaea8b97ce789b73b856ca9487f9", + "format": 1 + }, + { + "name": "tests/unit/plugins/module_utils/conftest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e5a27f1f046d82135ed8234fc5498aff6475f24f3d8bf00e44a149d88f48c9a", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/test_luks_device.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1bd899fb9c265b97a284dff4a950aeb2a1999b6c88aba589cc46c3210c1418c7", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/conftest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8af0953fce701c18e2dc9c5628d6b13f812ceb913b483004cdb94e58d7c8b0c5", + "format": 1 + }, + { + "name": "tests/unit/plugins/modules/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ba970dde023a0ba6e7b19bed18204aefb2cb9eada7a67a9fa6582adc5ffe326", + "format": 1 + }, + { + "name": "tests/unit/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/compat", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/unit/compat/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "format": 1 + }, + { + "name": "tests/unit/compat/mock.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0af958450cf6de3fbafe94b1111eae8ba5a8dbe1d785ffbb9df81f26e4946d99", + "format": 1 + }, + { + "name": "tests/unit/compat/unittest.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5401a046e5ce71fa19b6d905abd0f9bdf816c0c635f7bdda6730b3ef06e67096", + "format": 1 + }, + { + "name": "tests/unit/compat/builtins.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ca4cac919e166b25e601e11acb01f6957dddd574ff0a62569cb994a5ecb63e1", + "format": 1 + }, + { + "name": "tests/unit/requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8db2dfdbed318010d37aff16ff574233260a8f812a03cb65508f954c0974371", + "format": 1 + }, + { + "name": "tests/requirements.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "309eab525a905a19f04aa713a60a344b64f115cbdece10d237ddd35f5fc76311", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/crypto", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/pyopenssl_support.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9541ae5601b5b5e4f353cd31356b506fa01376ce5ef97e5c31cff388ffbe3c6a", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/openssh.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "51edcebbea97f4db0c44eab27c6f4b1b8ff73ff9b1d9ba5f5f3f7f56d0c74b07", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/basic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7b5ed5d76ea3e1b0f229a8a42201b5ae95178135c8bc6c5729faa079638f32f4", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/pem.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d7e83231539eda84570a352e8b21655f025aa69c76f7f3ff99d63db3c8c61367", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/_objects_data.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9fdbd518c7f8409398d8ea454abd1920ff6285a49cf6db3b50e31947b4ba7ee2", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/cryptography_crl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a389d11ae6d31a5cb53f653fe1dade5dc61db6c50cc7d605994b4a95c593ceff", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/__init__.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad730b64d27014fa91cedfb8706d4b6df75c01ba980d294e230a9ae19195b1fd", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/_asn1.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "450a8bd89a07898892312ef162980198af54d45258c1e109d6d061fd39211fc3", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate_entrust.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5e729b95a3d1bed3f135dfa67a5b36c588f9419d8422b20a7b5b55e25168cf8f", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e33a16cff527a3a1a80557d01ba2ce42e32d6835336a588faa123dd840c99de7", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate_acme.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3c2a4a8b47a8d987adfae86a5c9e74c22bc6000a4fb4c41028284202d8ebe174", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate_ownca.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "56c819e5b65c4a6175477e05edac81b26c7cab456a851be851efd4120138a118", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/common.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "476d675c73a1d0e745960953a5dc269b83d8dc89045a00b1b0d8712d36922ef6", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/privatekey.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "69dfa32fcd53ed8176ac009115bb1fc1823b2b08b25d51f97fc39c615ab6a2fc", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate_selfsigned.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "040a0804922beee023d3634236257f32b59997438feacd8170743996e05e86ff", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/csr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "90f716f3eae63d91379604b6100cc3951d212f400b85edfd36bc44a8ca7a0b85", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/module_backends/certificate_assertonly.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "171464855081ea09678a82d315540eb1472c70c99eb100d4fef88db88ed7213c", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/cryptography_support.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b14d7c894ac792e24307c37c9ff513b9c8bb63c2b3ecafeb8579a379664526fc", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/_objects.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7d4df3bfd8cb1ea3632cac3d5242011f1fb707d6e68453677194eae0c253ba4e", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/math.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "32ebed61bdce87d10e7032108ebcb259322f96c4b802c02bb90a476003ae874b", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/support.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44152c20b9ec72bbf2bf86920b31e511a4a82e97f9a1ce577ff85796d71c690d", + "format": 1 + }, + { + "name": "plugins/module_utils/crypto/_obj2txt.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b27397f311fb320ab3f5120d5e286cc24b802d0786a010b1e16d2dcdf5a6fca", + "format": 1 + }, + { + "name": "plugins/module_utils/compat", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/compat/ipaddress.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bcbd4e4132c093691c6be0593c7c9be2ceedc31a3c13a818a258fda6890aa983", + "format": 1 + }, + { + "name": "plugins/module_utils/acme.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c256a2e195e8d89494b0bbf1b644e4a3d5ee009c4b0bafdf4b65bf09cd50f505", + "format": 1 + }, + { + "name": "plugins/module_utils/ecs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/ecs/api.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e954698dc86d6cc99de89ee4289dad2562936bbd6692b3e595709b982e781ea5", + "format": 1 + }, + { + "name": "plugins/module_utils/io.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "43293ce46f574e883eeb25b158ae5a9888d89a037ada5857121f5961290f8766", + "format": 1 + }, + { + "name": "plugins/doc_fragments", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/doc_fragments/module_privatekey.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "23168ab46503fcf71ca19146783c920ba43311265b2d7009d053090f24fcc82c", + "format": 1 + }, + { + "name": "plugins/doc_fragments/module_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f57d011a60deb1096fe0be4551d74d740503ff1582a42c27b31f288e370e4ba", + "format": 1 + }, + { + "name": "plugins/doc_fragments/module_csr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da192fb71a7ebde98388d736688db687c60a7ee9aba797f63203dc4194c2b86c", + "format": 1 + }, + { + "name": "plugins/doc_fragments/acme.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b875423dbe6dc39cf1822bd78353c3b1f4600ece5803f9868f37b1ee4b4ca7da", + "format": 1 + }, + { + "name": "plugins/doc_fragments/ecs_credential.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "476dcf66aa896eba7c4eedf933160387c2f31c70e04446c785fc278c1c808eba", + "format": 1 + }, + { + "name": "plugins/plugin_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/plugin_utils/action_module.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc1d5baa916ac735e66a3de80db18d5e7ff5192a81c9cf7922227e8bc50283f8", + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/openssl_signature.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "947789438166405f623e2aa23562c80e36f6a5d12e4be3b399b91c47e64737d5", + "format": 1 + }, + { + "name": "plugins/modules/openssh_keypair.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8eeeda6195df2c9d58488af50cf75784004ec6791c3c65952dc01ad50b574b41", + "format": 1 + }, + { + "name": "plugins/modules/openssl_csr.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ebe0da1569b46cdd9b4a8e97f082f5d2fa261654c954476b46d2c20817bda56", + "format": 1 + }, + { + "name": "plugins/modules/get_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2a26b6ac88af0e4f71a6bd7fcb5b06bbc7e9ab96177bb89f818bf6124b376d1", + "format": 1 + }, + { + "name": "plugins/modules/ecs_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "84346b19a236361d1271a32b346de0903c8ae934e35b7850ef5721833d652a22", + "format": 1 + }, + { + "name": "plugins/modules/openssl_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a287c8868cba87efa0cf41b49a61ff7182f63a6547c4a971c595bcb89bcba75", + "format": 1 + }, + { + "name": "plugins/modules/openssl_privatekey_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "04a8e2d9c7447485bf5f64e4e79ecd220f96fb99d27142b8748b5312a55da06d", + "format": 1 + }, + { + "name": "plugins/modules/x509_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a287c8868cba87efa0cf41b49a61ff7182f63a6547c4a971c595bcb89bcba75", + "format": 1 + }, + { + "name": "plugins/modules/x509_certificate_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "02a61627af825a1c339c011562e0e412da624c3a559e8e2020c6ea072d275e0d", + "format": 1 + }, + { + "name": "plugins/modules/x509_certificate_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c41e7b4cd74c9565e98433ecd658b1a8bdc106f90021e271234fcb7e3d733e08", + "format": 1 + }, + { + "name": "plugins/modules/acme_account.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07f50c6c499f8014a8026b25ba6d19a75189b7e95fcf0fd60a0e35f7a5b85d23", + "format": 1 + }, + { + "name": "plugins/modules/openssl_pkcs12.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "676d2be3a22b174f78d9cb486303aa83dd998196f9228fa96283a48f456daeb1", + "format": 1 + }, + { + "name": "plugins/modules/acme_certificate_revoke.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "498c2e55336e1607c72a42b3fb264fd68d1c27c9c4f363ae64ebde1c2fd765ec", + "format": 1 + }, + { + "name": "plugins/modules/certificate_complete_chain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "376d5608224a14035f8a5a29913e23819358ceadaea4be33e0ec9eb271e14c86", + "format": 1 + }, + { + "name": "plugins/modules/ecs_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5207021e70b5b64ee7a0a848628e4fcc9534d077de8500a5954d72c54407a531", + "format": 1 + }, + { + "name": "plugins/modules/acme_account_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52fa00e91e18c1ed7d9e978e1e2f9d9fd04d0fc270cde2615261f8cb8a6355ff", + "format": 1 + }, + { + "name": "plugins/modules/openssl_privatekey.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5853904497700e0f19e830eaae0fa4af8d66a8eaf0d64eda440fb919cfd71f22", + "format": 1 + }, + { + "name": "plugins/modules/luks_device.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9e888157f93b39a09a8029eeb44e7a2d7367bbdd40987eb9385518a381b644ba", + "format": 1 + }, + { + "name": "plugins/modules/openssl_signature_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8aafbda6daaf618c28a242e1182fb53eccf6694aade65c5aa90d2046a924d861", + "format": 1 + }, + { + "name": "plugins/modules/acme_challenge_cert_helper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9328d9a97a0173bf20a792038839f5afa772b4b7be0135186be1d5bfb3402ea3", + "format": 1 + }, + { + "name": "plugins/modules/openssl_publickey.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7c107d9f55dfaf41e159fab2fb6e4a797a05a8d681caf5d1b2e42707779d352b", + "format": 1 + }, + { + "name": "plugins/modules/acme_inspect.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "76212aa20718c58baf5d8e5fdd53a19173d7e22fae6e01a92b4e583172fdfd68", + "format": 1 + }, + { + "name": "plugins/modules/openssl_dhparam.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "811df57004a2363aa236c730f20bd8a7728d32f5ee5ca3385142291cd58cd565", + "format": 1 + }, + { + "name": "plugins/modules/acme_account_facts.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "52fa00e91e18c1ed7d9e978e1e2f9d9fd04d0fc270cde2615261f8cb8a6355ff", + "format": 1 + }, + { + "name": "plugins/modules/x509_crl_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d0b7382b96dac1eab35faff0a0abc549bd3fa958571d4118caf727a2e9fa47a6", + "format": 1 + }, + { + "name": "plugins/modules/openssh_cert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63f32d005be2a635c550884f0968b98cea1788d6be6056fe64f7e4e6ba7e2804", + "format": 1 + }, + { + "name": "plugins/modules/x509_crl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5ada533df47f1f5f7fa644b5c787e68be0e5441aab67a77a7174bc07354c4b8a", + "format": 1 + }, + { + "name": "plugins/modules/openssl_privatekey_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9dd56204c7d00ebf5978c314187467564176da36824e42547b562ce8b5c92e5d", + "format": 1 + }, + { + "name": "plugins/modules/openssl_csr_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "526842de33ee2d1db5a4cd10f44d2e24696bf2405a3b953797b6a733b4b4d625", + "format": 1 + }, + { + "name": "plugins/modules/openssl_csr_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5ac370b5cb4233f41b35f00d367e842247b66f45f93054e8b9ecd9741ba049ec", + "format": 1 + }, + { + "name": "plugins/modules/openssl_certificate_info.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c41e7b4cd74c9565e98433ecd658b1a8bdc106f90021e271234fcb7e3d733e08", + "format": 1 + }, + { + "name": "plugins/modules/acme_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5f94769fd70efa8af41a371074d6f3a2557aec923d5bc59aaed0d9c4e61f8347", + "format": 1 + }, + { + "name": "plugins/action", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/action/openssl_privatekey_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "81be5ba0a3373dd8cfb6f9b8cd282664caa9afbbda66605e3881c48a2e6bb48f", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "117ffe129f26ea9ee28d0a967cf4375b60230e5c479189cae5b7b690a9927011", + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7a2fbd161c819ce66c8e6b2d108e54b4d6b17bafce3b55e00084b8516b85fd17", + "format": 1 + }, + { + "name": "CHANGELOG.rst", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78d470365656980e3a4db356aa7e8d91d98eee1ee8c55d7c8119921a27bad336", + "format": 1 + } + ], + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/crypto/MANIFEST.json b/collections-debian-merged/ansible_collections/community/crypto/MANIFEST.json new file mode 100644 index 00000000..253166bf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/MANIFEST.json @@ -0,0 +1,42 @@ +{ + "collection_info": { + "namespace": "community", + "name": "crypto", + "version": "1.4.0", + "authors": [ + "Ansible (github.com/ansible)" + ], + "readme": "README.md", + "tags": [ + "acme", + "certificate", + "community", + "crl", + "cryptography", + "csr", + "dhparam", + "entrust", + "letsencrypt", + "luks", + "openssl", + "openssh", + "pkcs12" + ], + "description": null, + "license": [], + "license_file": "COPYING", + "dependencies": {}, + "repository": "https://github.com/ansible-collections/community.crypto", + "documentation": "https://docs.ansible.com/ansible/latest/collections/community/crypto/", + "homepage": "https://github.com/ansible-collections/community.crypto", + "issues": "https://github.com/ansible-collections/community.crypto/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e2ce1778b4f78796699bed3cc845d2e1e188543021c0af4938d52825202616fd", + "format": 1 + }, + "format": 1 +}
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/crypto/README.md b/collections-debian-merged/ansible_collections/community/crypto/README.md new file mode 100644 index 00000000..982a2b95 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/README.md @@ -0,0 +1,111 @@ +# Ansible Community Crypto Collection + +[![Build Status](https://dev.azure.com/ansible/community.crypto/_apis/build/status/CI?branchName=main)](https://dev.azure.com/ansible/community.crypto/_build?definitionId=21) +[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.crypto)](https://codecov.io/gh/ansible-collections/community.crypto) + +Provides modules for [Ansible](https://www.ansible.com/community) for various cryptographic operations. + +You can find [documentation for this collection on the Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/). + +## Tested with Ansible + +Tested with both the current Ansible 2.9 and 2.10 releases and the current development version of Ansible. Ansible versions before 2.9.10 are not supported. + +## External requirements + +The exact requirements for every module are listed in the module documentation. + +Most modules require a recent enough version of [the Python cryptography library](https://pypi.org/project/cryptography/). See the module documentations for the minimal version supported for each module. + +## Included content + +- OpenSSL / PKI modules: + - openssl_csr_info + - openssl_csr + - openssl_dhparam + - openssl_pkcs12 + - openssl_privatekey_info + - openssl_privatekey + - openssl_publickey + - openssl_signature_info + - openssl_signature + - x509_certificate_info + - x509_certificate + - x509_crl_info + - x509_crl + - certificate_complete_chain +- OpenSSH modules: + - openssh_cert + - openssh_keypair +- ACME modules: + - acme_account_info + - acme_account + - acme_certificate + - acme_certificate_revoke + - acme_challenge_cert_helper + - acme_inspect +- ECS modules: + - ecs_certificate + - ecs_domain +- Miscellaneous modules: + - get_certificate + - luks_device + +You can also find a list of all modules with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/). + +## Using this collection + +Before using the crypto community collection, you need to install the collection with the `ansible-galaxy` CLI: + + ansible-galaxy collection install community.crypto + +You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml` using the format: + +```yaml +collections: +- name: community.crypto +``` + +See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. + +## Contributing to this collection + +<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). --> + +We're following the general Ansible contributor guidelines; see [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). + +If you want to clone this repositority (or a fork of it) to improve it, you can proceed as follows: +1. Create a directory `ansible_collections/community`; +2. In there, checkout this repository (or a fork) as `crypto`; +3. Add the directory containing `ansible_collections` to your [ANSIBLE_COLLECTIONS_PATH](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths). + +See [Ansible's dev guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) for more information. + +## Release notes + +See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.rst). + +## Roadmap + +We plan to regularly release minor and patch versions, whenever new features are added or bugs fixed. Our collection follows [semantic versioning](https://semver.org/), so breaking changes will only happen in major releases. + +Most modules will drop PyOpenSSL support in version 2.0.0 of the collection, i.e. in the next major version. We currently plan to release 2.0.0 somewhen during 2021. Around then, the supported versions of the most common distributions will contain a new enough version of ``cryptography``. + +Once 2.0.0 has been released, bugfixes will still be backported to 1.0.0 for some time, and some features might also be backported. If we do not want to backport something ourselves because we think it is not worth the effort, backport PRs by non-maintainers are usually accepted. + +In 2.0.0, the following notable features will be removed: +* PyOpenSSL backends of all modules, except ``openssl_pkcs12`` which does not have a ``cryptography`` backend due to lack of support of PKCS#12 functionality in ``cryptography``. +* The ``assertonly`` provider of ``x509_certificate`` will be removed. + +## More information + +- [Ansible Collection overview](https://github.com/ansible-collections/overview) +- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) +- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) +- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) + +## Licensing + +GNU General Public License v3.0 or later. + +See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/collections-debian-merged/ansible_collections/community/crypto/changelogs/changelog.yaml b/collections-debian-merged/ansible_collections/community/crypto/changelogs/changelog.yaml new file mode 100644 index 00000000..5f532287 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/changelogs/changelog.yaml @@ -0,0 +1,321 @@ +ancestor: null +releases: + 1.0.0: + changes: + bugfixes: + - 'ACME modules: fix bug in ACME v1 account update code' + - 'ACME modules: make sure some connection errors are handled properly' + - 'ACME modules: support Buypass'' ACME v1 endpoint' + - acme_certificate - fix crash when module is used with Python 2.x. + - acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account`` + set to ``false``. + - 'ecs_certificate - Always specify header ``connection: keep-alive`` for ECS + API connections.' + - ecs_certificate - Fix formatting of contents of ``full_chain_path``. + - get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900) + - openssh_keypair - add logic to avoid breaking password protected keys. + - openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969). + - openssh_keypair - public key's file attributes (permissions, owner, group, + etc.) are now set to the same values as the private key. + - openssl_* modules - prevent crash on fingerprint determination in FIPS mode + (https://github.com/ansible/ansible/issues/67213). + - 'openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive`` + header for ECS API connections.' + - openssl_certificate - ``provider`` option was documented as required, but + it was not checked whether it was provided. It is now only required when ``state`` + is ``present``. + - openssl_certificate - fix ``assertonly`` provider certificate verification, + causing 'private key mismatch' and 'subject mismatch' errors. + - openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support + for ``cryptography`` backend. This probably needs at least cryptography 2.8, + since older versions have problems with signing certificates or CSRs with + such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984) + - openssl_csr - a warning is issued if an unsupported value for ``version`` + is used for the ``cryptography`` backend. + - openssl_csr - the module will now enforce that ``privatekey_path`` is specified + when ``state=present``. + - openssl_publickey - fix a module crash caused when pyOpenSSL is not installed + (https://github.com/ansible/ansible/issues/67035). + deprecated_features: + - openssl_csr - all values for the ``version`` option except ``1`` are deprecated. + The value 1 denotes the current only standardized CSR version. + minor_changes: + - luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``. + - luks_device - add ``keysize`` parameter to set key size at LUKS container + creation + - luks_device - added support to use UUIDs, and labels with LUKS2 containers + - luks_device - added the ``type`` option that allows user explicit define the + LUKS container format version + - openssh_keypair - instead of regenerating some broken or password protected + keys, fail the module. Keys can still be regenerated by calling the module + with ``force=yes``. + - openssh_keypair - the ``regenerate`` option allows to configure the module's + behavior when it should or needs to regenerate private keys. + - openssl_* modules - the cryptography backend now properly supports ``dirName``, + ``otherName`` and ``RID`` (Registered ID) names. + - openssl_certificate - Add option for changing which ACME directory to use + with acme-tiny. Set the default ACME directory to Let's Encrypt instead of + using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time + being, so no action should be neccessary.) + - openssl_certificate - Change the required version of acme-tiny to >= 4.0.0 + - openssl_certificate - allow to provide content of some input files via the + ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and + ``ownca_content`` options. + - openssl_certificate - allow to return the existing/generated certificate directly + as ``certificate`` by setting ``return_content`` to ``yes``. + - openssl_certificate_info - allow to provide certificate content via ``content`` + option (https://github.com/ansible/ansible/issues/64776). + - openssl_csr - Add support for specifying the SAN ``otherName`` value in the + OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``. + - openssl_csr - allow to provide private key content via ``private_key_content`` + option. + - openssl_csr - allow to return the existing/generated CSR directly as ``csr`` + by setting ``return_content`` to ``yes``. + - openssl_csr_info - allow to provide CSR content via ``content`` option. + - openssl_dhparam - allow to return the existing/generated DH params directly + as ``dhparams`` by setting ``return_content`` to ``yes``. + - openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection + can be overwritten with the ``select_crypto_backend`` option. + - openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as + ``pkcs12`` by setting ``return_content`` to ``yes``. + - openssl_privatekey - add ``format`` and ``format_mismatch`` options. + - openssl_privatekey - allow to return the existing/generated private key directly + as ``privatekey`` by setting ``return_content`` to ``yes``. + - openssl_privatekey - the ``regenerate`` option allows to configure the module's + behavior when it should or needs to regenerate private keys. + - openssl_privatekey_info - allow to provide private key content via ``content`` + option. + - openssl_publickey - allow to provide private key content via ``private_key_content`` + option. + - openssl_publickey - allow to return the existing/generated public key directly + as ``publickey`` by setting ``return_content`` to ``yes``. + release_summary: 'This is the first proper release of the ``community.crypto`` + collection. This changelog contains all changes to the modules in this collection + that were added after the release of Ansible 2.9.0. + + ' + removed_features: + - The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead. + fragments: + - 1.0.0.yml + - 52408-luks-device.yaml + - 58973-luks_device_add-type-option.yml + - 58973_luks_device-add-label-and-uuid-support.yml + - 60388-openssl_privatekey-format.yml + - 61522-luks-device-add-option-to-define-keysize.yml + - 61658-openssh_keypair-public-key-permissions.yml + - 61693-acme-buypass-acme-v1.yml + - 61738-ecs-certificate-invalid-chain.yaml + - 62218-fix-to-entrust-api.yml + - 62790-openssl_certificate_fix_assert.yml + - 62991-openssl_dhparam-cryptography-backend.yml + - 63140-acme-fix-fetch-url-status-codes.yaml + - 63432-openssl_csr-version.yml + - 63984-openssl-ed25519-ed448.yml + - 64436-openssh_keypair-add-password-protected-key-check.yml + - 64501-fix-python2.x-backward-compatibility.yaml + - 64648-acme_certificate-acmev1.yml + - 65017-openssh_keypair-idempotence.yml + - 65400-openssl-output.yml + - 65435-openssl_csr-privatekey_path-required.yml + - 65633-crypto-argspec-fixup.yml + - 66384-openssl-content.yml + - 67036-openssl_publickey-backend.yml + - 67038-openssl-openssh-key-regenerate.yml + - 67109-openssl_certificate-acme-directory.yaml + - 67515-openssl-fingerprint-fips.yml + - 67669-cryptography-names.yml + - 67901-get_certificate-fix-cryptography.yml + - letsencrypt.yml + - openssl_csr-otherName.yml + modules: + - description: Request validation of a domain with the Entrust Certificate Services + (ECS) API + name: ecs_domain + namespace: '' + - description: Generate Certificate Revocation Lists (CRLs) + name: x509_crl + namespace: '' + - description: Retrieve information on Certificate Revocation Lists (CRLs) + name: x509_crl_info + namespace: '' + release_date: '2020-07-03' + 1.1.0: + changes: + bugfixes: + - acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86). + - get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used + (https://github.com/ansible-collections/community.crypto/pull/84). + - openssl_*, x509_* modules - fix handling of general names which refer to IP + networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92). + minor_changes: + - acme_account - add ``external_account_binding`` option to allow creation of + ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89). + - 'acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain`` + parameter (https://github.com/ansible-collections/community.crypto/pull/102).' + - cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39). + - get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69). + - luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97). + - openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95). + - openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness + for serial numbers, instead of a random number between 1000 and 99999. Please + note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76). + - openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46). + - openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46). + release_summary: 'Release for Ansible 2.10.0. + + ' + fragments: + - 1.1.0.yml + - 100-acme-account-external-account-binding.yml + - 102-acme-certificate-select-chain-first.yml + - 87-acme_inspect-python-3.5.yml + - 90-cryptography-oids.yml + - 90-openssl_certificate-pyopenssl-serial.yml + - 92-ip-networks.yml + - 92-openssl_csr-name-constraints.yml + - get_certificate-add_support_for_SNI.yml + - luks_device-add_encryption_option_on_create.yml + - openssh_cert-pkcs11.yml + modules: + - description: Sign data with openssl + name: openssl_signature + namespace: '' + - description: Verify signatures with openssl + name: openssl_signature_info + namespace: '' + release_date: '2020-08-18' + 1.1.1: + changes: + bugfixes: + - meta/runtime.yml - convert Ansible version numbers for old names of modules + to collection version numbers (https://github.com/ansible-collections/community.crypto/pull/108). + - openssl_csr - improve handling of IDNA errors (https://github.com/ansible-collections/community.crypto/issues/105). + release_summary: Bugfixes for Ansible 2.10.0. + fragments: + - 1.1.1.yml + - 106-openssl_csr-idna-errors.yml + - 108-meta-runtime-versions.yml + release_date: '2020-09-14' + 1.2.0: + changes: + bugfixes: + - openssl_pkcs12 - do not crash when reading PKCS#12 file which has no private + key and/or no main certificate (https://github.com/ansible-collections/community.crypto/issues/103). + minor_changes: + - acme_certificate - allow to pass CSR file as content with new option ``csr_content`` + (https://github.com/ansible-collections/community.crypto/pull/115). + - x509_certificate_info - add ``fingerprints`` return value which returns certificate + fingerprints (https://github.com/ansible-collections/community.crypto/pull/121). + release_summary: Please note that this release fixes a security issue (CVE-2020-25646). + security_fixes: + - openssl_csr - the option ``privatekey_content`` was not marked as ``no_log``, + resulting in it being dumped into the system log by default, and returned + in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + - openssl_privatekey_info - the option ``content`` was not marked as ``no_log``, + resulting in it being dumped into the system log by default, and returned + in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + - openssl_publickey - the option ``privatekey_content`` was not marked as ``no_log``, + resulting in it being dumped into the system log by default, and returned + in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + - openssl_signature - the option ``privatekey_content`` was not marked as ``no_log``, + resulting in it being dumped into the system log by default, and returned + in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + - x509_certificate - the options ``privatekey_content`` and ``ownca_privatekey_content`` + were not marked as ``no_log``, resulting in it being dumped into the system + log by default, and returned in the registered results in the ``invocation`` + field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + - x509_crl - the option ``privatekey_content`` was not marked as ``no_log``, + resulting in it being dumped into the system log by default, and returned + in the registered results in the ``invocation`` field (CVE-2020-25646, https://github.com/ansible-collections/community.crypto/pull/125). + fragments: + - 1.2.0.yml + - 109-openssl_pkcs12-crash-no-cert-key.yml + - 115-acme_certificate-csr_content.yml + - 121-x509_certificate_info-fingerprints.yml + - cve-2020-25646.yml + release_date: '2020-10-13' + 1.3.0: + changes: + bugfixes: + - openssl_pkcs12 - report the correct state when ``action`` is ``parse`` (https://github.com/ansible-collections/community.crypto/issues/143). + - support code - improve handling of certificate and certificate signing request + (CSR) loading with the ``cryptography`` backend when errors occur (https://github.com/ansible-collections/community.crypto/issues/138, + https://github.com/ansible-collections/community.crypto/pull/139). + - x509_certificate - fix ``entrust`` provider, which was broken since community.crypto + 0.1.0 due to a feature added before the collection move (https://github.com/ansible-collections/community.crypto/pull/135). + minor_changes: + - openssh_cert - add module parameter ``use_agent`` to enable using signing + keys stored in ssh-agent (https://github.com/ansible-collections/community.crypto/issues/116). + - openssl_csr - refactor module to allow code re-use by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123). + - openssl_privatekey - refactor module to allow code re-use by openssl_privatekey_pipe + (https://github.com/ansible-collections/community.crypto/pull/119). + - openssl_privatekey - the elliptic curve ``secp192r1`` now triggers a security + warning. Elliptic curves of at least 224 bits should be used for new keys; + see `here <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#elliptic-curves>`_ + (https://github.com/ansible-collections/community.crypto/pull/132). + - x509_certificate - for the ``selfsigned`` provider, a CSR is not required + anymore. If no CSR is provided, the module behaves as if a minimal CSR which + only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, + https://github.com/ansible-collections/community.crypto/pull/129). + - x509_certificate - refactor module to allow code re-use by x509_certificate_pipe + (https://github.com/ansible-collections/community.crypto/pull/135). + release_summary: 'Contains new modules ``openssl_privatekey_pipe``, ``openssl_csr_pipe`` + and ``x509_certificate_pipe`` which allow to create or update private keys, + CSRs and X.509 certificates without having to write them to disk. + + ' + fragments: + - 1.3.0.yml + - 117-openssh_cert-use-ssh-agent.yml + - 129-x509_certificate-no-csr-selfsigned.yml + - 132-openssl_privatekey-ecc-order.yml + - 135-x509_certificate-entrust.yml + - 139-improve-error-handling.yml + - 145-add-check-for-parsed-pkcs12-files.yml + - privatekey-csr-certificate-refactoring.yml + modules: + - description: Generate OpenSSL Certificate Signing Request (CSR) + name: openssl_csr_pipe + namespace: '' + - description: Generate OpenSSL private keys without disk access + name: openssl_privatekey_pipe + namespace: '' + - description: Generate and/or check OpenSSL certificates + name: x509_certificate_pipe + namespace: '' + release_date: '2020-11-24' + 1.4.0: + changes: + bugfixes: + - acme_certificate - error when requested challenge type is not found for non-valid + challenges, instead of hanging on step 2 (https://github.com/ansible-collections/community.crypto/issues/171, + https://github.com/ansible-collections/community.crypto/pull/173). + minor_changes: + - The ACME module_utils has been relicensed back from the Simplified BSD License + (https://opensource.org/licenses/BSD-2-Clause) to the GPLv3+ (same license + used by most other code in this collection). This undoes a licensing change + when the original GPLv3+ licensed code was moved to module_utils in https://github.com/ansible/ansible/pull/40697 + (https://github.com/ansible-collections/community.crypto/pull/165). + - The ``crypto/identify.py`` module_utils has been renamed to ``crypto/pem.py`` + (https://github.com/ansible-collections/community.crypto/pull/166). + - luks_device - ``new_keyfile``, ``new_passphrase``, ``remove_keyfile`` and + ``remove_passphrase`` are now idempotent (https://github.com/ansible-collections/community.crypto/issues/19, + https://github.com/ansible-collections/community.crypto/pull/168). + - luks_device - allow to configure PBKDF (https://github.com/ansible-collections/community.crypto/pull/163). + - openssl_csr, openssl_csr_pipe - allow to specify CRL distribution endpoints + with ``crl_distribution_points`` (https://github.com/ansible-collections/community.crypto/issues/147, + https://github.com/ansible-collections/community.crypto/pull/167). + - openssl_pkcs12 - allow to specify certificate bundles in ``other_certificates`` + by using new option ``other_certificates_parse_all`` (https://github.com/ansible-collections/community.crypto/issues/149, + https://github.com/ansible-collections/community.crypto/pull/166). + release_summary: Release with several new features and bugfixes. + fragments: + - 1.4.0.yml + - 163-luks-pbkdf.yml + - 166-openssl_pkcs12-certificate-bundles.yml + - 167-openssl_csr-crl-distribution-points.yml + - 168-luks_device-add-remove-idempotence.yml + - 173-acme_certificate-wrong-challenge.yml + - acme-module-utils-relicense.yml + release_date: '2021-01-26' diff --git a/collections-debian-merged/ansible_collections/community/crypto/changelogs/config.yaml b/collections-debian-merged/ansible_collections/community/crypto/changelogs/config.yaml new file mode 100644 index 00000000..fb55db1d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/changelogs/config.yaml @@ -0,0 +1,28 @@ +changelog_filename_template: ../CHANGELOG.rst +changelog_filename_version_depth: 0 +changes_file: changelog.yaml +changes_format: combined +keep_fragments: false +mention_ancestor: true +new_plugins_after_name: removed_features +notesdir: fragments +prelude_section_name: release_summary +prelude_section_title: Release Summary +sections: +- - major_changes + - Major Changes +- - minor_changes + - Minor Changes +- - breaking_changes + - Breaking Changes / Porting Guide +- - deprecated_features + - Deprecated Features +- - removed_features + - Removed Features (previously deprecated) +- - security_fixes + - Security Fixes +- - bugfixes + - Bugfixes +- - known_issues + - Known Issues +title: Community Crypto diff --git a/collections-debian-merged/ansible_collections/community/crypto/changelogs/fragments/.keep b/collections-debian-merged/ansible_collections/community/crypto/changelogs/fragments/.keep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/changelogs/fragments/.keep diff --git a/collections-debian-merged/ansible_collections/community/crypto/meta/runtime.yml b/collections-debian-merged/ansible_collections/community/crypto/meta/runtime.yml new file mode 100644 index 00000000..d31bccae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/meta/runtime.yml @@ -0,0 +1,31 @@ +requires_ansible: '>=2.9.10' + +action_groups: + acme: + - acme_inspect + - acme_certificate_revoke + - acme_certificate + - acme_account + - acme_account_facts + - acme_account_info + +plugin_routing: + modules: + acme_account_facts: + deprecation: + removal_version: 2.0.0 + warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'. + openssl_certificate: + deprecation: + removal_version: 2.0.0 + warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate' + openssl_certificate_info: + deprecation: + removal_version: 2.0.0 + warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info' + module_utils: + crypto.identify: + redirect: community.crypto.crypto.pem + deprecation: + removal_version: 2.0.0 + warning_text: The 'crypto/identify.py' module_utils has been renamed 'crypto/pem.py'. Please update your imports diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/action/openssl_privatekey_pipe.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/action/openssl_privatekey_pipe.py new file mode 100644 index 00000000..42d603ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/action/openssl_privatekey_pipe.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import base64 + +from ansible.module_utils._text import to_native, to_bytes + +from ansible_collections.community.crypto.plugins.plugin_utils.action_module import ActionModuleBase + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.privatekey import ( + select_backend, + get_privatekey_argument_spec, +) + + +class PrivateKeyModule(object): + def __init__(self, module, module_backend): + self.module = module + self.module_backend = module_backend + self.check_mode = module.check_mode + self.changed = False + self.return_current_key = module.params['return_current_key'] + + if module.params['content'] is not None: + if module.params['content_base64']: + try: + data = base64.b64decode(module.params['content']) + except Exception as e: + module.fail_json(msg='Cannot decode Base64 encoded data: {0}'.format(e)) + else: + data = to_bytes(module.params['content']) + module_backend.set_existing(data) + + def generate(self, module): + """Generate a keypair.""" + + if self.module_backend.needs_regeneration(): + # Regenerate + if not self.check_mode: + self.module_backend.generate_private_key() + privatekey_data = self.module_backend.get_private_key_data() + self.privatekey_bytes = privatekey_data + self.changed = True + elif self.module_backend.needs_conversion(): + # Convert + if not self.check_mode: + self.module_backend.convert_private_key() + privatekey_data = self.module_backend.get_private_key_data() + self.privatekey_bytes = privatekey_data + self.changed = True + + def dump(self): + """Serialize the object into a dictionary.""" + result = self.module_backend.dump(include_key=self.changed or self.return_current_key) + result['changed'] = self.changed + return result + + +class ActionModule(ActionModuleBase): + @staticmethod + def setup_module(): + argument_spec = get_privatekey_argument_spec() + argument_spec.argument_spec.update(dict( + content=dict(type='str', no_log=True), + content_base64=dict(type='bool', default=False), + return_current_key=dict(type='bool', default=False), + )) + return argument_spec, dict( + supports_check_mode=True, + ) + + @staticmethod + def run_module(module): + backend, module_backend = select_backend( + module=module, + backend=module.params['select_crypto_backend'], + ) + + try: + private_key = PrivateKeyModule(module, module_backend) + private_key.generate(module) + result = private_key.dump() + if private_key.return_current_key: + # In case the module's input (`content`) is returned as `privatekey`: + # Since `content` is no_log=True, `privatekey`'s value will get replaced by + # VALUE_SPECIFIED_IN_NO_LOG_PARAMETER. To avoid this, we remove the value of + # `content` from module.no_log_values. Since we explicitly set + # `module.no_log = True`, this should be safe. + module.no_log = True + try: + module.no_log_values.remove(module.params['content']) + except KeyError: + pass + module.params['content'] = 'ANSIBLE_NO_LOG_VALUE' + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/acme.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/acme.py new file mode 100644 index 00000000..98c673f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/acme.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +notes: + - "If a new enough version of the C(cryptography) library + is available (see Requirements for details), it will be used + instead of the C(openssl) binary. This can be explicitly disabled + or enabled with the C(select_crypto_backend) option. Note that using + the C(openssl) binary will be slower and less secure, as private key + contents always have to be stored on disk (see + C(account_key_content))." + - "Although the defaults are chosen so that the module can be used with + the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in + principle be used with any CA providing an ACME endpoint, such as + L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)." +requirements: + - python >= 2.6 + - either openssl or L(cryptography,https://cryptography.io/) >= 1.5 +options: + account_key_src: + description: + - "Path to a file containing the ACME account RSA or Elliptic Curve + key." + - "Private keys can be created with the + M(community.crypto.openssl_privatekey) module. If the requisites + (pyOpenSSL or cryptography) are not available, keys can also be + created directly with the C(openssl) command line tool: RSA keys + can be created with C(openssl genrsa ...). Elliptic curve keys can be + created with C(openssl ecparam -genkey ...). Any other tool creating + private keys in PEM format can be used as well." + - "Mutually exclusive with C(account_key_content)." + - "Required if C(account_key_content) is not used." + type: path + aliases: [ account_key ] + account_key_content: + description: + - "Content of the ACME account RSA or Elliptic Curve key." + - "Mutually exclusive with C(account_key_src)." + - "Required if C(account_key_src) is not used." + - "*Warning:* the content will be written into a temporary file, which will + be deleted by Ansible when the module completes. Since this is an + important private key — it can be used to change the account key, + or to revoke your certificates without knowing their private keys + —, this might not be acceptable." + - "In case C(cryptography) is used, the content is not written into a + temporary file. It can still happen that it is written to disk by + Ansible in the process of moving the module with its argument to + the node where it is executed." + type: str + account_uri: + description: + - "If specified, assumes that the account URI is as given. If the + account key does not match this account, or an account with this + URI does not exist, the module fails." + type: str + acme_version: + description: + - "The ACME version of the endpoint." + - "Must be 1 for the classic Let's Encrypt and Buypass ACME endpoints, + or 2 for standardized ACME v2 endpoints." + - "The default value is 1. Note that in community.crypto 2.0.0, this + option *will be required* and will no longer have a default." + - "Please also note that we will deprecate ACME v1 support eventually." + type: int + choices: [ 1, 2 ] + acme_directory: + description: + - "The ACME directory to use. This is the entry point URL to access + CA server API." + - "For safety reasons the default is set to the Let's Encrypt staging + server (for the ACME v1 protocol). This will create technically correct, + but untrusted certificates." + - "The default value is U(https://acme-staging.api.letsencrypt.org/directory). + Note that in community.crypto 2.0.0, this option *will be required* and + will no longer have a default." + - "For Let's Encrypt, all staging endpoints can be found here: + U(https://letsencrypt.org/docs/staging-environment/). For Buypass, all + endpoints can be found here: + U(https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints)" + - "For Let's Encrypt, the production directory URL for ACME v1 is + U(https://acme-v01.api.letsencrypt.org/directory), and the production + directory URL for ACME v2 is U(https://acme-v02.api.letsencrypt.org/directory)." + - "For Buypass, the production directory URL for ACME v2 and v1 is + U(https://api.buypass.com/acme/directory)." + - "*Warning:* So far, the module has only been tested against Let's Encrypt + (staging and production), Buypass (staging and production), and + L(Pebble testing server,https://github.com/letsencrypt/Pebble)." + type: str + validate_certs: + description: + - Whether calls to the ACME directory will validate TLS certificates. + - "*Warning:* Should *only ever* be set to C(no) for testing purposes, + for example when testing against a local Pebble server." + type: bool + default: yes + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to + C(openssl). + - If set to C(openssl), will try to use the C(openssl) binary. + - If set to C(cryptography), will try to use the + L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, openssl ] +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/ecs_credential.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/ecs_credential.py new file mode 100644 index 00000000..5f20fc7f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/ecs_credential.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Copyright (c), Entrust Datacard Corporation, 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Plugin options for Entrust Certificate Services (ECS) credentials + DOCUMENTATION = r''' +options: + entrust_api_user: + description: + - The username for authentication to the Entrust Certificate Services (ECS) API. + type: str + required: true + entrust_api_key: + description: + - The key (password) for authentication to the Entrust Certificate Services (ECS) API. + type: str + required: true + entrust_api_client_cert_path: + description: + - The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. + type: path + required: true + entrust_api_client_cert_key_path: + description: + - The path to the key for the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. + type: path + required: true + entrust_api_specification_path: + description: + - The path to the specification file defining the Entrust Certificate Services (ECS) API configuration. + - You can use this to keep a local copy of the specification to avoid downloading it every time the module is used. + type: path + default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml +requirements: + - "PyYAML >= 3.11" +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_certificate.py new file mode 100644 index 00000000..2c565e65 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_certificate.py @@ -0,0 +1,587 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +description: + - This module allows one to (re)generate OpenSSL certificates. + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. + - If both the cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with C(select_crypto_backend)). + Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. +requirements: + - PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned), C(ownca) or C(assertonly) provider) +options: + force: + description: + - Generate the certificate, even if it already exists. + type: bool + default: no + + csr_path: + description: + - Path to the Certificate Signing Request (CSR) used to generate this certificate. + - This is mutually exclusive with I(csr_content). + type: path + csr_content: + description: + - Content of the Certificate Signing Request (CSR) used to generate this certificate. + - This is mutually exclusive with I(csr_path). + type: str + + privatekey_path: + description: + - Path to the private key to use when signing the certificate. + - This is mutually exclusive with I(privatekey_content). + type: path + privatekey_content: + description: + - Path to the private key to use when signing the certificate. + - This is mutually exclusive with I(privatekey_path). + type: str + + privatekey_passphrase: + description: + - The passphrase for the I(privatekey_path) resp. I(privatekey_content). + - This is required if the private key is password protected. + type: str + + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +notes: + - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. + - Date specified should be UTC. Minutes and seconds are mandatory. + - For security reason, when you use C(ownca) provider, you should NOT run + M(community.crypto.x509_certificate) on a target machine, but on a dedicated CA machine. It + is recommended not to store the CA private key on the target machine. Once signed, the + certificate can be moved to the target machine. +seealso: +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_csr_pipe +- module: community.crypto.openssl_dhparam +- module: community.crypto.openssl_pkcs12 +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_privatekey_pipe +- module: community.crypto.openssl_publickey +''' + + BACKEND_ACME_DOCUMENTATION = r''' +description: + - This module allows one to (re)generate OpenSSL certificates. +requirements: + - acme-tiny >= 4.0.0 (if using the C(acme) provider) +options: + acme_accountkey_path: + description: + - The path to the accountkey for the C(acme) provider. + - This is only used by the C(acme) provider. + type: path + + acme_challenge_path: + description: + - The path to the ACME challenge directory that is served on U(http://<HOST>:80/.well-known/acme-challenge/) + - This is only used by the C(acme) provider. + type: path + + acme_chain: + description: + - Include the intermediate certificate to the generated certificate + - This is only used by the C(acme) provider. + - Note that this is only available for older versions of C(acme-tiny). + New versions include the chain automatically, and setting I(acme_chain) to C(yes) results in an error. + type: bool + default: no + + acme_directory: + description: + - "The ACME directory to use. You can use any directory that supports the ACME protocol, such as Buypass or Let's Encrypt." + - "Let's Encrypt recommends using their staging server while developing jobs. U(https://letsencrypt.org/docs/staging-environment/)." + type: str + default: https://acme-v02.api.letsencrypt.org/directory +''' + + BACKEND_ASSERTONLY_DOCUMENTATION = r''' +description: + - The C(assertonly) provider is intended for use cases where one is only interested in + checking properties of a supplied certificate. Please note that this provider has been + deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. See the examples on how + to emulate C(assertonly) usage with M(community.crypto.x509_certificate_info), + M(community.crypto.openssl_csr_info), M(community.crypto.openssl_privatekey_info) and + M(ansible.builtin.assert). This also allows more flexible checks than + the ones offered by the C(assertonly) provider. + - Many properties that can be specified in this module are for validation of an + existing or newly generated certificate. The proper place to specify them, if you + want to receive a certificate with these properties is a CSR (Certificate Signing Request). +options: + csr_path: + description: + - This is not required for the C(assertonly) provider. + + csr_content: + description: + - This is not required for the C(assertonly) provider. + + signature_algorithms: + description: + - A list of algorithms that you would accept the certificate to be signed with + (e.g. ['sha256WithRSAEncryption', 'sha512WithRSAEncryption']). + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: list + elements: str + + issuer: + description: + - The key/value pairs that must be present in the issuer name field of the certificate. + - If you need to specify more than one value with the same key, use a list as value. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: dict + + issuer_strict: + description: + - If set to C(yes), the I(issuer) field must contain only these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + + subject: + description: + - The key/value pairs that must be present in the subject name field of the certificate. + - If you need to specify more than one value with the same key, use a list as value. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: dict + + subject_strict: + description: + - If set to C(yes), the I(subject) field must contain only these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + + has_expired: + description: + - Checks if the certificate is expired/not expired at the time the module is executed. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + + version: + description: + - The version of the certificate. + - Nowadays it should almost always be 3. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: int + + valid_at: + description: + - The certificate must be valid at this point in time. + - The timestamp is formatted as an ASN.1 TIME. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: str + + invalid_at: + description: + - The certificate must be invalid at this point in time. + - The timestamp is formatted as an ASN.1 TIME. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: str + + not_before: + description: + - The certificate must start to become valid at this point in time. + - The timestamp is formatted as an ASN.1 TIME. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: str + aliases: [ notBefore ] + + not_after: + description: + - The certificate must expire at this point in time. + - The timestamp is formatted as an ASN.1 TIME. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: str + aliases: [ notAfter ] + + valid_in: + description: + - The certificate must still be valid at this relative time offset from now. + - Valid format is C([+-]timespec | number_of_seconds) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using this parameter, this module is NOT idempotent. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: str + + key_usage: + description: + - The I(key_usage) extension field must contain all these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: list + elements: str + aliases: [ keyUsage ] + + key_usage_strict: + description: + - If set to C(yes), the I(key_usage) extension field must contain only these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + aliases: [ keyUsage_strict ] + + extended_key_usage: + description: + - The I(extended_key_usage) extension field must contain all these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: list + elements: str + aliases: [ extendedKeyUsage ] + + extended_key_usage_strict: + description: + - If set to C(yes), the I(extended_key_usage) extension field must contain only these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + aliases: [ extendedKeyUsage_strict ] + + subject_alt_name: + description: + - The I(subject_alt_name) extension field must contain these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: list + elements: str + aliases: [ subjectAltName ] + + subject_alt_name_strict: + description: + - If set to C(yes), the I(subject_alt_name) extension field must contain only these values. + - This is only used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + aliases: [ subjectAltName_strict ] +''' + + BACKEND_ENTRUST_DOCUMENTATION = r''' +options: + entrust_cert_type: + description: + - Specify the type of certificate requested. + - This is only used by the C(entrust) provider. + type: str + default: STANDARD_SSL + choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ] + + entrust_requester_email: + description: + - The email of the requester of the certificate (for tracking purposes). + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: str + + entrust_requester_name: + description: + - The name of the requester of the certificate (for tracking purposes). + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: str + + entrust_requester_phone: + description: + - The phone number of the requester of the certificate (for tracking purposes). + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: str + + entrust_api_user: + description: + - The username for authentication to the Entrust Certificate Services (ECS) API. + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: str + + entrust_api_key: + description: + - The key (password) for authentication to the Entrust Certificate Services (ECS) API. + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: str + + entrust_api_client_cert_path: + description: + - The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: path + + entrust_api_client_cert_key_path: + description: + - The path to the private key of the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. + - This is only used by the C(entrust) provider. + - This is required if the provider is C(entrust). + type: path + + entrust_not_after: + description: + - The point in time at which the certificate stops being valid. + - Time can be specified either as relative time or as an absolute timestamp. + - A valid absolute time format is C(ASN.1 TIME) such as C(2019-06-18). + - A valid relative time format is C([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as C(+365d) or C(+32w1d2h)). + - Time will always be interpreted as UTC. + - Note that only the date (day, month, year) is supported for specifying the expiry date of the issued certificate. + - The full date-time is adjusted to EST (GMT -5:00) before issuance, which may result in a certificate with an expiration date one day + earlier than expected if a relative time is used. + - The minimum certificate lifetime is 90 days, and maximum is three years. + - If this value is not specified, the certificate will stop being valid 365 days the date of issue. + - This is only used by the C(entrust) provider. + type: str + default: +365d + + entrust_api_specification_path: + description: + - The path to the specification file defining the Entrust Certificate Services (ECS) API configuration. + - You can use this to keep a local copy of the specification to avoid downloading it every time the module is used. + - This is only used by the C(entrust) provider. + type: path + default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml +''' + + BACKEND_OWNCA_DOCUMENTATION = r''' +description: + - The C(ownca) provider is intended for generating an OpenSSL certificate signed with your own + CA (Certificate Authority) certificate (self-signed certificate). +options: + ownca_path: + description: + - Remote absolute path of the CA (Certificate Authority) certificate. + - This is only used by the C(ownca) provider. + - This is mutually exclusive with I(ownca_content). + type: path + ownca_content: + description: + - Content of the CA (Certificate Authority) certificate. + - This is only used by the C(ownca) provider. + - This is mutually exclusive with I(ownca_path). + type: str + + ownca_privatekey_path: + description: + - Path to the CA (Certificate Authority) private key to use when signing the certificate. + - This is only used by the C(ownca) provider. + - This is mutually exclusive with I(ownca_privatekey_content). + type: path + ownca_privatekey_content: + description: + - Content of the CA (Certificate Authority) private key to use when signing the certificate. + - This is only used by the C(ownca) provider. + - This is mutually exclusive with I(ownca_privatekey_path). + type: str + + ownca_privatekey_passphrase: + description: + - The passphrase for the I(ownca_privatekey_path) resp. I(ownca_privatekey_content). + - This is only used by the C(ownca) provider. + type: str + + ownca_digest: + description: + - The digest algorithm to be used for the C(ownca) certificate. + - This is only used by the C(ownca) provider. + type: str + default: sha256 + + ownca_version: + description: + - The version of the C(ownca) certificate. + - Nowadays it should almost always be C(3). + - This is only used by the C(ownca) provider. + type: int + default: 3 + + ownca_not_before: + description: + - The point in time the certificate is valid from. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent. + - If this value is not specified, the certificate will start being valid from now. + - This is only used by the C(ownca) provider. + type: str + default: +0s + + ownca_not_after: + description: + - The point in time at which the certificate stops being valid. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent. + - If this value is not specified, the certificate will stop being valid 10 years from now. + - This is only used by the C(ownca) provider. + - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. + Please see U(https://support.apple.com/en-us/HT210176) for more details. + type: str + default: +3650d + + ownca_create_subject_key_identifier: + description: + - Whether to create the Subject Key Identifier (SKI) from the public key. + - A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not + provide one. + - A value of C(always_create) always creates a SKI. If the CSR provides one, that one is + ignored. + - A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used. + - This is only used by the C(ownca) provider. + - Note that this is only supported if the C(cryptography) backend is used! + type: str + choices: [create_if_not_provided, always_create, never_create] + default: create_if_not_provided + + ownca_create_authority_key_identifier: + description: + - Create a Authority Key Identifier from the CA's certificate. If the CSR provided + a authority key identifier, it is ignored. + - The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier, + if available. If it is not available, the CA certificate's public key will be used. + - This is only used by the C(ownca) provider. + - Note that this is only supported if the C(cryptography) backend is used! + type: bool + default: yes +''' + + BACKEND_SELFSIGNED_DOCUMENTATION = r''' +notes: + - For the C(selfsigned) provider, I(csr_path) and I(csr_content) are optional. If not provided, a + certificate without any information (Subject, Subject Alternative Names, Key Usage, etc.) is created. + +options: + # NOTE: descriptions in options are overwritten, not appended. For that reason, the texts provided + # here for csr_path and csr_content are not visible to the user. That's why this information is + # added to the notes (see above). + + # csr_path: + # description: + # - This is optional for the C(selfsigned) provider. If not provided, a certificate + # without any information (Subject, Subject Alternative Names, Key Usage, etc.) is + # created. + + # csr_content: + # description: + # - This is optional for the C(selfsigned) provider. If not provided, a certificate + # without any information (Subject, Subject Alternative Names, Key Usage, etc.) is + # created. + + selfsigned_version: + description: + - Version of the C(selfsigned) certificate. + - Nowadays it should almost always be C(3). + - This is only used by the C(selfsigned) provider. + type: int + default: 3 + + selfsigned_digest: + description: + - Digest algorithm to be used when self-signing the certificate. + - This is only used by the C(selfsigned) provider. + type: str + default: sha256 + + selfsigned_not_before: + description: + - The point in time the certificate is valid from. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent. + - If this value is not specified, the certificate will start being valid from now. + - This is only used by the C(selfsigned) provider. + type: str + default: +0s + aliases: [ selfsigned_notBefore ] + + selfsigned_not_after: + description: + - The point in time at which the certificate stops being valid. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent. + - If this value is not specified, the certificate will stop being valid 10 years from now. + - This is only used by the C(selfsigned) provider. + - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. + Please see U(https://support.apple.com/en-us/HT210176) for more details. + type: str + default: +3650d + aliases: [ selfsigned_notAfter ] + + selfsigned_create_subject_key_identifier: + description: + - Whether to create the Subject Key Identifier (SKI) from the public key. + - A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not + provide one. + - A value of C(always_create) always creates a SKI. If the CSR provides one, that one is + ignored. + - A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used. + - This is only used by the C(selfsigned) provider. + - Note that this is only supported if the C(cryptography) backend is used! + type: str + choices: [create_if_not_provided, always_create, never_create] + default: create_if_not_provided +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_csr.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_csr.py new file mode 100644 index 00000000..8d9f0b73 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_csr.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- + +# Copyrigt: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +description: + - This module allows one to (re)generate OpenSSL certificate signing requests. + - This module supports the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple + extensions. + - "The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. Please note that the + PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0." +requirements: + - Either cryptography >= 1.3 + - Or pyOpenSSL >= 0.15 +options: + digest: + description: + - The digest used when signing the certificate signing request with the private key. + type: str + default: sha256 + privatekey_path: + description: + - The path to the private key to use when signing the certificate signing request. + - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. + type: path + privatekey_content: + description: + - The content of the private key to use when signing the certificate signing request. + - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. + type: str + privatekey_passphrase: + description: + - The passphrase for the private key. + - This is required if the private key is password protected. + type: str + version: + description: + - The version of the certificate signing request. + - "The only allowed value according to L(RFC 2986,https://tools.ietf.org/html/rfc2986#section-4.1) + is 1." + - This option will no longer accept unsupported values from community.crypto 2.0.0 on. + type: int + default: 1 + subject: + description: + - Key/value pairs that will be present in the subject name field of the certificate signing request. + - If you need to specify more than one value with the same key, use a list as value. + type: dict + country_name: + description: + - The countryName field of the certificate signing request subject. + type: str + aliases: [ C, countryName ] + state_or_province_name: + description: + - The stateOrProvinceName field of the certificate signing request subject. + type: str + aliases: [ ST, stateOrProvinceName ] + locality_name: + description: + - The localityName field of the certificate signing request subject. + type: str + aliases: [ L, localityName ] + organization_name: + description: + - The organizationName field of the certificate signing request subject. + type: str + aliases: [ O, organizationName ] + organizational_unit_name: + description: + - The organizationalUnitName field of the certificate signing request subject. + type: str + aliases: [ OU, organizationalUnitName ] + common_name: + description: + - The commonName field of the certificate signing request subject. + type: str + aliases: [ CN, commonName ] + email_address: + description: + - The emailAddress field of the certificate signing request subject. + type: str + aliases: [ E, emailAddress ] + subject_alt_name: + description: + - Subject Alternative Name (SAN) extension to attach to the certificate signing request. + - This can either be a 'comma separated string' or a YAML list. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA). + - Note that if no SAN is specified, but a common name, the common + name will be added as a SAN except if C(useCommonNameForSAN) is + set to I(false). + - More at U(https://tools.ietf.org/html/rfc5280#section-4.2.1.6). + type: list + elements: str + aliases: [ subjectAltName ] + subject_alt_name_critical: + description: + - Should the subjectAltName extension be considered as critical. + type: bool + default: false + aliases: [ subjectAltName_critical ] + use_common_name_for_san: + description: + - If set to C(yes), the module will fill the common name in for + C(subject_alt_name) with C(DNS:) prefix if no SAN is specified. + type: bool + default: yes + aliases: [ useCommonNameForSAN ] + key_usage: + description: + - This defines the purpose (e.g. encipherment, signature, certificate signing) + of the key contained in the certificate. + type: list + elements: str + aliases: [ keyUsage ] + key_usage_critical: + description: + - Should the keyUsage extension be considered as critical. + type: bool + default: false + aliases: [ keyUsage_critical ] + extended_key_usage: + description: + - Additional restrictions (e.g. client authentication, server authentication) + on the allowed purposes for which the public key may be used. + type: list + elements: str + aliases: [ extKeyUsage, extendedKeyUsage ] + extended_key_usage_critical: + description: + - Should the extkeyUsage extension be considered as critical. + type: bool + default: false + aliases: [ extKeyUsage_critical, extendedKeyUsage_critical ] + basic_constraints: + description: + - Indicates basic constraints, such as if the certificate is a CA. + type: list + elements: str + aliases: [ basicConstraints ] + basic_constraints_critical: + description: + - Should the basicConstraints extension be considered as critical. + type: bool + default: false + aliases: [ basicConstraints_critical ] + ocsp_must_staple: + description: + - Indicates that the certificate should contain the OCSP Must Staple + extension (U(https://tools.ietf.org/html/rfc7633)). + type: bool + default: false + aliases: [ ocspMustStaple ] + ocsp_must_staple_critical: + description: + - Should the OCSP Must Staple extension be considered as critical. + - Note that according to the RFC, this extension should not be marked + as critical, as old clients not knowing about OCSP Must Staple + are required to reject such certificates + (see U(https://tools.ietf.org/html/rfc7633#section-4)). + type: bool + default: false + aliases: [ ocspMustStaple_critical ] + name_constraints_permitted: + description: + - For CA certificates, this specifies a list of identifiers which describe + subtrees of names that this CA is allowed to issue certificates for. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA). + type: list + elements: str + name_constraints_excluded: + description: + - For CA certificates, this specifies a list of identifiers which describe + subtrees of names that this CA is *not* allowed to issue certificates for. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA). + type: list + elements: str + name_constraints_critical: + description: + - Should the Name Constraints extension be considered as critical. + type: bool + default: false + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + create_subject_key_identifier: + description: + - Create the Subject Key Identifier from the public key. + - "Please note that commercial CAs can ignore the value, respectively use a value of + their own choice instead. Specifying this option is mostly useful for self-signed + certificates or for own CAs." + - Note that this is only supported if the C(cryptography) backend is used! + type: bool + default: no + subject_key_identifier: + description: + - The subject key identifier as a hex string, where two bytes are separated by colons. + - "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)" + - "Please note that commercial CAs ignore this value, respectively use a value of their + own choice. Specifying this option is mostly useful for self-signed certificates + or for own CAs." + - Note that this option can only be used if I(create_subject_key_identifier) is C(no). + - Note that this is only supported if the C(cryptography) backend is used! + type: str + authority_key_identifier: + description: + - The authority key identifier as a hex string, where two bytes are separated by colons. + - "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)" + - If specified, I(authority_cert_issuer) must also be specified. + - "Please note that commercial CAs ignore this value, respectively use a value of their + own choice. Specifying this option is mostly useful for self-signed certificates + or for own CAs." + - Note that this is only supported if the C(cryptography) backend is used! + - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier), + I(authority_cert_issuer) and I(authority_cert_serial_number) is specified. + type: str + authority_cert_issuer: + description: + - Names that will be present in the authority cert issuer field of the certificate signing request. + - Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName), + C(otherName) and the ones specific to your CA) + - "Example: C(DNS:ca.example.org)" + - If specified, I(authority_key_identifier) must also be specified. + - "Please note that commercial CAs ignore this value, respectively use a value of their + own choice. Specifying this option is mostly useful for self-signed certificates + or for own CAs." + - Note that this is only supported if the C(cryptography) backend is used! + - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier), + I(authority_cert_issuer) and I(authority_cert_serial_number) is specified. + type: list + elements: str + authority_cert_serial_number: + description: + - The authority cert serial number. + - Note that this is only supported if the C(cryptography) backend is used! + - "Please note that commercial CAs ignore this value, respectively use a value of their + own choice. Specifying this option is mostly useful for self-signed certificates + or for own CAs." + - The C(AuthorityKeyIdentifier) will only be added if at least one of I(authority_key_identifier), + I(authority_cert_issuer) and I(authority_cert_serial_number) is specified. + type: int + crl_distribution_points: + description: + - Allows to specify one or multiple CRL distribution points. + - Only supported by the C(cryptography) backend. + type: list + elements: dict + suboptions: + full_name: + description: + - Describes how the CRL can be retrieved. + - Mutually exclusive with I(relative_name). + - "Example: C(URI:https://ca.example.com/revocations.crl)." + type: list + elements: str + relative_name: + description: + - Describes how the CRL can be retrieved relative to the CRL issuer. + - Mutually exclusive with I(full_name). + - "Example: C(/CN=example.com)." + - Can only be used when cryptography >= 1.6 is installed. + type: list + elements: str + crl_issuer: + description: + - Information about the issuer of the CRL. + type: list + elements: str + reasons: + description: + - List of reasons that this distribution point can be used for when performing revocation checks. + type: list + elements: str + choices: + - key_compromise + - ca_compromise + - affiliation_changed + - superseded + - cessation_of_operation + - certificate_hold + - privilege_withdrawn + - aa_compromise + version_added: 1.4.0 +notes: + - If the certificate signing request already exists it will be checked whether subjectAltName, + keyUsage, extendedKeyUsage and basicConstraints only contain the requested values, whether + OCSP Must Staple is as requested, and if the request was signed by the given private key. +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.x509_certificate_pipe +- module: community.crypto.openssl_dhparam +- module: community.crypto.openssl_pkcs12 +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_privatekey_pipe +- module: community.crypto.openssl_publickey +- module: community.crypto.openssl_csr_info +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_privatekey.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_privatekey.py new file mode 100644 index 00000000..e8141631 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/doc_fragments/module_privatekey.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = r''' +description: + - One can generate L(RSA,https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29), + L(DSA,https://en.wikipedia.org/wiki/Digital_Signature_Algorithm), + L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or + L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys. + - Keys are generated in PEM format. + - "Please note that the module regenerates private keys if they don't match + the module's options. In particular, if you provide another passphrase + (or specify none), change the keysize, etc., the private key will be + regenerated. If you are concerned that this could **overwrite your private key**, + consider using the I(backup) option." + - "The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. Please note that the + PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0." +requirements: + - Either cryptography >= 1.2.3 (older versions might work as well) + - Or pyOpenSSL +options: + size: + description: + - Size (in bits) of the TLS/SSL key to generate. + type: int + default: 4096 + type: + description: + - The algorithm used to generate the TLS/SSL private key. + - Note that C(ECC), C(X25519), C(X448), C(Ed25519) and C(Ed448) require the C(cryptography) backend. + C(X25519) needs cryptography 2.5 or newer, while C(X448), C(Ed25519) and C(Ed448) require + cryptography 2.6 or newer. For C(ECC), the minimal cryptography version required depends on the + I(curve) option. + type: str + default: RSA + choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ] + curve: + description: + - Note that not all curves are supported by all versions of C(cryptography). + - For maximal interoperability, C(secp384r1) or C(secp256r1) should be used. + - We use the curve names as defined in the + L(IANA registry for TLS,https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8). + - Please note that all curves except C(secp224r1), C(secp256k1), C(secp256r1), C(secp384r1) and C(secp521r1) + are discouraged for new private keys. + type: str + choices: + - secp224r1 + - secp256k1 + - secp256r1 + - secp384r1 + - secp521r1 + - secp192r1 + - brainpoolP256r1 + - brainpoolP384r1 + - brainpoolP512r1 + - sect163k1 + - sect163r2 + - sect233k1 + - sect233r1 + - sect283k1 + - sect283r1 + - sect409k1 + - sect409r1 + - sect571k1 + - sect571r1 + passphrase: + description: + - The passphrase for the private key. + type: str + cipher: + description: + - The cipher to encrypt the private key. (Valid values can be found by + running `openssl list -cipher-algorithms` or `openssl list-cipher-algorithms`, + depending on your OpenSSL version.) + - When using the C(cryptography) backend, use C(auto). + type: str + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + format: + description: + - Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format) + is used for all keys which support it. Please note that not every key can be exported in any format. + - The value C(auto) selects a fromat based on the key format. The value C(auto_ignore) does the same, + but for existing private key files, it will not force a regenerate when its format is not the automatically + selected one for generation. + - Note that if the format for an existing private key mismatches, the key is *regenerated* by default. + To change this behavior, use the I(format_mismatch) option. + - The I(format) option is only supported by the C(cryptography) backend. The C(pyopenssl) backend will + fail if a value different from C(auto_ignore) is used. + type: str + default: auto_ignore + choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ] + format_mismatch: + description: + - Determines behavior of the module if the format of a private key does not match the expected format, but all + other parameters are as expected. + - If set to C(regenerate) (default), generates a new private key. + - If set to C(convert), the key will be converted to the new format instead. + - Only supported by the C(cryptography) backend. + type: str + default: regenerate + choices: [ regenerate, convert ] + regenerate: + description: + - Allows to configure in which situations the module is allowed to regenerate private keys. + The module will always generate a new key if the destination file does not exist. + - By default, the key will be regenerated when it doesn't match the module's options, + except when the key cannot be read or the passphrase does not match. Please note that + this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence) + is specified. + - If set to C(never), the module will fail if the key cannot be read or the passphrase + isn't matching, and will never regenerate an existing key. + - If set to C(fail), the module will fail if the key does not correspond to the module's + options. + - If set to C(partial_idempotence), the key will be regenerated if it does not conform to + the module's options. The key is B(not) regenerated if it cannot be read (broken file), + the key is protected by an unknown passphrase, or when they key is not protected by a + passphrase, but a passphrase is specified. + - If set to C(full_idempotence), the key will be regenerated if it does not conform to the + module's options. This is also the case if the key cannot be read (broken file), the key + is protected by an unknown passphrase, or when they key is not protected by a passphrase, + but a passphrase is specified. Make sure you have a B(backup) when using this option! + - If set to C(always), the module will always regenerate the key. This is equivalent to + setting I(force) to C(yes). + - Note that if I(format_mismatch) is set to C(convert) and everything matches except the + format, the key will always be converted, except if I(regenerate) is set to C(always). + type: str + choices: + - never + - fail + - partial_idempotence + - full_idempotence + - always + default: full_idempotence +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.x509_certificate_pipe +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_csr_pipe +- module: community.crypto.openssl_dhparam +- module: community.crypto.openssl_pkcs12 +- module: community.crypto.openssl_publickey +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/acme.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/acme.py new file mode 100644 index 00000000..1026caa3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/acme.py @@ -0,0 +1,1156 @@ +# -*- coding: utf-8 -*- + +# (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import base64 +import binascii +import copy +import datetime +import hashlib +import json +import locale +import os +import re +import shutil +import sys +import tempfile +import traceback + +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.six.moves.urllib.parse import unquote +from ansible.module_utils._text import to_native, to_text, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress + +try: + import cryptography + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.hashes + import cryptography.hazmat.primitives.hmac + import cryptography.hazmat.primitives.asymmetric.ec + import cryptography.hazmat.primitives.asymmetric.padding + import cryptography.hazmat.primitives.asymmetric.rsa + import cryptography.hazmat.primitives.asymmetric.utils + import cryptography.hazmat.primitives.serialization + import cryptography.x509 + import cryptography.x509.oid + from distutils.version import LooseVersion + CRYPTOGRAPHY_VERSION = cryptography.__version__ + HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5')) + if HAS_CURRENT_CRYPTOGRAPHY: + _cryptography_backend = cryptography.hazmat.backends.default_backend() +except Exception as dummy: + HAS_CURRENT_CRYPTOGRAPHY = False + + +class ModuleFailException(Exception): + ''' + If raised, module.fail_json() will be called with the given parameters after cleanup. + ''' + def __init__(self, msg, **args): + super(ModuleFailException, self).__init__(self, msg) + self.msg = msg + self.module_fail_args = args + + def do_fail(self, module, **arguments): + module.fail_json(msg=self.msg, other=self.module_fail_args, **arguments) + + +def nopad_b64(data): + return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "") + + +def read_file(fn, mode='b'): + try: + with open(fn, 'r' + mode) as f: + return f.read() + except Exception as e: + raise ModuleFailException('Error while reading file "{0}": {1}'.format(fn, e)) + + +# function source: network/basics/uri.py +def write_file(module, dest, content): + ''' + Write content to destination file dest, only if the content + has changed. + ''' + changed = False + # create a tempfile + fd, tmpsrc = tempfile.mkstemp(text=False) + f = os.fdopen(fd, 'wb') + try: + f.write(content) + except Exception as err: + try: + f.close() + except Exception as dummy: + pass + os.remove(tmpsrc) + raise ModuleFailException("failed to create temporary content file: %s" % to_native(err), exception=traceback.format_exc()) + f.close() + checksum_src = None + checksum_dest = None + # raise an error if there is no tmpsrc file + if not os.path.exists(tmpsrc): + try: + os.remove(tmpsrc) + except Exception as dummy: + pass + raise ModuleFailException("Source %s does not exist" % (tmpsrc)) + if not os.access(tmpsrc, os.R_OK): + os.remove(tmpsrc) + raise ModuleFailException("Source %s not readable" % (tmpsrc)) + checksum_src = module.sha1(tmpsrc) + # check if there is no dest file + if os.path.exists(dest): + # raise an error if copy has no permission on dest + if not os.access(dest, os.W_OK): + os.remove(tmpsrc) + raise ModuleFailException("Destination %s not writable" % (dest)) + if not os.access(dest, os.R_OK): + os.remove(tmpsrc) + raise ModuleFailException("Destination %s not readable" % (dest)) + checksum_dest = module.sha1(dest) + else: + dirname = os.path.dirname(dest) or '.' + if not os.access(dirname, os.W_OK): + os.remove(tmpsrc) + raise ModuleFailException("Destination dir %s not writable" % (dirname)) + if checksum_src != checksum_dest: + try: + shutil.copyfile(tmpsrc, dest) + changed = True + except Exception as err: + os.remove(tmpsrc) + raise ModuleFailException("failed to copy %s to %s: %s" % (tmpsrc, dest, to_native(err)), exception=traceback.format_exc()) + os.remove(tmpsrc) + return changed + + +def pem_to_der(pem_filename, pem_content=None): + ''' + Load PEM file, or use PEM file's content, and convert to DER. + + If PEM contains multiple entities, the first entity will be used. + ''' + certificate_lines = [] + if pem_content is not None: + lines = pem_content.splitlines() + else: + try: + with open(pem_filename, "rt") as f: + lines = list(f) + except Exception as err: + raise ModuleFailException("cannot load PEM file {0}: {1}".format(pem_filename, to_native(err)), exception=traceback.format_exc()) + header_line_count = 0 + for line in lines: + if line.startswith('-----'): + header_line_count += 1 + if header_line_count == 2: + # If certificate file contains other certs appended + # (like intermediate certificates), ignore these. + break + continue + certificate_lines.append(line.strip()) + return base64.b64decode(''.join(certificate_lines)) + + +def _parse_key_openssl(openssl_binary, module, key_file=None, key_content=None): + ''' + Parses an RSA or Elliptic Curve key file in PEM format and returns a pair + (error, key_data). + ''' + # If key_file isn't given, but key_content, write that to a temporary file + if key_file is None: + fd, tmpsrc = tempfile.mkstemp() + module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit + f = os.fdopen(fd, 'wb') + try: + f.write(key_content.encode('utf-8')) + key_file = tmpsrc + except Exception as err: + try: + f.close() + except Exception as dummy: + pass + raise ModuleFailException("failed to create temporary content file: %s" % to_native(err), exception=traceback.format_exc()) + f.close() + # Parse key + account_key_type = None + with open(key_file, "rt") as f: + for line in f: + m = re.match(r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line) + if m is not None: + account_key_type = m.group(1).lower() + break + if account_key_type is None: + # This happens for example if openssl_privatekey created this key + # (as opposed to the OpenSSL binary). For now, we assume this is + # an RSA key. + # FIXME: add some kind of auto-detection + account_key_type = "rsa" + if account_key_type not in ("rsa", "ec"): + return 'unknown key type "%s"' % account_key_type, {} + + openssl_keydump_cmd = [openssl_binary, account_key_type, "-in", key_file, "-noout", "-text"] + dummy, out, dummy = module.run_command(openssl_keydump_cmd, check_rc=True) + + if account_key_type == 'rsa': + pub_hex, pub_exp = re.search( + r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)", + to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL).groups() + pub_exp = "{0:x}".format(int(pub_exp)) + if len(pub_exp) % 2: + pub_exp = "0{0}".format(pub_exp) + + return None, { + 'key_file': key_file, + 'type': 'rsa', + 'alg': 'RS256', + 'jwk': { + "kty": "RSA", + "e": nopad_b64(binascii.unhexlify(pub_exp.encode("utf-8"))), + "n": nopad_b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), + }, + 'hash': 'sha256', + } + elif account_key_type == 'ec': + pub_data = re.search( + r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?", + to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL) + if pub_data is None: + return 'cannot parse elliptic curve key', {} + pub_hex = binascii.unhexlify(re.sub(r"(\s|:)", "", pub_data.group(1)).encode("utf-8")) + asn1_oid_curve = pub_data.group(2).lower() + nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None + if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256': + bits = 256 + alg = 'ES256' + hashalg = 'sha256' + point_size = 32 + curve = 'P-256' + elif asn1_oid_curve == 'secp384r1' or nist_curve == 'p-384': + bits = 384 + alg = 'ES384' + hashalg = 'sha384' + point_size = 48 + curve = 'P-384' + elif asn1_oid_curve == 'secp521r1' or nist_curve == 'p-521': + # Not yet supported on Let's Encrypt side, see + # https://github.com/letsencrypt/boulder/issues/2217 + bits = 521 + alg = 'ES512' + hashalg = 'sha512' + point_size = 66 + curve = 'P-521' + else: + return 'unknown elliptic curve: %s / %s' % (asn1_oid_curve, nist_curve), {} + num_bytes = (bits + 7) // 8 + if len(pub_hex) != 2 * num_bytes: + return 'bad elliptic curve point (%s / %s)' % (asn1_oid_curve, nist_curve), {} + return None, { + 'key_file': key_file, + 'type': 'ec', + 'alg': alg, + 'jwk': { + "kty": "EC", + "crv": curve, + "x": nopad_b64(pub_hex[:num_bytes]), + "y": nopad_b64(pub_hex[num_bytes:]), + }, + 'hash': hashalg, + 'point_size': point_size, + } + + +def _create_mac_key_openssl(openssl_bin, module, alg, key): + if alg == 'HS256': + hashalg = 'sha256' + hashbytes = 32 + elif alg == 'HS384': + hashalg = 'sha384' + hashbytes = 48 + elif alg == 'HS512': + hashalg = 'sha512' + hashbytes = 64 + else: + raise ModuleFailException('Unsupported MAC key algorithm for OpenSSL backend: {0}'.format(alg)) + key_bytes = base64.urlsafe_b64decode(key) + if len(key_bytes) < hashbytes: + raise ModuleFailException( + '{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes)) + return { + 'type': 'hmac', + 'alg': alg, + 'jwk': { + 'kty': 'oct', + 'k': key, + }, + 'hash': hashalg, + } + + +def _sign_request_openssl(openssl_binary, module, payload64, protected64, key_data): + sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8') + if key_data['type'] == 'hmac': + hex_key = to_native(binascii.hexlify(base64.urlsafe_b64decode(key_data['jwk']['k']))) + cmd_postfix = ["-mac", "hmac", "-macopt", "hexkey:{0}".format(hex_key), "-binary"] + else: + cmd_postfix = ["-sign", key_data['key_file']] + openssl_sign_cmd = [openssl_binary, "dgst", "-{0}".format(key_data['hash'])] + cmd_postfix + + dummy, out, dummy = module.run_command(openssl_sign_cmd, data=sign_payload, check_rc=True, binary_data=True) + + if key_data['type'] == 'ec': + dummy, der_out, dummy = module.run_command( + [openssl_binary, "asn1parse", "-inform", "DER"], + data=out, binary_data=True) + expected_len = 2 * key_data['point_size'] + sig = re.findall( + r"prim:\s+INTEGER\s+:([0-9A-F]{1,%s})\n" % expected_len, + to_text(der_out, errors='surrogate_or_strict')) + if len(sig) != 2: + raise ModuleFailException( + "failed to generate Elliptic Curve signature; cannot parse DER output: {0}".format( + to_text(der_out, errors='surrogate_or_strict'))) + sig[0] = (expected_len - len(sig[0])) * '0' + sig[0] + sig[1] = (expected_len - len(sig[1])) * '0' + sig[1] + out = binascii.unhexlify(sig[0]) + binascii.unhexlify(sig[1]) + + return { + "protected": protected64, + "payload": payload64, + "signature": nopad_b64(to_bytes(out)), + } + + +if sys.version_info[0] >= 3: + # Python 3 (and newer) + def _count_bytes(n): + return (n.bit_length() + 7) // 8 if n > 0 else 0 + + def _convert_int_to_bytes(count, no): + return no.to_bytes(count, byteorder='big') + + def _pad_hex(n, digits): + res = hex(n)[2:] + if len(res) < digits: + res = '0' * (digits - len(res)) + res + return res +else: + # Python 2 + def _count_bytes(n): + if n <= 0: + return 0 + h = '%x' % n + return (len(h) + 1) // 2 + + def _convert_int_to_bytes(count, n): + h = '%x' % n + if len(h) > 2 * count: + raise Exception('Number {1} needs more than {0} bytes!'.format(count, n)) + return ('0' * (2 * count - len(h)) + h).decode('hex') + + def _pad_hex(n, digits): + h = '%x' % n + if len(h) < digits: + h = '0' * (digits - len(h)) + h + return h + + +def _parse_key_cryptography(module, key_file=None, key_content=None): + ''' + Parses an RSA or Elliptic Curve key file in PEM format and returns a pair + (error, key_data). + ''' + # If key_content isn't given, read key_file + if key_content is None: + key_content = read_file(key_file) + else: + key_content = to_bytes(key_content) + # Parse key + try: + key = cryptography.hazmat.primitives.serialization.load_pem_private_key(key_content, password=None, backend=_cryptography_backend) + except Exception as e: + return 'error while loading key: {0}'.format(e), None + if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + pk = key.public_key().public_numbers() + return None, { + 'key_obj': key, + 'type': 'rsa', + 'alg': 'RS256', + 'jwk': { + "kty": "RSA", + "e": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.e), pk.e)), + "n": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.n), pk.n)), + }, + 'hash': 'sha256', + } + elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + pk = key.public_key().public_numbers() + if pk.curve.name == 'secp256r1': + bits = 256 + alg = 'ES256' + hashalg = 'sha256' + point_size = 32 + curve = 'P-256' + elif pk.curve.name == 'secp384r1': + bits = 384 + alg = 'ES384' + hashalg = 'sha384' + point_size = 48 + curve = 'P-384' + elif pk.curve.name == 'secp521r1': + # Not yet supported on Let's Encrypt side, see + # https://github.com/letsencrypt/boulder/issues/2217 + bits = 521 + alg = 'ES512' + hashalg = 'sha512' + point_size = 66 + curve = 'P-521' + else: + return 'unknown elliptic curve: {0}'.format(pk.curve.name), {} + num_bytes = (bits + 7) // 8 + return None, { + 'key_obj': key, + 'type': 'ec', + 'alg': alg, + 'jwk': { + "kty": "EC", + "crv": curve, + "x": nopad_b64(_convert_int_to_bytes(num_bytes, pk.x)), + "y": nopad_b64(_convert_int_to_bytes(num_bytes, pk.y)), + }, + 'hash': hashalg, + 'point_size': point_size, + } + else: + return 'unknown key type "{0}"'.format(type(key)), {} + + +def _create_mac_key_cryptography(module, alg, key): + if alg == 'HS256': + hashalg = cryptography.hazmat.primitives.hashes.SHA256 + hashbytes = 32 + elif alg == 'HS384': + hashalg = cryptography.hazmat.primitives.hashes.SHA384 + hashbytes = 48 + elif alg == 'HS512': + hashalg = cryptography.hazmat.primitives.hashes.SHA512 + hashbytes = 64 + else: + raise ModuleFailException('Unsupported MAC key algorithm for cryptography backend: {0}'.format(alg)) + key_bytes = base64.urlsafe_b64decode(key) + if len(key_bytes) < hashbytes: + raise ModuleFailException( + '{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes)) + return { + 'mac_obj': lambda: cryptography.hazmat.primitives.hmac.HMAC( + key_bytes, + hashalg(), + _cryptography_backend), + 'type': 'hmac', + 'alg': alg, + 'jwk': { + 'kty': 'oct', + 'k': key, + }, + } + + +def _sign_request_cryptography(module, payload64, protected64, key_data): + sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8') + if 'mac_obj' in key_data: + mac = key_data['mac_obj']() + mac.update(sign_payload) + signature = mac.finalize() + elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15() + hashalg = cryptography.hazmat.primitives.hashes.SHA256 + signature = key_data['key_obj'].sign(sign_payload, padding, hashalg()) + elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + if key_data['hash'] == 'sha256': + hashalg = cryptography.hazmat.primitives.hashes.SHA256 + elif key_data['hash'] == 'sha384': + hashalg = cryptography.hazmat.primitives.hashes.SHA384 + elif key_data['hash'] == 'sha512': + hashalg = cryptography.hazmat.primitives.hashes.SHA512 + ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg()) + r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa)) + rr = _pad_hex(r, 2 * key_data['point_size']) + ss = _pad_hex(s, 2 * key_data['point_size']) + signature = binascii.unhexlify(rr) + binascii.unhexlify(ss) + + return { + "protected": protected64, + "payload": payload64, + "signature": nopad_b64(signature), + } + + +def _assert_fetch_url_success(response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True): + if info['status'] < 0: + raise ModuleFailException(msg="Failure downloading %s, %s" % (info['url'], info['msg'])) + + if (300 <= info['status'] < 400 and not allow_redirect) or \ + (400 <= info['status'] < 500 and not allow_client_error) or \ + (info['status'] >= 500 and not allow_server_error): + raise ModuleFailException("ACME request failed: CODE: {0} MGS: {1} RESULT: {2}".format(info['status'], info['msg'], response)) + + +class ACMEDirectory(object): + ''' + The ACME server directory. Gives access to the available resources, + and allows to obtain a Replay-Nonce. The acme_directory URL + needs to support unauthenticated GET requests; ACME endpoints + requiring authentication are not supported. + https://tools.ietf.org/html/rfc8555#section-7.1.1 + ''' + + def __init__(self, module, account): + self.module = module + self.directory_root = module.params['acme_directory'] + self.version = module.params['acme_version'] + + self.directory, dummy = account.get_request(self.directory_root, get_only=True) + + # Check whether self.version matches what we expect + if self.version == 1: + for key in ('new-reg', 'new-authz', 'new-cert'): + if key not in self.directory: + raise ModuleFailException("ACME directory does not seem to follow protocol ACME v1") + if self.version == 2: + for key in ('newNonce', 'newAccount', 'newOrder'): + if key not in self.directory: + raise ModuleFailException("ACME directory does not seem to follow protocol ACME v2") + + def __getitem__(self, key): + return self.directory[key] + + def get_nonce(self, resource=None): + url = self.directory_root if self.version == 1 else self.directory['newNonce'] + if resource is not None: + url = resource + dummy, info = fetch_url(self.module, url, method='HEAD') + if info['status'] not in (200, 204): + raise ModuleFailException("Failed to get replay-nonce, got status {0}".format(info['status'])) + return info['replay-nonce'] + + +class ACMEAccount(object): + ''' + ACME account object. Handles the authorized communication with the + ACME server. Provides access to account bound information like + the currently active authorizations and valid certificates + ''' + + def __init__(self, module): + # Set to true to enable logging of all signed requests + self._debug = False + + self.module = module + self.version = module.params['acme_version'] + # account_key path and content are mutually exclusive + self.key = module.params['account_key_src'] + self.key_content = module.params['account_key_content'] + + # Grab account URI from module parameters. + # Make sure empty string is treated as None. + self.uri = module.params.get('account_uri') or None + + self._openssl_bin = module.get_bin_path('openssl', True) + + if self.key is not None or self.key_content is not None: + error, self.key_data = self.parse_key(self.key, self.key_content) + if error: + raise ModuleFailException("error while parsing account key: %s" % error) + self.jwk = self.key_data['jwk'] + self.jws_header = { + "alg": self.key_data['alg'], + "jwk": self.jwk, + } + if self.uri: + # Make sure self.jws_header is updated + self.set_account_uri(self.uri) + + self.directory = ACMEDirectory(module, self) + + def get_keyauthorization(self, token): + ''' + Returns the key authorization for the given token + https://tools.ietf.org/html/rfc8555#section-8.1 + ''' + accountkey_json = json.dumps(self.jwk, sort_keys=True, separators=(',', ':')) + thumbprint = nopad_b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) + return "{0}.{1}".format(token, thumbprint) + + def parse_key(self, key_file=None, key_content=None): + ''' + Parses an RSA or Elliptic Curve key file in PEM format and returns a pair + (error, key_data). + ''' + if key_file is None and key_content is None: + raise AssertionError('One of key_file and key_content must be specified!') + if HAS_CURRENT_CRYPTOGRAPHY: + return _parse_key_cryptography(self.module, key_file, key_content) + else: + return _parse_key_openssl(self._openssl_bin, self.module, key_file, key_content) + + def sign_request(self, protected, payload, key_data, encode_payload=True): + try: + if payload is None: + # POST-as-GET + payload64 = '' + else: + # POST + if encode_payload: + payload = self.module.jsonify(payload).encode('utf8') + payload64 = nopad_b64(to_bytes(payload)) + protected64 = nopad_b64(self.module.jsonify(protected).encode('utf8')) + except Exception as e: + raise ModuleFailException("Failed to encode payload / headers as JSON: {0}".format(e)) + + if HAS_CURRENT_CRYPTOGRAPHY: + return _sign_request_cryptography(self.module, payload64, protected64, key_data) + else: + return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data) + + def _create_mac_key(self, alg, key): + '''Create a MAC key.''' + if HAS_CURRENT_CRYPTOGRAPHY: + return _create_mac_key_cryptography(self.module, alg, key) + else: + return _create_mac_key_openssl(self._openssl_bin, self.module, alg, key) + + def _log(self, msg, data=None): + ''' + Write arguments to acme.log when logging is enabled. + ''' + if self._debug: + with open('acme.log', 'ab') as f: + f.write('[{0}] {1}\n'.format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%s'), msg).encode('utf-8')) + if data is not None: + f.write('{0}\n\n'.format(json.dumps(data, indent=2, sort_keys=True)).encode('utf-8')) + + def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True): + ''' + Sends a JWS signed HTTP POST request to the ACME server and returns + the response as dictionary + https://tools.ietf.org/html/rfc8555#section-6.2 + + If payload is None, a POST-as-GET is performed. + (https://tools.ietf.org/html/rfc8555#section-6.3) + ''' + key_data = key_data or self.key_data + jws_header = jws_header or self.jws_header + failed_tries = 0 + while True: + protected = copy.deepcopy(jws_header) + protected["nonce"] = self.directory.get_nonce() + if self.version != 1: + protected["url"] = url + + self._log('URL', url) + self._log('protected', protected) + self._log('payload', payload) + data = self.sign_request(protected, payload, key_data, encode_payload=encode_payload) + if self.version == 1: + data["header"] = jws_header.copy() + for k, v in protected.items(): + hv = data["header"].pop(k, None) + self._log('signed request', data) + data = self.module.jsonify(data) + + headers = { + 'Content-Type': 'application/jose+json', + } + resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST') + _assert_fetch_url_success(resp, info) + result = {} + try: + content = resp.read() + except AttributeError: + content = info.pop('body', None) + + if content or not parse_json_result: + if (parse_json_result and info['content-type'].startswith('application/json')) or 400 <= info['status'] < 600: + try: + decoded_result = self.module.from_json(content.decode('utf8')) + self._log('parsed result', decoded_result) + # In case of badNonce error, try again (up to 5 times) + # (https://tools.ietf.org/html/rfc8555#section-6.7) + if (400 <= info['status'] < 600 and + decoded_result.get('type') == 'urn:ietf:params:acme:error:badNonce' and + failed_tries <= 5): + failed_tries += 1 + continue + if parse_json_result: + result = decoded_result + else: + result = content + except ValueError: + raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(url, content)) + else: + result = content + + return result, info + + def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True): + ''' + Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback + to GET if server replies with a status code of 405. + ''' + if not get_only and self.version != 1: + # Try POST-as-GET + content, info = self.send_signed_request(uri, None, parse_json_result=False) + if info['status'] == 405: + # Instead, do unauthenticated GET + get_only = True + else: + # Do unauthenticated GET + get_only = True + + if get_only: + # Perform unauthenticated GET + resp, info = fetch_url(self.module, uri, method='GET', headers=headers) + + _assert_fetch_url_success(resp, info) + + try: + content = resp.read() + except AttributeError: + content = info.pop('body', None) + + # Process result + if parse_json_result: + result = {} + if content: + if info['content-type'].startswith('application/json'): + try: + result = self.module.from_json(content.decode('utf8')) + except ValueError: + raise ModuleFailException("Failed to parse the ACME response: {0} {1}".format(uri, content)) + else: + result = content + else: + result = content + + if fail_on_error and (info['status'] < 200 or info['status'] >= 400): + raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], result)) + return result, info + + def set_account_uri(self, uri): + ''' + Set account URI. For ACME v2, it needs to be used to sending signed + requests. + ''' + self.uri = uri + if self.version != 1: + self.jws_header.pop('jwk') + self.jws_header['kid'] = self.uri + + def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True, + external_account_binding=None): + ''' + Registers a new ACME account. Returns a pair ``(created, data)``. + Here, ``created`` is ``True`` if the account was created and + ``False`` if it already existed (e.g. it was not newly created), + or does not exist. In case the account was created or exists, + ``data`` contains the account data; otherwise, it is ``None``. + + If specified, ``external_account_binding`` should be a dictionary + with keys ``kid``, ``alg`` and ``key`` + (https://tools.ietf.org/html/rfc8555#section-7.3.4). + + https://tools.ietf.org/html/rfc8555#section-7.3 + ''' + contact = contact or [] + + if self.version == 1: + new_reg = { + 'resource': 'new-reg', + 'contact': contact + } + if agreement: + new_reg['agreement'] = agreement + else: + new_reg['agreement'] = self.directory['meta']['terms-of-service'] + if external_account_binding is not None: + raise ModuleFailException('External account binding is not supported for ACME v1') + url = self.directory['new-reg'] + else: + if (external_account_binding is not None or self.directory['meta'].get('externalAccountRequired')) and allow_creation: + # Some ACME servers such as ZeroSSL do not like it when you try to register an existing account + # and provide external_account_binding credentials. Thus we first send a request with allow_creation=False + # to see whether the account already exists. + + # Note that we pass contact here: ZeroSSL does not accept regisration calls without contacts, even + # if onlyReturnExisting is set to true. + created, data = self._new_reg(contact=contact, allow_creation=False) + if data: + # An account already exists! Return data + return created, data + # An account does not yet exist. Try to create one next. + + new_reg = { + 'contact': contact + } + if not allow_creation: + # https://tools.ietf.org/html/rfc8555#section-7.3.1 + new_reg['onlyReturnExisting'] = True + if terms_agreed: + new_reg['termsOfServiceAgreed'] = True + url = self.directory['newAccount'] + if external_account_binding is not None: + new_reg['externalAccountBinding'] = self.sign_request( + { + 'alg': external_account_binding['alg'], + 'kid': external_account_binding['kid'], + 'url': url, + }, + self.jwk, + self._create_mac_key(external_account_binding['alg'], external_account_binding['key']) + ) + elif self.directory['meta'].get('externalAccountRequired') and allow_creation: + raise ModuleFailException( + 'To create an account, an external account binding must be specified. ' + 'Use the acme_account module with the external_account_binding option.' + ) + + result, info = self.send_signed_request(url, new_reg) + + if info['status'] in ([200, 201] if self.version == 1 else [201]): + # Account did not exist + if 'location' in info: + self.set_account_uri(info['location']) + return True, result + elif info['status'] == (409 if self.version == 1 else 200): + # Account did exist + if result.get('status') == 'deactivated': + # A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and + # Boulder (https://github.com/letsencrypt/boulder/issues/3971): this should + # not return a valid account object according to + # https://tools.ietf.org/html/rfc8555#section-7.3.6: + # "Once an account is deactivated, the server MUST NOT accept further + # requests authorized by that account's key." + if not allow_creation: + return False, None + else: + raise ModuleFailException("Account is deactivated") + if 'location' in info: + self.set_account_uri(info['location']) + return False, result + elif info['status'] == 400 and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation: + # Account does not exist (and we didn't try to create it) + return False, None + elif info['status'] == 403 and result['type'] == 'urn:ietf:params:acme:error:unauthorized' and 'deactivated' in (result.get('detail') or ''): + # Account has been deactivated; currently works for Pebble; hasn't been + # implemented for Boulder (https://github.com/letsencrypt/boulder/issues/3971), + # might need adjustment in error detection. + if not allow_creation: + return False, None + else: + raise ModuleFailException("Account is deactivated") + else: + raise ModuleFailException("Error registering: {0} {1}".format(info['status'], result)) + + def get_account_data(self): + ''' + Retrieve account information. Can only be called when the account + URI is already known (such as after calling setup_account). + Return None if the account was deactivated, or a dict otherwise. + ''' + if self.uri is None: + raise ModuleFailException("Account URI unknown") + if self.version == 1: + data = {} + data['resource'] = 'reg' + result, info = self.send_signed_request(self.uri, data) + else: + # try POST-as-GET first (draft-15 or newer) + data = None + result, info = self.send_signed_request(self.uri, data) + # check whether that failed with a malformed request error + if info['status'] >= 400 and result.get('type') == 'urn:ietf:params:acme:error:malformed': + # retry as a regular POST (with no changed data) for pre-draft-15 ACME servers + data = {} + result, info = self.send_signed_request(self.uri, data) + if info['status'] in (400, 403) and result.get('type') == 'urn:ietf:params:acme:error:unauthorized': + # Returned when account is deactivated + return None + if info['status'] in (400, 404) and result.get('type') == 'urn:ietf:params:acme:error:accountDoesNotExist': + # Returned when account does not exist + return None + if info['status'] < 200 or info['status'] >= 300: + raise ModuleFailException("Error getting account data from {2}: {0} {1}".format(info['status'], result, self.uri)) + return result + + def setup_account(self, contact=None, agreement=None, terms_agreed=False, + allow_creation=True, remove_account_uri_if_not_exists=False, + external_account_binding=None): + ''' + Detect or create an account on the ACME server. For ACME v1, + as the only way (without knowing an account URI) to test if an + account exists is to try and create one with the provided account + key, this method will always result in an account being present + (except on error situations). For ACME v2, a new account will + only be created if ``allow_creation`` is set to True. + + For ACME v2, ``check_mode`` is fully respected. For ACME v1, the + account might be created if it does not yet exist. + + Return a pair ``(created, account_data)``. Here, ``created`` will + be ``True`` in case the account was created or would be created + (check mode). ``account_data`` will be the current account data, + or ``None`` if the account does not exist. + + The account URI will be stored in ``self.uri``; if it is ``None``, + the account does not exist. + + If specified, ``external_account_binding`` should be a dictionary + with keys ``kid``, ``alg`` and ``key`` + (https://tools.ietf.org/html/rfc8555#section-7.3.4). + + https://tools.ietf.org/html/rfc8555#section-7.3 + ''' + + if self.uri is not None: + created = False + # Verify that the account key belongs to the URI. + # (If update_contact is True, this will be done below.) + account_data = self.get_account_data() + if account_data is None: + if remove_account_uri_if_not_exists and not allow_creation: + self.uri = None + else: + raise ModuleFailException("Account is deactivated or does not exist!") + else: + created, account_data = self._new_reg( + contact, + agreement=agreement, + terms_agreed=terms_agreed, + allow_creation=allow_creation and not self.module.check_mode, + external_account_binding=external_account_binding, + ) + if self.module.check_mode and self.uri is None and allow_creation: + created = True + account_data = { + 'contact': contact or [] + } + return created, account_data + + def update_account(self, account_data, contact=None): + ''' + Update an account on the ACME server. Check mode is fully respected. + + The current account data must be provided as ``account_data``. + + Return a pair ``(updated, account_data)``, where ``updated`` is + ``True`` in case something changed (contact info updated) or + would be changed (check mode), and ``account_data`` the updated + account data. + + https://tools.ietf.org/html/rfc8555#section-7.3.2 + ''' + # Create request + update_request = {} + if contact is not None and account_data.get('contact', []) != contact: + update_request['contact'] = list(contact) + + # No change? + if not update_request: + return False, dict(account_data) + + # Apply change + if self.module.check_mode: + account_data = dict(account_data) + account_data.update(update_request) + else: + if self.version == 1: + update_request['resource'] = 'reg' + account_data, dummy = self.send_signed_request(self.uri, update_request) + return True, account_data + + +def _normalize_ip(ip): + try: + return to_native(compat_ipaddress.ip_address(to_text(ip)).compressed) + except ValueError: + # We don't want to error out on something IPAddress() can't parse + return ip + + +def openssl_get_csr_identifiers(openssl_binary, module, csr_filename, csr_content=None): + ''' + Return a set of requested identifiers (CN and SANs) for the CSR. + Each identifier is a pair (type, identifier), where type is either + 'dns' or 'ip'. + ''' + filename = csr_filename + data = None + if csr_content is not None: + filename = '-' + data = csr_content.encode('utf-8') + + openssl_csr_cmd = [openssl_binary, "req", "-in", filename, "-noout", "-text"] + dummy, out, dummy = module.run_command(openssl_csr_cmd, data=data, check_rc=True) + + identifiers = set([]) + common_name = re.search(r"Subject:.* CN\s?=\s?([^\s,;/]+)", to_text(out, errors='surrogate_or_strict')) + if common_name is not None: + identifiers.add(('dns', common_name.group(1))) + subject_alt_names = re.search( + r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n", + to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL) + if subject_alt_names is not None: + for san in subject_alt_names.group(1).split(", "): + if san.lower().startswith("dns:"): + identifiers.add(('dns', san[4:])) + elif san.lower().startswith("ip:"): + identifiers.add(('ip', _normalize_ip(san[3:]))) + elif san.lower().startswith("ip address:"): + identifiers.add(('ip', _normalize_ip(san[11:]))) + else: + raise ModuleFailException('Found unsupported SAN identifier "{0}"'.format(san)) + return identifiers + + +def cryptography_get_csr_identifiers(module, csr_filename, csr_content=None): + ''' + Return a set of requested identifiers (CN and SANs) for the CSR. + Each identifier is a pair (type, identifier), where type is either + 'dns' or 'ip'. + ''' + identifiers = set([]) + if csr_content is None: + csr_content = read_file(csr_filename) + else: + csr_content = to_bytes(csr_content) + csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend) + for sub in csr.subject: + if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME: + identifiers.add(('dns', sub.value)) + for extension in csr.extensions: + if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME: + for name in extension.value: + if isinstance(name, cryptography.x509.DNSName): + identifiers.add(('dns', name.value)) + elif isinstance(name, cryptography.x509.IPAddress): + identifiers.add(('ip', name.value.compressed)) + else: + raise ModuleFailException('Found unsupported SAN identifier {0}'.format(name)) + return identifiers + + +def cryptography_get_cert_days(module, cert_file, now=None): + ''' + Return the days the certificate in cert_file remains valid and -1 + if the file was not found. If cert_file contains more than one + certificate, only the first one will be considered. + ''' + if not os.path.exists(cert_file): + return -1 + + try: + cert = cryptography.x509.load_pem_x509_certificate(read_file(cert_file), _cryptography_backend) + except Exception as e: + raise ModuleFailException('Cannot parse certificate {0}: {1}'.format(cert_file, e)) + if now is None: + now = datetime.datetime.now() + return (cert.not_valid_after - now).days + + +def set_crypto_backend(module): + ''' + Sets which crypto backend to use (default: auto detection). + + Does not care whether a new enough cryptoraphy is available or not. Must + be called before any real stuff is done which might evaluate + ``HAS_CURRENT_CRYPTOGRAPHY``. + ''' + global HAS_CURRENT_CRYPTOGRAPHY + # Choose backend + backend = module.params['select_crypto_backend'] + if backend == 'auto': + pass + elif backend == 'openssl': + HAS_CURRENT_CRYPTOGRAPHY = False + elif backend == 'cryptography': + try: + cryptography.__version__ + except Exception as dummy: + module.fail_json(msg=missing_required_lib('cryptography')) + HAS_CURRENT_CRYPTOGRAPHY = True + else: + module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend)) + # Inform about choices + if HAS_CURRENT_CRYPTOGRAPHY: + module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION)) + return 'cryptography' + else: + module.debug('Using OpenSSL binary backend') + return 'openssl' + + +def process_links(info, callback): + ''' + Process link header, calls callback for every link header with the URL and relation as options. + ''' + if 'link' in info: + link = info['link'] + for url, relation in re.findall(r'<([^>]+)>;\s*rel="(\w+)"', link): + callback(unquote(url), relation) + + +def get_default_argspec(): + ''' + Provides default argument spec for the options documented in the acme doc fragment. + ''' + return dict( + account_key_src=dict(type='path', aliases=['account_key']), + account_key_content=dict(type='str', no_log=True), + account_uri=dict(type='str'), + acme_directory=dict(type='str'), + acme_version=dict(type='int', choices=[1, 2]), + validate_certs=dict(type='bool', default=True), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']), + ) + + +def handle_standard_module_arguments(module, needs_acme_v2=False): + ''' + Do standard module setup, argument handling and warning emitting. + ''' + backend = set_crypto_backend(module) + + if not module.params['validate_certs']: + module.warn( + 'Disabling certificate validation for communications with ACME endpoint. ' + 'This should only be done for testing against a local ACME server for ' + 'development purposes, but *never* for production purposes.' + ) + + if module.params['acme_version'] is None: + module.params['acme_version'] = 1 + module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on", + version='2.0.0', collection_name='community.crypto') + + if module.params['acme_directory'] is None: + module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory' + module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on", + version='2.0.0', collection_name='community.crypto') + + if needs_acme_v2 and module.params['acme_version'] < 2: + module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name)) + + # AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates. + module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') + locale.setlocale(locale.LC_ALL, 'C') + + return backend diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/compat/ipaddress.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/compat/ipaddress.py new file mode 100644 index 00000000..49cce24d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/compat/ipaddress.py @@ -0,0 +1,2578 @@ +# -*- coding: utf-8 -*- + +# This code is part of Ansible, but is an independent component. +# This particular file, and this file only, is based on +# Lib/ipaddress.py of cpython +# It is licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +# +# 1. This LICENSE AGREEMENT is between the Python Software Foundation +# ("PSF"), and the Individual or Organization ("Licensee") accessing and +# otherwise using this software ("Python") in source or binary form and +# its associated documentation. +# +# 2. Subject to the terms and conditions of this License Agreement, PSF hereby +# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +# analyze, test, perform and/or display publicly, prepare derivative works, +# distribute, and otherwise use Python alone or in any derivative version, +# provided, however, that PSF's License Agreement and PSF's notice of copyright, +# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved" +# are retained in Python alone or in any derivative version prepared by Licensee. +# +# 3. In the event Licensee prepares a derivative work that is based on +# or incorporates Python or any part thereof, and wants to make +# the derivative work available to others as provided herein, then +# Licensee hereby agrees to include in any such work a brief summary of +# the changes made to Python. +# +# 4. PSF is making Python available to Licensee on an "AS IS" +# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +# INFRINGE ANY THIRD PARTY RIGHTS. +# +# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. +# +# 6. This License Agreement will automatically terminate upon a material +# breach of its terms and conditions. +# +# 7. Nothing in this License Agreement shall be deemed to create any +# relationship of agency, partnership, or joint venture between PSF and +# Licensee. This License Agreement does not grant permission to use PSF +# trademarks or trade name in a trademark sense to endorse or promote +# products or services of Licensee, or any third party. +# +# 8. By copying, installing or otherwise using Python, Licensee +# agrees to be bound by the terms and conditions of this License +# Agreement. + +# Copyright 2007 Google Inc. +# Licensed to PSF under a Contributor Agreement. + +"""A fast, lightweight IPv4/IPv6 manipulation library in Python. + +This library is used to create/poke/manipulate IPv4 and IPv6 addresses +and networks. + +""" + +from __future__ import unicode_literals + + +import itertools +import struct + + +# The following makes it easier for us to script updates of the bundled code and is not part of +# upstream +_BUNDLED_METADATA = {"pypi_name": "ipaddress", "version": "1.0.22"} + +__version__ = "1.0.22" + +# Compatibility functions +_compat_int_types = (int,) +try: + _compat_int_types = (int, long) +except NameError: + pass +try: + _compat_str = unicode +except NameError: + _compat_str = str + assert bytes != str +if b"\0"[0] == 0: # Python 3 semantics + + def _compat_bytes_to_byte_vals(byt): + return byt + + +else: + + def _compat_bytes_to_byte_vals(byt): + return [struct.unpack(b"!B", b)[0] for b in byt] + + +try: + _compat_int_from_byte_vals = int.from_bytes +except AttributeError: + + def _compat_int_from_byte_vals(bytvals, endianess): + assert endianess == "big" + res = 0 + for bv in bytvals: + assert isinstance(bv, _compat_int_types) + res = (res << 8) + bv + return res + + +def _compat_to_bytes(intval, length, endianess): + assert isinstance(intval, _compat_int_types) + assert endianess == "big" + if length == 4: + if intval < 0 or intval >= 2 ** 32: + raise struct.error("integer out of range for 'I' format code") + return struct.pack(b"!I", intval) + elif length == 16: + if intval < 0 or intval >= 2 ** 128: + raise struct.error("integer out of range for 'QQ' format code") + return struct.pack(b"!QQ", intval >> 64, intval & 0xFFFFFFFFFFFFFFFF) + else: + raise NotImplementedError() + + +if hasattr(int, "bit_length"): + # Not int.bit_length , since that won't work in 2.7 where long exists + def _compat_bit_length(i): + return i.bit_length() + + +else: + + def _compat_bit_length(i): + for res in itertools.count(): + if i >> res == 0: + return res + + +def _compat_range(start, end, step=1): + assert step > 0 + i = start + while i < end: + yield i + i += step + + +class _TotalOrderingMixin(object): + __slots__ = () + + # Helper that derives the other comparison operations from + # __lt__ and __eq__ + # We avoid functools.total_ordering because it doesn't handle + # NotImplemented correctly yet (http://bugs.python.org/issue10042) + def __eq__(self, other): + raise NotImplementedError + + def __ne__(self, other): + equal = self.__eq__(other) + if equal is NotImplemented: + return NotImplemented + return not equal + + def __lt__(self, other): + raise NotImplementedError + + def __le__(self, other): + less = self.__lt__(other) + if less is NotImplemented or not less: + return self.__eq__(other) + return less + + def __gt__(self, other): + less = self.__lt__(other) + if less is NotImplemented: + return NotImplemented + equal = self.__eq__(other) + if equal is NotImplemented: + return NotImplemented + return not (less or equal) + + def __ge__(self, other): + less = self.__lt__(other) + if less is NotImplemented: + return NotImplemented + return not less + + +IPV4LENGTH = 32 +IPV6LENGTH = 128 + + +class AddressValueError(ValueError): + """A Value Error related to the address.""" + + +class NetmaskValueError(ValueError): + """A Value Error related to the netmask.""" + + +def ip_address(address): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Address or IPv6Address object. + + Raises: + ValueError: if the *address* passed isn't either a v4 or a v6 + address + + """ + try: + return IPv4Address(address) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Address(address) + except (AddressValueError, NetmaskValueError): + pass + + if isinstance(address, bytes): + raise AddressValueError( + "%r does not appear to be an IPv4 or IPv6 address. " + "Did you pass in a bytes (str in Python 2) instead of" + " a unicode object?" % address + ) + + raise ValueError( + "%r does not appear to be an IPv4 or IPv6 address" % address + ) + + +def ip_network(address, strict=True): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP network. Either IPv4 or + IPv6 networks may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Network or IPv6Network object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. Or if the network has host bits set. + + """ + try: + return IPv4Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Network(address, strict) + except (AddressValueError, NetmaskValueError): + pass + + if isinstance(address, bytes): + raise AddressValueError( + "%r does not appear to be an IPv4 or IPv6 network. " + "Did you pass in a bytes (str in Python 2) instead of" + " a unicode object?" % address + ) + + raise ValueError( + "%r does not appear to be an IPv4 or IPv6 network" % address + ) + + +def ip_interface(address): + """Take an IP string/int and return an object of the correct type. + + Args: + address: A string or integer, the IP address. Either IPv4 or + IPv6 addresses may be supplied; integers less than 2**32 will + be considered to be IPv4 by default. + + Returns: + An IPv4Interface or IPv6Interface object. + + Raises: + ValueError: if the string passed isn't either a v4 or a v6 + address. + + Notes: + The IPv?Interface classes describe an Address on a particular + Network, so they're basically a combination of both the Address + and Network classes. + + """ + try: + return IPv4Interface(address) + except (AddressValueError, NetmaskValueError): + pass + + try: + return IPv6Interface(address) + except (AddressValueError, NetmaskValueError): + pass + + raise ValueError( + "%r does not appear to be an IPv4 or IPv6 interface" % address + ) + + +def v4_int_to_packed(address): + """Represent an address as 4 packed bytes in network (big-endian) order. + + Args: + address: An integer representation of an IPv4 IP address. + + Returns: + The integer address packed as 4 bytes in network (big-endian) order. + + Raises: + ValueError: If the integer is negative or too large to be an + IPv4 IP address. + + """ + try: + return _compat_to_bytes(address, 4, "big") + except (struct.error, OverflowError): + raise ValueError("Address negative or too large for IPv4") + + +def v6_int_to_packed(address): + """Represent an address as 16 packed bytes in network (big-endian) order. + + Args: + address: An integer representation of an IPv6 IP address. + + Returns: + The integer address packed as 16 bytes in network (big-endian) order. + + """ + try: + return _compat_to_bytes(address, 16, "big") + except (struct.error, OverflowError): + raise ValueError("Address negative or too large for IPv6") + + +def _split_optional_netmask(address): + """Helper to split the netmask and raise AddressValueError if needed""" + addr = _compat_str(address).split("/") + if len(addr) > 2: + raise AddressValueError("Only one '/' permitted in %r" % address) + return addr + + +def _find_address_range(addresses): + """Find a sequence of sorted deduplicated IPv#Address. + + Args: + addresses: a list of IPv#Address objects. + + Yields: + A tuple containing the first and last IP addresses in the sequence. + + """ + it = iter(addresses) + first = last = next(it) # pylint: disable=stop-iteration-return + for ip in it: + if ip._ip != last._ip + 1: + yield first, last + first = ip + last = ip + yield first, last + + +def _count_righthand_zero_bits(number, bits): + """Count the number of zero bits on the right hand side. + + Args: + number: an integer. + bits: maximum number of bits to count. + + Returns: + The number of zero bits on the right hand side of the number. + + """ + if number == 0: + return bits + return min(bits, _compat_bit_length(~number & (number - 1))) + + +def summarize_address_range(first, last): + """Summarize a network range given the first and last IP addresses. + + Example: + >>> list(summarize_address_range(IPv4Address('192.0.2.0'), + ... IPv4Address('192.0.2.130'))) + ... #doctest: +NORMALIZE_WHITESPACE + [IPv4Network('192.0.2.0/25'), IPv4Network('192.0.2.128/31'), + IPv4Network('192.0.2.130/32')] + + Args: + first: the first IPv4Address or IPv6Address in the range. + last: the last IPv4Address or IPv6Address in the range. + + Returns: + An iterator of the summarized IPv(4|6) network objects. + + Raise: + TypeError: + If the first and last objects are not IP addresses. + If the first and last objects are not the same version. + ValueError: + If the last object is not greater than the first. + If the version of the first address is not 4 or 6. + + """ + if not ( + isinstance(first, _BaseAddress) and isinstance(last, _BaseAddress) + ): + raise TypeError("first and last must be IP addresses, not networks") + if first.version != last.version: + raise TypeError( + "%s and %s are not of the same version" % (first, last) + ) + if first > last: + raise ValueError("last IP address must be greater than first") + + if first.version == 4: + ip = IPv4Network + elif first.version == 6: + ip = IPv6Network + else: + raise ValueError("unknown IP version") + + ip_bits = first._max_prefixlen + first_int = first._ip + last_int = last._ip + while first_int <= last_int: + nbits = min( + _count_righthand_zero_bits(first_int, ip_bits), + _compat_bit_length(last_int - first_int + 1) - 1, + ) + net = ip((first_int, ip_bits - nbits)) + yield net + first_int += 1 << nbits + if first_int - 1 == ip._ALL_ONES: + break + + +def _collapse_addresses_internal(addresses): + """Loops through the addresses, collapsing concurrent netblocks. + + Example: + + ip1 = IPv4Network('192.0.2.0/26') + ip2 = IPv4Network('192.0.2.64/26') + ip3 = IPv4Network('192.0.2.128/26') + ip4 = IPv4Network('192.0.2.192/26') + + _collapse_addresses_internal([ip1, ip2, ip3, ip4]) -> + [IPv4Network('192.0.2.0/24')] + + This shouldn't be called directly; it is called via + collapse_addresses([]). + + Args: + addresses: A list of IPv4Network's or IPv6Network's + + Returns: + A list of IPv4Network's or IPv6Network's depending on what we were + passed. + + """ + # First merge + to_merge = list(addresses) + subnets = {} + while to_merge: + net = to_merge.pop() + supernet = net.supernet() + existing = subnets.get(supernet) + if existing is None: + subnets[supernet] = net + elif existing != net: + # Merge consecutive subnets + del subnets[supernet] + to_merge.append(supernet) + # Then iterate over resulting networks, skipping subsumed subnets + last = None + for net in sorted(subnets.values()): + if last is not None: + # Since they are sorted, + # last.network_address <= net.network_address is a given. + if last.broadcast_address >= net.broadcast_address: + continue + yield net + last = net + + +def collapse_addresses(addresses): + """Collapse a list of IP objects. + + Example: + collapse_addresses([IPv4Network('192.0.2.0/25'), + IPv4Network('192.0.2.128/25')]) -> + [IPv4Network('192.0.2.0/24')] + + Args: + addresses: An iterator of IPv4Network or IPv6Network objects. + + Returns: + An iterator of the collapsed IPv(4|6)Network objects. + + Raises: + TypeError: If passed a list of mixed version objects. + + """ + addrs = [] + ips = [] + nets = [] + + # split IP addresses and networks + for ip in addresses: + if isinstance(ip, _BaseAddress): + if ips and ips[-1]._version != ip._version: + raise TypeError( + "%s and %s are not of the same version" % (ip, ips[-1]) + ) + ips.append(ip) + elif ip._prefixlen == ip._max_prefixlen: + if ips and ips[-1]._version != ip._version: + raise TypeError( + "%s and %s are not of the same version" % (ip, ips[-1]) + ) + try: + ips.append(ip.ip) + except AttributeError: + ips.append(ip.network_address) + else: + if nets and nets[-1]._version != ip._version: + raise TypeError( + "%s and %s are not of the same version" % (ip, nets[-1]) + ) + nets.append(ip) + + # sort and dedup + ips = sorted(set(ips)) + + # find consecutive address ranges in the sorted sequence and summarize them + if ips: + for first, last in _find_address_range(ips): + addrs.extend(summarize_address_range(first, last)) + + return _collapse_addresses_internal(addrs + nets) + + +def get_mixed_type_key(obj): + """Return a key suitable for sorting between networks and addresses. + + Address and Network objects are not sortable by default; they're + fundamentally different so the expression + + IPv4Address('192.0.2.0') <= IPv4Network('192.0.2.0/24') + + doesn't make any sense. There are some times however, where you may wish + to have ipaddress sort these for you anyway. If you need to do this, you + can use this function as the key= argument to sorted(). + + Args: + obj: either a Network or Address object. + Returns: + appropriate key. + + """ + if isinstance(obj, _BaseNetwork): + return obj._get_networks_key() + elif isinstance(obj, _BaseAddress): + return obj._get_address_key() + return NotImplemented + + +class _IPAddressBase(_TotalOrderingMixin): + + """The mother class.""" + + __slots__ = () + + @property + def exploded(self): + """Return the longhand version of the IP address as a string.""" + return self._explode_shorthand_ip_string() + + @property + def compressed(self): + """Return the shorthand version of the IP address as a string.""" + return _compat_str(self) + + @property + def reverse_pointer(self): + """The name of the reverse DNS pointer for the IP address, e.g.: + >>> ipaddress.ip_address("127.0.0.1").reverse_pointer + '1.0.0.127.in-addr.arpa' + >>> ipaddress.ip_address("2001:db8::1").reverse_pointer + '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' + + """ + return self._reverse_pointer() + + @property + def version(self): + msg = "%200s has no version specified" % (type(self),) + raise NotImplementedError(msg) + + def _check_int_address(self, address): + if address < 0: + msg = "%d (< 0) is not permitted as an IPv%d address" + raise AddressValueError(msg % (address, self._version)) + if address > self._ALL_ONES: + msg = "%d (>= 2**%d) is not permitted as an IPv%d address" + raise AddressValueError( + msg % (address, self._max_prefixlen, self._version) + ) + + def _check_packed_address(self, address, expected_len): + address_len = len(address) + if address_len != expected_len: + msg = ( + "%r (len %d != %d) is not permitted as an IPv%d address. " + "Did you pass in a bytes (str in Python 2) instead of" + " a unicode object?" + ) + raise AddressValueError( + msg % (address, address_len, expected_len, self._version) + ) + + @classmethod + def _ip_int_from_prefix(cls, prefixlen): + """Turn the prefix length into a bitwise netmask + + Args: + prefixlen: An integer, the prefix length. + + Returns: + An integer. + + """ + return cls._ALL_ONES ^ (cls._ALL_ONES >> prefixlen) + + @classmethod + def _prefix_from_ip_int(cls, ip_int): + """Return prefix length from the bitwise netmask. + + Args: + ip_int: An integer, the netmask in expanded bitwise format + + Returns: + An integer, the prefix length. + + Raises: + ValueError: If the input intermingles zeroes & ones + """ + trailing_zeroes = _count_righthand_zero_bits( + ip_int, cls._max_prefixlen + ) + prefixlen = cls._max_prefixlen - trailing_zeroes + leading_ones = ip_int >> trailing_zeroes + all_ones = (1 << prefixlen) - 1 + if leading_ones != all_ones: + byteslen = cls._max_prefixlen // 8 + details = _compat_to_bytes(ip_int, byteslen, "big") + msg = "Netmask pattern %r mixes zeroes & ones" + raise ValueError(msg % details) + return prefixlen + + @classmethod + def _report_invalid_netmask(cls, netmask_str): + msg = "%r is not a valid netmask" % netmask_str + raise NetmaskValueError(msg) + + @classmethod + def _prefix_from_prefix_string(cls, prefixlen_str): + """Return prefix length from a numeric string + + Args: + prefixlen_str: The string to be converted + + Returns: + An integer, the prefix length. + + Raises: + NetmaskValueError: If the input is not a valid netmask + """ + # int allows a leading +/- as well as surrounding whitespace, + # so we ensure that isn't the case + if not _BaseV4._DECIMAL_DIGITS.issuperset(prefixlen_str): + cls._report_invalid_netmask(prefixlen_str) + try: + prefixlen = int(prefixlen_str) + except ValueError: + cls._report_invalid_netmask(prefixlen_str) + if not (0 <= prefixlen <= cls._max_prefixlen): + cls._report_invalid_netmask(prefixlen_str) + return prefixlen + + @classmethod + def _prefix_from_ip_string(cls, ip_str): + """Turn a netmask/hostmask string into a prefix length + + Args: + ip_str: The netmask/hostmask to be converted + + Returns: + An integer, the prefix length. + + Raises: + NetmaskValueError: If the input is not a valid netmask/hostmask + """ + # Parse the netmask/hostmask like an IP address. + try: + ip_int = cls._ip_int_from_string(ip_str) + except AddressValueError: + cls._report_invalid_netmask(ip_str) + + # Try matching a netmask (this would be /1*0*/ as a bitwise regexp). + # Note that the two ambiguous cases (all-ones and all-zeroes) are + # treated as netmasks. + try: + return cls._prefix_from_ip_int(ip_int) + except ValueError: + pass + + # Invert the bits, and try matching a /0+1+/ hostmask instead. + ip_int ^= cls._ALL_ONES + try: + return cls._prefix_from_ip_int(ip_int) + except ValueError: + cls._report_invalid_netmask(ip_str) + + def __reduce__(self): + return self.__class__, (_compat_str(self),) + + +class _BaseAddress(_IPAddressBase): + + """A generic IP object. + + This IP class contains the version independent methods which are + used by single IP addresses. + """ + + __slots__ = () + + def __int__(self): + return self._ip + + def __eq__(self, other): + try: + return self._ip == other._ip and self._version == other._version + except AttributeError: + return NotImplemented + + def __lt__(self, other): + if not isinstance(other, _IPAddressBase): + return NotImplemented + if not isinstance(other, _BaseAddress): + raise TypeError( + "%s and %s are not of the same type" % (self, other) + ) + if self._version != other._version: + raise TypeError( + "%s and %s are not of the same version" % (self, other) + ) + if self._ip != other._ip: + return self._ip < other._ip + return False + + # Shorthand for Integer addition and subtraction. This is not + # meant to ever support addition/subtraction of addresses. + def __add__(self, other): + if not isinstance(other, _compat_int_types): + return NotImplemented + return self.__class__(int(self) + other) + + def __sub__(self, other): + if not isinstance(other, _compat_int_types): + return NotImplemented + return self.__class__(int(self) - other) + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, _compat_str(self)) + + def __str__(self): + return _compat_str(self._string_from_ip_int(self._ip)) + + def __hash__(self): + return hash(hex(int(self._ip))) + + def _get_address_key(self): + return (self._version, self) + + def __reduce__(self): + return self.__class__, (self._ip,) + + +class _BaseNetwork(_IPAddressBase): + + """A generic IP network object. + + This IP class contains the version independent methods which are + used by networks. + + """ + + def __init__(self, address): + self._cache = {} + + def __repr__(self): + return "%s(%r)" % (self.__class__.__name__, _compat_str(self)) + + def __str__(self): + return "%s/%d" % (self.network_address, self.prefixlen) + + def hosts(self): + """Generate Iterator over usable hosts in a network. + + This is like __iter__ except it doesn't return the network + or broadcast addresses. + + """ + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network + 1, broadcast): + yield self._address_class(x) + + def __iter__(self): + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network, broadcast + 1): + yield self._address_class(x) + + def __getitem__(self, n): + network = int(self.network_address) + broadcast = int(self.broadcast_address) + if n >= 0: + if network + n > broadcast: + raise IndexError("address out of range") + return self._address_class(network + n) + else: + n += 1 + if broadcast + n < network: + raise IndexError("address out of range") + return self._address_class(broadcast + n) + + def __lt__(self, other): + if not isinstance(other, _IPAddressBase): + return NotImplemented + if not isinstance(other, _BaseNetwork): + raise TypeError( + "%s and %s are not of the same type" % (self, other) + ) + if self._version != other._version: + raise TypeError( + "%s and %s are not of the same version" % (self, other) + ) + if self.network_address != other.network_address: + return self.network_address < other.network_address + if self.netmask != other.netmask: + return self.netmask < other.netmask + return False + + def __eq__(self, other): + try: + return ( + self._version == other._version + and self.network_address == other.network_address + and int(self.netmask) == int(other.netmask) + ) + except AttributeError: + return NotImplemented + + def __hash__(self): + return hash(int(self.network_address) ^ int(self.netmask)) + + def __contains__(self, other): + # always false if one is v4 and the other is v6. + if self._version != other._version: + return False + # dealing with another network. + if isinstance(other, _BaseNetwork): + return False + # dealing with another address + else: + # address + return ( + int(self.network_address) + <= int(other._ip) + <= int(self.broadcast_address) + ) + + def overlaps(self, other): + """Tell if self is partly contained in other.""" + return self.network_address in other or ( + self.broadcast_address in other + or ( + other.network_address in self + or (other.broadcast_address in self) + ) + ) + + @property + def broadcast_address(self): + x = self._cache.get("broadcast_address") + if x is None: + x = self._address_class( + int(self.network_address) | int(self.hostmask) + ) + self._cache["broadcast_address"] = x + return x + + @property + def hostmask(self): + x = self._cache.get("hostmask") + if x is None: + x = self._address_class(int(self.netmask) ^ self._ALL_ONES) + self._cache["hostmask"] = x + return x + + @property + def with_prefixlen(self): + return "%s/%d" % (self.network_address, self._prefixlen) + + @property + def with_netmask(self): + return "%s/%s" % (self.network_address, self.netmask) + + @property + def with_hostmask(self): + return "%s/%s" % (self.network_address, self.hostmask) + + @property + def num_addresses(self): + """Number of hosts in the current subnet.""" + return int(self.broadcast_address) - int(self.network_address) + 1 + + @property + def _address_class(self): + # Returning bare address objects (rather than interfaces) allows for + # more consistent behaviour across the network address, broadcast + # address and individual host addresses. + msg = "%200s has no associated address class" % (type(self),) + raise NotImplementedError(msg) + + @property + def prefixlen(self): + return self._prefixlen + + def address_exclude(self, other): + """Remove an address from a larger block. + + For example: + + addr1 = ip_network('192.0.2.0/28') + addr2 = ip_network('192.0.2.1/32') + list(addr1.address_exclude(addr2)) = + [IPv4Network('192.0.2.0/32'), IPv4Network('192.0.2.2/31'), + IPv4Network('192.0.2.4/30'), IPv4Network('192.0.2.8/29')] + + or IPv6: + + addr1 = ip_network('2001:db8::1/32') + addr2 = ip_network('2001:db8::1/128') + list(addr1.address_exclude(addr2)) = + [ip_network('2001:db8::1/128'), + ip_network('2001:db8::2/127'), + ip_network('2001:db8::4/126'), + ip_network('2001:db8::8/125'), + ... + ip_network('2001:db8:8000::/33')] + + Args: + other: An IPv4Network or IPv6Network object of the same type. + + Returns: + An iterator of the IPv(4|6)Network objects which is self + minus other. + + Raises: + TypeError: If self and other are of differing address + versions, or if other is not a network object. + ValueError: If other is not completely contained by self. + + """ + if not self._version == other._version: + raise TypeError( + "%s and %s are not of the same version" % (self, other) + ) + + if not isinstance(other, _BaseNetwork): + raise TypeError("%s is not a network object" % other) + + if not other.subnet_of(self): + raise ValueError("%s not contained in %s" % (other, self)) + if other == self: + return + + # Make sure we're comparing the network of other. + other = other.__class__( + "%s/%s" % (other.network_address, other.prefixlen) + ) + + s1, s2 = self.subnets() + while s1 != other and s2 != other: + if other.subnet_of(s1): + yield s2 + s1, s2 = s1.subnets() + elif other.subnet_of(s2): + yield s1 + s1, s2 = s2.subnets() + else: + # If we got here, there's a bug somewhere. + raise AssertionError( + "Error performing exclusion: " + "s1: %s s2: %s other: %s" % (s1, s2, other) + ) + if s1 == other: + yield s2 + elif s2 == other: + yield s1 + else: + # If we got here, there's a bug somewhere. + raise AssertionError( + "Error performing exclusion: " + "s1: %s s2: %s other: %s" % (s1, s2, other) + ) + + def compare_networks(self, other): + """Compare two IP objects. + + This is only concerned about the comparison of the integer + representation of the network addresses. This means that the + host bits aren't considered at all in this method. If you want + to compare host bits, you can easily enough do a + 'HostA._ip < HostB._ip' + + Args: + other: An IP object. + + Returns: + If the IP versions of self and other are the same, returns: + + -1 if self < other: + eg: IPv4Network('192.0.2.0/25') < IPv4Network('192.0.2.128/25') + IPv6Network('2001:db8::1000/124') < + IPv6Network('2001:db8::2000/124') + 0 if self == other + eg: IPv4Network('192.0.2.0/24') == IPv4Network('192.0.2.0/24') + IPv6Network('2001:db8::1000/124') == + IPv6Network('2001:db8::1000/124') + 1 if self > other + eg: IPv4Network('192.0.2.128/25') > IPv4Network('192.0.2.0/25') + IPv6Network('2001:db8::2000/124') > + IPv6Network('2001:db8::1000/124') + + Raises: + TypeError if the IP versions are different. + + """ + # does this need to raise a ValueError? + if self._version != other._version: + raise TypeError( + "%s and %s are not of the same type" % (self, other) + ) + # self._version == other._version below here: + if self.network_address < other.network_address: + return -1 + if self.network_address > other.network_address: + return 1 + # self.network_address == other.network_address below here: + if self.netmask < other.netmask: + return -1 + if self.netmask > other.netmask: + return 1 + return 0 + + def _get_networks_key(self): + """Network-only key function. + + Returns an object that identifies this address' network and + netmask. This function is a suitable "key" argument for sorted() + and list.sort(). + + """ + return (self._version, self.network_address, self.netmask) + + def subnets(self, prefixlen_diff=1, new_prefix=None): + """The subnets which join to make the current subnet. + + In the case that self contains only one IP + (self._prefixlen == 32 for IPv4 or self._prefixlen == 128 + for IPv6), yield an iterator with just ourself. + + Args: + prefixlen_diff: An integer, the amount the prefix length + should be increased by. This should not be set if + new_prefix is also set. + new_prefix: The desired new prefix length. This must be a + larger number (smaller prefix) than the existing prefix. + This should not be set if prefixlen_diff is also set. + + Returns: + An iterator of IPv(4|6) objects. + + Raises: + ValueError: The prefixlen_diff is too small or too large. + OR + prefixlen_diff and new_prefix are both set or new_prefix + is a smaller number than the current prefix (smaller + number means a larger network) + + """ + if self._prefixlen == self._max_prefixlen: + yield self + return + + if new_prefix is not None: + if new_prefix < self._prefixlen: + raise ValueError("new prefix must be longer") + if prefixlen_diff != 1: + raise ValueError("cannot set prefixlen_diff and new_prefix") + prefixlen_diff = new_prefix - self._prefixlen + + if prefixlen_diff < 0: + raise ValueError("prefix length diff must be > 0") + new_prefixlen = self._prefixlen + prefixlen_diff + + if new_prefixlen > self._max_prefixlen: + raise ValueError( + "prefix length diff %d is invalid for netblock %s" + % (new_prefixlen, self) + ) + + start = int(self.network_address) + end = int(self.broadcast_address) + 1 + step = (int(self.hostmask) + 1) >> prefixlen_diff + for new_addr in _compat_range(start, end, step): + current = self.__class__((new_addr, new_prefixlen)) + yield current + + def supernet(self, prefixlen_diff=1, new_prefix=None): + """The supernet containing the current network. + + Args: + prefixlen_diff: An integer, the amount the prefix length of + the network should be decreased by. For example, given a + /24 network and a prefixlen_diff of 3, a supernet with a + /21 netmask is returned. + + Returns: + An IPv4 network object. + + Raises: + ValueError: If self.prefixlen - prefixlen_diff < 0. I.e., you have + a negative prefix length. + OR + If prefixlen_diff and new_prefix are both set or new_prefix is a + larger number than the current prefix (larger number means a + smaller network) + + """ + if self._prefixlen == 0: + return self + + if new_prefix is not None: + if new_prefix > self._prefixlen: + raise ValueError("new prefix must be shorter") + if prefixlen_diff != 1: + raise ValueError("cannot set prefixlen_diff and new_prefix") + prefixlen_diff = self._prefixlen - new_prefix + + new_prefixlen = self.prefixlen - prefixlen_diff + if new_prefixlen < 0: + raise ValueError( + "current prefixlen is %d, cannot have a prefixlen_diff of %d" + % (self.prefixlen, prefixlen_diff) + ) + return self.__class__( + ( + int(self.network_address) + & (int(self.netmask) << prefixlen_diff), + new_prefixlen, + ) + ) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return ( + self.network_address.is_multicast + and self.broadcast_address.is_multicast + ) + + @staticmethod + def _is_subnet_of(a, b): + try: + # Always false if one is v4 and the other is v6. + if a._version != b._version: + raise TypeError( + "%s and %s are not of the same version" % (a, b) + ) + return ( + b.network_address <= a.network_address + and b.broadcast_address >= a.broadcast_address + ) + except AttributeError: + raise TypeError( + "Unable to test subnet containment " + "between %s and %s" % (a, b) + ) + + def subnet_of(self, other): + """Return True if this network is a subnet of other.""" + return self._is_subnet_of(self, other) + + def supernet_of(self, other): + """Return True if this network is a supernet of other.""" + return self._is_subnet_of(other, self) + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within one of the + reserved IPv6 Network ranges. + + """ + return ( + self.network_address.is_reserved + and self.broadcast_address.is_reserved + ) + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return ( + self.network_address.is_link_local + and self.broadcast_address.is_link_local + ) + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv4-special-registry or iana-ipv6-special-registry. + + """ + return ( + self.network_address.is_private + and self.broadcast_address.is_private + ) + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, True if the address is not reserved per + iana-ipv4-special-registry or iana-ipv6-special-registry. + + """ + return not self.is_private + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return ( + self.network_address.is_unspecified + and self.broadcast_address.is_unspecified + ) + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return ( + self.network_address.is_loopback + and self.broadcast_address.is_loopback + ) + + +class _BaseV4(object): + + """Base IPv4 object. + + The following methods are used by IPv4 objects in both single IP + addresses and networks. + + """ + + __slots__ = () + _version = 4 + # Equivalent to 255.255.255.255 or 32 bits of 1's. + _ALL_ONES = (2 ** IPV4LENGTH) - 1 + _DECIMAL_DIGITS = frozenset("0123456789") + + # the valid octets for host and netmasks. only useful for IPv4. + _valid_mask_octets = frozenset([255, 254, 252, 248, 240, 224, 192, 128, 0]) + + _max_prefixlen = IPV4LENGTH + # There are only a handful of valid v4 netmasks, so we cache them all + # when constructed (see _make_netmask()). + _netmask_cache = {} + + def _explode_shorthand_ip_string(self): + return _compat_str(self) + + @classmethod + def _make_netmask(cls, arg): + """Make a (netmask, prefix_len) tuple from the given argument. + + Argument can be: + - an integer (the prefix length) + - a string representing the prefix length (e.g. "24") + - a string representing the prefix netmask (e.g. "255.255.255.0") + """ + if arg not in cls._netmask_cache: + if isinstance(arg, _compat_int_types): + prefixlen = arg + else: + try: + # Check for a netmask in prefix length form + prefixlen = cls._prefix_from_prefix_string(arg) + except NetmaskValueError: + # Check for a netmask or hostmask in dotted-quad form. + # This may raise NetmaskValueError. + prefixlen = cls._prefix_from_ip_string(arg) + netmask = IPv4Address(cls._ip_int_from_prefix(prefixlen)) + cls._netmask_cache[arg] = netmask, prefixlen + return cls._netmask_cache[arg] + + @classmethod + def _ip_int_from_string(cls, ip_str): + """Turn the given IP string into an integer for comparison. + + Args: + ip_str: A string, the IP ip_str. + + Returns: + The IP ip_str as an integer. + + Raises: + AddressValueError: if ip_str isn't a valid IPv4 Address. + + """ + if not ip_str: + raise AddressValueError("Address cannot be empty") + + octets = ip_str.split(".") + if len(octets) != 4: + raise AddressValueError("Expected 4 octets in %r" % ip_str) + + try: + return _compat_int_from_byte_vals( + map(cls._parse_octet, octets), "big" + ) + except ValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + + @classmethod + def _parse_octet(cls, octet_str): + """Convert a decimal octet into an integer. + + Args: + octet_str: A string, the number to parse. + + Returns: + The octet as an integer. + + Raises: + ValueError: if the octet isn't strictly a decimal from [0..255]. + + """ + if not octet_str: + raise ValueError("Empty octet not permitted") + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not cls._DECIMAL_DIGITS.issuperset(octet_str): + msg = "Only decimal digits permitted in %r" + raise ValueError(msg % octet_str) + # We do the length check second, since the invalid character error + # is likely to be more informative for the user + if len(octet_str) > 3: + msg = "At most 3 characters permitted in %r" + raise ValueError(msg % octet_str) + # Convert to integer (we know digits are legal) + octet_int = int(octet_str, 10) + # Any octets that look like they *might* be written in octal, + # and which don't look exactly the same in both octal and + # decimal are rejected as ambiguous + if octet_int > 7 and octet_str[0] == "0": + msg = "Ambiguous (octal/decimal) value in %r not permitted" + raise ValueError(msg % octet_str) + if octet_int > 255: + raise ValueError("Octet %d (> 255) not permitted" % octet_int) + return octet_int + + @classmethod + def _string_from_ip_int(cls, ip_int): + """Turns a 32-bit integer into dotted decimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + The IP address as a string in dotted decimal notation. + + """ + return ".".join( + _compat_str( + struct.unpack(b"!B", b)[0] if isinstance(b, bytes) else b + ) + for b in _compat_to_bytes(ip_int, 4, "big") + ) + + def _is_hostmask(self, ip_str): + """Test if the IP string is a hostmask (rather than a netmask). + + Args: + ip_str: A string, the potential hostmask. + + Returns: + A boolean, True if the IP string is a hostmask. + + """ + bits = ip_str.split(".") + try: + parts = [x for x in map(int, bits) if x in self._valid_mask_octets] + except ValueError: + return False + if len(parts) != len(bits): + return False + if parts[0] < parts[-1]: + return True + return False + + def _reverse_pointer(self): + """Return the reverse DNS pointer name for the IPv4 address. + + This implements the method described in RFC1035 3.5. + + """ + reverse_octets = _compat_str(self).split(".")[::-1] + return ".".join(reverse_octets) + ".in-addr.arpa" + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def version(self): + return self._version + + +class IPv4Address(_BaseV4, _BaseAddress): + + """Represent and manipulate single IPv4 Addresses.""" + + __slots__ = ("_ip", "__weakref__") + + def __init__(self, address): + + """ + Args: + address: A string or integer representing the IP + + Additionally, an integer can be passed, so + IPv4Address('192.0.2.1') == IPv4Address(3221225985). + or, more generally + IPv4Address(int(IPv4Address('192.0.2.1'))) == + IPv4Address('192.0.2.1') + + Raises: + AddressValueError: If ipaddress isn't a valid IPv4 address. + + """ + # Efficient constructor from integer. + if isinstance(address, _compat_int_types): + self._check_int_address(address) + self._ip = address + return + + # Constructing from a packed address + if isinstance(address, bytes): + self._check_packed_address(address, 4) + bvs = _compat_bytes_to_byte_vals(address) + self._ip = _compat_int_from_byte_vals(bvs, "big") + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = _compat_str(address) + if "/" in addr_str: + raise AddressValueError("Unexpected '/' in %r" % address) + self._ip = self._ip_int_from_string(addr_str) + + @property + def packed(self): + """The binary representation of this address.""" + return v4_int_to_packed(self._ip) + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within the + reserved IPv4 Network range. + + """ + return self in self._constants._reserved_network + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv4-special-registry. + + """ + return any(self in net for net in self._constants._private_networks) + + @property + def is_global(self): + return ( + self not in self._constants._public_network and not self.is_private + ) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is multicast. + See RFC 3171 for details. + + """ + return self in self._constants._multicast_network + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 5735 3. + + """ + return self == self._constants._unspecified_address + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback per RFC 3330. + + """ + return self in self._constants._loopback_network + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is link-local per RFC 3927. + + """ + return self in self._constants._linklocal_network + + +class IPv4Interface(IPv4Address): + def __init__(self, address): + if isinstance(address, (bytes, _compat_int_types)): + IPv4Address.__init__(self, address) + self.network = IPv4Network(self._ip) + self._prefixlen = self._max_prefixlen + return + + if isinstance(address, tuple): + IPv4Address.__init__(self, address[0]) + if len(address) > 1: + self._prefixlen = int(address[1]) + else: + self._prefixlen = self._max_prefixlen + + self.network = IPv4Network(address, strict=False) + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + return + + addr = _split_optional_netmask(address) + IPv4Address.__init__(self, addr[0]) + + self.network = IPv4Network(address, strict=False) + self._prefixlen = self.network._prefixlen + + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + + def __str__(self): + return "%s/%d" % ( + self._string_from_ip_int(self._ip), + self.network.prefixlen, + ) + + def __eq__(self, other): + address_equal = IPv4Address.__eq__(self, other) + if not address_equal or address_equal is NotImplemented: + return address_equal + try: + return self.network == other.network + except AttributeError: + # An interface with an associated network is NOT the + # same as an unassociated address. That's why the hash + # takes the extra info into account. + return False + + def __lt__(self, other): + address_less = IPv4Address.__lt__(self, other) + if address_less is NotImplemented: + return NotImplemented + try: + return ( + self.network < other.network + or self.network == other.network + and address_less + ) + except AttributeError: + # We *do* allow addresses and interfaces to be sorted. The + # unassociated address is considered less than all interfaces. + return False + + def __hash__(self): + return self._ip ^ self._prefixlen ^ int(self.network.network_address) + + __reduce__ = _IPAddressBase.__reduce__ + + @property + def ip(self): + return IPv4Address(self._ip) + + @property + def with_prefixlen(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self._prefixlen) + + @property + def with_netmask(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self.netmask) + + @property + def with_hostmask(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self.hostmask) + + +class IPv4Network(_BaseV4, _BaseNetwork): + + """This class represents and manipulates 32-bit IPv4 network + addresses.. + + Attributes: [examples for IPv4Network('192.0.2.0/27')] + .network_address: IPv4Address('192.0.2.0') + .hostmask: IPv4Address('0.0.0.31') + .broadcast_address: IPv4Address('192.0.2.32') + .netmask: IPv4Address('255.255.255.224') + .prefixlen: 27 + + """ + + # Class to use when creating address objects + _address_class = IPv4Address + + def __init__(self, address, strict=True): + + """Instantiate a new IPv4 network object. + + Args: + address: A string or integer representing the IP [& network]. + '192.0.2.0/24' + '192.0.2.0/255.255.255.0' + '192.0.0.2/0.0.0.255' + are all functionally the same in IPv4. Similarly, + '192.0.2.1' + '192.0.2.1/255.255.255.255' + '192.0.2.1/32' + are also functionally equivalent. That is to say, failing to + provide a subnetmask will create an object with a mask of /32. + + If the mask (portion after the / in the argument) is given in + dotted quad form, it is treated as a netmask if it starts with a + non-zero field (e.g. /255.0.0.0 == /8) and as a hostmask if it + starts with a zero field (e.g. 0.255.255.255 == /8), with the + single exception of an all-zero mask which is treated as a + netmask == /0. If no mask is given, a default of /32 is used. + + Additionally, an integer can be passed, so + IPv4Network('192.0.2.1') == IPv4Network(3221225985) + or, more generally + IPv4Interface(int(IPv4Interface('192.0.2.1'))) == + IPv4Interface('192.0.2.1') + + Raises: + AddressValueError: If ipaddress isn't a valid IPv4 address. + NetmaskValueError: If the netmask isn't valid for + an IPv4 address. + ValueError: If strict is True and a network address is not + supplied. + + """ + _BaseNetwork.__init__(self, address) + + # Constructing from a packed address or integer + if isinstance(address, (_compat_int_types, bytes)): + self.network_address = IPv4Address(address) + self.netmask, self._prefixlen = self._make_netmask( + self._max_prefixlen + ) + # fixme: address/network test here. + return + + if isinstance(address, tuple): + if len(address) > 1: + arg = address[1] + else: + # We weren't given an address[1] + arg = self._max_prefixlen + self.network_address = IPv4Address(address[0]) + self.netmask, self._prefixlen = self._make_netmask(arg) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: + raise ValueError("%s has host bits set" % self) + else: + self.network_address = IPv4Address( + packed & int(self.netmask) + ) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = _split_optional_netmask(address) + self.network_address = IPv4Address(self._ip_int_from_string(addr[0])) + + if len(addr) == 2: + arg = addr[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + + if strict: + if ( + IPv4Address(int(self.network_address) & int(self.netmask)) + != self.network_address + ): + raise ValueError("%s has host bits set" % self) + self.network_address = IPv4Address( + int(self.network_address) & int(self.netmask) + ) + + if self._prefixlen == (self._max_prefixlen - 1): + self.hosts = self.__iter__ + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, True if the address is not reserved per + iana-ipv4-special-registry. + + """ + return ( + not ( + self.network_address in IPv4Network("100.64.0.0/10") + and self.broadcast_address in IPv4Network("100.64.0.0/10") + ) + and not self.is_private + ) + + +class _IPv4Constants(object): + + _linklocal_network = IPv4Network("169.254.0.0/16") + + _loopback_network = IPv4Network("127.0.0.0/8") + + _multicast_network = IPv4Network("224.0.0.0/4") + + _public_network = IPv4Network("100.64.0.0/10") + + _private_networks = [ + IPv4Network("0.0.0.0/8"), + IPv4Network("10.0.0.0/8"), + IPv4Network("127.0.0.0/8"), + IPv4Network("169.254.0.0/16"), + IPv4Network("172.16.0.0/12"), + IPv4Network("192.0.0.0/29"), + IPv4Network("192.0.0.170/31"), + IPv4Network("192.0.2.0/24"), + IPv4Network("192.168.0.0/16"), + IPv4Network("198.18.0.0/15"), + IPv4Network("198.51.100.0/24"), + IPv4Network("203.0.113.0/24"), + IPv4Network("240.0.0.0/4"), + IPv4Network("255.255.255.255/32"), + ] + + _reserved_network = IPv4Network("240.0.0.0/4") + + _unspecified_address = IPv4Address("0.0.0.0") + + +IPv4Address._constants = _IPv4Constants + + +class _BaseV6(object): + + """Base IPv6 object. + + The following methods are used by IPv6 objects in both single IP + addresses and networks. + + """ + + __slots__ = () + _version = 6 + _ALL_ONES = (2 ** IPV6LENGTH) - 1 + _HEXTET_COUNT = 8 + _HEX_DIGITS = frozenset("0123456789ABCDEFabcdef") + _max_prefixlen = IPV6LENGTH + + # There are only a bunch of valid v6 netmasks, so we cache them all + # when constructed (see _make_netmask()). + _netmask_cache = {} + + @classmethod + def _make_netmask(cls, arg): + """Make a (netmask, prefix_len) tuple from the given argument. + + Argument can be: + - an integer (the prefix length) + - a string representing the prefix length (e.g. "24") + - a string representing the prefix netmask (e.g. "255.255.255.0") + """ + if arg not in cls._netmask_cache: + if isinstance(arg, _compat_int_types): + prefixlen = arg + else: + prefixlen = cls._prefix_from_prefix_string(arg) + netmask = IPv6Address(cls._ip_int_from_prefix(prefixlen)) + cls._netmask_cache[arg] = netmask, prefixlen + return cls._netmask_cache[arg] + + @classmethod + def _ip_int_from_string(cls, ip_str): + """Turn an IPv6 ip_str into an integer. + + Args: + ip_str: A string, the IPv6 ip_str. + + Returns: + An int, the IPv6 address + + Raises: + AddressValueError: if ip_str isn't a valid IPv6 Address. + + """ + if not ip_str: + raise AddressValueError("Address cannot be empty") + + parts = ip_str.split(":") + + # An IPv6 address needs at least 2 colons (3 parts). + _min_parts = 3 + if len(parts) < _min_parts: + msg = "At least %d parts expected in %r" % (_min_parts, ip_str) + raise AddressValueError(msg) + + # If the address has an IPv4-style suffix, convert it to hexadecimal. + if "." in parts[-1]: + try: + ipv4_int = IPv4Address(parts.pop())._ip + except AddressValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + parts.append("%x" % ((ipv4_int >> 16) & 0xFFFF)) + parts.append("%x" % (ipv4_int & 0xFFFF)) + + # An IPv6 address can't have more than 8 colons (9 parts). + # The extra colon comes from using the "::" notation for a single + # leading or trailing zero part. + _max_parts = cls._HEXTET_COUNT + 1 + if len(parts) > _max_parts: + msg = "At most %d colons permitted in %r" % ( + _max_parts - 1, + ip_str, + ) + raise AddressValueError(msg) + + # Disregarding the endpoints, find '::' with nothing in between. + # This indicates that a run of zeroes has been skipped. + skip_index = None + for i in _compat_range(1, len(parts) - 1): + if not parts[i]: + if skip_index is not None: + # Can't have more than one '::' + msg = "At most one '::' permitted in %r" % ip_str + raise AddressValueError(msg) + skip_index = i + + # parts_hi is the number of parts to copy from above/before the '::' + # parts_lo is the number of parts to copy from below/after the '::' + if skip_index is not None: + # If we found a '::', then check if it also covers the endpoints. + parts_hi = skip_index + parts_lo = len(parts) - skip_index - 1 + if not parts[0]: + parts_hi -= 1 + if parts_hi: + msg = "Leading ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # ^: requires ^:: + if not parts[-1]: + parts_lo -= 1 + if parts_lo: + msg = "Trailing ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # :$ requires ::$ + parts_skipped = cls._HEXTET_COUNT - (parts_hi + parts_lo) + if parts_skipped < 1: + msg = "Expected at most %d other parts with '::' in %r" + raise AddressValueError(msg % (cls._HEXTET_COUNT - 1, ip_str)) + else: + # Otherwise, allocate the entire address to parts_hi. The + # endpoints could still be empty, but _parse_hextet() will check + # for that. + if len(parts) != cls._HEXTET_COUNT: + msg = "Exactly %d parts expected without '::' in %r" + raise AddressValueError(msg % (cls._HEXTET_COUNT, ip_str)) + if not parts[0]: + msg = "Leading ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # ^: requires ^:: + if not parts[-1]: + msg = "Trailing ':' only permitted as part of '::' in %r" + raise AddressValueError(msg % ip_str) # :$ requires ::$ + parts_hi = len(parts) + parts_lo = 0 + parts_skipped = 0 + + try: + # Now, parse the hextets into a 128-bit integer. + ip_int = 0 + for i in range(parts_hi): + ip_int <<= 16 + ip_int |= cls._parse_hextet(parts[i]) + ip_int <<= 16 * parts_skipped + for i in range(-parts_lo, 0): + ip_int <<= 16 + ip_int |= cls._parse_hextet(parts[i]) + return ip_int + except ValueError as exc: + raise AddressValueError("%s in %r" % (exc, ip_str)) + + @classmethod + def _parse_hextet(cls, hextet_str): + """Convert an IPv6 hextet string into an integer. + + Args: + hextet_str: A string, the number to parse. + + Returns: + The hextet as an integer. + + Raises: + ValueError: if the input isn't strictly a hex number from + [0..FFFF]. + + """ + # Whitelist the characters, since int() allows a lot of bizarre stuff. + if not cls._HEX_DIGITS.issuperset(hextet_str): + raise ValueError("Only hex digits permitted in %r" % hextet_str) + # We do the length check second, since the invalid character error + # is likely to be more informative for the user + if len(hextet_str) > 4: + msg = "At most 4 characters permitted in %r" + raise ValueError(msg % hextet_str) + # Length check means we can skip checking the integer value + return int(hextet_str, 16) + + @classmethod + def _compress_hextets(cls, hextets): + """Compresses a list of hextets. + + Compresses a list of strings, replacing the longest continuous + sequence of "0" in the list with "" and adding empty strings at + the beginning or at the end of the string such that subsequently + calling ":".join(hextets) will produce the compressed version of + the IPv6 address. + + Args: + hextets: A list of strings, the hextets to compress. + + Returns: + A list of strings. + + """ + best_doublecolon_start = -1 + best_doublecolon_len = 0 + doublecolon_start = -1 + doublecolon_len = 0 + for index, hextet in enumerate(hextets): + if hextet == "0": + doublecolon_len += 1 + if doublecolon_start == -1: + # Start of a sequence of zeros. + doublecolon_start = index + if doublecolon_len > best_doublecolon_len: + # This is the longest sequence of zeros so far. + best_doublecolon_len = doublecolon_len + best_doublecolon_start = doublecolon_start + else: + doublecolon_len = 0 + doublecolon_start = -1 + + if best_doublecolon_len > 1: + best_doublecolon_end = ( + best_doublecolon_start + best_doublecolon_len + ) + # For zeros at the end of the address. + if best_doublecolon_end == len(hextets): + hextets += [""] + hextets[best_doublecolon_start:best_doublecolon_end] = [""] + # For zeros at the beginning of the address. + if best_doublecolon_start == 0: + hextets = [""] + hextets + + return hextets + + @classmethod + def _string_from_ip_int(cls, ip_int=None): + """Turns a 128-bit integer into hexadecimal notation. + + Args: + ip_int: An integer, the IP address. + + Returns: + A string, the hexadecimal representation of the address. + + Raises: + ValueError: The address is bigger than 128 bits of all ones. + + """ + if ip_int is None: + ip_int = int(cls._ip) + + if ip_int > cls._ALL_ONES: + raise ValueError("IPv6 address is too large") + + hex_str = "%032x" % ip_int + hextets = ["%x" % int(hex_str[x:x + 4], 16) for x in range(0, 32, 4)] + + hextets = cls._compress_hextets(hextets) + return ":".join(hextets) + + def _explode_shorthand_ip_string(self): + """Expand a shortened IPv6 address. + + Args: + ip_str: A string, the IPv6 address. + + Returns: + A string, the expanded IPv6 address. + + """ + if isinstance(self, IPv6Network): + ip_str = _compat_str(self.network_address) + elif isinstance(self, IPv6Interface): + ip_str = _compat_str(self.ip) + else: + ip_str = _compat_str(self) + + ip_int = self._ip_int_from_string(ip_str) + hex_str = "%032x" % ip_int + parts = [hex_str[x:x + 4] for x in range(0, 32, 4)] + if isinstance(self, (_BaseNetwork, IPv6Interface)): + return "%s/%d" % (":".join(parts), self._prefixlen) + return ":".join(parts) + + def _reverse_pointer(self): + """Return the reverse DNS pointer name for the IPv6 address. + + This implements the method described in RFC3596 2.5. + + """ + reverse_chars = self.exploded[::-1].replace(":", "") + return ".".join(reverse_chars) + ".ip6.arpa" + + @property + def max_prefixlen(self): + return self._max_prefixlen + + @property + def version(self): + return self._version + + +class IPv6Address(_BaseV6, _BaseAddress): + + """Represent and manipulate single IPv6 Addresses.""" + + __slots__ = ("_ip", "__weakref__") + + def __init__(self, address): + """Instantiate a new IPv6 address object. + + Args: + address: A string or integer representing the IP + + Additionally, an integer can be passed, so + IPv6Address('2001:db8::') == + IPv6Address(42540766411282592856903984951653826560) + or, more generally + IPv6Address(int(IPv6Address('2001:db8::'))) == + IPv6Address('2001:db8::') + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + + """ + # Efficient constructor from integer. + if isinstance(address, _compat_int_types): + self._check_int_address(address) + self._ip = address + return + + # Constructing from a packed address + if isinstance(address, bytes): + self._check_packed_address(address, 16) + bvs = _compat_bytes_to_byte_vals(address) + self._ip = _compat_int_from_byte_vals(bvs, "big") + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP string. + addr_str = _compat_str(address) + if "/" in addr_str: + raise AddressValueError("Unexpected '/' in %r" % address) + self._ip = self._ip_int_from_string(addr_str) + + @property + def packed(self): + """The binary representation of this address.""" + return v6_int_to_packed(self._ip) + + @property + def is_multicast(self): + """Test if the address is reserved for multicast use. + + Returns: + A boolean, True if the address is a multicast address. + See RFC 2373 2.7 for details. + + """ + return self in self._constants._multicast_network + + @property + def is_reserved(self): + """Test if the address is otherwise IETF reserved. + + Returns: + A boolean, True if the address is within one of the + reserved IPv6 Network ranges. + + """ + return any(self in x for x in self._constants._reserved_networks) + + @property + def is_link_local(self): + """Test if the address is reserved for link-local. + + Returns: + A boolean, True if the address is reserved per RFC 4291. + + """ + return self in self._constants._linklocal_network + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return self in self._constants._sitelocal_network + + @property + def is_private(self): + """Test if this address is allocated for private networks. + + Returns: + A boolean, True if the address is reserved per + iana-ipv6-special-registry. + + """ + return any(self in net for net in self._constants._private_networks) + + @property + def is_global(self): + """Test if this address is allocated for public networks. + + Returns: + A boolean, true if the address is not reserved per + iana-ipv6-special-registry. + + """ + return not self.is_private + + @property + def is_unspecified(self): + """Test if the address is unspecified. + + Returns: + A boolean, True if this is the unspecified address as defined in + RFC 2373 2.5.2. + + """ + return self._ip == 0 + + @property + def is_loopback(self): + """Test if the address is a loopback address. + + Returns: + A boolean, True if the address is a loopback address as defined in + RFC 2373 2.5.3. + + """ + return self._ip == 1 + + @property + def ipv4_mapped(self): + """Return the IPv4 mapped address. + + Returns: + If the IPv6 address is a v4 mapped address, return the + IPv4 mapped address. Return None otherwise. + + """ + if (self._ip >> 32) != 0xFFFF: + return None + return IPv4Address(self._ip & 0xFFFFFFFF) + + @property + def teredo(self): + """Tuple of embedded teredo IPs. + + Returns: + Tuple of the (server, client) IPs or None if the address + doesn't appear to be a teredo address (doesn't start with + 2001::/32) + + """ + if (self._ip >> 96) != 0x20010000: + return None + return ( + IPv4Address((self._ip >> 64) & 0xFFFFFFFF), + IPv4Address(~self._ip & 0xFFFFFFFF), + ) + + @property + def sixtofour(self): + """Return the IPv4 6to4 embedded address. + + Returns: + The IPv4 6to4-embedded address if present or None if the + address doesn't appear to contain a 6to4 embedded address. + + """ + if (self._ip >> 112) != 0x2002: + return None + return IPv4Address((self._ip >> 80) & 0xFFFFFFFF) + + +class IPv6Interface(IPv6Address): + def __init__(self, address): + if isinstance(address, (bytes, _compat_int_types)): + IPv6Address.__init__(self, address) + self.network = IPv6Network(self._ip) + self._prefixlen = self._max_prefixlen + return + if isinstance(address, tuple): + IPv6Address.__init__(self, address[0]) + if len(address) > 1: + self._prefixlen = int(address[1]) + else: + self._prefixlen = self._max_prefixlen + self.network = IPv6Network(address, strict=False) + self.netmask = self.network.netmask + self.hostmask = self.network.hostmask + return + + addr = _split_optional_netmask(address) + IPv6Address.__init__(self, addr[0]) + self.network = IPv6Network(address, strict=False) + self.netmask = self.network.netmask + self._prefixlen = self.network._prefixlen + self.hostmask = self.network.hostmask + + def __str__(self): + return "%s/%d" % ( + self._string_from_ip_int(self._ip), + self.network.prefixlen, + ) + + def __eq__(self, other): + address_equal = IPv6Address.__eq__(self, other) + if not address_equal or address_equal is NotImplemented: + return address_equal + try: + return self.network == other.network + except AttributeError: + # An interface with an associated network is NOT the + # same as an unassociated address. That's why the hash + # takes the extra info into account. + return False + + def __lt__(self, other): + address_less = IPv6Address.__lt__(self, other) + if address_less is NotImplemented: + return NotImplemented + try: + return ( + self.network < other.network + or self.network == other.network + and address_less + ) + except AttributeError: + # We *do* allow addresses and interfaces to be sorted. The + # unassociated address is considered less than all interfaces. + return False + + def __hash__(self): + return self._ip ^ self._prefixlen ^ int(self.network.network_address) + + __reduce__ = _IPAddressBase.__reduce__ + + @property + def ip(self): + return IPv6Address(self._ip) + + @property + def with_prefixlen(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self._prefixlen) + + @property + def with_netmask(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self.netmask) + + @property + def with_hostmask(self): + return "%s/%s" % (self._string_from_ip_int(self._ip), self.hostmask) + + @property + def is_unspecified(self): + return self._ip == 0 and self.network.is_unspecified + + @property + def is_loopback(self): + return self._ip == 1 and self.network.is_loopback + + +class IPv6Network(_BaseV6, _BaseNetwork): + + """This class represents and manipulates 128-bit IPv6 networks. + + Attributes: [examples for IPv6('2001:db8::1000/124')] + .network_address: IPv6Address('2001:db8::1000') + .hostmask: IPv6Address('::f') + .broadcast_address: IPv6Address('2001:db8::100f') + .netmask: IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0') + .prefixlen: 124 + + """ + + # Class to use when creating address objects + _address_class = IPv6Address + + def __init__(self, address, strict=True): + """Instantiate a new IPv6 Network object. + + Args: + address: A string or integer representing the IPv6 network or the + IP and prefix/netmask. + '2001:db8::/128' + '2001:db8:0000:0000:0000:0000:0000:0000/128' + '2001:db8::' + are all functionally the same in IPv6. That is to say, + failing to provide a subnetmask will create an object with + a mask of /128. + + Additionally, an integer can be passed, so + IPv6Network('2001:db8::') == + IPv6Network(42540766411282592856903984951653826560) + or, more generally + IPv6Network(int(IPv6Network('2001:db8::'))) == + IPv6Network('2001:db8::') + + strict: A boolean. If true, ensure that we have been passed + A true network address, eg, 2001:db8::1000/124 and not an + IP address on a network, eg, 2001:db8::1/124. + + Raises: + AddressValueError: If address isn't a valid IPv6 address. + NetmaskValueError: If the netmask isn't valid for + an IPv6 address. + ValueError: If strict was True and a network address was not + supplied. + + """ + _BaseNetwork.__init__(self, address) + + # Efficient constructor from integer or packed address + if isinstance(address, (bytes, _compat_int_types)): + self.network_address = IPv6Address(address) + self.netmask, self._prefixlen = self._make_netmask( + self._max_prefixlen + ) + return + + if isinstance(address, tuple): + if len(address) > 1: + arg = address[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + self.network_address = IPv6Address(address[0]) + packed = int(self.network_address) + if packed & int(self.netmask) != packed: + if strict: + raise ValueError("%s has host bits set" % self) + else: + self.network_address = IPv6Address( + packed & int(self.netmask) + ) + return + + # Assume input argument to be string or any object representation + # which converts into a formatted IP prefix string. + addr = _split_optional_netmask(address) + + self.network_address = IPv6Address(self._ip_int_from_string(addr[0])) + + if len(addr) == 2: + arg = addr[1] + else: + arg = self._max_prefixlen + self.netmask, self._prefixlen = self._make_netmask(arg) + + if strict: + if ( + IPv6Address(int(self.network_address) & int(self.netmask)) + != self.network_address + ): + raise ValueError("%s has host bits set" % self) + self.network_address = IPv6Address( + int(self.network_address) & int(self.netmask) + ) + + if self._prefixlen == (self._max_prefixlen - 1): + self.hosts = self.__iter__ + + def hosts(self): + """Generate Iterator over usable hosts in a network. + + This is like __iter__ except it doesn't return the + Subnet-Router anycast address. + + """ + network = int(self.network_address) + broadcast = int(self.broadcast_address) + for x in _compat_range(network + 1, broadcast + 1): + yield self._address_class(x) + + @property + def is_site_local(self): + """Test if the address is reserved for site-local. + + Note that the site-local address space has been deprecated by RFC 3879. + Use is_private to test if this address is in the space of unique local + addresses as defined by RFC 4193. + + Returns: + A boolean, True if the address is reserved per RFC 3513 2.5.6. + + """ + return ( + self.network_address.is_site_local + and self.broadcast_address.is_site_local + ) + + +class _IPv6Constants(object): + + _linklocal_network = IPv6Network("fe80::/10") + + _multicast_network = IPv6Network("ff00::/8") + + _private_networks = [ + IPv6Network("::1/128"), + IPv6Network("::/128"), + IPv6Network("::ffff:0:0/96"), + IPv6Network("100::/64"), + IPv6Network("2001::/23"), + IPv6Network("2001:2::/48"), + IPv6Network("2001:db8::/32"), + IPv6Network("2001:10::/28"), + IPv6Network("fc00::/7"), + IPv6Network("fe80::/10"), + ] + + _reserved_networks = [ + IPv6Network("::/8"), + IPv6Network("100::/8"), + IPv6Network("200::/7"), + IPv6Network("400::/6"), + IPv6Network("800::/5"), + IPv6Network("1000::/4"), + IPv6Network("4000::/3"), + IPv6Network("6000::/3"), + IPv6Network("8000::/3"), + IPv6Network("A000::/3"), + IPv6Network("C000::/3"), + IPv6Network("E000::/4"), + IPv6Network("F000::/5"), + IPv6Network("F800::/6"), + IPv6Network("FE00::/9"), + ] + + _sitelocal_network = IPv6Network("fec0::/10") + + +IPv6Address._constants = _IPv6Constants diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/__init__.py new file mode 100644 index 00000000..16b87dc6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/__init__.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# +# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +# THIS FILE IS FOR COMPATIBILITY ONLY! YOU SHALL NOT IMPORT IT! +# +# This fill will be removed eventually, so if you're using it, +# please stop doing so. + +from .basic import ( + HAS_PYOPENSSL, + CRYPTOGRAPHY_HAS_X25519, + CRYPTOGRAPHY_HAS_X25519_FULL, + CRYPTOGRAPHY_HAS_X448, + CRYPTOGRAPHY_HAS_ED25519, + CRYPTOGRAPHY_HAS_ED448, + HAS_CRYPTOGRAPHY, + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from .cryptography_crl import ( + REVOCATION_REASON_MAP, + REVOCATION_REASON_MAP_INVERSE, + cryptography_decode_revoked_certificate, +) + +from .cryptography_support import ( + cryptography_get_extensions_from_cert, + cryptography_get_extensions_from_csr, + cryptography_name_to_oid, + cryptography_oid_to_name, + cryptography_get_name, + cryptography_decode_name, + cryptography_parse_key_usage_params, + cryptography_get_basic_constraints, + cryptography_key_needs_digest_for_signing, + cryptography_compare_public_keys, +) + +from .pem import ( + identify_private_key_format, +) + +from .math import ( + binary_exp_mod, + simple_gcd, + quick_is_not_prime, + count_bits, +) + +from ._obj2txt import obj2txt as _obj2txt + +from ._objects_data import OID_MAP as _OID_MAP + +from ._objects import OID_LOOKUP as _OID_LOOKUP +from ._objects import NORMALIZE_NAMES as _NORMALIZE_NAMES +from ._objects import NORMALIZE_NAMES_SHORT as _NORMALIZE_NAMES_SHORT + +from .pyopenssl_support import ( + pyopenssl_normalize_name, + pyopenssl_get_extensions_from_cert, + pyopenssl_get_extensions_from_csr, +) + +from .support import ( + get_fingerprint_of_bytes, + get_fingerprint, + load_privatekey, + load_certificate, + load_certificate_request, + parse_name_field, + convert_relative_to_datetime, + get_relative_time_option, + select_message_digest, + OpenSSLObject, +) + +from ..io import ( + load_file_if_exists, + write_file, +) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_asn1.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_asn1.py new file mode 100644 index 00000000..17feb2f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_asn1.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- + +# (c) 2020, Jordan Borean <jborean93@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import re + +from ansible.module_utils._text import to_bytes + + +""" +An ASN.1 serialized as a string in the OpenSSL format: + [modifier,]type[:value] + +modifier: + The modifier can be 'IMPLICIT:<tag_number><tag_class>,' or 'EXPLICIT:<tag_number><tag_class>' where IMPLICIT + changes the tag of the universal value to encode and EXPLICIT prefixes its tag to the existing universal value. + The tag_number must be set while the tag_class can be 'U', 'A', 'P', or 'C" for 'Universal', 'Application', + 'Private', or 'Context Specific' with C being the default. + +type: + The underlying ASN.1 type of the value specified. Currently only the following have been implemented: + UTF8: The value must be a UTF-8 encoded string. + +value: + The value to encode, the format of this value depends on the <type> specified. +""" +ASN1_STRING_REGEX = re.compile(r'^((?P<tag_type>IMPLICIT|EXPLICIT):(?P<tag_number>\d+)(?P<tag_class>U|A|P|C)?,)?' + r'(?P<value_type>[\w\d]+):(?P<value>.*)') + + +class TagClass: + universal = 0 + application = 1 + context_specific = 2 + private = 3 + + +# Universal tag numbers that can be encoded. +class TagNumber: + utf8_string = 12 + + +def _pack_octet_integer(value): + """ Packs an integer value into 1 or multiple octets. """ + # NOTE: This is *NOT* the same as packing an ASN.1 INTEGER like value. + octets = bytearray() + + # Continue to shift the number by 7 bits and pack into an octet until the + # value is fully packed. + while value: + octet_value = value & 0b01111111 + + # First round (last octet) must have the MSB set. + if len(octets): + octet_value |= 0b10000000 + + octets.append(octet_value) + value >>= 7 + + # Reverse to ensure the higher order octets are first. + octets.reverse() + return bytes(octets) + + +def serialize_asn1_string_as_der(value): + """ Deserializes an ASN.1 string to a DER encoded byte string. """ + asn1_match = ASN1_STRING_REGEX.match(value) + if not asn1_match: + raise ValueError("The ASN.1 serialized string must be in the format [modifier,]type[:value]") + + tag_type = asn1_match.group('tag_type') + tag_number = asn1_match.group('tag_number') + tag_class = asn1_match.group('tag_class') or 'C' + value_type = asn1_match.group('value_type') + asn1_value = asn1_match.group('value') + + if value_type != 'UTF8': + raise ValueError('The ASN.1 serialized string is not a known type "{0}", only UTF8 types are ' + 'supported'.format(value_type)) + + b_value = to_bytes(asn1_value, encoding='utf-8', errors='surrogate_or_strict') + + # We should only do a universal type tag if not IMPLICITLY tagged or the tag class is not universal. + if not tag_type or (tag_type == 'EXPLICIT' and tag_class != 'U'): + b_value = pack_asn1(TagClass.universal, False, TagNumber.utf8_string, b_value) + + if tag_type: + tag_class = { + 'U': TagClass.universal, + 'A': TagClass.application, + 'P': TagClass.private, + 'C': TagClass.context_specific, + }[tag_class] + + # When adding support for more types this should be looked into further. For now it works with UTF8Strings. + constructed = tag_type == 'EXPLICIT' and tag_class != TagClass.universal + b_value = pack_asn1(tag_class, constructed, int(tag_number), b_value) + + return b_value + + +def pack_asn1(tag_class, constructed, tag_number, b_data): + """Pack the value into an ASN.1 data structure. + + The structure for an ASN.1 element is + + | Identifier Octet(s) | Length Octet(s) | Data Octet(s) | + """ + b_asn1_data = bytearray() + + if tag_class < 0 or tag_class > 3: + raise ValueError("tag_class must be between 0 and 3 not %s" % tag_class) + + # Bit 8 and 7 denotes the class. + identifier_octets = tag_class << 6 + # Bit 6 denotes whether the value is primitive or constructed. + identifier_octets |= ((1 if constructed else 0) << 5) + + # Bits 5-1 contain the tag number, if it cannot be encoded in these 5 bits + # then they are set and another octet(s) is used to denote the tag number. + if tag_number < 31: + identifier_octets |= tag_number + b_asn1_data.append(identifier_octets) + else: + identifier_octets |= 31 + b_asn1_data.append(identifier_octets) + b_asn1_data.extend(_pack_octet_integer(tag_number)) + + length = len(b_data) + + # If the length can be encoded in 7 bits only 1 octet is required. + if length < 128: + b_asn1_data.append(length) + + else: + # Otherwise the length must be encoded across multiple octets + length_octets = bytearray() + while length: + length_octets.append(length & 0b11111111) + length >>= 8 + + length_octets.reverse() # Reverse to make the higher octets first. + + # The first length octet must have the MSB set alongside the number of + # octets the length was encoded in. + b_asn1_data.append(len(length_octets) | 0b10000000) + b_asn1_data.extend(length_octets) + + return bytes(b_asn1_data) + b_data diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_obj2txt.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_obj2txt.py new file mode 100644 index 00000000..f84774cb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_obj2txt.py @@ -0,0 +1,43 @@ +# This excerpt is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file at +# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details. +# +# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py +# +# Copyright (c) 2015, 2016 Paul Kehrer (@reaperhulk) +# Copyright (c) 2017 Fraser Tweedale (@frasertweedale) + +# Relevant commits from cryptography project (https://github.com/pyca/cryptography): +# pyca/cryptography@719d536dd691e84e208534798f2eb4f82aaa2e07 +# pyca/cryptography@5ab6d6a5c05572bd1c75f05baf264a2d0001894a +# pyca/cryptography@2e776e20eb60378e0af9b7439000d0e80da7c7e3 +# pyca/cryptography@fb309ed24647d1be9e319b61b1f2aa8ebb87b90b +# pyca/cryptography@2917e460993c475c72d7146c50dc3bbc2414280d +# pyca/cryptography@3057f91ea9a05fb593825006d87a391286a4d828 +# pyca/cryptography@d607dd7e5bc5c08854ec0c9baff70ba4a35be36f + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def obj2txt(openssl_lib, openssl_ffi, obj): + # Set to 80 on the recommendation of + # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values + # + # But OIDs longer than this occur in real life (e.g. Active + # Directory makes some very long OIDs). So we need to detect + # and properly handle the case where the default buffer is not + # big enough. + # + buf_len = 80 + buf = openssl_ffi.new("char[]", buf_len) + + # 'res' is the number of bytes that *would* be written if the + # buffer is large enough. If 'res' > buf_len - 1, we need to + # alloc a big-enough buffer and go again. + res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1) + if res > buf_len - 1: # account for terminating null byte + buf_len = res + 1 + buf = openssl_ffi.new("char[]", buf_len) + res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1) + return openssl_ffi.buffer(buf, res)[:].decode() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects.py new file mode 100644 index 00000000..3785c1b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ._objects_data import OID_MAP + +OID_LOOKUP = dict() +NORMALIZE_NAMES = dict() +NORMALIZE_NAMES_SHORT = dict() + +for dotted, names in OID_MAP.items(): + for name in names: + if name in NORMALIZE_NAMES and OID_LOOKUP[name] != dotted: + raise AssertionError( + 'Name collision during setup: "{0}" for OIDs {1} and {2}' + .format(name, dotted, OID_LOOKUP[name]) + ) + NORMALIZE_NAMES[name] = names[0] + NORMALIZE_NAMES_SHORT[name] = names[-1] + OID_LOOKUP[name] = dotted +for alias, original in [('userID', 'userId')]: + if alias in NORMALIZE_NAMES: + raise AssertionError( + 'Name collision during adding aliases: "{0}" (alias for "{1}") is already mapped to OID {2}' + .format(alias, original, OID_LOOKUP[alias]) + ) + NORMALIZE_NAMES[alias] = original + NORMALIZE_NAMES_SHORT[alias] = NORMALIZE_NAMES_SHORT[original] + OID_LOOKUP[alias] = OID_LOOKUP[original] diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects_data.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects_data.py new file mode 100644 index 00000000..c3b5446a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/_objects_data.py @@ -0,0 +1,1108 @@ +# This has been extracted from the OpenSSL project's objects.txt: +# https://github.com/openssl/openssl/blob/9537fe5757bb07761fa275d779bbd40bcf5530e4/crypto/objects/objects.txt +# Extracted with https://gist.github.com/felixfontein/376748017ad65ead093d56a45a5bf376 + +# In case the following data structure has any copyrightable content, note that it is licensed as follows: +# Copyright (c) the OpenSSL contributors +# Licensed under the Apache License 2.0 +# https://github.com/openssl/openssl/blob/master/LICENSE + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +OID_MAP = { + '0': ('itu-t', 'ITU-T', 'ccitt'), + '0.3.4401.5': ('ntt-ds', ), + '0.3.4401.5.3.1.9': ('camellia', ), + '0.3.4401.5.3.1.9.1': ('camellia-128-ecb', 'CAMELLIA-128-ECB'), + '0.3.4401.5.3.1.9.3': ('camellia-128-ofb', 'CAMELLIA-128-OFB'), + '0.3.4401.5.3.1.9.4': ('camellia-128-cfb', 'CAMELLIA-128-CFB'), + '0.3.4401.5.3.1.9.6': ('camellia-128-gcm', 'CAMELLIA-128-GCM'), + '0.3.4401.5.3.1.9.7': ('camellia-128-ccm', 'CAMELLIA-128-CCM'), + '0.3.4401.5.3.1.9.9': ('camellia-128-ctr', 'CAMELLIA-128-CTR'), + '0.3.4401.5.3.1.9.10': ('camellia-128-cmac', 'CAMELLIA-128-CMAC'), + '0.3.4401.5.3.1.9.21': ('camellia-192-ecb', 'CAMELLIA-192-ECB'), + '0.3.4401.5.3.1.9.23': ('camellia-192-ofb', 'CAMELLIA-192-OFB'), + '0.3.4401.5.3.1.9.24': ('camellia-192-cfb', 'CAMELLIA-192-CFB'), + '0.3.4401.5.3.1.9.26': ('camellia-192-gcm', 'CAMELLIA-192-GCM'), + '0.3.4401.5.3.1.9.27': ('camellia-192-ccm', 'CAMELLIA-192-CCM'), + '0.3.4401.5.3.1.9.29': ('camellia-192-ctr', 'CAMELLIA-192-CTR'), + '0.3.4401.5.3.1.9.30': ('camellia-192-cmac', 'CAMELLIA-192-CMAC'), + '0.3.4401.5.3.1.9.41': ('camellia-256-ecb', 'CAMELLIA-256-ECB'), + '0.3.4401.5.3.1.9.43': ('camellia-256-ofb', 'CAMELLIA-256-OFB'), + '0.3.4401.5.3.1.9.44': ('camellia-256-cfb', 'CAMELLIA-256-CFB'), + '0.3.4401.5.3.1.9.46': ('camellia-256-gcm', 'CAMELLIA-256-GCM'), + '0.3.4401.5.3.1.9.47': ('camellia-256-ccm', 'CAMELLIA-256-CCM'), + '0.3.4401.5.3.1.9.49': ('camellia-256-ctr', 'CAMELLIA-256-CTR'), + '0.3.4401.5.3.1.9.50': ('camellia-256-cmac', 'CAMELLIA-256-CMAC'), + '0.9': ('data', ), + '0.9.2342': ('pss', ), + '0.9.2342.19200300': ('ucl', ), + '0.9.2342.19200300.100': ('pilot', ), + '0.9.2342.19200300.100.1': ('pilotAttributeType', ), + '0.9.2342.19200300.100.1.1': ('userId', 'UID'), + '0.9.2342.19200300.100.1.2': ('textEncodedORAddress', ), + '0.9.2342.19200300.100.1.3': ('rfc822Mailbox', 'mail'), + '0.9.2342.19200300.100.1.4': ('info', ), + '0.9.2342.19200300.100.1.5': ('favouriteDrink', ), + '0.9.2342.19200300.100.1.6': ('roomNumber', ), + '0.9.2342.19200300.100.1.7': ('photo', ), + '0.9.2342.19200300.100.1.8': ('userClass', ), + '0.9.2342.19200300.100.1.9': ('host', ), + '0.9.2342.19200300.100.1.10': ('manager', ), + '0.9.2342.19200300.100.1.11': ('documentIdentifier', ), + '0.9.2342.19200300.100.1.12': ('documentTitle', ), + '0.9.2342.19200300.100.1.13': ('documentVersion', ), + '0.9.2342.19200300.100.1.14': ('documentAuthor', ), + '0.9.2342.19200300.100.1.15': ('documentLocation', ), + '0.9.2342.19200300.100.1.20': ('homeTelephoneNumber', ), + '0.9.2342.19200300.100.1.21': ('secretary', ), + '0.9.2342.19200300.100.1.22': ('otherMailbox', ), + '0.9.2342.19200300.100.1.23': ('lastModifiedTime', ), + '0.9.2342.19200300.100.1.24': ('lastModifiedBy', ), + '0.9.2342.19200300.100.1.25': ('domainComponent', 'DC'), + '0.9.2342.19200300.100.1.26': ('aRecord', ), + '0.9.2342.19200300.100.1.27': ('pilotAttributeType27', ), + '0.9.2342.19200300.100.1.28': ('mXRecord', ), + '0.9.2342.19200300.100.1.29': ('nSRecord', ), + '0.9.2342.19200300.100.1.30': ('sOARecord', ), + '0.9.2342.19200300.100.1.31': ('cNAMERecord', ), + '0.9.2342.19200300.100.1.37': ('associatedDomain', ), + '0.9.2342.19200300.100.1.38': ('associatedName', ), + '0.9.2342.19200300.100.1.39': ('homePostalAddress', ), + '0.9.2342.19200300.100.1.40': ('personalTitle', ), + '0.9.2342.19200300.100.1.41': ('mobileTelephoneNumber', ), + '0.9.2342.19200300.100.1.42': ('pagerTelephoneNumber', ), + '0.9.2342.19200300.100.1.43': ('friendlyCountryName', ), + '0.9.2342.19200300.100.1.44': ('uniqueIdentifier', 'uid'), + '0.9.2342.19200300.100.1.45': ('organizationalStatus', ), + '0.9.2342.19200300.100.1.46': ('janetMailbox', ), + '0.9.2342.19200300.100.1.47': ('mailPreferenceOption', ), + '0.9.2342.19200300.100.1.48': ('buildingName', ), + '0.9.2342.19200300.100.1.49': ('dSAQuality', ), + '0.9.2342.19200300.100.1.50': ('singleLevelQuality', ), + '0.9.2342.19200300.100.1.51': ('subtreeMinimumQuality', ), + '0.9.2342.19200300.100.1.52': ('subtreeMaximumQuality', ), + '0.9.2342.19200300.100.1.53': ('personalSignature', ), + '0.9.2342.19200300.100.1.54': ('dITRedirect', ), + '0.9.2342.19200300.100.1.55': ('audio', ), + '0.9.2342.19200300.100.1.56': ('documentPublisher', ), + '0.9.2342.19200300.100.3': ('pilotAttributeSyntax', ), + '0.9.2342.19200300.100.3.4': ('iA5StringSyntax', ), + '0.9.2342.19200300.100.3.5': ('caseIgnoreIA5StringSyntax', ), + '0.9.2342.19200300.100.4': ('pilotObjectClass', ), + '0.9.2342.19200300.100.4.3': ('pilotObject', ), + '0.9.2342.19200300.100.4.4': ('pilotPerson', ), + '0.9.2342.19200300.100.4.5': ('account', ), + '0.9.2342.19200300.100.4.6': ('document', ), + '0.9.2342.19200300.100.4.7': ('room', ), + '0.9.2342.19200300.100.4.9': ('documentSeries', ), + '0.9.2342.19200300.100.4.13': ('Domain', 'domain'), + '0.9.2342.19200300.100.4.14': ('rFC822localPart', ), + '0.9.2342.19200300.100.4.15': ('dNSDomain', ), + '0.9.2342.19200300.100.4.17': ('domainRelatedObject', ), + '0.9.2342.19200300.100.4.18': ('friendlyCountry', ), + '0.9.2342.19200300.100.4.19': ('simpleSecurityObject', ), + '0.9.2342.19200300.100.4.20': ('pilotOrganization', ), + '0.9.2342.19200300.100.4.21': ('pilotDSA', ), + '0.9.2342.19200300.100.4.22': ('qualityLabelledData', ), + '0.9.2342.19200300.100.10': ('pilotGroups', ), + '1': ('iso', 'ISO'), + '1.0.9797.3.4': ('gmac', 'GMAC'), + '1.0.10118.3.0.55': ('whirlpool', ), + '1.2': ('ISO Member Body', 'member-body'), + '1.2.156': ('ISO CN Member Body', 'ISO-CN'), + '1.2.156.10197': ('oscca', ), + '1.2.156.10197.1': ('sm-scheme', ), + '1.2.156.10197.1.104.1': ('sm4-ecb', 'SM4-ECB'), + '1.2.156.10197.1.104.2': ('sm4-cbc', 'SM4-CBC'), + '1.2.156.10197.1.104.3': ('sm4-ofb', 'SM4-OFB'), + '1.2.156.10197.1.104.4': ('sm4-cfb', 'SM4-CFB'), + '1.2.156.10197.1.104.5': ('sm4-cfb1', 'SM4-CFB1'), + '1.2.156.10197.1.104.6': ('sm4-cfb8', 'SM4-CFB8'), + '1.2.156.10197.1.104.7': ('sm4-ctr', 'SM4-CTR'), + '1.2.156.10197.1.301': ('sm2', 'SM2'), + '1.2.156.10197.1.401': ('sm3', 'SM3'), + '1.2.156.10197.1.501': ('SM2-with-SM3', 'SM2-SM3'), + '1.2.156.10197.1.504': ('sm3WithRSAEncryption', 'RSA-SM3'), + '1.2.392.200011.61.1.1.1.2': ('camellia-128-cbc', 'CAMELLIA-128-CBC'), + '1.2.392.200011.61.1.1.1.3': ('camellia-192-cbc', 'CAMELLIA-192-CBC'), + '1.2.392.200011.61.1.1.1.4': ('camellia-256-cbc', 'CAMELLIA-256-CBC'), + '1.2.392.200011.61.1.1.3.2': ('id-camellia128-wrap', ), + '1.2.392.200011.61.1.1.3.3': ('id-camellia192-wrap', ), + '1.2.392.200011.61.1.1.3.4': ('id-camellia256-wrap', ), + '1.2.410.200004': ('kisa', 'KISA'), + '1.2.410.200004.1.3': ('seed-ecb', 'SEED-ECB'), + '1.2.410.200004.1.4': ('seed-cbc', 'SEED-CBC'), + '1.2.410.200004.1.5': ('seed-cfb', 'SEED-CFB'), + '1.2.410.200004.1.6': ('seed-ofb', 'SEED-OFB'), + '1.2.410.200046.1.1': ('aria', ), + '1.2.410.200046.1.1.1': ('aria-128-ecb', 'ARIA-128-ECB'), + '1.2.410.200046.1.1.2': ('aria-128-cbc', 'ARIA-128-CBC'), + '1.2.410.200046.1.1.3': ('aria-128-cfb', 'ARIA-128-CFB'), + '1.2.410.200046.1.1.4': ('aria-128-ofb', 'ARIA-128-OFB'), + '1.2.410.200046.1.1.5': ('aria-128-ctr', 'ARIA-128-CTR'), + '1.2.410.200046.1.1.6': ('aria-192-ecb', 'ARIA-192-ECB'), + '1.2.410.200046.1.1.7': ('aria-192-cbc', 'ARIA-192-CBC'), + '1.2.410.200046.1.1.8': ('aria-192-cfb', 'ARIA-192-CFB'), + '1.2.410.200046.1.1.9': ('aria-192-ofb', 'ARIA-192-OFB'), + '1.2.410.200046.1.1.10': ('aria-192-ctr', 'ARIA-192-CTR'), + '1.2.410.200046.1.1.11': ('aria-256-ecb', 'ARIA-256-ECB'), + '1.2.410.200046.1.1.12': ('aria-256-cbc', 'ARIA-256-CBC'), + '1.2.410.200046.1.1.13': ('aria-256-cfb', 'ARIA-256-CFB'), + '1.2.410.200046.1.1.14': ('aria-256-ofb', 'ARIA-256-OFB'), + '1.2.410.200046.1.1.15': ('aria-256-ctr', 'ARIA-256-CTR'), + '1.2.410.200046.1.1.34': ('aria-128-gcm', 'ARIA-128-GCM'), + '1.2.410.200046.1.1.35': ('aria-192-gcm', 'ARIA-192-GCM'), + '1.2.410.200046.1.1.36': ('aria-256-gcm', 'ARIA-256-GCM'), + '1.2.410.200046.1.1.37': ('aria-128-ccm', 'ARIA-128-CCM'), + '1.2.410.200046.1.1.38': ('aria-192-ccm', 'ARIA-192-CCM'), + '1.2.410.200046.1.1.39': ('aria-256-ccm', 'ARIA-256-CCM'), + '1.2.643.2.2': ('cryptopro', ), + '1.2.643.2.2.3': ('GOST R 34.11-94 with GOST R 34.10-2001', 'id-GostR3411-94-with-GostR3410-2001'), + '1.2.643.2.2.4': ('GOST R 34.11-94 with GOST R 34.10-94', 'id-GostR3411-94-with-GostR3410-94'), + '1.2.643.2.2.9': ('GOST R 34.11-94', 'md_gost94'), + '1.2.643.2.2.10': ('HMAC GOST 34.11-94', 'id-HMACGostR3411-94'), + '1.2.643.2.2.14.0': ('id-Gost28147-89-None-KeyMeshing', ), + '1.2.643.2.2.14.1': ('id-Gost28147-89-CryptoPro-KeyMeshing', ), + '1.2.643.2.2.19': ('GOST R 34.10-2001', 'gost2001'), + '1.2.643.2.2.20': ('GOST R 34.10-94', 'gost94'), + '1.2.643.2.2.20.1': ('id-GostR3410-94-a', ), + '1.2.643.2.2.20.2': ('id-GostR3410-94-aBis', ), + '1.2.643.2.2.20.3': ('id-GostR3410-94-b', ), + '1.2.643.2.2.20.4': ('id-GostR3410-94-bBis', ), + '1.2.643.2.2.21': ('GOST 28147-89', 'gost89'), + '1.2.643.2.2.22': ('GOST 28147-89 MAC', 'gost-mac'), + '1.2.643.2.2.23': ('GOST R 34.11-94 PRF', 'prf-gostr3411-94'), + '1.2.643.2.2.30.0': ('id-GostR3411-94-TestParamSet', ), + '1.2.643.2.2.30.1': ('id-GostR3411-94-CryptoProParamSet', ), + '1.2.643.2.2.31.0': ('id-Gost28147-89-TestParamSet', ), + '1.2.643.2.2.31.1': ('id-Gost28147-89-CryptoPro-A-ParamSet', ), + '1.2.643.2.2.31.2': ('id-Gost28147-89-CryptoPro-B-ParamSet', ), + '1.2.643.2.2.31.3': ('id-Gost28147-89-CryptoPro-C-ParamSet', ), + '1.2.643.2.2.31.4': ('id-Gost28147-89-CryptoPro-D-ParamSet', ), + '1.2.643.2.2.31.5': ('id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet', ), + '1.2.643.2.2.31.6': ('id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet', ), + '1.2.643.2.2.31.7': ('id-Gost28147-89-CryptoPro-RIC-1-ParamSet', ), + '1.2.643.2.2.32.0': ('id-GostR3410-94-TestParamSet', ), + '1.2.643.2.2.32.2': ('id-GostR3410-94-CryptoPro-A-ParamSet', ), + '1.2.643.2.2.32.3': ('id-GostR3410-94-CryptoPro-B-ParamSet', ), + '1.2.643.2.2.32.4': ('id-GostR3410-94-CryptoPro-C-ParamSet', ), + '1.2.643.2.2.32.5': ('id-GostR3410-94-CryptoPro-D-ParamSet', ), + '1.2.643.2.2.33.1': ('id-GostR3410-94-CryptoPro-XchA-ParamSet', ), + '1.2.643.2.2.33.2': ('id-GostR3410-94-CryptoPro-XchB-ParamSet', ), + '1.2.643.2.2.33.3': ('id-GostR3410-94-CryptoPro-XchC-ParamSet', ), + '1.2.643.2.2.35.0': ('id-GostR3410-2001-TestParamSet', ), + '1.2.643.2.2.35.1': ('id-GostR3410-2001-CryptoPro-A-ParamSet', ), + '1.2.643.2.2.35.2': ('id-GostR3410-2001-CryptoPro-B-ParamSet', ), + '1.2.643.2.2.35.3': ('id-GostR3410-2001-CryptoPro-C-ParamSet', ), + '1.2.643.2.2.36.0': ('id-GostR3410-2001-CryptoPro-XchA-ParamSet', ), + '1.2.643.2.2.36.1': ('id-GostR3410-2001-CryptoPro-XchB-ParamSet', ), + '1.2.643.2.2.98': ('GOST R 34.10-2001 DH', 'id-GostR3410-2001DH'), + '1.2.643.2.2.99': ('GOST R 34.10-94 DH', 'id-GostR3410-94DH'), + '1.2.643.2.9': ('cryptocom', ), + '1.2.643.2.9.1.3.3': ('GOST R 34.11-94 with GOST R 34.10-94 Cryptocom', 'id-GostR3411-94-with-GostR3410-94-cc'), + '1.2.643.2.9.1.3.4': ('GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom', 'id-GostR3411-94-with-GostR3410-2001-cc'), + '1.2.643.2.9.1.5.3': ('GOST 34.10-94 Cryptocom', 'gost94cc'), + '1.2.643.2.9.1.5.4': ('GOST 34.10-2001 Cryptocom', 'gost2001cc'), + '1.2.643.2.9.1.6.1': ('GOST 28147-89 Cryptocom ParamSet', 'id-Gost28147-89-cc'), + '1.2.643.2.9.1.8.1': ('GOST R 3410-2001 Parameter Set Cryptocom', 'id-GostR3410-2001-ParamSet-cc'), + '1.2.643.3.131.1.1': ('INN', 'INN'), + '1.2.643.7.1': ('id-tc26', ), + '1.2.643.7.1.1': ('id-tc26-algorithms', ), + '1.2.643.7.1.1.1': ('id-tc26-sign', ), + '1.2.643.7.1.1.1.1': ('GOST R 34.10-2012 with 256 bit modulus', 'gost2012_256'), + '1.2.643.7.1.1.1.2': ('GOST R 34.10-2012 with 512 bit modulus', 'gost2012_512'), + '1.2.643.7.1.1.2': ('id-tc26-digest', ), + '1.2.643.7.1.1.2.2': ('GOST R 34.11-2012 with 256 bit hash', 'md_gost12_256'), + '1.2.643.7.1.1.2.3': ('GOST R 34.11-2012 with 512 bit hash', 'md_gost12_512'), + '1.2.643.7.1.1.3': ('id-tc26-signwithdigest', ), + '1.2.643.7.1.1.3.2': ('GOST R 34.10-2012 with GOST R 34.11-2012 (256 bit)', 'id-tc26-signwithdigest-gost3410-2012-256'), + '1.2.643.7.1.1.3.3': ('GOST R 34.10-2012 with GOST R 34.11-2012 (512 bit)', 'id-tc26-signwithdigest-gost3410-2012-512'), + '1.2.643.7.1.1.4': ('id-tc26-mac', ), + '1.2.643.7.1.1.4.1': ('HMAC GOST 34.11-2012 256 bit', 'id-tc26-hmac-gost-3411-2012-256'), + '1.2.643.7.1.1.4.2': ('HMAC GOST 34.11-2012 512 bit', 'id-tc26-hmac-gost-3411-2012-512'), + '1.2.643.7.1.1.5': ('id-tc26-cipher', ), + '1.2.643.7.1.1.5.1': ('id-tc26-cipher-gostr3412-2015-magma', ), + '1.2.643.7.1.1.5.1.1': ('id-tc26-cipher-gostr3412-2015-magma-ctracpkm', ), + '1.2.643.7.1.1.5.1.2': ('id-tc26-cipher-gostr3412-2015-magma-ctracpkm-omac', ), + '1.2.643.7.1.1.5.2': ('id-tc26-cipher-gostr3412-2015-kuznyechik', ), + '1.2.643.7.1.1.5.2.1': ('id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm', ), + '1.2.643.7.1.1.5.2.2': ('id-tc26-cipher-gostr3412-2015-kuznyechik-ctracpkm-omac', ), + '1.2.643.7.1.1.6': ('id-tc26-agreement', ), + '1.2.643.7.1.1.6.1': ('id-tc26-agreement-gost-3410-2012-256', ), + '1.2.643.7.1.1.6.2': ('id-tc26-agreement-gost-3410-2012-512', ), + '1.2.643.7.1.1.7': ('id-tc26-wrap', ), + '1.2.643.7.1.1.7.1': ('id-tc26-wrap-gostr3412-2015-magma', ), + '1.2.643.7.1.1.7.1.1': ('id-tc26-wrap-gostr3412-2015-magma-kexp15', 'id-tc26-wrap-gostr3412-2015-kuznyechik-kexp15'), + '1.2.643.7.1.1.7.2': ('id-tc26-wrap-gostr3412-2015-kuznyechik', ), + '1.2.643.7.1.2': ('id-tc26-constants', ), + '1.2.643.7.1.2.1': ('id-tc26-sign-constants', ), + '1.2.643.7.1.2.1.1': ('id-tc26-gost-3410-2012-256-constants', ), + '1.2.643.7.1.2.1.1.1': ('GOST R 34.10-2012 (256 bit) ParamSet A', 'id-tc26-gost-3410-2012-256-paramSetA'), + '1.2.643.7.1.2.1.1.2': ('GOST R 34.10-2012 (256 bit) ParamSet B', 'id-tc26-gost-3410-2012-256-paramSetB'), + '1.2.643.7.1.2.1.1.3': ('GOST R 34.10-2012 (256 bit) ParamSet C', 'id-tc26-gost-3410-2012-256-paramSetC'), + '1.2.643.7.1.2.1.1.4': ('GOST R 34.10-2012 (256 bit) ParamSet D', 'id-tc26-gost-3410-2012-256-paramSetD'), + '1.2.643.7.1.2.1.2': ('id-tc26-gost-3410-2012-512-constants', ), + '1.2.643.7.1.2.1.2.0': ('GOST R 34.10-2012 (512 bit) testing parameter set', 'id-tc26-gost-3410-2012-512-paramSetTest'), + '1.2.643.7.1.2.1.2.1': ('GOST R 34.10-2012 (512 bit) ParamSet A', 'id-tc26-gost-3410-2012-512-paramSetA'), + '1.2.643.7.1.2.1.2.2': ('GOST R 34.10-2012 (512 bit) ParamSet B', 'id-tc26-gost-3410-2012-512-paramSetB'), + '1.2.643.7.1.2.1.2.3': ('GOST R 34.10-2012 (512 bit) ParamSet C', 'id-tc26-gost-3410-2012-512-paramSetC'), + '1.2.643.7.1.2.2': ('id-tc26-digest-constants', ), + '1.2.643.7.1.2.5': ('id-tc26-cipher-constants', ), + '1.2.643.7.1.2.5.1': ('id-tc26-gost-28147-constants', ), + '1.2.643.7.1.2.5.1.1': ('GOST 28147-89 TC26 parameter set', 'id-tc26-gost-28147-param-Z'), + '1.2.643.100.1': ('OGRN', 'OGRN'), + '1.2.643.100.3': ('SNILS', 'SNILS'), + '1.2.643.100.111': ('Signing Tool of Subject', 'subjectSignTool'), + '1.2.643.100.112': ('Signing Tool of Issuer', 'issuerSignTool'), + '1.2.804': ('ISO-UA', ), + '1.2.804.2.1.1.1': ('ua-pki', ), + '1.2.804.2.1.1.1.1.1.1': ('DSTU Gost 28147-2009', 'dstu28147'), + '1.2.804.2.1.1.1.1.1.1.2': ('DSTU Gost 28147-2009 OFB mode', 'dstu28147-ofb'), + '1.2.804.2.1.1.1.1.1.1.3': ('DSTU Gost 28147-2009 CFB mode', 'dstu28147-cfb'), + '1.2.804.2.1.1.1.1.1.1.5': ('DSTU Gost 28147-2009 key wrap', 'dstu28147-wrap'), + '1.2.804.2.1.1.1.1.1.2': ('HMAC DSTU Gost 34311-95', 'hmacWithDstu34311'), + '1.2.804.2.1.1.1.1.2.1': ('DSTU Gost 34311-95', 'dstu34311'), + '1.2.804.2.1.1.1.1.3.1.1': ('DSTU 4145-2002 little endian', 'dstu4145le'), + '1.2.804.2.1.1.1.1.3.1.1.1.1': ('DSTU 4145-2002 big endian', 'dstu4145be'), + '1.2.804.2.1.1.1.1.3.1.1.2.0': ('DSTU curve 0', 'uacurve0'), + '1.2.804.2.1.1.1.1.3.1.1.2.1': ('DSTU curve 1', 'uacurve1'), + '1.2.804.2.1.1.1.1.3.1.1.2.2': ('DSTU curve 2', 'uacurve2'), + '1.2.804.2.1.1.1.1.3.1.1.2.3': ('DSTU curve 3', 'uacurve3'), + '1.2.804.2.1.1.1.1.3.1.1.2.4': ('DSTU curve 4', 'uacurve4'), + '1.2.804.2.1.1.1.1.3.1.1.2.5': ('DSTU curve 5', 'uacurve5'), + '1.2.804.2.1.1.1.1.3.1.1.2.6': ('DSTU curve 6', 'uacurve6'), + '1.2.804.2.1.1.1.1.3.1.1.2.7': ('DSTU curve 7', 'uacurve7'), + '1.2.804.2.1.1.1.1.3.1.1.2.8': ('DSTU curve 8', 'uacurve8'), + '1.2.804.2.1.1.1.1.3.1.1.2.9': ('DSTU curve 9', 'uacurve9'), + '1.2.840': ('ISO US Member Body', 'ISO-US'), + '1.2.840.10040': ('X9.57', 'X9-57'), + '1.2.840.10040.2': ('holdInstruction', ), + '1.2.840.10040.2.1': ('Hold Instruction None', 'holdInstructionNone'), + '1.2.840.10040.2.2': ('Hold Instruction Call Issuer', 'holdInstructionCallIssuer'), + '1.2.840.10040.2.3': ('Hold Instruction Reject', 'holdInstructionReject'), + '1.2.840.10040.4': ('X9.57 CM ?', 'X9cm'), + '1.2.840.10040.4.1': ('dsaEncryption', 'DSA'), + '1.2.840.10040.4.3': ('dsaWithSHA1', 'DSA-SHA1'), + '1.2.840.10045': ('ANSI X9.62', 'ansi-X9-62'), + '1.2.840.10045.1': ('id-fieldType', ), + '1.2.840.10045.1.1': ('prime-field', ), + '1.2.840.10045.1.2': ('characteristic-two-field', ), + '1.2.840.10045.1.2.3': ('id-characteristic-two-basis', ), + '1.2.840.10045.1.2.3.1': ('onBasis', ), + '1.2.840.10045.1.2.3.2': ('tpBasis', ), + '1.2.840.10045.1.2.3.3': ('ppBasis', ), + '1.2.840.10045.2': ('id-publicKeyType', ), + '1.2.840.10045.2.1': ('id-ecPublicKey', ), + '1.2.840.10045.3': ('ellipticCurve', ), + '1.2.840.10045.3.0': ('c-TwoCurve', ), + '1.2.840.10045.3.0.1': ('c2pnb163v1', ), + '1.2.840.10045.3.0.2': ('c2pnb163v2', ), + '1.2.840.10045.3.0.3': ('c2pnb163v3', ), + '1.2.840.10045.3.0.4': ('c2pnb176v1', ), + '1.2.840.10045.3.0.5': ('c2tnb191v1', ), + '1.2.840.10045.3.0.6': ('c2tnb191v2', ), + '1.2.840.10045.3.0.7': ('c2tnb191v3', ), + '1.2.840.10045.3.0.8': ('c2onb191v4', ), + '1.2.840.10045.3.0.9': ('c2onb191v5', ), + '1.2.840.10045.3.0.10': ('c2pnb208w1', ), + '1.2.840.10045.3.0.11': ('c2tnb239v1', ), + '1.2.840.10045.3.0.12': ('c2tnb239v2', ), + '1.2.840.10045.3.0.13': ('c2tnb239v3', ), + '1.2.840.10045.3.0.14': ('c2onb239v4', ), + '1.2.840.10045.3.0.15': ('c2onb239v5', ), + '1.2.840.10045.3.0.16': ('c2pnb272w1', ), + '1.2.840.10045.3.0.17': ('c2pnb304w1', ), + '1.2.840.10045.3.0.18': ('c2tnb359v1', ), + '1.2.840.10045.3.0.19': ('c2pnb368w1', ), + '1.2.840.10045.3.0.20': ('c2tnb431r1', ), + '1.2.840.10045.3.1': ('primeCurve', ), + '1.2.840.10045.3.1.1': ('prime192v1', ), + '1.2.840.10045.3.1.2': ('prime192v2', ), + '1.2.840.10045.3.1.3': ('prime192v3', ), + '1.2.840.10045.3.1.4': ('prime239v1', ), + '1.2.840.10045.3.1.5': ('prime239v2', ), + '1.2.840.10045.3.1.6': ('prime239v3', ), + '1.2.840.10045.3.1.7': ('prime256v1', ), + '1.2.840.10045.4': ('id-ecSigType', ), + '1.2.840.10045.4.1': ('ecdsa-with-SHA1', ), + '1.2.840.10045.4.2': ('ecdsa-with-Recommended', ), + '1.2.840.10045.4.3': ('ecdsa-with-Specified', ), + '1.2.840.10045.4.3.1': ('ecdsa-with-SHA224', ), + '1.2.840.10045.4.3.2': ('ecdsa-with-SHA256', ), + '1.2.840.10045.4.3.3': ('ecdsa-with-SHA384', ), + '1.2.840.10045.4.3.4': ('ecdsa-with-SHA512', ), + '1.2.840.10046.2.1': ('X9.42 DH', 'dhpublicnumber'), + '1.2.840.113533.7.66.10': ('cast5-cbc', 'CAST5-CBC'), + '1.2.840.113533.7.66.12': ('pbeWithMD5AndCast5CBC', ), + '1.2.840.113533.7.66.13': ('password based MAC', 'id-PasswordBasedMAC'), + '1.2.840.113533.7.66.30': ('Diffie-Hellman based MAC', 'id-DHBasedMac'), + '1.2.840.113549': ('RSA Data Security, Inc.', 'rsadsi'), + '1.2.840.113549.1': ('RSA Data Security, Inc. PKCS', 'pkcs'), + '1.2.840.113549.1.1': ('pkcs1', ), + '1.2.840.113549.1.1.1': ('rsaEncryption', ), + '1.2.840.113549.1.1.2': ('md2WithRSAEncryption', 'RSA-MD2'), + '1.2.840.113549.1.1.3': ('md4WithRSAEncryption', 'RSA-MD4'), + '1.2.840.113549.1.1.4': ('md5WithRSAEncryption', 'RSA-MD5'), + '1.2.840.113549.1.1.5': ('sha1WithRSAEncryption', 'RSA-SHA1'), + '1.2.840.113549.1.1.6': ('rsaOAEPEncryptionSET', ), + '1.2.840.113549.1.1.7': ('rsaesOaep', 'RSAES-OAEP'), + '1.2.840.113549.1.1.8': ('mgf1', 'MGF1'), + '1.2.840.113549.1.1.9': ('pSpecified', 'PSPECIFIED'), + '1.2.840.113549.1.1.10': ('rsassaPss', 'RSASSA-PSS'), + '1.2.840.113549.1.1.11': ('sha256WithRSAEncryption', 'RSA-SHA256'), + '1.2.840.113549.1.1.12': ('sha384WithRSAEncryption', 'RSA-SHA384'), + '1.2.840.113549.1.1.13': ('sha512WithRSAEncryption', 'RSA-SHA512'), + '1.2.840.113549.1.1.14': ('sha224WithRSAEncryption', 'RSA-SHA224'), + '1.2.840.113549.1.1.15': ('sha512-224WithRSAEncryption', 'RSA-SHA512/224'), + '1.2.840.113549.1.1.16': ('sha512-256WithRSAEncryption', 'RSA-SHA512/256'), + '1.2.840.113549.1.3': ('pkcs3', ), + '1.2.840.113549.1.3.1': ('dhKeyAgreement', ), + '1.2.840.113549.1.5': ('pkcs5', ), + '1.2.840.113549.1.5.1': ('pbeWithMD2AndDES-CBC', 'PBE-MD2-DES'), + '1.2.840.113549.1.5.3': ('pbeWithMD5AndDES-CBC', 'PBE-MD5-DES'), + '1.2.840.113549.1.5.4': ('pbeWithMD2AndRC2-CBC', 'PBE-MD2-RC2-64'), + '1.2.840.113549.1.5.6': ('pbeWithMD5AndRC2-CBC', 'PBE-MD5-RC2-64'), + '1.2.840.113549.1.5.10': ('pbeWithSHA1AndDES-CBC', 'PBE-SHA1-DES'), + '1.2.840.113549.1.5.11': ('pbeWithSHA1AndRC2-CBC', 'PBE-SHA1-RC2-64'), + '1.2.840.113549.1.5.12': ('PBKDF2', ), + '1.2.840.113549.1.5.13': ('PBES2', ), + '1.2.840.113549.1.5.14': ('PBMAC1', ), + '1.2.840.113549.1.7': ('pkcs7', ), + '1.2.840.113549.1.7.1': ('pkcs7-data', ), + '1.2.840.113549.1.7.2': ('pkcs7-signedData', ), + '1.2.840.113549.1.7.3': ('pkcs7-envelopedData', ), + '1.2.840.113549.1.7.4': ('pkcs7-signedAndEnvelopedData', ), + '1.2.840.113549.1.7.5': ('pkcs7-digestData', ), + '1.2.840.113549.1.7.6': ('pkcs7-encryptedData', ), + '1.2.840.113549.1.9': ('pkcs9', ), + '1.2.840.113549.1.9.1': ('emailAddress', ), + '1.2.840.113549.1.9.2': ('unstructuredName', ), + '1.2.840.113549.1.9.3': ('contentType', ), + '1.2.840.113549.1.9.4': ('messageDigest', ), + '1.2.840.113549.1.9.5': ('signingTime', ), + '1.2.840.113549.1.9.6': ('countersignature', ), + '1.2.840.113549.1.9.7': ('challengePassword', ), + '1.2.840.113549.1.9.8': ('unstructuredAddress', ), + '1.2.840.113549.1.9.9': ('extendedCertificateAttributes', ), + '1.2.840.113549.1.9.14': ('Extension Request', 'extReq'), + '1.2.840.113549.1.9.15': ('S/MIME Capabilities', 'SMIME-CAPS'), + '1.2.840.113549.1.9.16': ('S/MIME', 'SMIME'), + '1.2.840.113549.1.9.16.0': ('id-smime-mod', ), + '1.2.840.113549.1.9.16.0.1': ('id-smime-mod-cms', ), + '1.2.840.113549.1.9.16.0.2': ('id-smime-mod-ess', ), + '1.2.840.113549.1.9.16.0.3': ('id-smime-mod-oid', ), + '1.2.840.113549.1.9.16.0.4': ('id-smime-mod-msg-v3', ), + '1.2.840.113549.1.9.16.0.5': ('id-smime-mod-ets-eSignature-88', ), + '1.2.840.113549.1.9.16.0.6': ('id-smime-mod-ets-eSignature-97', ), + '1.2.840.113549.1.9.16.0.7': ('id-smime-mod-ets-eSigPolicy-88', ), + '1.2.840.113549.1.9.16.0.8': ('id-smime-mod-ets-eSigPolicy-97', ), + '1.2.840.113549.1.9.16.1': ('id-smime-ct', ), + '1.2.840.113549.1.9.16.1.1': ('id-smime-ct-receipt', ), + '1.2.840.113549.1.9.16.1.2': ('id-smime-ct-authData', ), + '1.2.840.113549.1.9.16.1.3': ('id-smime-ct-publishCert', ), + '1.2.840.113549.1.9.16.1.4': ('id-smime-ct-TSTInfo', ), + '1.2.840.113549.1.9.16.1.5': ('id-smime-ct-TDTInfo', ), + '1.2.840.113549.1.9.16.1.6': ('id-smime-ct-contentInfo', ), + '1.2.840.113549.1.9.16.1.7': ('id-smime-ct-DVCSRequestData', ), + '1.2.840.113549.1.9.16.1.8': ('id-smime-ct-DVCSResponseData', ), + '1.2.840.113549.1.9.16.1.9': ('id-smime-ct-compressedData', ), + '1.2.840.113549.1.9.16.1.19': ('id-smime-ct-contentCollection', ), + '1.2.840.113549.1.9.16.1.23': ('id-smime-ct-authEnvelopedData', ), + '1.2.840.113549.1.9.16.1.27': ('id-ct-asciiTextWithCRLF', ), + '1.2.840.113549.1.9.16.1.28': ('id-ct-xml', ), + '1.2.840.113549.1.9.16.2': ('id-smime-aa', ), + '1.2.840.113549.1.9.16.2.1': ('id-smime-aa-receiptRequest', ), + '1.2.840.113549.1.9.16.2.2': ('id-smime-aa-securityLabel', ), + '1.2.840.113549.1.9.16.2.3': ('id-smime-aa-mlExpandHistory', ), + '1.2.840.113549.1.9.16.2.4': ('id-smime-aa-contentHint', ), + '1.2.840.113549.1.9.16.2.5': ('id-smime-aa-msgSigDigest', ), + '1.2.840.113549.1.9.16.2.6': ('id-smime-aa-encapContentType', ), + '1.2.840.113549.1.9.16.2.7': ('id-smime-aa-contentIdentifier', ), + '1.2.840.113549.1.9.16.2.8': ('id-smime-aa-macValue', ), + '1.2.840.113549.1.9.16.2.9': ('id-smime-aa-equivalentLabels', ), + '1.2.840.113549.1.9.16.2.10': ('id-smime-aa-contentReference', ), + '1.2.840.113549.1.9.16.2.11': ('id-smime-aa-encrypKeyPref', ), + '1.2.840.113549.1.9.16.2.12': ('id-smime-aa-signingCertificate', ), + '1.2.840.113549.1.9.16.2.13': ('id-smime-aa-smimeEncryptCerts', ), + '1.2.840.113549.1.9.16.2.14': ('id-smime-aa-timeStampToken', ), + '1.2.840.113549.1.9.16.2.15': ('id-smime-aa-ets-sigPolicyId', ), + '1.2.840.113549.1.9.16.2.16': ('id-smime-aa-ets-commitmentType', ), + '1.2.840.113549.1.9.16.2.17': ('id-smime-aa-ets-signerLocation', ), + '1.2.840.113549.1.9.16.2.18': ('id-smime-aa-ets-signerAttr', ), + '1.2.840.113549.1.9.16.2.19': ('id-smime-aa-ets-otherSigCert', ), + '1.2.840.113549.1.9.16.2.20': ('id-smime-aa-ets-contentTimestamp', ), + '1.2.840.113549.1.9.16.2.21': ('id-smime-aa-ets-CertificateRefs', ), + '1.2.840.113549.1.9.16.2.22': ('id-smime-aa-ets-RevocationRefs', ), + '1.2.840.113549.1.9.16.2.23': ('id-smime-aa-ets-certValues', ), + '1.2.840.113549.1.9.16.2.24': ('id-smime-aa-ets-revocationValues', ), + '1.2.840.113549.1.9.16.2.25': ('id-smime-aa-ets-escTimeStamp', ), + '1.2.840.113549.1.9.16.2.26': ('id-smime-aa-ets-certCRLTimestamp', ), + '1.2.840.113549.1.9.16.2.27': ('id-smime-aa-ets-archiveTimeStamp', ), + '1.2.840.113549.1.9.16.2.28': ('id-smime-aa-signatureType', ), + '1.2.840.113549.1.9.16.2.29': ('id-smime-aa-dvcs-dvc', ), + '1.2.840.113549.1.9.16.2.47': ('id-smime-aa-signingCertificateV2', ), + '1.2.840.113549.1.9.16.3': ('id-smime-alg', ), + '1.2.840.113549.1.9.16.3.1': ('id-smime-alg-ESDHwith3DES', ), + '1.2.840.113549.1.9.16.3.2': ('id-smime-alg-ESDHwithRC2', ), + '1.2.840.113549.1.9.16.3.3': ('id-smime-alg-3DESwrap', ), + '1.2.840.113549.1.9.16.3.4': ('id-smime-alg-RC2wrap', ), + '1.2.840.113549.1.9.16.3.5': ('id-smime-alg-ESDH', ), + '1.2.840.113549.1.9.16.3.6': ('id-smime-alg-CMS3DESwrap', ), + '1.2.840.113549.1.9.16.3.7': ('id-smime-alg-CMSRC2wrap', ), + '1.2.840.113549.1.9.16.3.8': ('zlib compression', 'ZLIB'), + '1.2.840.113549.1.9.16.3.9': ('id-alg-PWRI-KEK', ), + '1.2.840.113549.1.9.16.4': ('id-smime-cd', ), + '1.2.840.113549.1.9.16.4.1': ('id-smime-cd-ldap', ), + '1.2.840.113549.1.9.16.5': ('id-smime-spq', ), + '1.2.840.113549.1.9.16.5.1': ('id-smime-spq-ets-sqt-uri', ), + '1.2.840.113549.1.9.16.5.2': ('id-smime-spq-ets-sqt-unotice', ), + '1.2.840.113549.1.9.16.6': ('id-smime-cti', ), + '1.2.840.113549.1.9.16.6.1': ('id-smime-cti-ets-proofOfOrigin', ), + '1.2.840.113549.1.9.16.6.2': ('id-smime-cti-ets-proofOfReceipt', ), + '1.2.840.113549.1.9.16.6.3': ('id-smime-cti-ets-proofOfDelivery', ), + '1.2.840.113549.1.9.16.6.4': ('id-smime-cti-ets-proofOfSender', ), + '1.2.840.113549.1.9.16.6.5': ('id-smime-cti-ets-proofOfApproval', ), + '1.2.840.113549.1.9.16.6.6': ('id-smime-cti-ets-proofOfCreation', ), + '1.2.840.113549.1.9.20': ('friendlyName', ), + '1.2.840.113549.1.9.21': ('localKeyID', ), + '1.2.840.113549.1.9.22': ('certTypes', ), + '1.2.840.113549.1.9.22.1': ('x509Certificate', ), + '1.2.840.113549.1.9.22.2': ('sdsiCertificate', ), + '1.2.840.113549.1.9.23': ('crlTypes', ), + '1.2.840.113549.1.9.23.1': ('x509Crl', ), + '1.2.840.113549.1.12': ('pkcs12', ), + '1.2.840.113549.1.12.1': ('pkcs12-pbeids', ), + '1.2.840.113549.1.12.1.1': ('pbeWithSHA1And128BitRC4', 'PBE-SHA1-RC4-128'), + '1.2.840.113549.1.12.1.2': ('pbeWithSHA1And40BitRC4', 'PBE-SHA1-RC4-40'), + '1.2.840.113549.1.12.1.3': ('pbeWithSHA1And3-KeyTripleDES-CBC', 'PBE-SHA1-3DES'), + '1.2.840.113549.1.12.1.4': ('pbeWithSHA1And2-KeyTripleDES-CBC', 'PBE-SHA1-2DES'), + '1.2.840.113549.1.12.1.5': ('pbeWithSHA1And128BitRC2-CBC', 'PBE-SHA1-RC2-128'), + '1.2.840.113549.1.12.1.6': ('pbeWithSHA1And40BitRC2-CBC', 'PBE-SHA1-RC2-40'), + '1.2.840.113549.1.12.10': ('pkcs12-Version1', ), + '1.2.840.113549.1.12.10.1': ('pkcs12-BagIds', ), + '1.2.840.113549.1.12.10.1.1': ('keyBag', ), + '1.2.840.113549.1.12.10.1.2': ('pkcs8ShroudedKeyBag', ), + '1.2.840.113549.1.12.10.1.3': ('certBag', ), + '1.2.840.113549.1.12.10.1.4': ('crlBag', ), + '1.2.840.113549.1.12.10.1.5': ('secretBag', ), + '1.2.840.113549.1.12.10.1.6': ('safeContentsBag', ), + '1.2.840.113549.2.2': ('md2', 'MD2'), + '1.2.840.113549.2.4': ('md4', 'MD4'), + '1.2.840.113549.2.5': ('md5', 'MD5'), + '1.2.840.113549.2.6': ('hmacWithMD5', ), + '1.2.840.113549.2.7': ('hmacWithSHA1', ), + '1.2.840.113549.2.8': ('hmacWithSHA224', ), + '1.2.840.113549.2.9': ('hmacWithSHA256', ), + '1.2.840.113549.2.10': ('hmacWithSHA384', ), + '1.2.840.113549.2.11': ('hmacWithSHA512', ), + '1.2.840.113549.2.12': ('hmacWithSHA512-224', ), + '1.2.840.113549.2.13': ('hmacWithSHA512-256', ), + '1.2.840.113549.3.2': ('rc2-cbc', 'RC2-CBC'), + '1.2.840.113549.3.4': ('rc4', 'RC4'), + '1.2.840.113549.3.7': ('des-ede3-cbc', 'DES-EDE3-CBC'), + '1.2.840.113549.3.8': ('rc5-cbc', 'RC5-CBC'), + '1.2.840.113549.3.10': ('des-cdmf', 'DES-CDMF'), + '1.3': ('identified-organization', 'org', 'ORG'), + '1.3.6': ('dod', 'DOD'), + '1.3.6.1': ('iana', 'IANA', 'internet'), + '1.3.6.1.1': ('Directory', 'directory'), + '1.3.6.1.2': ('Management', 'mgmt'), + '1.3.6.1.3': ('Experimental', 'experimental'), + '1.3.6.1.4': ('Private', 'private'), + '1.3.6.1.4.1': ('Enterprises', 'enterprises'), + '1.3.6.1.4.1.188.7.1.1.2': ('idea-cbc', 'IDEA-CBC'), + '1.3.6.1.4.1.311.2.1.14': ('Microsoft Extension Request', 'msExtReq'), + '1.3.6.1.4.1.311.2.1.21': ('Microsoft Individual Code Signing', 'msCodeInd'), + '1.3.6.1.4.1.311.2.1.22': ('Microsoft Commercial Code Signing', 'msCodeCom'), + '1.3.6.1.4.1.311.10.3.1': ('Microsoft Trust List Signing', 'msCTLSign'), + '1.3.6.1.4.1.311.10.3.3': ('Microsoft Server Gated Crypto', 'msSGC'), + '1.3.6.1.4.1.311.10.3.4': ('Microsoft Encrypted File System', 'msEFS'), + '1.3.6.1.4.1.311.17.1': ('Microsoft CSP Name', 'CSPName'), + '1.3.6.1.4.1.311.17.2': ('Microsoft Local Key set', 'LocalKeySet'), + '1.3.6.1.4.1.311.20.2.2': ('Microsoft Smartcardlogin', 'msSmartcardLogin'), + '1.3.6.1.4.1.311.20.2.3': ('Microsoft Universal Principal Name', 'msUPN'), + '1.3.6.1.4.1.311.60.2.1.1': ('jurisdictionLocalityName', 'jurisdictionL'), + '1.3.6.1.4.1.311.60.2.1.2': ('jurisdictionStateOrProvinceName', 'jurisdictionST'), + '1.3.6.1.4.1.311.60.2.1.3': ('jurisdictionCountryName', 'jurisdictionC'), + '1.3.6.1.4.1.1466.344': ('dcObject', 'dcobject'), + '1.3.6.1.4.1.1722.12.2.1.16': ('blake2b512', 'BLAKE2b512'), + '1.3.6.1.4.1.1722.12.2.2.8': ('blake2s256', 'BLAKE2s256'), + '1.3.6.1.4.1.3029.1.2': ('bf-cbc', 'BF-CBC'), + '1.3.6.1.4.1.11129.2.4.2': ('CT Precertificate SCTs', 'ct_precert_scts'), + '1.3.6.1.4.1.11129.2.4.3': ('CT Precertificate Poison', 'ct_precert_poison'), + '1.3.6.1.4.1.11129.2.4.4': ('CT Precertificate Signer', 'ct_precert_signer'), + '1.3.6.1.4.1.11129.2.4.5': ('CT Certificate SCTs', 'ct_cert_scts'), + '1.3.6.1.4.1.11591.4.11': ('scrypt', 'id-scrypt'), + '1.3.6.1.5': ('Security', 'security'), + '1.3.6.1.5.2.3': ('id-pkinit', ), + '1.3.6.1.5.2.3.4': ('PKINIT Client Auth', 'pkInitClientAuth'), + '1.3.6.1.5.2.3.5': ('Signing KDC Response', 'pkInitKDC'), + '1.3.6.1.5.5.7': ('PKIX', ), + '1.3.6.1.5.5.7.0': ('id-pkix-mod', ), + '1.3.6.1.5.5.7.0.1': ('id-pkix1-explicit-88', ), + '1.3.6.1.5.5.7.0.2': ('id-pkix1-implicit-88', ), + '1.3.6.1.5.5.7.0.3': ('id-pkix1-explicit-93', ), + '1.3.6.1.5.5.7.0.4': ('id-pkix1-implicit-93', ), + '1.3.6.1.5.5.7.0.5': ('id-mod-crmf', ), + '1.3.6.1.5.5.7.0.6': ('id-mod-cmc', ), + '1.3.6.1.5.5.7.0.7': ('id-mod-kea-profile-88', ), + '1.3.6.1.5.5.7.0.8': ('id-mod-kea-profile-93', ), + '1.3.6.1.5.5.7.0.9': ('id-mod-cmp', ), + '1.3.6.1.5.5.7.0.10': ('id-mod-qualified-cert-88', ), + '1.3.6.1.5.5.7.0.11': ('id-mod-qualified-cert-93', ), + '1.3.6.1.5.5.7.0.12': ('id-mod-attribute-cert', ), + '1.3.6.1.5.5.7.0.13': ('id-mod-timestamp-protocol', ), + '1.3.6.1.5.5.7.0.14': ('id-mod-ocsp', ), + '1.3.6.1.5.5.7.0.15': ('id-mod-dvcs', ), + '1.3.6.1.5.5.7.0.16': ('id-mod-cmp2000', ), + '1.3.6.1.5.5.7.1': ('id-pe', ), + '1.3.6.1.5.5.7.1.1': ('Authority Information Access', 'authorityInfoAccess'), + '1.3.6.1.5.5.7.1.2': ('Biometric Info', 'biometricInfo'), + '1.3.6.1.5.5.7.1.3': ('qcStatements', ), + '1.3.6.1.5.5.7.1.4': ('ac-auditEntity', ), + '1.3.6.1.5.5.7.1.5': ('ac-targeting', ), + '1.3.6.1.5.5.7.1.6': ('aaControls', ), + '1.3.6.1.5.5.7.1.7': ('sbgp-ipAddrBlock', ), + '1.3.6.1.5.5.7.1.8': ('sbgp-autonomousSysNum', ), + '1.3.6.1.5.5.7.1.9': ('sbgp-routerIdentifier', ), + '1.3.6.1.5.5.7.1.10': ('ac-proxying', ), + '1.3.6.1.5.5.7.1.11': ('Subject Information Access', 'subjectInfoAccess'), + '1.3.6.1.5.5.7.1.14': ('Proxy Certificate Information', 'proxyCertInfo'), + '1.3.6.1.5.5.7.1.24': ('TLS Feature', 'tlsfeature'), + '1.3.6.1.5.5.7.2': ('id-qt', ), + '1.3.6.1.5.5.7.2.1': ('Policy Qualifier CPS', 'id-qt-cps'), + '1.3.6.1.5.5.7.2.2': ('Policy Qualifier User Notice', 'id-qt-unotice'), + '1.3.6.1.5.5.7.2.3': ('textNotice', ), + '1.3.6.1.5.5.7.3': ('id-kp', ), + '1.3.6.1.5.5.7.3.1': ('TLS Web Server Authentication', 'serverAuth'), + '1.3.6.1.5.5.7.3.2': ('TLS Web Client Authentication', 'clientAuth'), + '1.3.6.1.5.5.7.3.3': ('Code Signing', 'codeSigning'), + '1.3.6.1.5.5.7.3.4': ('E-mail Protection', 'emailProtection'), + '1.3.6.1.5.5.7.3.5': ('IPSec End System', 'ipsecEndSystem'), + '1.3.6.1.5.5.7.3.6': ('IPSec Tunnel', 'ipsecTunnel'), + '1.3.6.1.5.5.7.3.7': ('IPSec User', 'ipsecUser'), + '1.3.6.1.5.5.7.3.8': ('Time Stamping', 'timeStamping'), + '1.3.6.1.5.5.7.3.9': ('OCSP Signing', 'OCSPSigning'), + '1.3.6.1.5.5.7.3.10': ('dvcs', 'DVCS'), + '1.3.6.1.5.5.7.3.17': ('ipsec Internet Key Exchange', 'ipsecIKE'), + '1.3.6.1.5.5.7.3.18': ('Ctrl/provision WAP Access', 'capwapAC'), + '1.3.6.1.5.5.7.3.19': ('Ctrl/Provision WAP Termination', 'capwapWTP'), + '1.3.6.1.5.5.7.3.21': ('SSH Client', 'secureShellClient'), + '1.3.6.1.5.5.7.3.22': ('SSH Server', 'secureShellServer'), + '1.3.6.1.5.5.7.3.23': ('Send Router', 'sendRouter'), + '1.3.6.1.5.5.7.3.24': ('Send Proxied Router', 'sendProxiedRouter'), + '1.3.6.1.5.5.7.3.25': ('Send Owner', 'sendOwner'), + '1.3.6.1.5.5.7.3.26': ('Send Proxied Owner', 'sendProxiedOwner'), + '1.3.6.1.5.5.7.3.27': ('CMC Certificate Authority', 'cmcCA'), + '1.3.6.1.5.5.7.3.28': ('CMC Registration Authority', 'cmcRA'), + '1.3.6.1.5.5.7.4': ('id-it', ), + '1.3.6.1.5.5.7.4.1': ('id-it-caProtEncCert', ), + '1.3.6.1.5.5.7.4.2': ('id-it-signKeyPairTypes', ), + '1.3.6.1.5.5.7.4.3': ('id-it-encKeyPairTypes', ), + '1.3.6.1.5.5.7.4.4': ('id-it-preferredSymmAlg', ), + '1.3.6.1.5.5.7.4.5': ('id-it-caKeyUpdateInfo', ), + '1.3.6.1.5.5.7.4.6': ('id-it-currentCRL', ), + '1.3.6.1.5.5.7.4.7': ('id-it-unsupportedOIDs', ), + '1.3.6.1.5.5.7.4.8': ('id-it-subscriptionRequest', ), + '1.3.6.1.5.5.7.4.9': ('id-it-subscriptionResponse', ), + '1.3.6.1.5.5.7.4.10': ('id-it-keyPairParamReq', ), + '1.3.6.1.5.5.7.4.11': ('id-it-keyPairParamRep', ), + '1.3.6.1.5.5.7.4.12': ('id-it-revPassphrase', ), + '1.3.6.1.5.5.7.4.13': ('id-it-implicitConfirm', ), + '1.3.6.1.5.5.7.4.14': ('id-it-confirmWaitTime', ), + '1.3.6.1.5.5.7.4.15': ('id-it-origPKIMessage', ), + '1.3.6.1.5.5.7.4.16': ('id-it-suppLangTags', ), + '1.3.6.1.5.5.7.5': ('id-pkip', ), + '1.3.6.1.5.5.7.5.1': ('id-regCtrl', ), + '1.3.6.1.5.5.7.5.1.1': ('id-regCtrl-regToken', ), + '1.3.6.1.5.5.7.5.1.2': ('id-regCtrl-authenticator', ), + '1.3.6.1.5.5.7.5.1.3': ('id-regCtrl-pkiPublicationInfo', ), + '1.3.6.1.5.5.7.5.1.4': ('id-regCtrl-pkiArchiveOptions', ), + '1.3.6.1.5.5.7.5.1.5': ('id-regCtrl-oldCertID', ), + '1.3.6.1.5.5.7.5.1.6': ('id-regCtrl-protocolEncrKey', ), + '1.3.6.1.5.5.7.5.2': ('id-regInfo', ), + '1.3.6.1.5.5.7.5.2.1': ('id-regInfo-utf8Pairs', ), + '1.3.6.1.5.5.7.5.2.2': ('id-regInfo-certReq', ), + '1.3.6.1.5.5.7.6': ('id-alg', ), + '1.3.6.1.5.5.7.6.1': ('id-alg-des40', ), + '1.3.6.1.5.5.7.6.2': ('id-alg-noSignature', ), + '1.3.6.1.5.5.7.6.3': ('id-alg-dh-sig-hmac-sha1', ), + '1.3.6.1.5.5.7.6.4': ('id-alg-dh-pop', ), + '1.3.6.1.5.5.7.7': ('id-cmc', ), + '1.3.6.1.5.5.7.7.1': ('id-cmc-statusInfo', ), + '1.3.6.1.5.5.7.7.2': ('id-cmc-identification', ), + '1.3.6.1.5.5.7.7.3': ('id-cmc-identityProof', ), + '1.3.6.1.5.5.7.7.4': ('id-cmc-dataReturn', ), + '1.3.6.1.5.5.7.7.5': ('id-cmc-transactionId', ), + '1.3.6.1.5.5.7.7.6': ('id-cmc-senderNonce', ), + '1.3.6.1.5.5.7.7.7': ('id-cmc-recipientNonce', ), + '1.3.6.1.5.5.7.7.8': ('id-cmc-addExtensions', ), + '1.3.6.1.5.5.7.7.9': ('id-cmc-encryptedPOP', ), + '1.3.6.1.5.5.7.7.10': ('id-cmc-decryptedPOP', ), + '1.3.6.1.5.5.7.7.11': ('id-cmc-lraPOPWitness', ), + '1.3.6.1.5.5.7.7.15': ('id-cmc-getCert', ), + '1.3.6.1.5.5.7.7.16': ('id-cmc-getCRL', ), + '1.3.6.1.5.5.7.7.17': ('id-cmc-revokeRequest', ), + '1.3.6.1.5.5.7.7.18': ('id-cmc-regInfo', ), + '1.3.6.1.5.5.7.7.19': ('id-cmc-responseInfo', ), + '1.3.6.1.5.5.7.7.21': ('id-cmc-queryPending', ), + '1.3.6.1.5.5.7.7.22': ('id-cmc-popLinkRandom', ), + '1.3.6.1.5.5.7.7.23': ('id-cmc-popLinkWitness', ), + '1.3.6.1.5.5.7.7.24': ('id-cmc-confirmCertAcceptance', ), + '1.3.6.1.5.5.7.8': ('id-on', ), + '1.3.6.1.5.5.7.8.1': ('id-on-personalData', ), + '1.3.6.1.5.5.7.8.3': ('Permanent Identifier', 'id-on-permanentIdentifier'), + '1.3.6.1.5.5.7.9': ('id-pda', ), + '1.3.6.1.5.5.7.9.1': ('id-pda-dateOfBirth', ), + '1.3.6.1.5.5.7.9.2': ('id-pda-placeOfBirth', ), + '1.3.6.1.5.5.7.9.3': ('id-pda-gender', ), + '1.3.6.1.5.5.7.9.4': ('id-pda-countryOfCitizenship', ), + '1.3.6.1.5.5.7.9.5': ('id-pda-countryOfResidence', ), + '1.3.6.1.5.5.7.10': ('id-aca', ), + '1.3.6.1.5.5.7.10.1': ('id-aca-authenticationInfo', ), + '1.3.6.1.5.5.7.10.2': ('id-aca-accessIdentity', ), + '1.3.6.1.5.5.7.10.3': ('id-aca-chargingIdentity', ), + '1.3.6.1.5.5.7.10.4': ('id-aca-group', ), + '1.3.6.1.5.5.7.10.5': ('id-aca-role', ), + '1.3.6.1.5.5.7.10.6': ('id-aca-encAttrs', ), + '1.3.6.1.5.5.7.11': ('id-qcs', ), + '1.3.6.1.5.5.7.11.1': ('id-qcs-pkixQCSyntax-v1', ), + '1.3.6.1.5.5.7.12': ('id-cct', ), + '1.3.6.1.5.5.7.12.1': ('id-cct-crs', ), + '1.3.6.1.5.5.7.12.2': ('id-cct-PKIData', ), + '1.3.6.1.5.5.7.12.3': ('id-cct-PKIResponse', ), + '1.3.6.1.5.5.7.21': ('id-ppl', ), + '1.3.6.1.5.5.7.21.0': ('Any language', 'id-ppl-anyLanguage'), + '1.3.6.1.5.5.7.21.1': ('Inherit all', 'id-ppl-inheritAll'), + '1.3.6.1.5.5.7.21.2': ('Independent', 'id-ppl-independent'), + '1.3.6.1.5.5.7.48': ('id-ad', ), + '1.3.6.1.5.5.7.48.1': ('OCSP', 'OCSP', 'id-pkix-OCSP'), + '1.3.6.1.5.5.7.48.1.1': ('Basic OCSP Response', 'basicOCSPResponse'), + '1.3.6.1.5.5.7.48.1.2': ('OCSP Nonce', 'Nonce'), + '1.3.6.1.5.5.7.48.1.3': ('OCSP CRL ID', 'CrlID'), + '1.3.6.1.5.5.7.48.1.4': ('Acceptable OCSP Responses', 'acceptableResponses'), + '1.3.6.1.5.5.7.48.1.5': ('OCSP No Check', 'noCheck'), + '1.3.6.1.5.5.7.48.1.6': ('OCSP Archive Cutoff', 'archiveCutoff'), + '1.3.6.1.5.5.7.48.1.7': ('OCSP Service Locator', 'serviceLocator'), + '1.3.6.1.5.5.7.48.1.8': ('Extended OCSP Status', 'extendedStatus'), + '1.3.6.1.5.5.7.48.1.9': ('valid', ), + '1.3.6.1.5.5.7.48.1.10': ('path', ), + '1.3.6.1.5.5.7.48.1.11': ('Trust Root', 'trustRoot'), + '1.3.6.1.5.5.7.48.2': ('CA Issuers', 'caIssuers'), + '1.3.6.1.5.5.7.48.3': ('AD Time Stamping', 'ad_timestamping'), + '1.3.6.1.5.5.7.48.4': ('ad dvcs', 'AD_DVCS'), + '1.3.6.1.5.5.7.48.5': ('CA Repository', 'caRepository'), + '1.3.6.1.5.5.8.1.1': ('hmac-md5', 'HMAC-MD5'), + '1.3.6.1.5.5.8.1.2': ('hmac-sha1', 'HMAC-SHA1'), + '1.3.6.1.6': ('SNMPv2', 'snmpv2'), + '1.3.6.1.7': ('Mail', ), + '1.3.6.1.7.1': ('MIME MHS', 'mime-mhs'), + '1.3.6.1.7.1.1': ('mime-mhs-headings', 'mime-mhs-headings'), + '1.3.6.1.7.1.1.1': ('id-hex-partial-message', 'id-hex-partial-message'), + '1.3.6.1.7.1.1.2': ('id-hex-multipart-message', 'id-hex-multipart-message'), + '1.3.6.1.7.1.2': ('mime-mhs-bodies', 'mime-mhs-bodies'), + '1.3.14.3.2': ('algorithm', 'algorithm'), + '1.3.14.3.2.3': ('md5WithRSA', 'RSA-NP-MD5'), + '1.3.14.3.2.6': ('des-ecb', 'DES-ECB'), + '1.3.14.3.2.7': ('des-cbc', 'DES-CBC'), + '1.3.14.3.2.8': ('des-ofb', 'DES-OFB'), + '1.3.14.3.2.9': ('des-cfb', 'DES-CFB'), + '1.3.14.3.2.11': ('rsaSignature', ), + '1.3.14.3.2.12': ('dsaEncryption-old', 'DSA-old'), + '1.3.14.3.2.13': ('dsaWithSHA', 'DSA-SHA'), + '1.3.14.3.2.15': ('shaWithRSAEncryption', 'RSA-SHA'), + '1.3.14.3.2.17': ('des-ede', 'DES-EDE'), + '1.3.14.3.2.18': ('sha', 'SHA'), + '1.3.14.3.2.26': ('sha1', 'SHA1'), + '1.3.14.3.2.27': ('dsaWithSHA1-old', 'DSA-SHA1-old'), + '1.3.14.3.2.29': ('sha1WithRSA', 'RSA-SHA1-2'), + '1.3.36.3.2.1': ('ripemd160', 'RIPEMD160'), + '1.3.36.3.3.1.2': ('ripemd160WithRSA', 'RSA-RIPEMD160'), + '1.3.36.3.3.2.8.1.1.1': ('brainpoolP160r1', ), + '1.3.36.3.3.2.8.1.1.2': ('brainpoolP160t1', ), + '1.3.36.3.3.2.8.1.1.3': ('brainpoolP192r1', ), + '1.3.36.3.3.2.8.1.1.4': ('brainpoolP192t1', ), + '1.3.36.3.3.2.8.1.1.5': ('brainpoolP224r1', ), + '1.3.36.3.3.2.8.1.1.6': ('brainpoolP224t1', ), + '1.3.36.3.3.2.8.1.1.7': ('brainpoolP256r1', ), + '1.3.36.3.3.2.8.1.1.8': ('brainpoolP256t1', ), + '1.3.36.3.3.2.8.1.1.9': ('brainpoolP320r1', ), + '1.3.36.3.3.2.8.1.1.10': ('brainpoolP320t1', ), + '1.3.36.3.3.2.8.1.1.11': ('brainpoolP384r1', ), + '1.3.36.3.3.2.8.1.1.12': ('brainpoolP384t1', ), + '1.3.36.3.3.2.8.1.1.13': ('brainpoolP512r1', ), + '1.3.36.3.3.2.8.1.1.14': ('brainpoolP512t1', ), + '1.3.36.8.3.3': ('Professional Information or basis for Admission', 'x509ExtAdmission'), + '1.3.101.1.4.1': ('Strong Extranet ID', 'SXNetID'), + '1.3.101.110': ('X25519', ), + '1.3.101.111': ('X448', ), + '1.3.101.112': ('ED25519', ), + '1.3.101.113': ('ED448', ), + '1.3.111': ('ieee', ), + '1.3.111.2.1619': ('IEEE Security in Storage Working Group', 'ieee-siswg'), + '1.3.111.2.1619.0.1.1': ('aes-128-xts', 'AES-128-XTS'), + '1.3.111.2.1619.0.1.2': ('aes-256-xts', 'AES-256-XTS'), + '1.3.132': ('certicom-arc', ), + '1.3.132.0': ('secg_ellipticCurve', ), + '1.3.132.0.1': ('sect163k1', ), + '1.3.132.0.2': ('sect163r1', ), + '1.3.132.0.3': ('sect239k1', ), + '1.3.132.0.4': ('sect113r1', ), + '1.3.132.0.5': ('sect113r2', ), + '1.3.132.0.6': ('secp112r1', ), + '1.3.132.0.7': ('secp112r2', ), + '1.3.132.0.8': ('secp160r1', ), + '1.3.132.0.9': ('secp160k1', ), + '1.3.132.0.10': ('secp256k1', ), + '1.3.132.0.15': ('sect163r2', ), + '1.3.132.0.16': ('sect283k1', ), + '1.3.132.0.17': ('sect283r1', ), + '1.3.132.0.22': ('sect131r1', ), + '1.3.132.0.23': ('sect131r2', ), + '1.3.132.0.24': ('sect193r1', ), + '1.3.132.0.25': ('sect193r2', ), + '1.3.132.0.26': ('sect233k1', ), + '1.3.132.0.27': ('sect233r1', ), + '1.3.132.0.28': ('secp128r1', ), + '1.3.132.0.29': ('secp128r2', ), + '1.3.132.0.30': ('secp160r2', ), + '1.3.132.0.31': ('secp192k1', ), + '1.3.132.0.32': ('secp224k1', ), + '1.3.132.0.33': ('secp224r1', ), + '1.3.132.0.34': ('secp384r1', ), + '1.3.132.0.35': ('secp521r1', ), + '1.3.132.0.36': ('sect409k1', ), + '1.3.132.0.37': ('sect409r1', ), + '1.3.132.0.38': ('sect571k1', ), + '1.3.132.0.39': ('sect571r1', ), + '1.3.132.1': ('secg-scheme', ), + '1.3.132.1.11.0': ('dhSinglePass-stdDH-sha224kdf-scheme', ), + '1.3.132.1.11.1': ('dhSinglePass-stdDH-sha256kdf-scheme', ), + '1.3.132.1.11.2': ('dhSinglePass-stdDH-sha384kdf-scheme', ), + '1.3.132.1.11.3': ('dhSinglePass-stdDH-sha512kdf-scheme', ), + '1.3.132.1.14.0': ('dhSinglePass-cofactorDH-sha224kdf-scheme', ), + '1.3.132.1.14.1': ('dhSinglePass-cofactorDH-sha256kdf-scheme', ), + '1.3.132.1.14.2': ('dhSinglePass-cofactorDH-sha384kdf-scheme', ), + '1.3.132.1.14.3': ('dhSinglePass-cofactorDH-sha512kdf-scheme', ), + '1.3.133.16.840.63.0': ('x9-63-scheme', ), + '1.3.133.16.840.63.0.2': ('dhSinglePass-stdDH-sha1kdf-scheme', ), + '1.3.133.16.840.63.0.3': ('dhSinglePass-cofactorDH-sha1kdf-scheme', ), + '2': ('joint-iso-itu-t', 'JOINT-ISO-ITU-T', 'joint-iso-ccitt'), + '2.5': ('directory services (X.500)', 'X500'), + '2.5.1.5': ('Selected Attribute Types', 'selected-attribute-types'), + '2.5.1.5.55': ('clearance', ), + '2.5.4': ('X509', ), + '2.5.4.3': ('commonName', 'CN'), + '2.5.4.4': ('surname', 'SN'), + '2.5.4.5': ('serialNumber', ), + '2.5.4.6': ('countryName', 'C'), + '2.5.4.7': ('localityName', 'L'), + '2.5.4.8': ('stateOrProvinceName', 'ST'), + '2.5.4.9': ('streetAddress', 'street'), + '2.5.4.10': ('organizationName', 'O'), + '2.5.4.11': ('organizationalUnitName', 'OU'), + '2.5.4.12': ('title', 'title'), + '2.5.4.13': ('description', ), + '2.5.4.14': ('searchGuide', ), + '2.5.4.15': ('businessCategory', ), + '2.5.4.16': ('postalAddress', ), + '2.5.4.17': ('postalCode', ), + '2.5.4.18': ('postOfficeBox', ), + '2.5.4.19': ('physicalDeliveryOfficeName', ), + '2.5.4.20': ('telephoneNumber', ), + '2.5.4.21': ('telexNumber', ), + '2.5.4.22': ('teletexTerminalIdentifier', ), + '2.5.4.23': ('facsimileTelephoneNumber', ), + '2.5.4.24': ('x121Address', ), + '2.5.4.25': ('internationaliSDNNumber', ), + '2.5.4.26': ('registeredAddress', ), + '2.5.4.27': ('destinationIndicator', ), + '2.5.4.28': ('preferredDeliveryMethod', ), + '2.5.4.29': ('presentationAddress', ), + '2.5.4.30': ('supportedApplicationContext', ), + '2.5.4.31': ('member', ), + '2.5.4.32': ('owner', ), + '2.5.4.33': ('roleOccupant', ), + '2.5.4.34': ('seeAlso', ), + '2.5.4.35': ('userPassword', ), + '2.5.4.36': ('userCertificate', ), + '2.5.4.37': ('cACertificate', ), + '2.5.4.38': ('authorityRevocationList', ), + '2.5.4.39': ('certificateRevocationList', ), + '2.5.4.40': ('crossCertificatePair', ), + '2.5.4.41': ('name', 'name'), + '2.5.4.42': ('givenName', 'GN'), + '2.5.4.43': ('initials', 'initials'), + '2.5.4.44': ('generationQualifier', ), + '2.5.4.45': ('x500UniqueIdentifier', ), + '2.5.4.46': ('dnQualifier', 'dnQualifier'), + '2.5.4.47': ('enhancedSearchGuide', ), + '2.5.4.48': ('protocolInformation', ), + '2.5.4.49': ('distinguishedName', ), + '2.5.4.50': ('uniqueMember', ), + '2.5.4.51': ('houseIdentifier', ), + '2.5.4.52': ('supportedAlgorithms', ), + '2.5.4.53': ('deltaRevocationList', ), + '2.5.4.54': ('dmdName', ), + '2.5.4.65': ('pseudonym', ), + '2.5.4.72': ('role', 'role'), + '2.5.4.97': ('organizationIdentifier', ), + '2.5.4.98': ('countryCode3c', 'c3'), + '2.5.4.99': ('countryCode3n', 'n3'), + '2.5.4.100': ('dnsName', ), + '2.5.8': ('directory services - algorithms', 'X500algorithms'), + '2.5.8.1.1': ('rsa', 'RSA'), + '2.5.8.3.100': ('mdc2WithRSA', 'RSA-MDC2'), + '2.5.8.3.101': ('mdc2', 'MDC2'), + '2.5.29': ('id-ce', ), + '2.5.29.9': ('X509v3 Subject Directory Attributes', 'subjectDirectoryAttributes'), + '2.5.29.14': ('X509v3 Subject Key Identifier', 'subjectKeyIdentifier'), + '2.5.29.15': ('X509v3 Key Usage', 'keyUsage'), + '2.5.29.16': ('X509v3 Private Key Usage Period', 'privateKeyUsagePeriod'), + '2.5.29.17': ('X509v3 Subject Alternative Name', 'subjectAltName'), + '2.5.29.18': ('X509v3 Issuer Alternative Name', 'issuerAltName'), + '2.5.29.19': ('X509v3 Basic Constraints', 'basicConstraints'), + '2.5.29.20': ('X509v3 CRL Number', 'crlNumber'), + '2.5.29.21': ('X509v3 CRL Reason Code', 'CRLReason'), + '2.5.29.23': ('Hold Instruction Code', 'holdInstructionCode'), + '2.5.29.24': ('Invalidity Date', 'invalidityDate'), + '2.5.29.27': ('X509v3 Delta CRL Indicator', 'deltaCRL'), + '2.5.29.28': ('X509v3 Issuing Distribution Point', 'issuingDistributionPoint'), + '2.5.29.29': ('X509v3 Certificate Issuer', 'certificateIssuer'), + '2.5.29.30': ('X509v3 Name Constraints', 'nameConstraints'), + '2.5.29.31': ('X509v3 CRL Distribution Points', 'crlDistributionPoints'), + '2.5.29.32': ('X509v3 Certificate Policies', 'certificatePolicies'), + '2.5.29.32.0': ('X509v3 Any Policy', 'anyPolicy'), + '2.5.29.33': ('X509v3 Policy Mappings', 'policyMappings'), + '2.5.29.35': ('X509v3 Authority Key Identifier', 'authorityKeyIdentifier'), + '2.5.29.36': ('X509v3 Policy Constraints', 'policyConstraints'), + '2.5.29.37': ('X509v3 Extended Key Usage', 'extendedKeyUsage'), + '2.5.29.37.0': ('Any Extended Key Usage', 'anyExtendedKeyUsage'), + '2.5.29.46': ('X509v3 Freshest CRL', 'freshestCRL'), + '2.5.29.54': ('X509v3 Inhibit Any Policy', 'inhibitAnyPolicy'), + '2.5.29.55': ('X509v3 AC Targeting', 'targetInformation'), + '2.5.29.56': ('X509v3 No Revocation Available', 'noRevAvail'), + '2.16.840.1.101.3': ('csor', ), + '2.16.840.1.101.3.4': ('nistAlgorithms', ), + '2.16.840.1.101.3.4.1': ('aes', ), + '2.16.840.1.101.3.4.1.1': ('aes-128-ecb', 'AES-128-ECB'), + '2.16.840.1.101.3.4.1.2': ('aes-128-cbc', 'AES-128-CBC'), + '2.16.840.1.101.3.4.1.3': ('aes-128-ofb', 'AES-128-OFB'), + '2.16.840.1.101.3.4.1.4': ('aes-128-cfb', 'AES-128-CFB'), + '2.16.840.1.101.3.4.1.5': ('id-aes128-wrap', ), + '2.16.840.1.101.3.4.1.6': ('aes-128-gcm', 'id-aes128-GCM'), + '2.16.840.1.101.3.4.1.7': ('aes-128-ccm', 'id-aes128-CCM'), + '2.16.840.1.101.3.4.1.8': ('id-aes128-wrap-pad', ), + '2.16.840.1.101.3.4.1.21': ('aes-192-ecb', 'AES-192-ECB'), + '2.16.840.1.101.3.4.1.22': ('aes-192-cbc', 'AES-192-CBC'), + '2.16.840.1.101.3.4.1.23': ('aes-192-ofb', 'AES-192-OFB'), + '2.16.840.1.101.3.4.1.24': ('aes-192-cfb', 'AES-192-CFB'), + '2.16.840.1.101.3.4.1.25': ('id-aes192-wrap', ), + '2.16.840.1.101.3.4.1.26': ('aes-192-gcm', 'id-aes192-GCM'), + '2.16.840.1.101.3.4.1.27': ('aes-192-ccm', 'id-aes192-CCM'), + '2.16.840.1.101.3.4.1.28': ('id-aes192-wrap-pad', ), + '2.16.840.1.101.3.4.1.41': ('aes-256-ecb', 'AES-256-ECB'), + '2.16.840.1.101.3.4.1.42': ('aes-256-cbc', 'AES-256-CBC'), + '2.16.840.1.101.3.4.1.43': ('aes-256-ofb', 'AES-256-OFB'), + '2.16.840.1.101.3.4.1.44': ('aes-256-cfb', 'AES-256-CFB'), + '2.16.840.1.101.3.4.1.45': ('id-aes256-wrap', ), + '2.16.840.1.101.3.4.1.46': ('aes-256-gcm', 'id-aes256-GCM'), + '2.16.840.1.101.3.4.1.47': ('aes-256-ccm', 'id-aes256-CCM'), + '2.16.840.1.101.3.4.1.48': ('id-aes256-wrap-pad', ), + '2.16.840.1.101.3.4.2': ('nist_hashalgs', ), + '2.16.840.1.101.3.4.2.1': ('sha256', 'SHA256'), + '2.16.840.1.101.3.4.2.2': ('sha384', 'SHA384'), + '2.16.840.1.101.3.4.2.3': ('sha512', 'SHA512'), + '2.16.840.1.101.3.4.2.4': ('sha224', 'SHA224'), + '2.16.840.1.101.3.4.2.5': ('sha512-224', 'SHA512-224'), + '2.16.840.1.101.3.4.2.6': ('sha512-256', 'SHA512-256'), + '2.16.840.1.101.3.4.2.7': ('sha3-224', 'SHA3-224'), + '2.16.840.1.101.3.4.2.8': ('sha3-256', 'SHA3-256'), + '2.16.840.1.101.3.4.2.9': ('sha3-384', 'SHA3-384'), + '2.16.840.1.101.3.4.2.10': ('sha3-512', 'SHA3-512'), + '2.16.840.1.101.3.4.2.11': ('shake128', 'SHAKE128'), + '2.16.840.1.101.3.4.2.12': ('shake256', 'SHAKE256'), + '2.16.840.1.101.3.4.2.13': ('hmac-sha3-224', 'id-hmacWithSHA3-224'), + '2.16.840.1.101.3.4.2.14': ('hmac-sha3-256', 'id-hmacWithSHA3-256'), + '2.16.840.1.101.3.4.2.15': ('hmac-sha3-384', 'id-hmacWithSHA3-384'), + '2.16.840.1.101.3.4.2.16': ('hmac-sha3-512', 'id-hmacWithSHA3-512'), + '2.16.840.1.101.3.4.3': ('dsa_with_sha2', 'sigAlgs'), + '2.16.840.1.101.3.4.3.1': ('dsa_with_SHA224', ), + '2.16.840.1.101.3.4.3.2': ('dsa_with_SHA256', ), + '2.16.840.1.101.3.4.3.3': ('dsa_with_SHA384', 'id-dsa-with-sha384'), + '2.16.840.1.101.3.4.3.4': ('dsa_with_SHA512', 'id-dsa-with-sha512'), + '2.16.840.1.101.3.4.3.5': ('dsa_with_SHA3-224', 'id-dsa-with-sha3-224'), + '2.16.840.1.101.3.4.3.6': ('dsa_with_SHA3-256', 'id-dsa-with-sha3-256'), + '2.16.840.1.101.3.4.3.7': ('dsa_with_SHA3-384', 'id-dsa-with-sha3-384'), + '2.16.840.1.101.3.4.3.8': ('dsa_with_SHA3-512', 'id-dsa-with-sha3-512'), + '2.16.840.1.101.3.4.3.9': ('ecdsa_with_SHA3-224', 'id-ecdsa-with-sha3-224'), + '2.16.840.1.101.3.4.3.10': ('ecdsa_with_SHA3-256', 'id-ecdsa-with-sha3-256'), + '2.16.840.1.101.3.4.3.11': ('ecdsa_with_SHA3-384', 'id-ecdsa-with-sha3-384'), + '2.16.840.1.101.3.4.3.12': ('ecdsa_with_SHA3-512', 'id-ecdsa-with-sha3-512'), + '2.16.840.1.101.3.4.3.13': ('RSA-SHA3-224', 'id-rsassa-pkcs1-v1_5-with-sha3-224'), + '2.16.840.1.101.3.4.3.14': ('RSA-SHA3-256', 'id-rsassa-pkcs1-v1_5-with-sha3-256'), + '2.16.840.1.101.3.4.3.15': ('RSA-SHA3-384', 'id-rsassa-pkcs1-v1_5-with-sha3-384'), + '2.16.840.1.101.3.4.3.16': ('RSA-SHA3-512', 'id-rsassa-pkcs1-v1_5-with-sha3-512'), + '2.16.840.1.113730': ('Netscape Communications Corp.', 'Netscape'), + '2.16.840.1.113730.1': ('Netscape Certificate Extension', 'nsCertExt'), + '2.16.840.1.113730.1.1': ('Netscape Cert Type', 'nsCertType'), + '2.16.840.1.113730.1.2': ('Netscape Base Url', 'nsBaseUrl'), + '2.16.840.1.113730.1.3': ('Netscape Revocation Url', 'nsRevocationUrl'), + '2.16.840.1.113730.1.4': ('Netscape CA Revocation Url', 'nsCaRevocationUrl'), + '2.16.840.1.113730.1.7': ('Netscape Renewal Url', 'nsRenewalUrl'), + '2.16.840.1.113730.1.8': ('Netscape CA Policy Url', 'nsCaPolicyUrl'), + '2.16.840.1.113730.1.12': ('Netscape SSL Server Name', 'nsSslServerName'), + '2.16.840.1.113730.1.13': ('Netscape Comment', 'nsComment'), + '2.16.840.1.113730.2': ('Netscape Data Type', 'nsDataType'), + '2.16.840.1.113730.2.5': ('Netscape Certificate Sequence', 'nsCertSequence'), + '2.16.840.1.113730.4.1': ('Netscape Server Gated Crypto', 'nsSGC'), + '2.23': ('International Organizations', 'international-organizations'), + '2.23.42': ('Secure Electronic Transactions', 'id-set'), + '2.23.42.0': ('content types', 'set-ctype'), + '2.23.42.0.0': ('setct-PANData', ), + '2.23.42.0.1': ('setct-PANToken', ), + '2.23.42.0.2': ('setct-PANOnly', ), + '2.23.42.0.3': ('setct-OIData', ), + '2.23.42.0.4': ('setct-PI', ), + '2.23.42.0.5': ('setct-PIData', ), + '2.23.42.0.6': ('setct-PIDataUnsigned', ), + '2.23.42.0.7': ('setct-HODInput', ), + '2.23.42.0.8': ('setct-AuthResBaggage', ), + '2.23.42.0.9': ('setct-AuthRevReqBaggage', ), + '2.23.42.0.10': ('setct-AuthRevResBaggage', ), + '2.23.42.0.11': ('setct-CapTokenSeq', ), + '2.23.42.0.12': ('setct-PInitResData', ), + '2.23.42.0.13': ('setct-PI-TBS', ), + '2.23.42.0.14': ('setct-PResData', ), + '2.23.42.0.16': ('setct-AuthReqTBS', ), + '2.23.42.0.17': ('setct-AuthResTBS', ), + '2.23.42.0.18': ('setct-AuthResTBSX', ), + '2.23.42.0.19': ('setct-AuthTokenTBS', ), + '2.23.42.0.20': ('setct-CapTokenData', ), + '2.23.42.0.21': ('setct-CapTokenTBS', ), + '2.23.42.0.22': ('setct-AcqCardCodeMsg', ), + '2.23.42.0.23': ('setct-AuthRevReqTBS', ), + '2.23.42.0.24': ('setct-AuthRevResData', ), + '2.23.42.0.25': ('setct-AuthRevResTBS', ), + '2.23.42.0.26': ('setct-CapReqTBS', ), + '2.23.42.0.27': ('setct-CapReqTBSX', ), + '2.23.42.0.28': ('setct-CapResData', ), + '2.23.42.0.29': ('setct-CapRevReqTBS', ), + '2.23.42.0.30': ('setct-CapRevReqTBSX', ), + '2.23.42.0.31': ('setct-CapRevResData', ), + '2.23.42.0.32': ('setct-CredReqTBS', ), + '2.23.42.0.33': ('setct-CredReqTBSX', ), + '2.23.42.0.34': ('setct-CredResData', ), + '2.23.42.0.35': ('setct-CredRevReqTBS', ), + '2.23.42.0.36': ('setct-CredRevReqTBSX', ), + '2.23.42.0.37': ('setct-CredRevResData', ), + '2.23.42.0.38': ('setct-PCertReqData', ), + '2.23.42.0.39': ('setct-PCertResTBS', ), + '2.23.42.0.40': ('setct-BatchAdminReqData', ), + '2.23.42.0.41': ('setct-BatchAdminResData', ), + '2.23.42.0.42': ('setct-CardCInitResTBS', ), + '2.23.42.0.43': ('setct-MeAqCInitResTBS', ), + '2.23.42.0.44': ('setct-RegFormResTBS', ), + '2.23.42.0.45': ('setct-CertReqData', ), + '2.23.42.0.46': ('setct-CertReqTBS', ), + '2.23.42.0.47': ('setct-CertResData', ), + '2.23.42.0.48': ('setct-CertInqReqTBS', ), + '2.23.42.0.49': ('setct-ErrorTBS', ), + '2.23.42.0.50': ('setct-PIDualSignedTBE', ), + '2.23.42.0.51': ('setct-PIUnsignedTBE', ), + '2.23.42.0.52': ('setct-AuthReqTBE', ), + '2.23.42.0.53': ('setct-AuthResTBE', ), + '2.23.42.0.54': ('setct-AuthResTBEX', ), + '2.23.42.0.55': ('setct-AuthTokenTBE', ), + '2.23.42.0.56': ('setct-CapTokenTBE', ), + '2.23.42.0.57': ('setct-CapTokenTBEX', ), + '2.23.42.0.58': ('setct-AcqCardCodeMsgTBE', ), + '2.23.42.0.59': ('setct-AuthRevReqTBE', ), + '2.23.42.0.60': ('setct-AuthRevResTBE', ), + '2.23.42.0.61': ('setct-AuthRevResTBEB', ), + '2.23.42.0.62': ('setct-CapReqTBE', ), + '2.23.42.0.63': ('setct-CapReqTBEX', ), + '2.23.42.0.64': ('setct-CapResTBE', ), + '2.23.42.0.65': ('setct-CapRevReqTBE', ), + '2.23.42.0.66': ('setct-CapRevReqTBEX', ), + '2.23.42.0.67': ('setct-CapRevResTBE', ), + '2.23.42.0.68': ('setct-CredReqTBE', ), + '2.23.42.0.69': ('setct-CredReqTBEX', ), + '2.23.42.0.70': ('setct-CredResTBE', ), + '2.23.42.0.71': ('setct-CredRevReqTBE', ), + '2.23.42.0.72': ('setct-CredRevReqTBEX', ), + '2.23.42.0.73': ('setct-CredRevResTBE', ), + '2.23.42.0.74': ('setct-BatchAdminReqTBE', ), + '2.23.42.0.75': ('setct-BatchAdminResTBE', ), + '2.23.42.0.76': ('setct-RegFormReqTBE', ), + '2.23.42.0.77': ('setct-CertReqTBE', ), + '2.23.42.0.78': ('setct-CertReqTBEX', ), + '2.23.42.0.79': ('setct-CertResTBE', ), + '2.23.42.0.80': ('setct-CRLNotificationTBS', ), + '2.23.42.0.81': ('setct-CRLNotificationResTBS', ), + '2.23.42.0.82': ('setct-BCIDistributionTBS', ), + '2.23.42.1': ('message extensions', 'set-msgExt'), + '2.23.42.1.1': ('generic cryptogram', 'setext-genCrypt'), + '2.23.42.1.3': ('merchant initiated auth', 'setext-miAuth'), + '2.23.42.1.4': ('setext-pinSecure', ), + '2.23.42.1.5': ('setext-pinAny', ), + '2.23.42.1.7': ('setext-track2', ), + '2.23.42.1.8': ('additional verification', 'setext-cv'), + '2.23.42.3': ('set-attr', ), + '2.23.42.3.0': ('setAttr-Cert', ), + '2.23.42.3.0.0': ('set-rootKeyThumb', ), + '2.23.42.3.0.1': ('set-addPolicy', ), + '2.23.42.3.1': ('payment gateway capabilities', 'setAttr-PGWYcap'), + '2.23.42.3.2': ('setAttr-TokenType', ), + '2.23.42.3.2.1': ('setAttr-Token-EMV', ), + '2.23.42.3.2.2': ('setAttr-Token-B0Prime', ), + '2.23.42.3.3': ('issuer capabilities', 'setAttr-IssCap'), + '2.23.42.3.3.3': ('setAttr-IssCap-CVM', ), + '2.23.42.3.3.3.1': ('generate cryptogram', 'setAttr-GenCryptgrm'), + '2.23.42.3.3.4': ('setAttr-IssCap-T2', ), + '2.23.42.3.3.4.1': ('encrypted track 2', 'setAttr-T2Enc'), + '2.23.42.3.3.4.2': ('cleartext track 2', 'setAttr-T2cleartxt'), + '2.23.42.3.3.5': ('setAttr-IssCap-Sig', ), + '2.23.42.3.3.5.1': ('ICC or token signature', 'setAttr-TokICCsig'), + '2.23.42.3.3.5.2': ('secure device signature', 'setAttr-SecDevSig'), + '2.23.42.5': ('set-policy', ), + '2.23.42.5.0': ('set-policy-root', ), + '2.23.42.7': ('certificate extensions', 'set-certExt'), + '2.23.42.7.0': ('setCext-hashedRoot', ), + '2.23.42.7.1': ('setCext-certType', ), + '2.23.42.7.2': ('setCext-merchData', ), + '2.23.42.7.3': ('setCext-cCertRequired', ), + '2.23.42.7.4': ('setCext-tunneling', ), + '2.23.42.7.5': ('setCext-setExt', ), + '2.23.42.7.6': ('setCext-setQualf', ), + '2.23.42.7.7': ('setCext-PGWYcapabilities', ), + '2.23.42.7.8': ('setCext-TokenIdentifier', ), + '2.23.42.7.9': ('setCext-Track2Data', ), + '2.23.42.7.10': ('setCext-TokenType', ), + '2.23.42.7.11': ('setCext-IssuerCapabilities', ), + '2.23.42.8': ('set-brand', ), + '2.23.42.8.1': ('set-brand-IATA-ATA', ), + '2.23.42.8.4': ('set-brand-Visa', ), + '2.23.42.8.5': ('set-brand-MasterCard', ), + '2.23.42.8.30': ('set-brand-Diners', ), + '2.23.42.8.34': ('set-brand-AmericanExpress', ), + '2.23.42.8.35': ('set-brand-JCB', ), + '2.23.42.8.6011': ('set-brand-Novus', ), + '2.23.43': ('wap', ), + '2.23.43.1': ('wap-wsg', ), + '2.23.43.1.4': ('wap-wsg-idm-ecid', ), + '2.23.43.1.4.1': ('wap-wsg-idm-ecid-wtls1', ), + '2.23.43.1.4.3': ('wap-wsg-idm-ecid-wtls3', ), + '2.23.43.1.4.4': ('wap-wsg-idm-ecid-wtls4', ), + '2.23.43.1.4.5': ('wap-wsg-idm-ecid-wtls5', ), + '2.23.43.1.4.6': ('wap-wsg-idm-ecid-wtls6', ), + '2.23.43.1.4.7': ('wap-wsg-idm-ecid-wtls7', ), + '2.23.43.1.4.8': ('wap-wsg-idm-ecid-wtls8', ), + '2.23.43.1.4.9': ('wap-wsg-idm-ecid-wtls9', ), + '2.23.43.1.4.10': ('wap-wsg-idm-ecid-wtls10', ), + '2.23.43.1.4.11': ('wap-wsg-idm-ecid-wtls11', ), + '2.23.43.1.4.12': ('wap-wsg-idm-ecid-wtls12', ), +} diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/basic.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/basic.py new file mode 100644 index 00000000..7ba8dcba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/basic.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# +# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# (c) 2020, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from distutils.version import LooseVersion + +try: + import OpenSSL # noqa + from OpenSSL import crypto # noqa + HAS_PYOPENSSL = True +except ImportError: + # Error handled in the calling module. + HAS_PYOPENSSL = False + +try: + import cryptography + from cryptography import x509 + + # Older versions of cryptography (< 2.1) do not have __hash__ functions for + # general name objects (DNSName, IPAddress, ...), while providing overloaded + # equality and string representation operations. This makes it impossible to + # use them in hash-based data structures such as set or dict. Since we are + # actually doing that in x509_certificate, and potentially in other code, + # we need to monkey-patch __hash__ for these classes to make sure our code + # works fine. + if LooseVersion(cryptography.__version__) < LooseVersion('2.1'): + # A very simply hash function which relies on the representation + # of an object to be implemented. This is the case since at least + # cryptography 1.0, see + # https://github.com/pyca/cryptography/commit/7a9abce4bff36c05d26d8d2680303a6f64a0e84f + def simple_hash(self): + return hash(repr(self)) + + # The hash functions for the following types were added for cryptography 2.1: + # https://github.com/pyca/cryptography/commit/fbfc36da2a4769045f2373b004ddf0aff906cf38 + x509.DNSName.__hash__ = simple_hash + x509.DirectoryName.__hash__ = simple_hash + x509.GeneralName.__hash__ = simple_hash + x509.IPAddress.__hash__ = simple_hash + x509.OtherName.__hash__ = simple_hash + x509.RegisteredID.__hash__ = simple_hash + + if LooseVersion(cryptography.__version__) < LooseVersion('1.2'): + # The hash functions for the following types were added for cryptography 1.2: + # https://github.com/pyca/cryptography/commit/b642deed88a8696e5f01ce6855ccf89985fc35d0 + # https://github.com/pyca/cryptography/commit/d1b5681f6db2bde7a14625538bd7907b08dfb486 + x509.RFC822Name.__hash__ = simple_hash + x509.UniformResourceIdentifier.__hash__ = simple_hash + + # Test whether we have support for DSA, EC, Ed25519, Ed448, RSA, X25519 and/or X448 + try: + # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/ + import cryptography.hazmat.primitives.asymmetric.dsa + CRYPTOGRAPHY_HAS_DSA = True + try: + # added later in 1.5 + cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign + CRYPTOGRAPHY_HAS_DSA_SIGN = True + except AttributeError: + CRYPTOGRAPHY_HAS_DSA_SIGN = False + except ImportError: + CRYPTOGRAPHY_HAS_DSA = False + CRYPTOGRAPHY_HAS_DSA_SIGN = False + try: + # added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/ + import cryptography.hazmat.primitives.asymmetric.ed25519 + CRYPTOGRAPHY_HAS_ED25519 = True + try: + # added with the primitive in 2.6 + cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.sign + CRYPTOGRAPHY_HAS_ED25519_SIGN = True + except AttributeError: + CRYPTOGRAPHY_HAS_ED25519_SIGN = False + except ImportError: + CRYPTOGRAPHY_HAS_ED25519 = False + CRYPTOGRAPHY_HAS_ED25519_SIGN = False + try: + # added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/ + import cryptography.hazmat.primitives.asymmetric.ed448 + CRYPTOGRAPHY_HAS_ED448 = True + try: + # added with the primitive in 2.6 + cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.sign + CRYPTOGRAPHY_HAS_ED448_SIGN = True + except AttributeError: + CRYPTOGRAPHY_HAS_ED448_SIGN = False + except ImportError: + CRYPTOGRAPHY_HAS_ED448 = False + CRYPTOGRAPHY_HAS_ED448_SIGN = False + try: + # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ + import cryptography.hazmat.primitives.asymmetric.ec + CRYPTOGRAPHY_HAS_EC = True + try: + # added later in 1.5 + cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign + CRYPTOGRAPHY_HAS_EC_SIGN = True + except AttributeError: + CRYPTOGRAPHY_HAS_EC_SIGN = False + except ImportError: + CRYPTOGRAPHY_HAS_EC = False + CRYPTOGRAPHY_HAS_EC_SIGN = False + try: + # added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/ + import cryptography.hazmat.primitives.asymmetric.rsa + CRYPTOGRAPHY_HAS_RSA = True + try: + # added later in 1.4 + cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign + CRYPTOGRAPHY_HAS_RSA_SIGN = True + except AttributeError: + CRYPTOGRAPHY_HAS_RSA_SIGN = False + except ImportError: + CRYPTOGRAPHY_HAS_RSA = False + CRYPTOGRAPHY_HAS_RSA_SIGN = False + try: + # added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/ + import cryptography.hazmat.primitives.asymmetric.x25519 + CRYPTOGRAPHY_HAS_X25519 = True + try: + # added later in 2.5 + cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes + CRYPTOGRAPHY_HAS_X25519_FULL = True + except AttributeError: + CRYPTOGRAPHY_HAS_X25519_FULL = False + except ImportError: + CRYPTOGRAPHY_HAS_X25519 = False + CRYPTOGRAPHY_HAS_X25519_FULL = False + try: + # added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/ + import cryptography.hazmat.primitives.asymmetric.x448 + CRYPTOGRAPHY_HAS_X448 = True + except ImportError: + CRYPTOGRAPHY_HAS_X448 = False + + HAS_CRYPTOGRAPHY = True +except ImportError: + # Error handled in the calling module. + CRYPTOGRAPHY_HAS_EC = False + CRYPTOGRAPHY_HAS_EC_SIGN = False + CRYPTOGRAPHY_HAS_ED25519 = False + CRYPTOGRAPHY_HAS_ED25519_SIGN = False + CRYPTOGRAPHY_HAS_ED448 = False + CRYPTOGRAPHY_HAS_ED448_SIGN = False + CRYPTOGRAPHY_HAS_DSA = False + CRYPTOGRAPHY_HAS_DSA_SIGN = False + CRYPTOGRAPHY_HAS_RSA = False + CRYPTOGRAPHY_HAS_RSA_SIGN = False + CRYPTOGRAPHY_HAS_X25519 = False + CRYPTOGRAPHY_HAS_X25519_FULL = False + CRYPTOGRAPHY_HAS_X448 = False + HAS_CRYPTOGRAPHY = False + + +class OpenSSLObjectError(Exception): + pass + + +class OpenSSLBadPassphraseError(OpenSSLObjectError): + pass diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_crl.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_crl.py new file mode 100644 index 00000000..aa73b512 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_crl.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +try: + from cryptography import x509 +except ImportError: + # Error handled in the calling module. + pass + +from .basic import ( + HAS_CRYPTOGRAPHY, +) + +from .cryptography_support import ( + cryptography_decode_name, +) + +from ._obj2txt import ( + obj2txt, +) + + +TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" + + +if HAS_CRYPTOGRAPHY: + REVOCATION_REASON_MAP = { + 'unspecified': x509.ReasonFlags.unspecified, + 'key_compromise': x509.ReasonFlags.key_compromise, + 'ca_compromise': x509.ReasonFlags.ca_compromise, + 'affiliation_changed': x509.ReasonFlags.affiliation_changed, + 'superseded': x509.ReasonFlags.superseded, + 'cessation_of_operation': x509.ReasonFlags.cessation_of_operation, + 'certificate_hold': x509.ReasonFlags.certificate_hold, + 'privilege_withdrawn': x509.ReasonFlags.privilege_withdrawn, + 'aa_compromise': x509.ReasonFlags.aa_compromise, + 'remove_from_crl': x509.ReasonFlags.remove_from_crl, + } + REVOCATION_REASON_MAP_INVERSE = dict() + for k, v in REVOCATION_REASON_MAP.items(): + REVOCATION_REASON_MAP_INVERSE[v] = k + +else: + REVOCATION_REASON_MAP = dict() + REVOCATION_REASON_MAP_INVERSE = dict() + + +def cryptography_decode_revoked_certificate(cert): + result = { + 'serial_number': cert.serial_number, + 'revocation_date': cert.revocation_date, + 'issuer': None, + 'issuer_critical': False, + 'reason': None, + 'reason_critical': False, + 'invalidity_date': None, + 'invalidity_date_critical': False, + } + try: + ext = cert.extensions.get_extension_for_class(x509.CertificateIssuer) + result['issuer'] = list(ext.value) + result['issuer_critical'] = ext.critical + except x509.ExtensionNotFound: + pass + try: + ext = cert.extensions.get_extension_for_class(x509.CRLReason) + result['reason'] = ext.value.reason + result['reason_critical'] = ext.critical + except x509.ExtensionNotFound: + pass + try: + ext = cert.extensions.get_extension_for_class(x509.InvalidityDate) + result['invalidity_date'] = ext.value.invalidity_date + result['invalidity_date_critical'] = ext.critical + except x509.ExtensionNotFound: + pass + return result + + +def cryptography_dump_revoked(entry): + return { + 'serial_number': entry['serial_number'], + 'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT), + 'issuer': + [cryptography_decode_name(issuer) for issuer in entry['issuer']] + if entry['issuer'] is not None else None, + 'issuer_critical': entry['issuer_critical'], + 'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None, + 'reason_critical': entry['reason_critical'], + 'invalidity_date': + entry['invalidity_date'].strftime(TIMESTAMP_FORMAT) + if entry['invalidity_date'] is not None else None, + 'invalidity_date_critical': entry['invalidity_date_critical'], + } + + +def cryptography_get_signature_algorithm_oid_from_crl(crl): + try: + return crl.signature_algorithm_oid + except AttributeError: + # Older cryptography versions don't have signature_algorithm_oid yet + dotted = obj2txt( + crl._backend._lib, + crl._backend._ffi, + crl._x509_crl.sig_alg.algorithm + ) + return x509.oid.ObjectIdentifier(dotted) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_support.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_support.py new file mode 100644 index 00000000..67ec34f4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/cryptography_support.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import base64 +import binascii +import re + +from ansible.module_utils._text import to_text +from ._asn1 import serialize_asn1_string_as_der + +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.primitives import serialization + import ipaddress +except ImportError: + # Error handled in the calling module. + pass + +from .basic import ( + CRYPTOGRAPHY_HAS_ED25519, + CRYPTOGRAPHY_HAS_ED448, + OpenSSLObjectError, +) + +from ._objects import ( + OID_LOOKUP, + OID_MAP, + NORMALIZE_NAMES_SHORT, + NORMALIZE_NAMES, +) + +from ._obj2txt import obj2txt + + +DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$') + + +def cryptography_get_extensions_from_cert(cert): + # Since cryptography won't give us the DER value for an extension + # (that is only stored for unrecognized extensions), we have to re-do + # the extension parsing outselves. + result = dict() + backend = cert._backend + x509_obj = cert._x509 + + for i in range(backend._lib.X509_get_ext_count(x509_obj)): + ext = backend._lib.X509_get_ext(x509_obj, i) + if ext == backend._ffi.NULL: + continue + crit = backend._lib.X509_EXTENSION_get_critical(ext) + data = backend._lib.X509_EXTENSION_get_data(ext) + backend.openssl_assert(data != backend._ffi.NULL) + der = backend._ffi.buffer(data.data, data.length)[:] + entry = dict( + critical=(crit == 1), + value=base64.b64encode(der), + ) + oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext)) + result[oid] = entry + return result + + +def cryptography_get_extensions_from_csr(csr): + # Since cryptography won't give us the DER value for an extension + # (that is only stored for unrecognized extensions), we have to re-do + # the extension parsing outselves. + result = dict() + backend = csr._backend + + extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req) + extensions = backend._ffi.gc( + extensions, + lambda ext: backend._lib.sk_X509_EXTENSION_pop_free( + ext, + backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free") + ) + ) + + for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)): + ext = backend._lib.sk_X509_EXTENSION_value(extensions, i) + if ext == backend._ffi.NULL: + continue + crit = backend._lib.X509_EXTENSION_get_critical(ext) + data = backend._lib.X509_EXTENSION_get_data(ext) + backend.openssl_assert(data != backend._ffi.NULL) + der = backend._ffi.buffer(data.data, data.length)[:] + entry = dict( + critical=(crit == 1), + value=base64.b64encode(der), + ) + oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext)) + result[oid] = entry + return result + + +def cryptography_name_to_oid(name): + dotted = OID_LOOKUP.get(name) + if dotted is None: + if DOTTED_OID.match(name): + return x509.oid.ObjectIdentifier(name) + raise OpenSSLObjectError('Cannot find OID for "{0}"'.format(name)) + return x509.oid.ObjectIdentifier(dotted) + + +def cryptography_oid_to_name(oid, short=False): + dotted_string = oid.dotted_string + names = OID_MAP.get(dotted_string) + if names: + name = names[0] + else: + name = oid._name + if name == 'Unknown OID': + name = dotted_string + if short: + return NORMALIZE_NAMES_SHORT.get(name, name) + else: + return NORMALIZE_NAMES.get(name, name) + + +def _get_hex(bytesstr): + if bytesstr is None: + return bytesstr + data = binascii.hexlify(bytesstr) + data = to_text(b':'.join(data[i:i + 2] for i in range(0, len(data), 2))) + return data + + +def _parse_hex(bytesstr): + if bytesstr is None: + return bytesstr + data = ''.join([('0' * (2 - len(p)) + p) if len(p) < 2 else p for p in to_text(bytesstr).split(':')]) + data = binascii.unhexlify(data) + return data + + +DN_COMPONENT_START_RE = re.compile(r'^ *([a-zA-z0-9]+) *= *') + + +def _parse_dn_component(name, sep=',', sep_str='\\', decode_remainder=True): + m = DN_COMPONENT_START_RE.match(name) + if not m: + raise OpenSSLObjectError('cannot start part in "{0}"'.format(name)) + oid = cryptography_name_to_oid(m.group(1)) + idx = len(m.group(0)) + decoded_name = [] + if decode_remainder: + length = len(name) + while idx < length: + i = idx + while i < length and name[i] not in sep_str: + i += 1 + if i > idx: + decoded_name.append(name[idx:i]) + idx = i + while idx + 1 < length and name[idx] == '\\': + decoded_name.append(name[idx + 1]) + idx += 2 + if idx < length and name[idx] == sep: + break + else: + decoded_name.append(name[idx:]) + idx = len(name) + return x509.NameAttribute(oid, ''.join(decoded_name)), name[idx:] + + +def _parse_dn(name): + ''' + Parse a Distinguished Name. + + Can be of the form ``CN=Test, O = Something`` or ``CN = Test,O= Something``. + ''' + original_name = name + name = name.lstrip() + sep = ',' + if name.startswith('/'): + sep = '/' + name = name[1:] + sep_str = sep + '\\' + result = [] + while name: + try: + attribute, name = _parse_dn_component(name, sep=sep, sep_str=sep_str) + except OpenSSLObjectError as e: + raise OpenSSLObjectError('Error while parsing distinguished name "{0}": {1}'.format(original_name, e)) + result.append(attribute) + if name: + if name[0] != sep or len(name) < 2: + raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name)) + name = name[1:] + return result + + +def cryptography_parse_relative_distinguished_name(rdn): + names = [] + for part in rdn: + try: + names.append(_parse_dn_component(to_text(part), decode_remainder=False)[0]) + except OpenSSLObjectError as e: + raise OpenSSLObjectError('Error while parsing relative distinguished name "{0}": {1}'.format(part, e)) + return cryptography.x509.RelativeDistinguishedName(names) + + +def cryptography_get_name(name, what='Subject Alternative Name'): + ''' + Given a name string, returns a cryptography x509.GeneralName object. + Raises an OpenSSLObjectError if the name is unknown or cannot be parsed. + ''' + try: + if name.startswith('DNS:'): + return x509.DNSName(to_text(name[4:])) + if name.startswith('IP:'): + address = to_text(name[3:]) + if '/' in address: + return x509.IPAddress(ipaddress.ip_network(address)) + return x509.IPAddress(ipaddress.ip_address(address)) + if name.startswith('email:'): + return x509.RFC822Name(to_text(name[6:])) + if name.startswith('URI:'): + return x509.UniformResourceIdentifier(to_text(name[4:])) + if name.startswith('RID:'): + m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:])) + if not m: + raise OpenSSLObjectError('Cannot parse {what} "{name}"'.format(name=name, what=what)) + return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1))) + if name.startswith('otherName:'): + # otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with. + m = re.match(r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:])) + if m: + return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2))) + + # See https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html - Subject Alternative Name for more + # defailts on the format expected. + name = to_text(name[10:], errors='surrogate_or_strict') + if ';' not in name: + raise OpenSSLObjectError('Cannot parse {what} otherName "{name}", must be in the ' + 'format "otherName:<OID>;<ASN.1 OpenSSL Encoded String>" or ' + '"otherName:<OID>;<hex string>"'.format(name=name, what=what)) + + oid, value = name.split(';', 1) + b_value = serialize_asn1_string_as_der(value) + return x509.OtherName(x509.ObjectIdentifier(oid), b_value) + if name.startswith('dirName:'): + return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:])))) + except Exception as e: + raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e)) + if ':' not in name: + raise OpenSSLObjectError('Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'.format(name=name, what=what)) + raise OpenSSLObjectError('Cannot parse {what} "{name}" (potentially unsupported by cryptography backend)'.format(name=name, what=what)) + + +def _dn_escape_value(value): + ''' + Escape Distinguished Name's attribute value. + ''' + value = value.replace('\\', '\\\\') + for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']: + value = value.replace(ch, '\\%s' % ch) + if value.startswith(' '): + value = r'\ ' + value[1:] + return value + + +def cryptography_decode_name(name): + ''' + Given a cryptography x509.GeneralName object, returns a string. + Raises an OpenSSLObjectError if the name is not supported. + ''' + if isinstance(name, x509.DNSName): + return 'DNS:{0}'.format(name.value) + if isinstance(name, x509.IPAddress): + if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)): + return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen) + return 'IP:{0}'.format(name.value.compressed) + if isinstance(name, x509.RFC822Name): + return 'email:{0}'.format(name.value) + if isinstance(name, x509.UniformResourceIdentifier): + return 'URI:{0}'.format(name.value) + if isinstance(name, x509.DirectoryName): + return 'dirName:' + ''.join([ + '/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value)) + for attribute in name.value + ]) + if isinstance(name, x509.RegisteredID): + return 'RID:{0}'.format(name.value.dotted_string) + if isinstance(name, x509.OtherName): + return 'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value)) + raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name)) + + +def _cryptography_get_keyusage(usage): + ''' + Given a key usage identifier string, returns the parameter name used by cryptography's x509.KeyUsage(). + Raises an OpenSSLObjectError if the identifier is unknown. + ''' + if usage in ('Digital Signature', 'digitalSignature'): + return 'digital_signature' + if usage in ('Non Repudiation', 'nonRepudiation'): + return 'content_commitment' + if usage in ('Key Encipherment', 'keyEncipherment'): + return 'key_encipherment' + if usage in ('Data Encipherment', 'dataEncipherment'): + return 'data_encipherment' + if usage in ('Key Agreement', 'keyAgreement'): + return 'key_agreement' + if usage in ('Certificate Sign', 'keyCertSign'): + return 'key_cert_sign' + if usage in ('CRL Sign', 'cRLSign'): + return 'crl_sign' + if usage in ('Encipher Only', 'encipherOnly'): + return 'encipher_only' + if usage in ('Decipher Only', 'decipherOnly'): + return 'decipher_only' + raise OpenSSLObjectError('Unknown key usage "{0}"'.format(usage)) + + +def cryptography_parse_key_usage_params(usages): + ''' + Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage(). + Raises an OpenSSLObjectError if an identifier is unknown. + ''' + params = dict( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ) + for usage in usages: + params[_cryptography_get_keyusage(usage)] = True + return params + + +def cryptography_get_basic_constraints(constraints): + ''' + Given a list of constraints, returns a tuple (ca, path_length). + Raises an OpenSSLObjectError if a constraint is unknown or cannot be parsed. + ''' + ca = False + path_length = None + if constraints: + for constraint in constraints: + if constraint.startswith('CA:'): + if constraint == 'CA:TRUE': + ca = True + elif constraint == 'CA:FALSE': + ca = False + else: + raise OpenSSLObjectError('Unknown basic constraint value "{0}" for CA'.format(constraint[3:])) + elif constraint.startswith('pathlen:'): + v = constraint[len('pathlen:'):] + try: + path_length = int(v) + except Exception as e: + raise OpenSSLObjectError('Cannot parse path length constraint "{0}" ({1})'.format(v, e)) + else: + raise OpenSSLObjectError('Unknown basic constraint "{0}"'.format(constraint)) + return ca, path_length + + +def cryptography_key_needs_digest_for_signing(key): + '''Tests whether the given private key requires a digest algorithm for signing. + + Ed25519 and Ed448 keys do not; they need None to be passed as the digest algorithm. + ''' + if CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + return False + if CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + return False + return True + + +def cryptography_compare_public_keys(key1, key2): + '''Tests whether two public keys are the same. + + Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers(). + ''' + if CRYPTOGRAPHY_HAS_ED25519: + a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey) + b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey) + if a or b: + if not a or not b: + return False + a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + return a == b + if CRYPTOGRAPHY_HAS_ED448: + a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey) + b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey) + if a or b: + if not a or not b: + return False + a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw) + return a == b + return key1.public_numbers() == key2.public_numbers() + + +def cryptography_serial_number_of_cert(cert): + '''Returns cert.serial_number. + + Also works for old versions of cryptography. + ''' + try: + return cert.serial_number + except AttributeError: + # The property was called "serial" before cryptography 1.4 + return cert.serial diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py new file mode 100644 index 00000000..c98eb1ed --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import sys + + +def binary_exp_mod(f, e, m): + '''Computes f^e mod m in O(log e) multiplications modulo m.''' + # Compute len_e = floor(log_2(e)) + len_e = -1 + x = e + while x > 0: + x >>= 1 + len_e += 1 + # Compute f**e mod m + result = 1 + for k in range(len_e, -1, -1): + result = (result * result) % m + if ((e >> k) & 1) != 0: + result = (result * f) % m + return result + + +def simple_gcd(a, b): + '''Compute GCD of its two inputs.''' + while b != 0: + a, b = b, a % b + return a + + +def quick_is_not_prime(n): + '''Does some quick checks to see if we can poke a hole into the primality of n. + + A result of `False` does **not** mean that the number is prime; it just means + that we couldn't detect quickly whether it is not prime. + ''' + if n <= 2: + return True + # The constant in the next line is the product of all primes < 200 + if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1: + return True + # TODO: maybe do some iterations of Miller-Rabin to increase confidence + # (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test) + return False + + +python_version = (sys.version_info[0], sys.version_info[1]) +if python_version >= (2, 7) or python_version >= (3, 1): + # Ansible still supports Python 2.6 on remote nodes + def count_bits(no): + no = abs(no) + if no == 0: + return 0 + return no.bit_length() +else: + # Slow, but works + def count_bits(no): + no = abs(no) + count = 0 + while no > 0: + no >>= 1 + count += 1 + return count diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py new file mode 100644 index 00000000..e82cf886 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py @@ -0,0 +1,392 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils import six +from ansible.module_utils.basic import missing_required_lib + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_privatekey, + load_certificate, + load_certificate_request, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_compare_public_keys, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_PYOPENSSL_VERSION = '0.15' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +CRYPTOGRAPHY_VERSION = None +try: + import cryptography + from cryptography import x509 + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class CertificateError(OpenSSLObjectError): + pass + + +@six.add_metaclass(abc.ABCMeta) +class CertificateBackend(object): + def __init__(self, module, backend): + self.module = module + self.backend = backend + + self.force = module.params['force'] + self.privatekey_path = module.params['privatekey_path'] + self.privatekey_content = module.params['privatekey_content'] + if self.privatekey_content is not None: + self.privatekey_content = self.privatekey_content.encode('utf-8') + self.privatekey_passphrase = module.params['privatekey_passphrase'] + self.csr_path = module.params['csr_path'] + self.csr_content = module.params['csr_content'] + if self.csr_content is not None: + self.csr_content = self.csr_content.encode('utf-8') + + # The following are default values which make sure check() works as + # before if providers do not explicitly change these properties. + self.create_subject_key_identifier = 'never_create' + self.create_authority_key_identifier = False + + self.privatekey = None + self.csr = None + self.cert = None + self.existing_certificate = None + self.existing_certificate_bytes = None + + self.check_csr_subject = True + self.check_csr_extensions = True + + @abc.abstractmethod + def generate_certificate(self): + """(Re-)Generate certificate.""" + pass + + @abc.abstractmethod + def get_certificate_data(self): + """Return bytes for self.cert.""" + pass + + def set_existing(self, certificate_bytes): + """Set existing certificate bytes. None indicates that the key does not exist.""" + self.existing_certificate_bytes = certificate_bytes + + def has_existing(self): + """Query whether an existing certificate is/has been there.""" + return self.existing_certificate_bytes is not None + + def _ensure_private_key_loaded(self): + """Load the provided private key into self.privatekey.""" + if self.privatekey is not None: + return + if self.privatekey_path is None and self.privatekey_content is None: + return + try: + self.privatekey = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + except OpenSSLBadPassphraseError as exc: + raise CertificateError(exc) + + def _ensure_csr_loaded(self): + """Load the CSR into self.csr.""" + if self.csr is not None: + return + if self.csr_path is None and self.csr_content is None: + return + self.csr = load_certificate_request( + path=self.csr_path, + content=self.csr_content, + backend=self.backend, + ) + + def _ensure_existing_certificate_loaded(self): + """Load the existing certificate into self.existing_certificate.""" + if self.existing_certificate is not None: + return + if self.existing_certificate_bytes is None: + return + self.existing_certificate = load_certificate( + path=None, + content=self.existing_certificate_bytes, + backend=self.backend, + ) + + def _check_privatekey(self): + """Check whether provided parameters match, assuming self.existing_certificate and self.privatekey have been populated.""" + if self.backend == 'pyopenssl': + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) + ctx.use_privatekey(self.privatekey) + ctx.use_certificate(self.existing_certificate) + try: + ctx.check_privatekey() + return True + except OpenSSL.SSL.Error: + return False + elif self.backend == 'cryptography': + return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key()) + + def _check_csr(self): + """Check whether provided parameters match, assuming self.existing_certificate and self.csr have been populated.""" + if self.backend == 'pyopenssl': + # Verify that CSR is signed by certificate's private key + try: + self.csr.verify(self.existing_certificate.get_pubkey()) + except OpenSSL.crypto.Error: + return False + # Check subject + if self.check_csr_subject and self.csr.get_subject() != self.existing_certificate.get_subject(): + return False + # Check extensions + if not self.check_csr_extensions: + return True + csr_extensions = self.csr.get_extensions() + cert_extension_count = self.existing_certificate.get_extension_count() + if len(csr_extensions) != cert_extension_count: + return False + for extension_number in range(0, cert_extension_count): + cert_extension = self.existing_certificate.get_extension(extension_number) + csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions) + if cert_extension.get_data() != list(csr_extension)[0].get_data(): + return False + return True + elif self.backend == 'cryptography': + # Verify that CSR is signed by certificate's private key + if not self.csr.is_signature_valid: + return False + if not cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key()): + return False + # Check subject + if self.check_csr_subject and self.csr.subject != self.existing_certificate.subject: + return False + # Check extensions + if not self.check_csr_extensions: + return True + cert_exts = list(self.existing_certificate.extensions) + csr_exts = list(self.csr.extensions) + if self.create_subject_key_identifier != 'never_create': + # Filter out SubjectKeyIdentifier extension before comparison + cert_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), cert_exts)) + csr_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), csr_exts)) + if self.create_authority_key_identifier: + # Filter out AuthorityKeyIdentifier extension before comparison + cert_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), cert_exts)) + csr_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), csr_exts)) + if len(cert_exts) != len(csr_exts): + return False + for cert_ext in cert_exts: + try: + csr_ext = self.csr.extensions.get_extension_for_oid(cert_ext.oid) + if cert_ext != csr_ext: + return False + except cryptography.x509.ExtensionNotFound as dummy: + return False + return True + + def _check_subject_key_identifier(self): + """Check whether Subject Key Identifier matches, assuming self.existing_certificate has been populated.""" + if self.backend != 'cryptography': + # We do not support SKI with pyOpenSSL backend + return True + + # Get hold of certificate's SKI + try: + ext = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + except cryptography.x509.ExtensionNotFound as dummy: + return False + # Get hold of CSR's SKI for 'create_if_not_provided' + csr_ext = None + if self.create_subject_key_identifier == 'create_if_not_provided': + try: + csr_ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + except cryptography.x509.ExtensionNotFound as dummy: + pass + if csr_ext is None: + # If CSR had no SKI, or we chose to ignore it ('always_create'), compare with created SKI + if ext.value.digest != x509.SubjectKeyIdentifier.from_public_key(self.existing_certificate.public_key()).digest: + return False + else: + # If CSR had SKI and we didn't ignore it ('create_if_not_provided'), compare SKIs + if ext.value.digest != csr_ext.value.digest: + return False + return True + + def needs_regeneration(self): + """Check whether a regeneration is necessary.""" + if self.force or self.existing_certificate_bytes is None: + return True + + try: + self._ensure_existing_certificate_loaded() + except Exception as dummy: + return True + + # Check whether private key matches + self._ensure_private_key_loaded() + if self.privatekey is not None and not self._check_privatekey(): + return True + + # Check whether CSR matches + self._ensure_csr_loaded() + if self.csr is not None and not self._check_csr(): + return True + + # Check SubjectKeyIdentifier + if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier(): + return True + + return False + + def dump(self, include_certificate): + """Serialize the object into a dictionary.""" + result = { + 'privatekey': self.privatekey_path, + 'csr': self.csr_path + } + if include_certificate: + # Get hold of certificate bytes + certificate_bytes = self.existing_certificate_bytes + if self.cert is not None: + certificate_bytes = self.get_certificate_data() + # Store result + result['certificate'] = certificate_bytes.decode('utf-8') if certificate_bytes else None + return result + + +@six.add_metaclass(abc.ABCMeta) +class CertificateProvider(object): + @abc.abstractmethod + def validate_module_args(self, module): + """Check module arguments""" + + @abc.abstractmethod + def needs_version_two_certs(self, module): + """Whether the provider needs to create a version 2 certificate.""" + + def needs_pyopenssl_get_extensions(self, module): + """Whether the provider needs to use get_extensions() with pyOpenSSL.""" + return True + + @abc.abstractmethod + def create_backend(self, module, backend): + """Create an implementation for a backend. + + Return value must be instance of CertificateBackend. + """ + + +def select_backend(module, backend, provider): + """ + :type module: AnsibleModule + :type backend: str + :type provider: CertificateProvider + """ + provider.validate_module_args(module) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + if provider.needs_version_two_certs(module): + module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates') + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + if provider.needs_pyopenssl_get_extensions(module): + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') + + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + if provider.needs_version_two_certs(module): + module.fail_json(msg='The cryptography backend does not support v2 certificates, ' + 'use select_crypto_backend=pyopenssl for v2 certificates') + + return provider.create_backend(module, backend) + + +def get_certificate_argument_spec(): + return ArgumentSpec( + argument_spec=dict( + provider=dict(type='str', choices=[]), # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py + force=dict(type='bool', default=False,), + csr_path=dict(type='path'), + csr_content=dict(type='str'), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + + # General properties of a certificate + privatekey_path=dict(type='path'), + privatekey_content=dict(type='str', no_log=True), + privatekey_passphrase=dict(type='str', no_log=True), + ), + mutually_exclusive=[ + ['csr_path', 'csr_content'], + ['privatekey_path', 'privatekey_content'], + ], + ) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_acme.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_acme.py new file mode 100644 index 00000000..3215b0ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_acme.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import os +import tempfile +import traceback + +from ansible.module_utils._text import to_native, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + CertificateError, + CertificateBackend, + CertificateProvider, +) + + +class AcmeCertificateBackend(CertificateBackend): + def __init__(self, module, backend): + super(AcmeCertificateBackend, self).__init__(module, backend) + self.accountkey_path = module.params['acme_accountkey_path'] + self.challenge_path = module.params['acme_challenge_path'] + self.use_chain = module.params['acme_chain'] + self.acme_directory = module.params['acme_directory'] + + if self.csr_content is None and self.csr_path is None: + raise CertificateError( + 'csr_path or csr_content is required for ownca provider' + ) + if self.csr_content is None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file %s does not exist' % self.csr_path + ) + + if not os.path.exists(self.accountkey_path): + raise CertificateError( + 'The account key %s does not exist' % self.accountkey_path + ) + + if not os.path.exists(self.challenge_path): + raise CertificateError( + 'The challenge path %s does not exist' % self.challenge_path + ) + + self.acme_tiny_path = self.module.get_bin_path('acme-tiny', required=True) + + def generate_certificate(self): + """(Re-)Generate certificate.""" + + command = [self.acme_tiny_path] + if self.use_chain: + command.append('--chain') + command.extend(['--account-key', self.accountkey_path]) + if self.csr_content is not None: + # We need to temporarily write the CSR to disk + fd, tmpsrc = tempfile.mkstemp() + self.module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit + f = os.fdopen(fd, 'wb') + try: + f.write(self.csr_content) + except Exception as err: + try: + f.close() + except Exception as dummy: + pass + self.module.fail_json( + msg="failed to create temporary CSR file: %s" % to_native(err), + exception=traceback.format_exc() + ) + f.close() + command.extend(['--csr', tmpsrc]) + else: + command.extend(['--csr', self.csr_path]) + command.extend(['--acme-dir', self.challenge_path]) + command.extend(['--directory-url', self.acme_directory]) + + try: + self.cert = to_bytes(self.module.run_command(command, check_rc=True)[1]) + except OSError as exc: + raise CertificateError(exc) + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return self.cert + + def dump(self, include_certificate): + result = super(AcmeCertificateBackend, self).dump(include_certificate) + result['accountkey'] = self.accountkey_path + return result + + +class AcmeCertificateProvider(CertificateProvider): + def validate_module_args(self, module): + if module.params['acme_accountkey_path'] is None: + module.fail_json(msg='The acme_accountkey_path option must be specified for the acme provider.') + if module.params['acme_challenge_path'] is None: + module.fail_json(msg='The acme_challenge_path option must be specified for the acme provider.') + + def needs_version_two_certs(self, module): + return False + + def create_backend(self, module, backend): + return AcmeCertificateBackend(module, backend) + + +def add_acme_provider_to_argument_spec(argument_spec): + argument_spec.argument_spec['provider']['choices'].append('acme') + argument_spec.argument_spec.update(dict( + acme_accountkey_path=dict(type='path'), + acme_challenge_path=dict(type='path'), + acme_chain=dict(type='bool', default=False), + acme_directory=dict(type='str', default="https://acme-v02.api.letsencrypt.org/directory"), + )) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_assertonly.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_assertonly.py new file mode 100644 index 00000000..62ef6c21 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_assertonly.py @@ -0,0 +1,664 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import datetime + +from ansible.module_utils._text import to_native, to_bytes, to_text + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + parse_name_field, + get_relative_time_option, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_compare_public_keys, + cryptography_get_name, + cryptography_name_to_oid, + cryptography_parse_key_usage_params, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( + pyopenssl_normalize_name_attribute, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + CertificateBackend, + CertificateProvider, +) + +try: + import OpenSSL + from OpenSSL import crypto +except ImportError: + pass + +try: + import cryptography + from cryptography import x509 + from cryptography.x509 import NameAttribute, Name +except ImportError: + pass + + +def compare_sets(subset, superset, equality=False): + if equality: + return set(subset) == set(superset) + else: + return all(x in superset for x in subset) + + +def compare_dicts(subset, superset, equality=False): + if equality: + return subset == superset + else: + return all(superset.get(x) == v for x, v in subset.items()) + + +NO_EXTENSION = 'no extension' + + +class AssertOnlyCertificateBackend(CertificateBackend): + def __init__(self, module, backend): + super(AssertOnlyCertificateBackend, self).__init__(module, backend) + + self.signature_algorithms = module.params['signature_algorithms'] + if module.params['subject']: + self.subject = parse_name_field(module.params['subject']) + else: + self.subject = [] + self.subject_strict = module.params['subject_strict'] + if module.params['issuer']: + self.issuer = parse_name_field(module.params['issuer']) + else: + self.issuer = [] + self.issuer_strict = module.params['issuer_strict'] + self.has_expired = module.params['has_expired'] + self.version = module.params['version'] + self.key_usage = module.params['key_usage'] + self.key_usage_strict = module.params['key_usage_strict'] + self.extended_key_usage = module.params['extended_key_usage'] + self.extended_key_usage_strict = module.params['extended_key_usage_strict'] + self.subject_alt_name = module.params['subject_alt_name'] + self.subject_alt_name_strict = module.params['subject_alt_name_strict'] + self.not_before = module.params['not_before'] + self.not_after = module.params['not_after'] + self.valid_at = module.params['valid_at'] + self.invalid_at = module.params['invalid_at'] + self.valid_in = module.params['valid_in'] + if self.valid_in and not self.valid_in.startswith("+") and not self.valid_in.startswith("-"): + try: + int(self.valid_in) + except ValueError: + module.fail_json(msg='The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in) + self.valid_in = "+" + self.valid_in + "s" + + # Load objects + self._ensure_private_key_loaded() + self._ensure_csr_loaded() + + @abc.abstractmethod + def _validate_privatekey(self): + pass + + @abc.abstractmethod + def _validate_csr_signature(self): + pass + + @abc.abstractmethod + def _validate_csr_subject(self): + pass + + @abc.abstractmethod + def _validate_csr_extensions(self): + pass + + @abc.abstractmethod + def _validate_signature_algorithms(self): + pass + + @abc.abstractmethod + def _validate_subject(self): + pass + + @abc.abstractmethod + def _validate_issuer(self): + pass + + @abc.abstractmethod + def _validate_has_expired(self): + pass + + @abc.abstractmethod + def _validate_version(self): + pass + + @abc.abstractmethod + def _validate_key_usage(self): + pass + + @abc.abstractmethod + def _validate_extended_key_usage(self): + pass + + @abc.abstractmethod + def _validate_subject_alt_name(self): + pass + + @abc.abstractmethod + def _validate_not_before(self): + pass + + @abc.abstractmethod + def _validate_not_after(self): + pass + + @abc.abstractmethod + def _validate_valid_at(self): + pass + + @abc.abstractmethod + def _validate_invalid_at(self): + pass + + @abc.abstractmethod + def _validate_valid_in(self): + pass + + def assertonly(self): + messages = [] + if self.privatekey_path is not None or self.privatekey_content is not None: + if not self._validate_privatekey(): + messages.append( + 'Certificate %s and private key %s do not match' % + (self.path, self.privatekey_path or '(provided in module options)') + ) + + if self.csr_path is not None or self.csr_content is not None: + if not self._validate_csr_signature(): + messages.append( + 'Certificate %s and CSR %s do not match: private key mismatch' % + (self.path, self.csr_path or '(provided in module options)') + ) + if not self._validate_csr_subject(): + messages.append( + 'Certificate %s and CSR %s do not match: subject mismatch' % + (self.path, self.csr_path or '(provided in module options)') + ) + if not self._validate_csr_extensions(): + messages.append( + 'Certificate %s and CSR %s do not match: extensions mismatch' % + (self.path, self.csr_path or '(provided in module options)') + ) + + if self.signature_algorithms is not None: + wrong_alg = self._validate_signature_algorithms() + if wrong_alg: + messages.append( + 'Invalid signature algorithm (got %s, expected one of %s)' % + (wrong_alg, self.signature_algorithms) + ) + + if self.subject is not None: + failure = self._validate_subject() + if failure: + dummy, cert_subject = failure + messages.append( + 'Invalid subject component (got %s, expected all of %s to be present)' % + (cert_subject, self.subject) + ) + + if self.issuer is not None: + failure = self._validate_issuer() + if failure: + dummy, cert_issuer = failure + messages.append( + 'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer) + ) + + if self.has_expired is not None: + cert_expired = self._validate_has_expired() + if cert_expired != self.has_expired: + messages.append( + 'Certificate expiration check failed (certificate expiration is %s, expected %s)' % + (cert_expired, self.has_expired) + ) + + if self.version is not None: + cert_version = self._validate_version() + if cert_version != self.version: + messages.append( + 'Invalid certificate version number (got %s, expected %s)' % + (cert_version, self.version) + ) + + if self.key_usage is not None: + failure = self._validate_key_usage() + if failure == NO_EXTENSION: + messages.append('Found no keyUsage extension') + elif failure: + dummy, cert_key_usage = failure + messages.append( + 'Invalid keyUsage components (got %s, expected all of %s to be present)' % + (cert_key_usage, self.key_usage) + ) + + if self.extended_key_usage is not None: + failure = self._validate_extended_key_usage() + if failure == NO_EXTENSION: + messages.append('Found no extendedKeyUsage extension') + elif failure: + dummy, ext_cert_key_usage = failure + messages.append( + 'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (ext_cert_key_usage, self.extended_key_usage) + ) + + if self.subject_alt_name is not None: + failure = self._validate_subject_alt_name() + if failure == NO_EXTENSION: + messages.append('Found no subjectAltName extension') + elif failure: + dummy, cert_san = failure + messages.append( + 'Invalid subjectAltName component (got %s, expected all of %s to be present)' % + (cert_san, self.subject_alt_name) + ) + + if self.not_before is not None: + cert_not_valid_before = self._validate_not_before() + if cert_not_valid_before != get_relative_time_option(self.not_before, 'not_before', backend=self.backend): + messages.append( + 'Invalid not_before component (got %s, expected %s to be present)' % + (cert_not_valid_before, self.not_before) + ) + + if self.not_after is not None: + cert_not_valid_after = self._validate_not_after() + if cert_not_valid_after != get_relative_time_option(self.not_after, 'not_after', backend=self.backend): + messages.append( + 'Invalid not_after component (got %s, expected %s to be present)' % + (cert_not_valid_after, self.not_after) + ) + + if self.valid_at is not None: + not_before, valid_at, not_after = self._validate_valid_at() + if not (not_before <= valid_at <= not_after): + messages.append( + 'Certificate is not valid for the specified date (%s) - not_before: %s - not_after: %s' % + (self.valid_at, not_before, not_after) + ) + + if self.invalid_at is not None: + not_before, invalid_at, not_after = self._validate_invalid_at() + if not_before <= invalid_at <= not_after: + messages.append( + 'Certificate is not invalid for the specified date (%s) - not_before: %s - not_after: %s' % + (self.invalid_at, not_before, not_after) + ) + + if self.valid_in is not None: + not_before, valid_in, not_after = self._validate_valid_in() + if not not_before <= valid_in <= not_after: + messages.append( + 'Certificate is not valid in %s from now (that would be %s) - not_before: %s - not_after: %s' % + (self.valid_in, valid_in, not_before, not_after) + ) + return messages + + def needs_regeneration(self): + self._ensure_existing_certificate_loaded() + if self.existing_certificate is None: + self.messages = ['Certificate not provided'] + else: + self.messages = self.assertonly() + + return len(self.messages) != 0 + + def generate_certificate(self): + self.module.fail_json(msg=' | '.join(self.messages)) + + def get_certificate_data(self): + return self.existing_certificate_bytes + + +class AssertOnlyCertificateBackendCryptography(AssertOnlyCertificateBackend): + """Validate the supplied cert, using the cryptography backend""" + def __init__(self, module): + super(AssertOnlyCertificateBackendCryptography, self).__init__(module, 'cryptography') + + def _validate_privatekey(self): + return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key()) + + def _validate_csr_signature(self): + if not self.csr.is_signature_valid: + return False + return cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key()) + + def _validate_csr_subject(self): + return self.csr.subject == self.existing_certificate.subject + + def _validate_csr_extensions(self): + cert_exts = self.existing_certificate.extensions + csr_exts = self.csr.extensions + if len(cert_exts) != len(csr_exts): + return False + for cert_ext in cert_exts: + try: + csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid) + if cert_ext != csr_ext: + return False + except cryptography.x509.ExtensionNotFound as dummy: + return False + return True + + def _validate_signature_algorithms(self): + if self.existing_certificate.signature_algorithm_oid._name not in self.signature_algorithms: + return self.existing_certificate.signature_algorithm_oid._name + + def _validate_subject(self): + expected_subject = Name([NameAttribute(oid=cryptography_name_to_oid(sub[0]), value=to_text(sub[1])) + for sub in self.subject]) + cert_subject = self.existing_certificate.subject + if not compare_sets(expected_subject, cert_subject, self.subject_strict): + return expected_subject, cert_subject + + def _validate_issuer(self): + expected_issuer = Name([NameAttribute(oid=cryptography_name_to_oid(iss[0]), value=to_text(iss[1])) + for iss in self.issuer]) + cert_issuer = self.existing_certificate.issuer + if not compare_sets(expected_issuer, cert_issuer, self.issuer_strict): + return self.issuer, cert_issuer + + def _validate_has_expired(self): + cert_not_after = self.existing_certificate.not_valid_after + cert_expired = cert_not_after < datetime.datetime.utcnow() + return cert_expired + + def _validate_version(self): + if self.existing_certificate.version == x509.Version.v1: + return 1 + if self.existing_certificate.version == x509.Version.v3: + return 3 + return "unknown" + + def _validate_key_usage(self): + try: + current_key_usage = self.existing_certificate.extensions.get_extension_for_class(x509.KeyUsage).value + test_key_usage = dict( + digital_signature=current_key_usage.digital_signature, + content_commitment=current_key_usage.content_commitment, + key_encipherment=current_key_usage.key_encipherment, + data_encipherment=current_key_usage.data_encipherment, + key_agreement=current_key_usage.key_agreement, + key_cert_sign=current_key_usage.key_cert_sign, + crl_sign=current_key_usage.crl_sign, + encipher_only=False, + decipher_only=False + ) + if test_key_usage['key_agreement']: + test_key_usage.update(dict( + encipher_only=current_key_usage.encipher_only, + decipher_only=current_key_usage.decipher_only + )) + + key_usages = cryptography_parse_key_usage_params(self.key_usage) + if not compare_dicts(key_usages, test_key_usage, self.key_usage_strict): + return self.key_usage, [k for k, v in test_key_usage.items() if v is True] + + except cryptography.x509.ExtensionNotFound: + # This is only bad if the user specified a non-empty list + if self.key_usage: + return NO_EXTENSION + + def _validate_extended_key_usage(self): + try: + current_ext_keyusage = self.existing_certificate.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value + usages = [cryptography_name_to_oid(usage) for usage in self.extended_key_usage] + expected_ext_keyusage = x509.ExtendedKeyUsage(usages) + if not compare_sets(expected_ext_keyusage, current_ext_keyusage, self.extended_key_usage_strict): + return [eku.value for eku in expected_ext_keyusage], [eku.value for eku in current_ext_keyusage] + + except cryptography.x509.ExtensionNotFound: + # This is only bad if the user specified a non-empty list + if self.extended_key_usage: + return NO_EXTENSION + + def _validate_subject_alt_name(self): + try: + current_san = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + expected_san = [cryptography_get_name(san) for san in self.subject_alt_name] + if not compare_sets(expected_san, current_san, self.subject_alt_name_strict): + return self.subject_alt_name, current_san + except cryptography.x509.ExtensionNotFound: + # This is only bad if the user specified a non-empty list + if self.subject_alt_name: + return NO_EXTENSION + + def _validate_not_before(self): + return self.existing_certificate.not_valid_before + + def _validate_not_after(self): + return self.existing_certificate.not_valid_after + + def _validate_valid_at(self): + rt = get_relative_time_option(self.valid_at, 'valid_at', backend=self.backend) + return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after + + def _validate_invalid_at(self): + rt = get_relative_time_option(self.invalid_at, 'invalid_at', backend=self.backend) + return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after + + def _validate_valid_in(self): + valid_in_date = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend) + return self.existing_certificate.not_valid_before, valid_in_date, self.existing_certificate.not_valid_after + + +class AssertOnlyCertificateBackendPyOpenSSL(AssertOnlyCertificateBackend): + """validate the supplied certificate.""" + + def __init__(self, module): + super(AssertOnlyCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl') + + # Ensure inputs are properly sanitized before comparison. + for param in ['signature_algorithms', 'key_usage', 'extended_key_usage', + 'subject_alt_name', 'subject', 'issuer', 'not_before', + 'not_after', 'valid_at', 'invalid_at']: + attr = getattr(self, param) + if isinstance(attr, list) and attr: + if isinstance(attr[0], str): + setattr(self, param, [to_bytes(item) for item in attr]) + elif isinstance(attr[0], tuple): + setattr(self, param, [(to_bytes(item[0]), to_bytes(item[1])) for item in attr]) + elif isinstance(attr, tuple): + setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) + elif isinstance(attr, dict): + setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) + elif isinstance(attr, str): + setattr(self, param, to_bytes(attr)) + + def _validate_privatekey(self): + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) + ctx.use_privatekey(self.privatekey) + ctx.use_certificate(self.existing_certificate) + try: + ctx.check_privatekey() + return True + except OpenSSL.SSL.Error: + return False + + def _validate_csr_signature(self): + try: + self.csr.verify(self.existing_certificate.get_pubkey()) + except OpenSSL.crypto.Error: + return False + + def _validate_csr_subject(self): + if self.csr.get_subject() != self.existing_certificate.get_subject(): + return False + + def _validate_csr_extensions(self): + csr_extensions = self.csr.get_extensions() + cert_extension_count = self.existing_certificate.get_extension_count() + if len(csr_extensions) != cert_extension_count: + return False + for extension_number in range(0, cert_extension_count): + cert_extension = self.existing_certificate.get_extension(extension_number) + csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions) + if cert_extension.get_data() != list(csr_extension)[0].get_data(): + return False + return True + + def _validate_signature_algorithms(self): + if self.existing_certificate.get_signature_algorithm() not in self.signature_algorithms: + return self.existing_certificate.get_signature_algorithm() + + def _validate_subject(self): + expected_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in self.subject] + cert_subject = self.existing_certificate.get_subject().get_components() + current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in cert_subject] + if not compare_sets(expected_subject, current_subject, self.subject_strict): + return expected_subject, current_subject + + def _validate_issuer(self): + expected_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in self.issuer] + cert_issuer = self.existing_certificate.get_issuer().get_components() + current_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in cert_issuer] + if not compare_sets(expected_issuer, current_issuer, self.issuer_strict): + return self.issuer, cert_issuer + + def _validate_has_expired(self): + # The following 3 lines are the same as the current PyOpenSSL code for cert.has_expired(). + # Older version of PyOpenSSL have a buggy implementation, + # to avoid issues with those we added the code from a more recent release here. + + time_string = to_native(self.existing_certificate.get_notAfter()) + not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + cert_expired = not_after < datetime.datetime.utcnow() + return cert_expired + + def _validate_version(self): + # Version numbers in certs are off by one: + # v1: 0, v2: 1, v3: 2 ... + return self.existing_certificate.get_version() + 1 + + def _validate_key_usage(self): + found = False + for extension_idx in range(0, self.existing_certificate.get_extension_count()): + extension = self.existing_certificate.get_extension(extension_idx) + if extension.get_short_name() == b'keyUsage': + found = True + expected_extension = crypto.X509Extension(b"keyUsage", False, b', '.join(self.key_usage)) + key_usage = [usage.strip() for usage in to_text(expected_extension, errors='surrogate_or_strict').split(',')] + current_ku = [usage.strip() for usage in to_text(extension, errors='surrogate_or_strict').split(',')] + if not compare_sets(key_usage, current_ku, self.key_usage_strict): + return self.key_usage, str(extension).split(', ') + if not found: + # This is only bad if the user specified a non-empty list + if self.key_usage: + return NO_EXTENSION + + def _validate_extended_key_usage(self): + found = False + for extension_idx in range(0, self.existing_certificate.get_extension_count()): + extension = self.existing_certificate.get_extension(extension_idx) + if extension.get_short_name() == b'extendedKeyUsage': + found = True + extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extended_key_usage] + current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in + to_bytes(extension, errors='surrogate_or_strict').split(b',')] + if not compare_sets(extKeyUsage, current_xku, self.extended_key_usage_strict): + return self.extended_key_usage, str(extension).split(', ') + if not found: + # This is only bad if the user specified a non-empty list + if self.extended_key_usage: + return NO_EXTENSION + + def _validate_subject_alt_name(self): + found = False + for extension_idx in range(0, self.existing_certificate.get_extension_count()): + extension = self.existing_certificate.get_extension(extension_idx) + if extension.get_short_name() == b'subjectAltName': + found = True + l_altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in + to_text(extension, errors='surrogate_or_strict').split(', ')] + sans = [pyopenssl_normalize_name_attribute(to_text(san, errors='surrogate_or_strict')) for san in self.subject_alt_name] + if not compare_sets(sans, l_altnames, self.subject_alt_name_strict): + return self.subject_alt_name, l_altnames + if not found: + # This is only bad if the user specified a non-empty list + if self.subject_alt_name: + return NO_EXTENSION + + def _validate_not_before(self): + return self.existing_certificate.get_notBefore() + + def _validate_not_after(self): + return self.existing_certificate.get_notAfter() + + def _validate_valid_at(self): + rt = get_relative_time_option(self.valid_at, "valid_at", backend=self.backend) + rt = to_bytes(rt, errors='surrogate_or_strict') + return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter() + + def _validate_invalid_at(self): + rt = get_relative_time_option(self.invalid_at, "invalid_at", backend=self.backend) + rt = to_bytes(rt, errors='surrogate_or_strict') + return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter() + + def _validate_valid_in(self): + valid_in_asn1 = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend) + valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict') + return self.existing_certificate.get_notBefore(), valid_in_date, self.existing_certificate.get_notAfter() + + +class AssertOnlyCertificateProvider(CertificateProvider): + def validate_module_args(self, module): + module.deprecate("The 'assertonly' provider is deprecated; please see the examples of " + "the 'x509_certificate' module on how to replace it with other modules", + version='2.0.0', collection_name='community.crypto') + + def needs_version_two_certs(self, module): + return False + + def create_backend(self, module, backend): + if backend == 'cryptography': + return AssertOnlyCertificateBackendCryptography(module) + if backend == 'pyopenssl': + return AssertOnlyCertificateBackendPyOpenSSL(module) + + +def add_assertonly_provider_to_argument_spec(argument_spec): + argument_spec.argument_spec['provider']['choices'].append('assertonly') + argument_spec.argument_spec.update(dict( + signature_algorithms=dict(type='list', elements='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + subject=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + subject_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'), + issuer=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + issuer_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'), + has_expired=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'), + version=dict(type='int', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + key_usage=dict(type='list', elements='str', aliases=['keyUsage'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + key_usage_strict=dict(type='bool', default=False, aliases=['keyUsage_strict'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + extended_key_usage=dict(type='list', elements='str', aliases=['extendedKeyUsage'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + extended_key_usage_strict=dict(type='bool', default=False, aliases=['extendedKeyUsage_strict'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + subject_alt_name_strict=dict(type='bool', default=False, aliases=['subjectAltName_strict'], + removed_in_version='2.0.0', removed_from_collection='community.crypto'), + not_before=dict(type='str', aliases=['notBefore'], removed_in_version='2.0.0', removed_from_collection='community.crypto'), + not_after=dict(type='str', aliases=['notAfter'], removed_in_version='2.0.0', removed_from_collection='community.crypto'), + valid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + invalid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + valid_in=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'), + )) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py new file mode 100644 index 00000000..0e25ce30 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import datetime +import time +import os + +from ansible.module_utils._text import to_native, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.ecs.api import ECSClient, RestOperationException, SessionConfigurationException + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_certificate, + get_relative_time_option, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + CertificateError, + CertificateBackend, + CertificateProvider, +) + +try: + from cryptography.x509.oid import NameOID +except ImportError: + pass + + +class EntrustCertificateBackend(CertificateBackend): + def __init__(self, module, backend): + super(EntrustCertificateBackend, self).__init__(module, backend) + self.trackingId = None + self.notAfter = get_relative_time_option(module.params['entrust_not_after'], 'entrust_not_after', backend=self.backend) + + if self.csr_content is None and self.csr_path is None: + raise CertificateError( + 'csr_path or csr_content is required for entrust provider' + ) + if self.csr_content is None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file {0} does not exist'.format(self.csr_path) + ) + + self._ensure_csr_loaded() + + # ECS API defaults to using the validated organization tied to the account. + # We want to always force behavior of trying to use the organization provided in the CSR. + # To that end we need to parse out the organization from the CSR. + self.csr_org = None + if self.backend == 'pyopenssl': + csr_subject = self.csr.get_subject() + csr_subject_components = csr_subject.get_components() + for k, v in csr_subject_components: + if k.upper() == 'O': + # Entrust does not support multiple validated organizations in a single certificate + if self.csr_org is not None: + self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations " + "found in Subject DN: '{0}'. ".format(csr_subject))) + else: + self.csr_org = v + elif self.backend == 'cryptography': + csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME) + if len(csr_subject_orgs) == 1: + self.csr_org = csr_subject_orgs[0].value + elif len(csr_subject_orgs) > 1: + self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations found in " + "Subject DN: '{0}'. ".format(self.csr.subject))) + # If no organization in the CSR, explicitly tell ECS that it should be blank in issued cert, not defaulted to + # organization tied to the account. + if self.csr_org is None: + self.csr_org = '' + + try: + self.ecs_client = ECSClient( + entrust_api_user=self.module.params['entrust_api_user'], + entrust_api_key=self.module.params['entrust_api_key'], + entrust_api_cert=self.module.params['entrust_api_client_cert_path'], + entrust_api_cert_key=self.module.params['entrust_api_client_cert_key_path'], + entrust_api_specification_path=self.module.params['entrust_api_specification_path'] + ) + except SessionConfigurationException as e: + module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e.message))) + + def generate_certificate(self): + """(Re-)Generate certificate.""" + body = {} + + # Read the CSR that was generated for us + if self.csr_content is not None: + # csr_content contains bytes + body['csr'] = to_native(self.csr_content) + else: + with open(self.csr_path, 'r') as csr_file: + body['csr'] = csr_file.read() + + body['certType'] = self.module.params['entrust_cert_type'] + + # Handle expiration (30 days if not specified) + expiry = self.notAfter + if not expiry: + gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())) + expiry = gmt_now + datetime.timedelta(days=365) + + expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") + body['certExpiryDate'] = expiry_iso3339 + body['org'] = self.csr_org + body['tracking'] = { + 'requesterName': self.module.params['entrust_requester_name'], + 'requesterEmail': self.module.params['entrust_requester_email'], + 'requesterPhone': self.module.params['entrust_requester_phone'], + } + + try: + result = self.ecs_client.NewCertRequest(Body=body) + self.trackingId = result.get('trackingId') + except RestOperationException as e: + self.module.fail_json(msg='Failed to request new certificate from Entrust Certificate Services (ECS): {0}'.format(to_native(e.message))) + + self.cert_bytes = to_bytes(result.get('endEntityCert')) + self.cert = load_certificate(path=None, content=self.cert_bytes, backend=self.backend) + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return self.cert_bytes + + def needs_regeneration(self): + parent_check = super(EntrustCertificateBackend, self).needs_regeneration() + + try: + cert_details = self._get_cert_details() + except RestOperationException as e: + self.module.fail_json(msg='Failed to get status of existing certificate from Entrust Certificate Services (ECS): {0}.'.format(to_native(e.message))) + + # Always issue a new certificate if the certificate is expired, suspended or revoked + status = cert_details.get('status', False) + if status == 'EXPIRED' or status == 'SUSPENDED' or status == 'REVOKED': + return True + + # If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed + if self.module.params['entrust_cert_type'] and cert_details.get('certType') and self.module.params['entrust_cert_type'] != cert_details.get('certType'): + return True + + return parent_check + + def _get_cert_details(self): + cert_details = {} + try: + self._ensure_existing_certificate_loaded() + except Exception as dummy: + return + if self.existing_certificate: + serial_number = None + expiry = None + if self.backend == 'pyopenssl': + serial_number = "{0:X}".format(self.existing_certificate.get_serial_number()) + time_string = to_native(self.existing_certificate.get_notAfter()) + expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + elif self.backend == 'cryptography': + serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate)) + expiry = self.existing_certificate.not_valid_after + + # get some information about the expiry of this certificate + expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") + cert_details['expiresAfter'] = expiry_iso3339 + + # If a trackingId is not already defined (from the result of a generate) + # use the serial number to identify the tracking Id + if self.trackingId is None and serial_number is not None: + cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {}) + + # Finding 0 or more than 1 result is a very unlikely use case, it simply means we cannot perform additional checks + # on the 'state' as returned by Entrust Certificate Services (ECS). The general certificate validity is + # still checked as it is in the rest of the module. + if len(cert_results) == 1: + self.trackingId = cert_results[0].get('trackingId') + + if self.trackingId is not None: + cert_details.update(self.ecs_client.GetCertificate(trackingId=self.trackingId)) + + return cert_details + + +class EntrustCertificateProvider(CertificateProvider): + def validate_module_args(self, module): + pass + + def needs_version_two_certs(self, module): + return False + + def create_backend(self, module, backend): + return EntrustCertificateBackend(module, backend) + + +def add_entrust_provider_to_argument_spec(argument_spec): + argument_spec.argument_spec['provider']['choices'].append('entrust') + argument_spec.argument_spec.update(dict( + entrust_cert_type=dict(type='str', default='STANDARD_SSL', + choices=['STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', + 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT']), + entrust_requester_email=dict(type='str'), + entrust_requester_name=dict(type='str'), + entrust_requester_phone=dict(type='str'), + entrust_api_user=dict(type='str'), + entrust_api_key=dict(type='str', no_log=True), + entrust_api_client_cert_path=dict(type='path'), + entrust_api_client_cert_key_path=dict(type='path', no_log=True), + entrust_api_specification_path=dict(type='path', default='https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml'), + entrust_not_after=dict(type='str', default='+365d'), + )) + argument_spec.required_if.append( + ['provider', 'entrust', ['entrust_requester_email', 'entrust_requester_name', 'entrust_requester_phone', + 'entrust_api_user', 'entrust_api_key', 'entrust_api_client_cert_path', + 'entrust_api_client_cert_key_path']] + ) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py new file mode 100644 index 00000000..a2e68bbd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py @@ -0,0 +1,363 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import os + +from distutils.version import LooseVersion +from random import randrange + +from ansible.module_utils._text import to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_privatekey, + load_certificate, + get_relative_time_option, + select_message_digest, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_key_needs_digest_for_signing, + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + CRYPTOGRAPHY_VERSION, + CertificateError, + CertificateBackend, + CertificateProvider, +) + +try: + from OpenSSL import crypto +except ImportError: + pass + +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.serialization import Encoding +except ImportError: + pass + + +class OwnCACertificateBackendCryptography(CertificateBackend): + def __init__(self, module): + super(OwnCACertificateBackendCryptography, self).__init__(module, 'cryptography') + + self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier'] + self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier'] + self.notBefore = get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend) + self.notAfter = get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend) + self.digest = select_message_digest(module.params['ownca_digest']) + self.version = module.params['ownca_version'] + self.serial_number = x509.random_serial_number() + self.ca_cert_path = module.params['ownca_path'] + self.ca_cert_content = module.params['ownca_content'] + if self.ca_cert_content is not None: + self.ca_cert_content = self.ca_cert_content.encode('utf-8') + self.ca_privatekey_path = module.params['ownca_privatekey_path'] + self.ca_privatekey_content = module.params['ownca_privatekey_content'] + if self.ca_privatekey_content is not None: + self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8') + self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase'] + + if self.csr_content is None and self.csr_path is None: + raise CertificateError( + 'csr_path or csr_content is required for ownca provider' + ) + if self.csr_content is None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file {0} does not exist'.format(self.csr_path) + ) + if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path): + raise CertificateError( + 'The CA certificate file {0} does not exist'.format(self.ca_cert_path) + ) + if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path): + raise CertificateError( + 'The CA private key file {0} does not exist'.format(self.ca_privatekey_path) + ) + + self._ensure_csr_loaded() + self.ca_cert = load_certificate( + path=self.ca_cert_path, + content=self.ca_cert_content, + backend=self.backend + ) + try: + self.ca_private_key = load_privatekey( + path=self.ca_privatekey_path, + content=self.ca_privatekey_content, + passphrase=self.ca_privatekey_passphrase, + backend=self.backend + ) + except OpenSSLBadPassphraseError as exc: + module.fail_json(msg=str(exc)) + + if cryptography_key_needs_digest_for_signing(self.ca_private_key): + if self.digest is None: + raise CertificateError( + 'The digest %s is not supported with the cryptography backend' % module.params['ownca_digest'] + ) + else: + self.digest = None + + def generate_certificate(self): + """(Re-)Generate certificate.""" + cert_builder = x509.CertificateBuilder() + cert_builder = cert_builder.subject_name(self.csr.subject) + cert_builder = cert_builder.issuer_name(self.ca_cert.subject) + cert_builder = cert_builder.serial_number(self.serial_number) + cert_builder = cert_builder.not_valid_before(self.notBefore) + cert_builder = cert_builder.not_valid_after(self.notAfter) + cert_builder = cert_builder.public_key(self.csr.public_key()) + has_ski = False + for extension in self.csr.extensions: + if isinstance(extension.value, x509.SubjectKeyIdentifier): + if self.create_subject_key_identifier == 'always_create': + continue + has_ski = True + if self.create_authority_key_identifier and isinstance(extension.value, x509.AuthorityKeyIdentifier): + continue + cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) + if not has_ski and self.create_subject_key_identifier != 'never_create': + cert_builder = cert_builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(self.csr.public_key()), + critical=False + ) + if self.create_authority_key_identifier: + try: + ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + cert_builder = cert_builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value) + if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext), + critical=False + ) + except cryptography.x509.ExtensionNotFound: + cert_builder = cert_builder.add_extension( + x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()), + critical=False + ) + + try: + certificate = cert_builder.sign( + private_key=self.ca_private_key, algorithm=self.digest, + backend=default_backend() + ) + except TypeError as e: + if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None: + self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') + raise + + self.cert = certificate + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return self.cert.public_bytes(Encoding.PEM) + + def needs_regeneration(self): + if super(OwnCACertificateBackendCryptography, self).needs_regeneration(): + return True + + # Check AuthorityKeyIdentifier + if self.create_authority_key_identifier: + try: + ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + expected_ext = ( + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value) + if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext) + ) + except cryptography.x509.ExtensionNotFound: + expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()) + + self._ensure_existing_certificate_loaded() + try: + ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) + if ext.value != expected_ext: + return True + except cryptography.x509.ExtensionNotFound as dummy: + return True + + return False + + def dump(self, include_certificate): + result = super(OwnCACertificateBackendCryptography, self).dump(include_certificate) + result.update({ + 'ca_cert': self.ca_cert_path, + 'ca_privatekey': self.ca_privatekey_path, + }) + + if self.module.check_mode: + result.update({ + 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.serial_number, + }) + else: + if self.cert is None: + self.cert = self.existing_certificate + result.update({ + 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': cryptography_serial_number_of_cert(self.cert), + }) + + return result + + +def generate_serial_number(): + """Generate a serial number for a certificate""" + while True: + result = randrange(0, 1 << 160) + if result >= 1000: + return result + + +class OwnCACertificateBackendPyOpenSSL(CertificateBackend): + def __init__(self, module): + super(OwnCACertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl') + + self.notBefore = get_relative_time_option(self.module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend) + self.notAfter = get_relative_time_option(self.module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend) + self.digest = self.module.params['ownca_digest'] + self.version = self.module.params['ownca_version'] + self.serial_number = generate_serial_number() + if self.module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided': + self.module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!') + if self.module.params['ownca_create_authority_key_identifier']: + self.module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!') + self.ca_cert_path = self.module.params['ownca_path'] + self.ca_cert_content = self.module.params['ownca_content'] + if self.ca_cert_content is not None: + self.ca_cert_content = self.ca_cert_content.encode('utf-8') + self.ca_privatekey_path = self.module.params['ownca_privatekey_path'] + self.ca_privatekey_content = self.module.params['ownca_privatekey_content'] + if self.ca_privatekey_content is not None: + self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8') + self.ca_privatekey_passphrase = self.module.params['ownca_privatekey_passphrase'] + + if self.csr_content is None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file {0} does not exist'.format(self.csr_path) + ) + if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path): + raise CertificateError( + 'The CA certificate file {0} does not exist'.format(self.ca_cert_path) + ) + if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path): + raise CertificateError( + 'The CA private key file {0} does not exist'.format(self.ca_privatekey_path) + ) + + self._ensure_csr_loaded() + self.ca_cert = load_certificate( + path=self.ca_cert_path, + content=self.ca_cert_content, + ) + try: + self.ca_privatekey = load_privatekey( + path=self.ca_privatekey_path, + content=self.ca_privatekey_content, + passphrase=self.ca_privatekey_passphrase + ) + except OpenSSLBadPassphraseError as exc: + self.module.fail_json(msg=str(exc)) + + def generate_certificate(self): + """(Re-)Generate certificate.""" + cert = crypto.X509() + cert.set_serial_number(self.serial_number) + cert.set_notBefore(to_bytes(self.notBefore)) + cert.set_notAfter(to_bytes(self.notAfter)) + cert.set_subject(self.csr.get_subject()) + cert.set_issuer(self.ca_cert.get_subject()) + cert.set_version(self.version - 1) + cert.set_pubkey(self.csr.get_pubkey()) + cert.add_extensions(self.csr.get_extensions()) + + cert.sign(self.ca_privatekey, self.digest) + self.cert = cert + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) + + def dump(self, include_certificate): + result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate) + result.update({ + 'ca_cert': self.ca_cert_path, + 'ca_privatekey': self.ca_privatekey_path, + }) + + if self.module.check_mode: + result.update({ + 'notBefore': self.notBefore, + 'notAfter': self.notAfter, + 'serial_number': self.serial_number, + }) + else: + if self.cert is None: + self.cert = self.existing_certificate + result.update({ + 'notBefore': self.cert.get_notBefore(), + 'notAfter': self.cert.get_notAfter(), + 'serial_number': self.cert.get_serial_number(), + }) + + return result + + +class OwnCACertificateProvider(CertificateProvider): + def validate_module_args(self, module): + if module.params['ownca_path'] is None and module.params['ownca_content'] is None: + module.fail_json(msg='One of ownca_path and ownca_content must be specified for the ownca provider.') + if module.params['ownca_privatekey_path'] is None and module.params['ownca_privatekey_content'] is None: + module.fail_json(msg='One of ownca_privatekey_path and ownca_privatekey_content must be specified for the ownca provider.') + + def needs_version_two_certs(self, module): + return module.params['ownca_version'] == 2 + + def create_backend(self, module, backend): + if backend == 'cryptography': + return OwnCACertificateBackendCryptography(module) + if backend == 'pyopenssl': + return OwnCACertificateBackendPyOpenSSL(module) + + +def add_ownca_provider_to_argument_spec(argument_spec): + argument_spec.argument_spec['provider']['choices'].append('ownca') + argument_spec.argument_spec.update(dict( + ownca_path=dict(type='path'), + ownca_content=dict(type='str'), + ownca_privatekey_path=dict(type='path'), + ownca_privatekey_content=dict(type='str', no_log=True), + ownca_privatekey_passphrase=dict(type='str', no_log=True), + ownca_digest=dict(type='str', default='sha256'), + ownca_version=dict(type='int', default=3), + ownca_not_before=dict(type='str', default='+0s'), + ownca_not_after=dict(type='str', default='+3650d'), + ownca_create_subject_key_identifier=dict( + type='str', + default='create_if_not_provided', + choices=['create_if_not_provided', 'always_create', 'never_create'] + ), + ownca_create_authority_key_identifier=dict(type='bool', default=True), + )) + argument_spec.mutually_exclusive.extend([ + ['ownca_path', 'ownca_content'], + ['ownca_privatekey_path', 'ownca_privatekey_content'], + ]) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py new file mode 100644 index 00000000..ae58ca27 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import os + +from random import randrange + +from ansible.module_utils._text import to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + get_relative_time_option, + select_message_digest, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_key_needs_digest_for_signing, + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + CertificateError, + CertificateBackend, + CertificateProvider, +) + +try: + from OpenSSL import crypto +except ImportError: + pass + +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.serialization import Encoding +except ImportError: + pass + + +class SelfSignedCertificateBackendCryptography(CertificateBackend): + def __init__(self, module): + super(SelfSignedCertificateBackendCryptography, self).__init__(module, 'cryptography') + + self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier'] + self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend) + self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend) + self.digest = select_message_digest(module.params['selfsigned_digest']) + self.version = module.params['selfsigned_version'] + self.serial_number = x509.random_serial_number() + + if self.csr_path is not None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file {0} does not exist'.format(self.csr_path) + ) + if self.privatekey_content is None and not os.path.exists(self.privatekey_path): + raise CertificateError( + 'The private key file {0} does not exist'.format(self.privatekey_path) + ) + + self._module = module + + self._ensure_private_key_loaded() + + self._ensure_csr_loaded() + if self.csr is None: + # Create empty CSR on the fly + csr = cryptography.x509.CertificateSigningRequestBuilder() + csr = csr.subject_name(cryptography.x509.Name([])) + digest = None + if cryptography_key_needs_digest_for_signing(self.privatekey): + digest = self.digest + if digest is None: + self.module.fail_json(msg='Unsupported digest "{0}"'.format(module.params['selfsigned_digest'])) + try: + self.csr = csr.sign(self.privatekey, digest, default_backend()) + except TypeError as e: + if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None: + self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') + raise + + if cryptography_key_needs_digest_for_signing(self.privatekey): + if self.digest is None: + raise CertificateError( + 'The digest %s is not supported with the cryptography backend' % module.params['selfsigned_digest'] + ) + else: + self.digest = None + + def generate_certificate(self): + """(Re-)Generate certificate.""" + try: + cert_builder = x509.CertificateBuilder() + cert_builder = cert_builder.subject_name(self.csr.subject) + cert_builder = cert_builder.issuer_name(self.csr.subject) + cert_builder = cert_builder.serial_number(self.serial_number) + cert_builder = cert_builder.not_valid_before(self.notBefore) + cert_builder = cert_builder.not_valid_after(self.notAfter) + cert_builder = cert_builder.public_key(self.privatekey.public_key()) + has_ski = False + for extension in self.csr.extensions: + if isinstance(extension.value, x509.SubjectKeyIdentifier): + if self.create_subject_key_identifier == 'always_create': + continue + has_ski = True + cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) + if not has_ski and self.create_subject_key_identifier != 'never_create': + cert_builder = cert_builder.add_extension( + x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()), + critical=False + ) + except ValueError as e: + raise CertificateError(str(e)) + + try: + certificate = cert_builder.sign( + private_key=self.privatekey, algorithm=self.digest, + backend=default_backend() + ) + except TypeError as e: + if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None: + self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') + raise + + self.cert = certificate + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return self.cert.public_bytes(Encoding.PEM) + + def dump(self, include_certificate): + result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate) + + if self.module.check_mode: + result.update({ + 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.serial_number, + }) + else: + if self.cert is None: + self.cert = self.existing_certificate + result.update({ + 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': cryptography_serial_number_of_cert(self.cert), + }) + + return result + + +def generate_serial_number(): + """Generate a serial number for a certificate""" + while True: + result = randrange(0, 1 << 160) + if result >= 1000: + return result + + +class SelfSignedCertificateBackendPyOpenSSL(CertificateBackend): + def __init__(self, module): + super(SelfSignedCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl') + + if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided': + module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!') + self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend) + self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend) + self.digest = module.params['selfsigned_digest'] + self.version = module.params['selfsigned_version'] + self.serial_number = generate_serial_number() + + if self.csr_path is not None and not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file {0} does not exist'.format(self.csr_path) + ) + if self.privatekey_content is None and not os.path.exists(self.privatekey_path): + raise CertificateError( + 'The private key file {0} does not exist'.format(self.privatekey_path) + ) + + self._ensure_private_key_loaded() + + self._ensure_csr_loaded() + if self.csr is None: + # Create empty CSR on the fly + self.csr = crypto.X509Req() + self.csr.set_pubkey(self.privatekey) + self.csr.sign(self.privatekey, self.digest) + + def generate_certificate(self): + """(Re-)Generate certificate.""" + cert = crypto.X509() + cert.set_serial_number(self.serial_number) + cert.set_notBefore(to_bytes(self.notBefore)) + cert.set_notAfter(to_bytes(self.notAfter)) + cert.set_subject(self.csr.get_subject()) + cert.set_issuer(self.csr.get_subject()) + cert.set_version(self.version - 1) + cert.set_pubkey(self.csr.get_pubkey()) + cert.add_extensions(self.csr.get_extensions()) + + cert.sign(self.privatekey, self.digest) + self.cert = cert + + def get_certificate_data(self): + """Return bytes for self.cert.""" + return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) + + def dump(self, include_certificate): + result = super(SelfSignedCertificateBackendPyOpenSSL, self).dump(include_certificate) + + if self.module.check_mode: + result.update({ + 'notBefore': self.notBefore, + 'notAfter': self.notAfter, + 'serial_number': self.serial_number, + }) + else: + if self.cert is None: + self.cert = self.existing_certificate + result.update({ + 'notBefore': self.cert.get_notBefore(), + 'notAfter': self.cert.get_notAfter(), + 'serial_number': self.cert.get_serial_number(), + }) + + return result + + +class SelfSignedCertificateProvider(CertificateProvider): + def validate_module_args(self, module): + if module.params['privatekey_path'] is None and module.params['privatekey_content'] is None: + module.fail_json(msg='One of privatekey_path and privatekey_content must be specified for the selfsigned provider.') + + def needs_version_two_certs(self, module): + return module.params['selfsigned_version'] == 2 + + def create_backend(self, module, backend): + if backend == 'cryptography': + return SelfSignedCertificateBackendCryptography(module) + if backend == 'pyopenssl': + return SelfSignedCertificateBackendPyOpenSSL(module) + + +def add_selfsigned_provider_to_argument_spec(argument_spec): + argument_spec.argument_spec['provider']['choices'].append('selfsigned') + argument_spec.argument_spec.update(dict( + selfsigned_version=dict(type='int', default=3), + selfsigned_digest=dict(type='str', default='sha256'), + selfsigned_not_before=dict(type='str', default='+0s', aliases=['selfsigned_notBefore']), + selfsigned_not_after=dict(type='str', default='+3650d', aliases=['selfsigned_notAfter']), + selfsigned_create_subject_key_identifier=dict( + type='str', + default='create_if_not_provided', + choices=['create_if_not_provided', 'always_create', 'never_create'] + ), + )) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py new file mode 100644 index 00000000..39222227 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.basic import AnsibleModule + + +class ArgumentSpec: + def __init__(self, argument_spec, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None): + self.argument_spec = argument_spec + self.mutually_exclusive = mutually_exclusive or [] + self.required_together = required_together or [] + self.required_one_of = required_one_of or [] + self.required_if = required_if or [] + self.required_by = required_by or {} + + def create_ansible_module_helper(self, clazz, args, **kwargs): + return clazz( + *args, + argument_spec=self.argument_spec, + mutually_exclusive=self.mutually_exclusive, + required_together=self.required_together, + required_one_of=self.required_one_of, + required_if=self.required_if, + required_by=self.required_by, + **kwargs) + + def create_ansible_module(self, **kwargs): + return self.create_ansible_module_helper(AnsibleModule, (), **kwargs) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py new file mode 100644 index 00000000..f5ba21ff --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py @@ -0,0 +1,842 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import binascii +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils import six +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils._text import to_bytes, to_text + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_privatekey, + load_certificate_request, + parse_name_field, + select_message_digest, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_get_basic_constraints, + cryptography_get_name, + cryptography_name_to_oid, + cryptography_key_needs_digest_for_signing, + cryptography_parse_key_usage_params, + cryptography_parse_relative_distinguished_name, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import ( + REVOCATION_REASON_MAP, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( + pyopenssl_normalize_name_attribute, + pyopenssl_parse_name_constraints, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec + + +MINIMAL_PYOPENSSL_VERSION = '0.15' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.3' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # OpenSSL 1.1.0 or newer + OPENSSL_MUST_STAPLE_NAME = b"tlsfeature" + OPENSSL_MUST_STAPLE_VALUE = b"status_request" + else: + # OpenSSL 1.0.x or older + OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" + OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.x509 + import cryptography.x509.oid + import cryptography.exceptions + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.serialization + import cryptography.hazmat.primitives.hashes + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + CRYPTOGRAPHY_MUST_STAPLE_NAME = cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24") + CRYPTOGRAPHY_MUST_STAPLE_VALUE = b"\x30\x03\x02\x01\x05" + + +class CertificateSigningRequestError(OpenSSLObjectError): + pass + + +# From the object called `module`, only the following properties are used: +# +# - module.params[] +# - module.warn(msg: str) +# - module.fail_json(msg: str, **kwargs) + + +@six.add_metaclass(abc.ABCMeta) +class CertificateSigningRequestBackend(object): + def __init__(self, module, backend): + self.module = module + self.backend = backend + self.digest = module.params['digest'] + self.privatekey_path = module.params['privatekey_path'] + self.privatekey_content = module.params['privatekey_content'] + if self.privatekey_content is not None: + self.privatekey_content = self.privatekey_content.encode('utf-8') + self.privatekey_passphrase = module.params['privatekey_passphrase'] + self.version = module.params['version'] + self.subjectAltName = module.params['subject_alt_name'] + self.subjectAltName_critical = module.params['subject_alt_name_critical'] + self.keyUsage = module.params['key_usage'] + self.keyUsage_critical = module.params['key_usage_critical'] + self.extendedKeyUsage = module.params['extended_key_usage'] + self.extendedKeyUsage_critical = module.params['extended_key_usage_critical'] + self.basicConstraints = module.params['basic_constraints'] + self.basicConstraints_critical = module.params['basic_constraints_critical'] + self.ocspMustStaple = module.params['ocsp_must_staple'] + self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical'] + self.name_constraints_permitted = module.params['name_constraints_permitted'] or [] + self.name_constraints_excluded = module.params['name_constraints_excluded'] or [] + self.name_constraints_critical = module.params['name_constraints_critical'] + self.create_subject_key_identifier = module.params['create_subject_key_identifier'] + self.subject_key_identifier = module.params['subject_key_identifier'] + self.authority_key_identifier = module.params['authority_key_identifier'] + self.authority_cert_issuer = module.params['authority_cert_issuer'] + self.authority_cert_serial_number = module.params['authority_cert_serial_number'] + self.crl_distribution_points = module.params['crl_distribution_points'] + self.csr = None + self.privatekey = None + + if self.create_subject_key_identifier and self.subject_key_identifier is not None: + module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true') + + self.subject = [ + ('C', module.params['country_name']), + ('ST', module.params['state_or_province_name']), + ('L', module.params['locality_name']), + ('O', module.params['organization_name']), + ('OU', module.params['organizational_unit_name']), + ('CN', module.params['common_name']), + ('emailAddress', module.params['email_address']), + ] + + if module.params['subject']: + self.subject = self.subject + parse_name_field(module.params['subject']) + self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]] + + self.using_common_name_for_san = False + if not self.subjectAltName and module.params['use_common_name_for_san']: + for sub in self.subject: + if sub[0] in ('commonName', 'CN'): + self.subjectAltName = ['DNS:%s' % sub[1]] + self.using_common_name_for_san = True + break + + if self.subject_key_identifier is not None: + try: + self.subject_key_identifier = binascii.unhexlify(self.subject_key_identifier.replace(':', '')) + except Exception as e: + raise CertificateSigningRequestError('Cannot parse subject_key_identifier: {0}'.format(e)) + + if self.authority_key_identifier is not None: + try: + self.authority_key_identifier = binascii.unhexlify(self.authority_key_identifier.replace(':', '')) + except Exception as e: + raise CertificateSigningRequestError('Cannot parse authority_key_identifier: {0}'.format(e)) + + self.existing_csr = None + self.existing_csr_bytes = None + + @abc.abstractmethod + def generate_csr(self): + """(Re-)Generate CSR.""" + pass + + @abc.abstractmethod + def get_csr_data(self): + """Return bytes for self.csr.""" + pass + + def set_existing(self, csr_bytes): + """Set existing CSR bytes. None indicates that the CSR does not exist.""" + self.existing_csr_bytes = csr_bytes + + def has_existing(self): + """Query whether an existing CSR is/has been there.""" + return self.existing_csr_bytes is not None + + def _ensure_private_key_loaded(self): + """Load the provided private key into self.privatekey.""" + if self.privatekey is not None: + return + try: + self.privatekey = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + except OpenSSLBadPassphraseError as exc: + raise CertificateSigningRequestError(exc) + + @abc.abstractmethod + def _check_csr(self): + """Check whether provided parameters, assuming self.existing_csr and self.privatekey have been populated.""" + pass + + def needs_regeneration(self): + """Check whether a regeneration is necessary.""" + if self.existing_csr_bytes is None: + return True + try: + self.existing_csr = load_certificate_request(None, content=self.existing_csr_bytes, backend=self.backend) + except Exception as dummy: + return True + self._ensure_private_key_loaded() + return not self._check_csr() + + def dump(self, include_csr): + """Serialize the object into a dictionary.""" + result = { + 'privatekey': self.privatekey_path, + 'subject': self.subject, + 'subjectAltName': self.subjectAltName, + 'keyUsage': self.keyUsage, + 'extendedKeyUsage': self.extendedKeyUsage, + 'basicConstraints': self.basicConstraints, + 'ocspMustStaple': self.ocspMustStaple, + 'name_constraints_permitted': self.name_constraints_permitted, + 'name_constraints_excluded': self.name_constraints_excluded, + } + if include_csr: + # Get hold of CSR bytes + csr_bytes = self.existing_csr_bytes + if self.csr is not None: + csr_bytes = self.get_csr_data() + # Store result + result['csr'] = csr_bytes.decode('utf-8') if csr_bytes else None + return result + + +# Implementation with using pyOpenSSL +class CertificateSigningRequestPyOpenSSLBackend(CertificateSigningRequestBackend): + def __init__(self, module): + for o in ('create_subject_key_identifier', ): + if module.params[o]: + module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o)) + for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number', 'crl_distribution_points'): + if module.params[o] is not None: + module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o)) + super(CertificateSigningRequestPyOpenSSLBackend, self).__init__(module, 'pyopenssl') + + def generate_csr(self): + """(Re-)Generate CSR.""" + self._ensure_private_key_loaded() + + req = crypto.X509Req() + req.set_version(self.version - 1) + subject = req.get_subject() + for entry in self.subject: + if entry[1] is not None: + # Workaround for https://github.com/pyca/pyopenssl/issues/165 + nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(entry[0])) + if nid == 0: + raise CertificateSigningRequestError('Unknown subject field identifier "{0}"'.format(entry[0])) + res = OpenSSL._util.lib.X509_NAME_add_entry_by_NID(subject._name, nid, OpenSSL._util.lib.MBSTRING_UTF8, to_bytes(entry[1]), -1, -1, 0) + if res == 0: + raise CertificateSigningRequestError('Invalid value for subject field identifier "{0}": {1}'.format(entry[0], entry[1])) + + extensions = [] + if self.subjectAltName: + altnames = ', '.join(self.subjectAltName) + try: + extensions.append(crypto.X509Extension(b"subjectAltName", self.subjectAltName_critical, altnames.encode('ascii'))) + except OpenSSL.crypto.Error as e: + raise CertificateSigningRequestError( + 'Error while parsing Subject Alternative Names {0} (check for missing type prefix, such as "DNS:"!): {1}'.format( + ', '.join(["{0}".format(san) for san in self.subjectAltName]), str(e) + ) + ) + + if self.keyUsage: + usages = ', '.join(self.keyUsage) + extensions.append(crypto.X509Extension(b"keyUsage", self.keyUsage_critical, usages.encode('ascii'))) + + if self.extendedKeyUsage: + usages = ', '.join(self.extendedKeyUsage) + extensions.append(crypto.X509Extension(b"extendedKeyUsage", self.extendedKeyUsage_critical, usages.encode('ascii'))) + + if self.basicConstraints: + usages = ', '.join(self.basicConstraints) + extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii'))) + + if self.name_constraints_permitted or self.name_constraints_excluded: + usages = ', '.join( + ['permitted;{0}'.format(name) for name in self.name_constraints_permitted] + + ['excluded;{0}'.format(name) for name in self.name_constraints_excluded] + ) + extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii'))) + + if self.ocspMustStaple: + extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE)) + + if extensions: + req.add_extensions(extensions) + + req.set_pubkey(self.privatekey) + req.sign(self.privatekey, self.digest) + self.csr = req + + def get_csr_data(self): + """Return bytes for self.csr.""" + return crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.csr) + + def _check_csr(self): + def _check_subject(csr): + subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject] + current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in csr.get_subject().get_components()] + if not set(subject) == set(current_subject): + return False + + return True + + def _check_subjectAltName(extensions): + altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '') + altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in + to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()] + if self.subjectAltName: + if (set(altnames) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.subjectAltName]) or + altnames_ext.get_critical() != self.subjectAltName_critical): + return False + else: + if altnames: + return False + + return True + + def _check_keyUsage_(extensions, extName, expected, critical): + usages_ext = [ext for ext in extensions if ext.get_short_name() == extName] + if (not usages_ext and expected) or (usages_ext and not expected): + return False + elif not usages_ext and not expected: + return True + else: + current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')] + expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected] + return set(current) == set(expected) and usages_ext[0].get_critical() == critical + + def _check_keyUsage(extensions): + usages_ext = [ext for ext in extensions if ext.get_short_name() == b'keyUsage'] + if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage): + return False + elif not usages_ext and not self.keyUsage: + return True + else: + # OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values + # (since keyUsage has a fixed bitfield for these values and is not extensible). + # Therefore, we create an extension for the wanted values, and compare the + # data of the extensions (which is the serialized bitfield). + expected_ext = crypto.X509Extension(b"keyUsage", False, ', '.join(self.keyUsage).encode('ascii')) + return usages_ext[0].get_data() == expected_ext.get_data() and usages_ext[0].get_critical() == self.keyUsage_critical + + def _check_extenededKeyUsage(extensions): + return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical) + + def _check_basicConstraints(extensions): + return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical) + + def _check_nameConstraints(extensions): + nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '') + permitted, excluded = pyopenssl_parse_name_constraints(nc_ext) + if self.name_constraints_permitted or self.name_constraints_excluded: + if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]): + return False + if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]): + return False + if nc_ext.get_critical() != self.name_constraints_critical: + return False + else: + if permitted or excluded: + return False + + return True + + def _check_ocspMustStaple(extensions): + oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE] + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: + # Older versions of libssl don't know about OCSP Must Staple + oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05']) + if self.ocspMustStaple: + return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical + else: + return len(oms_ext) == 0 + + def _check_extensions(csr): + extensions = csr.get_extensions() + return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and + _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and + _check_ocspMustStaple(extensions) and _check_nameConstraints(extensions)) + + def _check_signature(csr): + try: + return csr.verify(self.privatekey) + except crypto.Error: + return False + + return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr) + + +def parse_crl_distribution_points(module, crl_distribution_points): + result = [] + for index, parse_crl_distribution_point in enumerate(crl_distribution_points): + try: + params = dict( + full_name=None, + relative_name=None, + crl_issuer=None, + reasons=None, + ) + if parse_crl_distribution_point['full_name'] is not None: + params['full_name'] = [cryptography_get_name(name, 'full name') for name in parse_crl_distribution_point['full_name']] + if parse_crl_distribution_point['relative_name'] is not None: + try: + params['relative_name'] = cryptography_parse_relative_distinguished_name(parse_crl_distribution_point['relative_name']) + except Exception: + # If cryptography's version is < 1.6, the error is probably caused by that + if CRYPTOGRAPHY_VERSION < LooseVersion('1.6'): + raise OpenSSLObjectError('Cannot specify relative_name for cryptography < 1.6') + raise + if parse_crl_distribution_point['crl_issuer'] is not None: + params['crl_issuer'] = [cryptography_get_name(name, 'CRL issuer') for name in parse_crl_distribution_point['crl_issuer']] + if parse_crl_distribution_point['reasons'] is not None: + reasons = [] + for reason in parse_crl_distribution_point['reasons']: + reasons.append(REVOCATION_REASON_MAP[reason]) + params['reasons'] = frozenset(reasons) + result.append(cryptography.x509.DistributionPoint(**params)) + except OpenSSLObjectError as e: + raise OpenSSLObjectError('Error while parsing CRL distribution point #{index}: {error}'.format(index=index, error=e)) + return result + + +# Implementation with using cryptography +class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend): + def __init__(self, module): + super(CertificateSigningRequestCryptographyBackend, self).__init__(module, 'cryptography') + self.cryptography_backend = cryptography.hazmat.backends.default_backend() + if self.version != 1: + module.warn('The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)') + + if self.crl_distribution_points: + self.crl_distribution_points = parse_crl_distribution_points(module, self.crl_distribution_points) + + def generate_csr(self): + """(Re-)Generate CSR.""" + self._ensure_private_key_loaded() + + csr = cryptography.x509.CertificateSigningRequestBuilder() + try: + csr = csr.subject_name(cryptography.x509.Name([ + cryptography.x509.NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject + ])) + except ValueError as e: + raise CertificateSigningRequestError(e) + + if self.subjectAltName: + csr = csr.add_extension(cryptography.x509.SubjectAlternativeName([ + cryptography_get_name(name) for name in self.subjectAltName + ]), critical=self.subjectAltName_critical) + + if self.keyUsage: + params = cryptography_parse_key_usage_params(self.keyUsage) + csr = csr.add_extension(cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical) + + if self.extendedKeyUsage: + usages = [cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage] + csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages), critical=self.extendedKeyUsage_critical) + + if self.basicConstraints: + params = {} + ca, path_length = cryptography_get_basic_constraints(self.basicConstraints) + csr = csr.add_extension(cryptography.x509.BasicConstraints(ca, path_length), critical=self.basicConstraints_critical) + + if self.ocspMustStaple: + try: + # This only works with cryptography >= 2.1 + csr = csr.add_extension(cryptography.x509.TLSFeature([cryptography.x509.TLSFeatureType.status_request]), critical=self.ocspMustStaple_critical) + except AttributeError as dummy: + csr = csr.add_extension( + cryptography.x509.UnrecognizedExtension(CRYPTOGRAPHY_MUST_STAPLE_NAME, CRYPTOGRAPHY_MUST_STAPLE_VALUE), + critical=self.ocspMustStaple_critical + ) + + if self.name_constraints_permitted or self.name_constraints_excluded: + try: + csr = csr.add_extension(cryptography.x509.NameConstraints( + [cryptography_get_name(name, 'name constraints permitted') for name in self.name_constraints_permitted], + [cryptography_get_name(name, 'name constraints excluded') for name in self.name_constraints_excluded], + ), critical=self.name_constraints_critical) + except TypeError as e: + raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e)) + + if self.create_subject_key_identifier: + csr = csr.add_extension( + cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()), + critical=False + ) + elif self.subject_key_identifier is not None: + csr = csr.add_extension(cryptography.x509.SubjectKeyIdentifier(self.subject_key_identifier), critical=False) + + if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None: + issuers = None + if self.authority_cert_issuer is not None: + issuers = [cryptography_get_name(n, 'authority cert issuer') for n in self.authority_cert_issuer] + csr = csr.add_extension( + cryptography.x509.AuthorityKeyIdentifier(self.authority_key_identifier, issuers, self.authority_cert_serial_number), + critical=False + ) + + if self.crl_distribution_points: + csr = csr.add_extension( + cryptography.x509.CRLDistributionPoints(self.crl_distribution_points), + critical=False + ) + + digest = None + if cryptography_key_needs_digest_for_signing(self.privatekey): + digest = select_message_digest(self.digest) + if digest is None: + raise CertificateSigningRequestError('Unsupported digest "{0}"'.format(self.digest)) + try: + self.csr = csr.sign(self.privatekey, digest, self.cryptography_backend) + except TypeError as e: + if str(e) == 'Algorithm must be a registered hash algorithm.' and digest is None: + self.module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') + raise + except UnicodeError as e: + # This catches IDNAErrors, which happens when a bad name is passed as a SAN + # (https://github.com/ansible-collections/community.crypto/issues/105). + # For older cryptography versions, this is handled by idna, which raises + # an idna.core.IDNAError. Later versions of cryptography deprecated and stopped + # requiring idna, whence we cannot easily handle this error. Fortunately, in + # most versions of idna, IDNAError extends UnicodeError. There is only version + # 2.3 where it extends Exception instead (see + # https://github.com/kjd/idna/commit/ebefacd3134d0f5da4745878620a6a1cba86d130 + # and then + # https://github.com/kjd/idna/commit/ea03c7b5db7d2a99af082e0239da2b68aeea702a). + msg = 'Error while creating CSR: {0}\n'.format(e) + if self.using_common_name_for_san: + self.module.fail_json(msg=msg + 'This is probably caused because the Common Name is used as a SAN.' + ' Specifying use_common_name_for_san=false might fix this.') + self.module.fail_json(msg=msg + 'This is probably caused by an invalid Subject Alternative DNS Name.') + + def get_csr_data(self): + """Return bytes for self.csr.""" + return self.csr.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM) + + def _check_csr(self): + """Check whether provided parameters, assuming self.existing_csr and self.privatekey have been populated.""" + def _check_subject(csr): + subject = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject] + current_subject = [(sub.oid, sub.value) for sub in csr.subject] + return set(subject) == set(current_subject) + + def _find_extension(extensions, exttype): + return next( + (ext for ext in extensions if isinstance(ext.value, exttype)), + None + ) + + def _check_subjectAltName(extensions): + current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName) + current_altnames = [str(altname) for altname in current_altnames_ext.value] if current_altnames_ext else [] + altnames = [str(cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else [] + if set(altnames) != set(current_altnames): + return False + if altnames: + if current_altnames_ext.critical != self.subjectAltName_critical: + return False + return True + + def _check_keyUsage(extensions): + current_keyusage_ext = _find_extension(extensions, cryptography.x509.KeyUsage) + if not self.keyUsage: + return current_keyusage_ext is None + elif current_keyusage_ext is None: + return False + params = cryptography_parse_key_usage_params(self.keyUsage) + for param in params: + if getattr(current_keyusage_ext.value, '_' + param) != params[param]: + return False + if current_keyusage_ext.critical != self.keyUsage_critical: + return False + return True + + def _check_extenededKeyUsage(extensions): + current_usages_ext = _find_extension(extensions, cryptography.x509.ExtendedKeyUsage) + current_usages = [str(usage) for usage in current_usages_ext.value] if current_usages_ext else [] + usages = [str(cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else [] + if set(current_usages) != set(usages): + return False + if usages: + if current_usages_ext.critical != self.extendedKeyUsage_critical: + return False + return True + + def _check_basicConstraints(extensions): + bc_ext = _find_extension(extensions, cryptography.x509.BasicConstraints) + current_ca = bc_ext.value.ca if bc_ext else False + current_path_length = bc_ext.value.path_length if bc_ext else None + ca, path_length = cryptography_get_basic_constraints(self.basicConstraints) + # Check CA flag + if ca != current_ca: + return False + # Check path length + if path_length != current_path_length: + return False + # Check criticality + if self.basicConstraints: + if bc_ext.critical != self.basicConstraints_critical: + return False + return True + + def _check_ocspMustStaple(extensions): + try: + # This only works with cryptography >= 2.1 + tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature) + has_tlsfeature = True + except AttributeError as dummy: + tlsfeature_ext = next( + (ext for ext in extensions if ext.value.oid == CRYPTOGRAPHY_MUST_STAPLE_NAME), + None + ) + has_tlsfeature = False + if self.ocspMustStaple: + if not tlsfeature_ext or tlsfeature_ext.critical != self.ocspMustStaple_critical: + return False + if has_tlsfeature: + return cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value + else: + return tlsfeature_ext.value.value == CRYPTOGRAPHY_MUST_STAPLE_VALUE + else: + return tlsfeature_ext is None + + def _check_nameConstraints(extensions): + current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints) + current_nc_perm = [str(altname) for altname in current_nc_ext.value.permitted_subtrees] if current_nc_ext else [] + current_nc_excl = [str(altname) for altname in current_nc_ext.value.excluded_subtrees] if current_nc_ext else [] + nc_perm = [str(cryptography_get_name(altname, 'name constraints permitted')) for altname in self.name_constraints_permitted] + nc_excl = [str(cryptography_get_name(altname, 'name constraints excluded')) for altname in self.name_constraints_excluded] + if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl): + return False + if nc_perm or nc_excl: + if current_nc_ext.critical != self.name_constraints_critical: + return False + return True + + def _check_subject_key_identifier(extensions): + ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier) + if self.create_subject_key_identifier or self.subject_key_identifier is not None: + if not ext or ext.critical: + return False + if self.create_subject_key_identifier: + digest = cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()).digest + return ext.value.digest == digest + else: + return ext.value.digest == self.subject_key_identifier + else: + return ext is None + + def _check_authority_key_identifier(extensions): + ext = _find_extension(extensions, cryptography.x509.AuthorityKeyIdentifier) + if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None: + if not ext or ext.critical: + return False + aci = None + csr_aci = None + if self.authority_cert_issuer is not None: + aci = [str(cryptography_get_name(n, 'authority cert issuer')) for n in self.authority_cert_issuer] + if ext.value.authority_cert_issuer is not None: + csr_aci = [str(n) for n in ext.value.authority_cert_issuer] + return (ext.value.key_identifier == self.authority_key_identifier + and csr_aci == aci + and ext.value.authority_cert_serial_number == self.authority_cert_serial_number) + else: + return ext is None + + def _check_crl_distribution_points(extensions): + ext = _find_extension(extensions, cryptography.x509.CRLDistributionPoints) + if self.crl_distribution_points is None: + return ext is None + if not ext: + return False + return list(ext.value) == self.crl_distribution_points + + def _check_extensions(csr): + extensions = csr.extensions + return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and + _check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and + _check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and + _check_authority_key_identifier(extensions) and _check_nameConstraints(extensions) and + _check_crl_distribution_points(extensions)) + + def _check_signature(csr): + if not csr.is_signature_valid: + return False + # To check whether public key of CSR belongs to private key, + # encode both public keys and compare PEMs. + key_a = csr.public_key().public_bytes( + cryptography.hazmat.primitives.serialization.Encoding.PEM, + cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo + ) + key_b = self.privatekey.public_key().public_bytes( + cryptography.hazmat.primitives.serialization.Encoding.PEM, + cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo + ) + return key_a == key_b + + return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr) + + +def select_backend(module, backend): + if module.params['version'] != 1: + module.deprecate('The version option will only support allowed values from community.crypto 2.0.0 on. ' + 'Currently, only the value 1 is allowed by RFC 2986', + version='2.0.0', collection_name='community.crypto') + + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # First try cryptography, then pyOpenSSL + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs') + + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + return backend, CertificateSigningRequestPyOpenSSLBackend(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + return backend, CertificateSigningRequestCryptographyBackend(module) + else: + raise Exception('Unsupported value for backend: {0}'.format(backend)) + + +def get_csr_argument_spec(): + return ArgumentSpec( + argument_spec=dict( + digest=dict(type='str', default='sha256'), + privatekey_path=dict(type='path'), + privatekey_content=dict(type='str', no_log=True), + privatekey_passphrase=dict(type='str', no_log=True), + version=dict(type='int', default=1), + subject=dict(type='dict'), + country_name=dict(type='str', aliases=['C', 'countryName']), + state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']), + locality_name=dict(type='str', aliases=['L', 'localityName']), + organization_name=dict(type='str', aliases=['O', 'organizationName']), + organizational_unit_name=dict(type='str', aliases=['OU', 'organizationalUnitName']), + common_name=dict(type='str', aliases=['CN', 'commonName']), + email_address=dict(type='str', aliases=['E', 'emailAddress']), + subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName']), + subject_alt_name_critical=dict(type='bool', default=False, aliases=['subjectAltName_critical']), + use_common_name_for_san=dict(type='bool', default=True, aliases=['useCommonNameForSAN']), + key_usage=dict(type='list', elements='str', aliases=['keyUsage']), + key_usage_critical=dict(type='bool', default=False, aliases=['keyUsage_critical']), + extended_key_usage=dict(type='list', elements='str', aliases=['extKeyUsage', 'extendedKeyUsage']), + extended_key_usage_critical=dict(type='bool', default=False, aliases=['extKeyUsage_critical', 'extendedKeyUsage_critical']), + basic_constraints=dict(type='list', elements='str', aliases=['basicConstraints']), + basic_constraints_critical=dict(type='bool', default=False, aliases=['basicConstraints_critical']), + ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']), + ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']), + name_constraints_permitted=dict(type='list', elements='str'), + name_constraints_excluded=dict(type='list', elements='str'), + name_constraints_critical=dict(type='bool', default=False), + create_subject_key_identifier=dict(type='bool', default=False), + subject_key_identifier=dict(type='str'), + authority_key_identifier=dict(type='str'), + authority_cert_issuer=dict(type='list', elements='str'), + authority_cert_serial_number=dict(type='int'), + crl_distribution_points=dict( + type='list', + elements='dict', + options=dict( + full_name=dict(type='list', elements='str'), + relative_name=dict(type='list', elements='str'), + crl_issuer=dict(type='list', elements='str'), + reasons=dict(type='list', elements='str', choices=[ + 'key_compromise', + 'ca_compromise', + 'affiliation_changed', + 'superseded', + 'cessation_of_operation', + 'certificate_hold', + 'privilege_withdrawn', + 'aa_compromise', + ]), + ), + mutually_exclusive=[('full_name', 'relative_name')] + ), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + ), + required_together=[ + ['authority_cert_issuer', 'authority_cert_serial_number'], + ], + mutually_exclusive=[ + ['privatekey_path', 'privatekey_content'], + ], + required_one_of=[ + ['privatekey_path', 'privatekey_content'], + ], + ) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py new file mode 100644 index 00000000..0c8809a3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py @@ -0,0 +1,590 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import base64 +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils import six +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils._text import to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + CRYPTOGRAPHY_HAS_X25519, + CRYPTOGRAPHY_HAS_X25519_FULL, + CRYPTOGRAPHY_HAS_X448, + CRYPTOGRAPHY_HAS_ED25519, + CRYPTOGRAPHY_HAS_ED448, + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_privatekey, + get_fingerprint_of_privatekey, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + identify_private_key_format, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec + + +MINIMAL_PYOPENSSL_VERSION = '0.6' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.exceptions + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.serialization + import cryptography.hazmat.primitives.asymmetric.rsa + import cryptography.hazmat.primitives.asymmetric.dsa + import cryptography.hazmat.primitives.asymmetric.ec + import cryptography.hazmat.primitives.asymmetric.utils + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class PrivateKeyError(OpenSSLObjectError): + pass + + +# From the object called `module`, only the following properties are used: +# +# - module.params[] +# - module.warn(msg: str) +# - module.fail_json(msg: str, **kwargs) + + +@six.add_metaclass(abc.ABCMeta) +class PrivateKeyBackend: + def __init__(self, module, backend): + self.module = module + self.type = module.params['type'] + self.size = module.params['size'] + self.curve = module.params['curve'] + self.passphrase = module.params['passphrase'] + self.cipher = module.params['cipher'] + self.format = module.params['format'] + self.format_mismatch = module.params.get('format_mismatch', 'regenerate') + self.regenerate = module.params.get('regenerate', 'full_idempotence') + self.backend = backend + + self.private_key = None + + self.existing_private_key = None + self.existing_private_key_bytes = None + + @abc.abstractmethod + def generate_private_key(self): + """(Re-)Generate private key.""" + pass + + def convert_private_key(self): + """Convert existing private key (self.existing_private_key) to new private key (self.private_key). + + This is effectively a copy without active conversion. The conversion is done + during load and store; get_private_key_data() uses the destination format to + serialize the key. + """ + self._ensure_existing_private_key_loaded() + self.private_key = self.existing_private_key + + @abc.abstractmethod + def get_private_key_data(self): + """Return bytes for self.private_key.""" + pass + + def set_existing(self, privatekey_bytes): + """Set existing private key bytes. None indicates that the key does not exist.""" + self.existing_private_key_bytes = privatekey_bytes + + def has_existing(self): + """Query whether an existing private key is/has been there.""" + return self.existing_private_key_bytes is not None + + @abc.abstractmethod + def _check_passphrase(self): + """Check whether provided passphrase matches, assuming self.existing_private_key_bytes has been populated.""" + pass + + @abc.abstractmethod + def _ensure_existing_private_key_loaded(self): + """Make sure that self.existing_private_key is populated from self.existing_private_key_bytes.""" + pass + + @abc.abstractmethod + def _check_size_and_type(self): + """Check whether provided size and type matches, assuming self.existing_private_key has been populated.""" + pass + + @abc.abstractmethod + def _check_format(self): + """Check whether the key file format, assuming self.existing_private_key and self.existing_private_key_bytes has been populated.""" + pass + + def needs_regeneration(self): + """Check whether a regeneration is necessary.""" + if self.regenerate == 'always': + return True + if not self.has_existing(): + # key does not exist + return True + if not self._check_passphrase(): + if self.regenerate == 'full_idempotence': + return True + self.module.fail_json(msg='Unable to read the key. The key is protected with a another passphrase / no passphrase or broken.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `full_idempotence` or `always`, or with `force=yes`.') + self._ensure_existing_private_key_loaded() + if self.regenerate != 'never': + if not self._check_size_and_type(): + if self.regenerate in ('partial_idempotence', 'full_idempotence'): + return True + self.module.fail_json(msg='Key has wrong type and/or size.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.') + # During generation step, regenerate if format does not match and format_mismatch == 'regenerate' + if self.format_mismatch == 'regenerate' and self.regenerate != 'never': + if not self._check_format(): + if self.regenerate in ('partial_idempotence', 'full_idempotence'): + return True + self.module.fail_json(msg='Key has wrong format.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.' + ' To convert the key, set `format_mismatch` to `convert`.') + return False + + def needs_conversion(self): + """Check whether a conversion is necessary. Must only be called if needs_regeneration() returned False.""" + # During conversion step, convert if format does not match and format_mismatch == 'convert' + self._ensure_existing_private_key_loaded() + return self.has_existing() and self.format_mismatch == 'convert' and not self._check_format() + + def _get_fingerprint(self): + if self.private_key: + return get_fingerprint_of_privatekey(self.private_key, backend=self.backend) + try: + self._ensure_existing_private_key_loaded() + except Exception as dummy: + # Ignore errors + pass + if self.existing_private_key: + return get_fingerprint_of_privatekey(self.existing_private_key, backend=self.backend) + + def dump(self, include_key): + """Serialize the object into a dictionary.""" + + if not self.private_key: + try: + self._ensure_existing_private_key_loaded() + except Exception as dummy: + # Ignore errors + pass + result = { + 'type': self.type, + 'size': self.size, + 'fingerprint': self._get_fingerprint(), + } + if self.type == 'ECC': + result['curve'] = self.curve + if include_key: + # Get hold of private key bytes + pk_bytes = self.existing_private_key_bytes + if self.private_key is not None: + pk_bytes = self.get_private_key_data() + # Store result + if pk_bytes: + if identify_private_key_format(pk_bytes) == 'raw': + result['privatekey'] = base64.b64encode(pk_bytes) + else: + result['privatekey'] = pk_bytes.decode('utf-8') + else: + result['privatekey'] = None + + return result + + +# Implementation with using pyOpenSSL +class PrivateKeyPyOpenSSLBackend(PrivateKeyBackend): + + def __init__(self, module): + super(PrivateKeyPyOpenSSLBackend, self).__init__(module=module, backend='pyopenssl') + + if self.type == 'RSA': + self.openssl_type = crypto.TYPE_RSA + elif self.type == 'DSA': + self.openssl_type = crypto.TYPE_DSA + else: + self.module.fail_json(msg="PyOpenSSL backend only supports RSA and DSA keys.") + + if self.format != 'auto_ignore': + self.module.fail_json(msg="PyOpenSSL backend only supports auto_ignore format.") + + def generate_private_key(self): + """(Re-)Generate private key.""" + self.private_key = crypto.PKey() + try: + self.private_key.generate_key(self.openssl_type, self.size) + except (TypeError, ValueError) as exc: + raise PrivateKeyError(exc) + + def _ensure_existing_private_key_loaded(self): + if self.existing_private_key is None and self.has_existing(): + try: + self.existing_private_key = load_privatekey( + None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend) + except OpenSSLBadPassphraseError as exc: + raise PrivateKeyError(exc) + + def get_private_key_data(self): + """Return bytes for self.private_key""" + if self.cipher and self.passphrase: + return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key, + self.cipher, to_bytes(self.passphrase)) + else: + return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key) + + def _check_passphrase(self): + try: + load_privatekey(None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend) + return True + except Exception as dummy: + return False + + def _check_size_and_type(self): + return self.size == self.existing_private_key.bits() and self.openssl_type == self.existing_private_key.type() + + def _check_format(self): + # Not supported by this backend + return True + + +# Implementation with using cryptography +class PrivateKeyCryptographyBackend(PrivateKeyBackend): + + def _get_ec_class(self, ectype): + ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(ectype) + if ecclass is None: + self.module.fail_json(msg='Your cryptography version does not support {0}'.format(ectype)) + return ecclass + + def _add_curve(self, name, ectype, deprecated=False): + def create(size): + ecclass = self._get_ec_class(ectype) + return ecclass() + + def verify(privatekey): + ecclass = self._get_ec_class(ectype) + return isinstance(privatekey.private_numbers().public_numbers.curve, ecclass) + + self.curves[name] = { + 'create': create, + 'verify': verify, + 'deprecated': deprecated, + } + + def __init__(self, module): + super(PrivateKeyCryptographyBackend, self).__init__(module=module, backend='cryptography') + + self.curves = dict() + self._add_curve('secp224r1', 'SECP224R1') + self._add_curve('secp256k1', 'SECP256K1') + self._add_curve('secp256r1', 'SECP256R1') + self._add_curve('secp384r1', 'SECP384R1') + self._add_curve('secp521r1', 'SECP521R1') + self._add_curve('secp192r1', 'SECP192R1', deprecated=True) + self._add_curve('sect163k1', 'SECT163K1', deprecated=True) + self._add_curve('sect163r2', 'SECT163R2', deprecated=True) + self._add_curve('sect233k1', 'SECT233K1', deprecated=True) + self._add_curve('sect233r1', 'SECT233R1', deprecated=True) + self._add_curve('sect283k1', 'SECT283K1', deprecated=True) + self._add_curve('sect283r1', 'SECT283R1', deprecated=True) + self._add_curve('sect409k1', 'SECT409K1', deprecated=True) + self._add_curve('sect409r1', 'SECT409R1', deprecated=True) + self._add_curve('sect571k1', 'SECT571K1', deprecated=True) + self._add_curve('sect571r1', 'SECT571R1', deprecated=True) + self._add_curve('brainpoolP256r1', 'BrainpoolP256R1', deprecated=True) + self._add_curve('brainpoolP384r1', 'BrainpoolP384R1', deprecated=True) + self._add_curve('brainpoolP512r1', 'BrainpoolP512R1', deprecated=True) + + self.cryptography_backend = cryptography.hazmat.backends.default_backend() + + if not CRYPTOGRAPHY_HAS_X25519 and self.type == 'X25519': + self.module.fail_json(msg='Your cryptography version does not support X25519') + if not CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519': + self.module.fail_json(msg='Your cryptography version does not support X25519 serialization') + if not CRYPTOGRAPHY_HAS_X448 and self.type == 'X448': + self.module.fail_json(msg='Your cryptography version does not support X448') + if not CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519': + self.module.fail_json(msg='Your cryptography version does not support Ed25519') + if not CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448': + self.module.fail_json(msg='Your cryptography version does not support Ed448') + + def _get_wanted_format(self): + if self.format not in ('auto', 'auto_ignore'): + return self.format + if self.type in ('X25519', 'X448', 'Ed25519', 'Ed448'): + return 'pkcs8' + else: + return 'pkcs1' + + def generate_private_key(self): + """(Re-)Generate private key.""" + try: + if self.type == 'RSA': + self.private_key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key( + public_exponent=65537, # OpenSSL always uses this + key_size=self.size, + backend=self.cryptography_backend + ) + if self.type == 'DSA': + self.private_key = cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key( + key_size=self.size, + backend=self.cryptography_backend + ) + if CRYPTOGRAPHY_HAS_X25519_FULL and self.type == 'X25519': + self.private_key = cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate() + if CRYPTOGRAPHY_HAS_X448 and self.type == 'X448': + self.private_key = cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.generate() + if CRYPTOGRAPHY_HAS_ED25519 and self.type == 'Ed25519': + self.private_key = cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.generate() + if CRYPTOGRAPHY_HAS_ED448 and self.type == 'Ed448': + self.private_key = cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.generate() + if self.type == 'ECC' and self.curve in self.curves: + if self.curves[self.curve]['deprecated']: + self.module.warn('Elliptic curves of type {0} should not be used for new keys!'.format(self.curve)) + self.private_key = cryptography.hazmat.primitives.asymmetric.ec.generate_private_key( + curve=self.curves[self.curve]['create'](self.size), + backend=self.cryptography_backend + ) + except cryptography.exceptions.UnsupportedAlgorithm as dummy: + self.module.fail_json(msg='Cryptography backend does not support the algorithm required for {0}'.format(self.type)) + + def get_private_key_data(self): + """Return bytes for self.private_key""" + # Select export format and encoding + try: + export_format = self._get_wanted_format() + export_encoding = cryptography.hazmat.primitives.serialization.Encoding.PEM + if export_format == 'pkcs1': + # "TraditionalOpenSSL" format is PKCS1 + export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL + elif export_format == 'pkcs8': + export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8 + elif export_format == 'raw': + export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.Raw + export_encoding = cryptography.hazmat.primitives.serialization.Encoding.Raw + except AttributeError: + self.module.fail_json(msg='Cryptography backend does not support the selected output format "{0}"'.format(self.format)) + + # Select key encryption + encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption() + if self.cipher and self.passphrase: + if self.cipher == 'auto': + encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(to_bytes(self.passphrase)) + else: + self.module.fail_json(msg='Cryptography backend can only use "auto" for cipher option.') + + # Serialize key + try: + return self.private_key.private_bytes( + encoding=export_encoding, + format=export_format, + encryption_algorithm=encryption_algorithm + ) + except ValueError as dummy: + self.module.fail_json( + msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(self.format) + ) + except Exception as dummy: + self.module.fail_json( + msg='Error while serializing the private key in the required format "{0}"'.format(self.format), + exception=traceback.format_exc() + ) + + def _load_privatekey(self): + data = self.existing_private_key_bytes + try: + # Interpret bytes depending on format. + format = identify_private_key_format(data) + if format == 'raw': + if len(data) == 56 and CRYPTOGRAPHY_HAS_X448: + return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data) + if len(data) == 57 and CRYPTOGRAPHY_HAS_ED448: + return cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(data) + if len(data) == 32: + if CRYPTOGRAPHY_HAS_X25519 and (self.type == 'X25519' or not CRYPTOGRAPHY_HAS_ED25519): + return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data) + if CRYPTOGRAPHY_HAS_ED25519 and (self.type == 'Ed25519' or not CRYPTOGRAPHY_HAS_X25519): + return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data) + if CRYPTOGRAPHY_HAS_X25519 and CRYPTOGRAPHY_HAS_ED25519: + try: + return cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data) + except Exception: + return cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data) + raise PrivateKeyError('Cannot load raw key') + else: + return cryptography.hazmat.primitives.serialization.load_pem_private_key( + data, + None if self.passphrase is None else to_bytes(self.passphrase), + backend=self.cryptography_backend + ) + except Exception as e: + raise PrivateKeyError(e) + + def _ensure_existing_private_key_loaded(self): + if self.existing_private_key is None and self.has_existing(): + self.existing_private_key = self._load_privatekey() + + def _check_passphrase(self): + try: + format = identify_private_key_format(self.existing_private_key_bytes) + if format == 'raw': + # Raw keys cannot be encrypted. To avoid incompatibilities, we try to + # actually load the key (and return False when this fails). + self._load_privatekey() + # Loading the key succeeded. Only return True when no passphrase was + # provided. + return self.passphrase is None + else: + return cryptography.hazmat.primitives.serialization.load_pem_private_key( + self.existing_private_key_bytes, + None if self.passphrase is None else to_bytes(self.passphrase), + backend=self.cryptography_backend + ) + except Exception as dummy: + return False + + def _check_size_and_type(self): + if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + return self.type == 'RSA' and self.size == self.existing_private_key.key_size + if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey): + return self.type == 'DSA' and self.size == self.existing_private_key.key_size + if CRYPTOGRAPHY_HAS_X25519 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey): + return self.type == 'X25519' + if CRYPTOGRAPHY_HAS_X448 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey): + return self.type == 'X448' + if CRYPTOGRAPHY_HAS_ED25519 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + return self.type == 'Ed25519' + if CRYPTOGRAPHY_HAS_ED448 and isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + return self.type == 'Ed448' + if isinstance(self.existing_private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + if self.type != 'ECC': + return False + if self.curve not in self.curves: + return False + return self.curves[self.curve]['verify'](self.existing_private_key) + + return False + + def _check_format(self): + if self.format == 'auto_ignore': + return True + try: + format = identify_private_key_format(self.existing_private_key_bytes) + return format == self._get_wanted_format() + except Exception as dummy: + return False + + +def select_backend(module, backend): + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # Decision + if module.params['cipher'] and module.params['passphrase'] and module.params['cipher'] != 'auto': + # First try pyOpenSSL, then cryptography + if can_use_pyopenssl: + backend = 'pyopenssl' + elif can_use_cryptography: + backend = 'cryptography' + else: + # First try cryptography, then pyOpenSSL + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + return backend, PrivateKeyPyOpenSSLBackend(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + return backend, PrivateKeyCryptographyBackend(module) + else: + raise Exception('Unsupported value for backend: {0}'.format(backend)) + + +def get_privatekey_argument_spec(): + return ArgumentSpec( + argument_spec=dict( + size=dict(type='int', default=4096), + type=dict(type='str', default='RSA', choices=[ + 'DSA', 'ECC', 'Ed25519', 'Ed448', 'RSA', 'X25519', 'X448' + ]), + curve=dict(type='str', choices=[ + 'secp224r1', 'secp256k1', 'secp256r1', 'secp384r1', 'secp521r1', + 'secp192r1', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1', + 'sect163k1', 'sect163r2', 'sect233k1', 'sect233r1', 'sect283k1', + 'sect283r1', 'sect409k1', 'sect409r1', 'sect571k1', 'sect571r1', + ]), + passphrase=dict(type='str', no_log=True), + cipher=dict(type='str'), + format=dict(type='str', default='auto_ignore', choices=['pkcs1', 'pkcs8', 'raw', 'auto', 'auto_ignore']), + format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']), + select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'), + regenerate=dict( + type='str', + default='full_idempotence', + choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always'] + ), + ), + required_together=[ + ['cipher', 'passphrase'] + ], + required_if=[ + ['type', 'ECC', ['curve']], + ], + ) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/openssh.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/openssh.py new file mode 100644 index 00000000..b41e0161 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/openssh.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# (c) 2020, Doug Stanley <doug+ansible@technologixllc.com> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import re + + +def parse_openssh_version(version_string): + """Parse the version output of ssh -V and return version numbers that can be compared""" + + parsed_result = re.match( + r"^.*openssh_(?P<version>[0-9.]+)(p?[0-9]+)[^0-9]*.*$", version_string.lower() + ) + if parsed_result is not None: + version = parsed_result.group("version").strip() + else: + version = None + + return version diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pem.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pem.py new file mode 100644 index 00000000..a59f710a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pem.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +PEM_START = '-----BEGIN ' +PEM_END = '-----' +PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY') +PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY' + + +def identify_pem_format(content): + '''Given the contents of a binary file, tests whether this could be a PEM file.''' + try: + lines = content.decode('utf-8').splitlines(False) + if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END): + return True + except UnicodeDecodeError: + pass + return False + + +def identify_private_key_format(content): + '''Given the contents of a private key file, identifies its format.''' + # See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85 + # (PEM_read_bio_PrivateKey) + # and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47 + # (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF) + try: + lines = content.decode('utf-8').splitlines(False) + if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END): + name = lines[0][len(PEM_START):-len(PEM_END)] + if name in PKCS8_PRIVATEKEY_NAMES: + return 'pkcs8' + if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(PKCS1_PRIVATEKEY_SUFFIX): + return 'pkcs1' + return 'unknown-pem' + except UnicodeDecodeError: + pass + return 'raw' + + +def split_pem_list(text, keep_inbetween=False): + ''' + Split concatenated PEM objects into a list of strings, where each is one PEM object. + ''' + result = [] + current = [] if keep_inbetween else None + for line in text.splitlines(True): + if line.strip(): + if not keep_inbetween and line.startswith('-----BEGIN '): + current = [] + if current is not None: + current.append(line) + if line.startswith('-----END '): + result.append(''.join(current)) + current = [] if keep_inbetween else None + return result diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pyopenssl_support.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pyopenssl_support.py new file mode 100644 index 00000000..17c98d27 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/pyopenssl_support.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# +# (c) 2019, Felix Fontein <felix@fontein.de> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import base64 + +from ansible.module_utils._text import to_bytes, to_text + +from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress + +try: + import OpenSSL +except ImportError: + # Error handled in the calling module. + pass + +from ._objects import ( + NORMALIZE_NAMES_SHORT, + NORMALIZE_NAMES, +) + +from ._obj2txt import obj2txt + +from .basic import ( + OpenSSLObjectError, +) + + +def pyopenssl_normalize_name(name, short=False): + nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(name)) + if nid != 0: + b_name = OpenSSL._util.lib.OBJ_nid2ln(nid) + name = to_text(OpenSSL._util.ffi.string(b_name)) + if short: + return NORMALIZE_NAMES_SHORT.get(name, name) + else: + return NORMALIZE_NAMES.get(name, name) + + +def pyopenssl_normalize_name_attribute(san): + # apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string + # although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004) + if san.startswith('IP Address:'): + san = 'IP:' + san[len('IP Address:'):] + if san.startswith('IP:'): + address = san[3:] + if '/' in address: + ip = compat_ipaddress.ip_network(address) + san = 'IP:{0}/{1}'.format(ip.network_address.compressed, ip.prefixlen) + else: + ip = compat_ipaddress.ip_address(address) + san = 'IP:{0}'.format(ip.compressed) + if san.startswith('Registered ID:'): + san = 'RID:' + san[len('Registered ID:'):] + # Some versions of OpenSSL apparently forgot the colon. Happens in CI with Ubuntu 16.04 and FreeBSD 11.1 + if san.startswith('Registered ID'): + san = 'RID:' + san[len('Registered ID'):] + return san + + +def pyopenssl_get_extensions_from_cert(cert): + # While pyOpenSSL allows us to get an extension's DER value, it won't + # give us the dotted string for an OID. So we have to do some magic to + # get hold of it. + result = dict() + ext_count = cert.get_extension_count() + for i in range(0, ext_count): + ext = cert.get_extension(i) + entry = dict( + critical=bool(ext.get_critical()), + value=base64.b64encode(ext.get_data()), + ) + oid = obj2txt( + OpenSSL._util.lib, + OpenSSL._util.ffi, + OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension) + ) + # This could also be done a bit simpler: + # + # oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid)) + # + # Unfortunately this gives the wrong result in case the linked OpenSSL + # doesn't know the OID. That's why we have to get the OID dotted string + # similarly to how cryptography does it. + result[oid] = entry + return result + + +def pyopenssl_get_extensions_from_csr(csr): + # While pyOpenSSL allows us to get an extension's DER value, it won't + # give us the dotted string for an OID. So we have to do some magic to + # get hold of it. + result = dict() + for ext in csr.get_extensions(): + entry = dict( + critical=bool(ext.get_critical()), + value=base64.b64encode(ext.get_data()), + ) + oid = obj2txt( + OpenSSL._util.lib, + OpenSSL._util.ffi, + OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension) + ) + # This could also be done a bit simpler: + # + # oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid)) + # + # Unfortunately this gives the wrong result in case the linked OpenSSL + # doesn't know the OID. That's why we have to get the OID dotted string + # similarly to how cryptography does it. + result[oid] = entry + return result + + +def pyopenssl_parse_name_constraints(name_constraints_extension): + lines = to_text(name_constraints_extension, errors='surrogate_or_strict').splitlines() + exclude = None + excluded = [] + permitted = [] + for line in lines: + if line.startswith(' ') or line.startswith('\t'): + name = pyopenssl_normalize_name_attribute(line.strip()) + if exclude is True: + excluded.append(name) + elif exclude is False: + permitted.append(name) + else: + raise OpenSSLObjectError('Unexpected nameConstraint line: "{0}"'.format(line)) + else: + line_lc = line.lower() + if line_lc.startswith('exclud'): + exclude = True + elif line_lc.startswith('includ') or line_lc.startswith('permitt'): + exclude = False + else: + raise OpenSSLObjectError('Cannot parse nameConstraint line: "{0}"'.format(line)) + return permitted, excluded diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py new file mode 100644 index 00000000..7850c6bb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +# +# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import datetime +import errno +import hashlib +import os +import re + +from ansible.module_utils import six +from ansible.module_utils._text import to_native, to_bytes + +try: + from OpenSSL import crypto + HAS_PYOPENSSL = True +except ImportError: + # Error handled in the calling module. + HAS_PYOPENSSL = False + +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend as cryptography_backend + from cryptography.hazmat.primitives.serialization import load_pem_private_key + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives import serialization +except ImportError: + # Error handled in the calling module. + pass + +from .basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + + +def get_fingerprint_of_bytes(source): + """Generate the fingerprint of the given bytes.""" + + fingerprint = {} + + try: + algorithms = hashlib.algorithms + except AttributeError: + try: + algorithms = hashlib.algorithms_guaranteed + except AttributeError: + return None + + for algo in algorithms: + f = getattr(hashlib, algo) + try: + h = f(source) + except ValueError: + # This can happen for hash algorithms not supported in FIPS mode + # (https://github.com/ansible/ansible/issues/67213) + continue + try: + # Certain hash functions have a hexdigest() which expects a length parameter + pubkey_digest = h.hexdigest() + except TypeError: + pubkey_digest = h.hexdigest(32) + fingerprint[algo] = ':'.join(pubkey_digest[i:i + 2] for i in range(0, len(pubkey_digest), 2)) + + return fingerprint + + +def get_fingerprint_of_privatekey(privatekey, backend='pyopenssl'): + """Generate the fingerprint of the public key. """ + + if backend == 'pyopenssl': + try: + publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey) + except AttributeError: + # If PyOpenSSL < 16.0 crypto.dump_publickey() will fail. + try: + bio = crypto._new_mem_buf() + rc = crypto._lib.i2d_PUBKEY_bio(bio, privatekey._pkey) + if rc != 1: + crypto._raise_current_error() + publickey = crypto._bio_to_string(bio) + except AttributeError: + # By doing this we prevent the code from raising an error + # yet we return no value in the fingerprint hash. + return None + elif backend == 'cryptography': + publickey = privatekey.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + return get_fingerprint_of_bytes(publickey) + + +def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl'): + """Generate the fingerprint of the public key. """ + + privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend) + + return get_fingerprint_of_privatekey(privatekey, backend=backend) + + +def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'): + """Load the specified OpenSSL private key. + + The content can also be specified via content; in that case, + this function will not load the key from disk. + """ + + try: + if content is None: + with open(path, 'rb') as b_priv_key_fh: + priv_key_detail = b_priv_key_fh.read() + else: + priv_key_detail = content + except (IOError, OSError) as exc: + raise OpenSSLObjectError(exc) + + if backend == 'pyopenssl': + + # First try: try to load with real passphrase (resp. empty string) + # Will work if this is the correct passphrase, or the key is not + # password-protected. + try: + result = crypto.load_privatekey(crypto.FILETYPE_PEM, + priv_key_detail, + to_bytes(passphrase or '')) + except crypto.Error as e: + if len(e.args) > 0 and len(e.args[0]) > 0: + if e.args[0][0][2] in ('bad decrypt', 'bad password read'): + # This happens in case we have the wrong passphrase. + if passphrase is not None: + raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!') + else: + raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') + raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e)) + if check_passphrase: + # Next we want to make sure that the key is actually protected by + # a passphrase (in case we did try the empty string before, make + # sure that the key is not protected by the empty string) + try: + crypto.load_privatekey(crypto.FILETYPE_PEM, + priv_key_detail, + to_bytes('y' if passphrase == 'x' else 'x')) + if passphrase is not None: + # Since we can load the key without an exception, the + # key isn't password-protected + raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!') + except crypto.Error as e: + if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0: + if e.args[0][0][2] in ('bad decrypt', 'bad password read'): + # The key is obviously protected by the empty string. + # Don't do this at home (if it's possible at all)... + raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') + elif backend == 'cryptography': + try: + result = load_pem_private_key(priv_key_detail, + None if passphrase is None else to_bytes(passphrase), + cryptography_backend()) + except TypeError: + raise OpenSSLBadPassphraseError('Wrong or empty passphrase provided for private key') + except ValueError: + raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key') + + return result + + +def load_certificate(path, content=None, backend='pyopenssl'): + """Load the specified certificate.""" + + try: + if content is None: + with open(path, 'rb') as cert_fh: + cert_content = cert_fh.read() + else: + cert_content = content + except (IOError, OSError) as exc: + raise OpenSSLObjectError(exc) + if backend == 'pyopenssl': + return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content) + elif backend == 'cryptography': + try: + return x509.load_pem_x509_certificate(cert_content, cryptography_backend()) + except ValueError as exc: + raise OpenSSLObjectError(exc) + + +def load_certificate_request(path, content=None, backend='pyopenssl'): + """Load the specified certificate signing request.""" + try: + if content is None: + with open(path, 'rb') as csr_fh: + csr_content = csr_fh.read() + else: + csr_content = content + except (IOError, OSError) as exc: + raise OpenSSLObjectError(exc) + if backend == 'pyopenssl': + return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content) + elif backend == 'cryptography': + try: + return x509.load_pem_x509_csr(csr_content, cryptography_backend()) + except ValueError as exc: + raise OpenSSLObjectError(exc) + + +def parse_name_field(input_dict): + """Take a dict with key: value or key: list_of_values mappings and return a list of tuples""" + + result = [] + for key in input_dict: + if isinstance(input_dict[key], list): + for entry in input_dict[key]: + result.append((key, entry)) + else: + result.append((key, input_dict[key])) + return result + + +def convert_relative_to_datetime(relative_time_string): + """Get a datetime.datetime or None from a string in the time format described in sshd_config(5)""" + + parsed_result = re.match( + r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$", + relative_time_string) + + if parsed_result is None or len(relative_time_string) == 1: + # not matched or only a single "+" or "-" + return None + + offset = datetime.timedelta(0) + if parsed_result.group("weeks") is not None: + offset += datetime.timedelta(weeks=int(parsed_result.group("weeks"))) + if parsed_result.group("days") is not None: + offset += datetime.timedelta(days=int(parsed_result.group("days"))) + if parsed_result.group("hours") is not None: + offset += datetime.timedelta(hours=int(parsed_result.group("hours"))) + if parsed_result.group("minutes") is not None: + offset += datetime.timedelta( + minutes=int(parsed_result.group("minutes"))) + if parsed_result.group("seconds") is not None: + offset += datetime.timedelta( + seconds=int(parsed_result.group("seconds"))) + + if parsed_result.group("prefix") == "+": + return datetime.datetime.utcnow() + offset + else: + return datetime.datetime.utcnow() - offset + + +def get_relative_time_option(input_string, input_name, backend='cryptography'): + """Return an absolute timespec if a relative timespec or an ASN1 formatted + string is provided. + + The return value will be a datetime object for the cryptography backend, + and a ASN1 formatted string for the pyopenssl backend.""" + result = to_native(input_string) + if result is None: + raise OpenSSLObjectError( + 'The timespec "%s" for %s is not valid' % + input_string, input_name) + # Relative time + if result.startswith("+") or result.startswith("-"): + result_datetime = convert_relative_to_datetime(result) + if backend == 'pyopenssl': + return result_datetime.strftime("%Y%m%d%H%M%SZ") + elif backend == 'cryptography': + return result_datetime + # Absolute time + if backend == 'pyopenssl': + return input_string + elif backend == 'cryptography': + for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']: + try: + return datetime.datetime.strptime(result, date_fmt) + except ValueError: + pass + + raise OpenSSLObjectError( + 'The time spec "%s" for %s is invalid' % + (input_string, input_name) + ) + + +def select_message_digest(digest_string): + digest = None + if digest_string == 'sha256': + digest = hashes.SHA256() + elif digest_string == 'sha384': + digest = hashes.SHA384() + elif digest_string == 'sha512': + digest = hashes.SHA512() + elif digest_string == 'sha1': + digest = hashes.SHA1() + elif digest_string == 'md5': + digest = hashes.MD5() + return digest + + +@six.add_metaclass(abc.ABCMeta) +class OpenSSLObject(object): + + def __init__(self, path, state, force, check_mode): + self.path = path + self.state = state + self.force = force + self.name = os.path.basename(path) + self.changed = False + self.check_mode = check_mode + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + + def _check_state(): + return os.path.exists(self.path) + + def _check_perms(module): + file_args = module.load_file_common_arguments(module.params) + return not module.set_fs_attributes_if_different(file_args, False) + + if not perms_required: + return _check_state() + + return _check_state() and _check_perms(module) + + @abc.abstractmethod + def dump(self): + """Serialize the object into a dictionary.""" + + pass + + @abc.abstractmethod + def generate(self): + """Generate the resource.""" + + pass + + def remove(self, module): + """Remove the resource from the filesystem.""" + if self.check_mode: + if os.path.exists(self.path): + self.changed = True + return + + try: + os.remove(self.path) + self.changed = True + except OSError as exc: + if exc.errno != errno.ENOENT: + raise OpenSSLObjectError(exc) + else: + pass diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/ecs/api.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/ecs/api.py new file mode 100644 index 00000000..d89b0333 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/ecs/api.py @@ -0,0 +1,364 @@ +# -*- coding: utf-8 -*- + +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is licensed under the +# Modified BSD License. Modules you write using this snippet, which is embedded +# dynamically by Ansible, still belong to the author of the module, and may assign +# their own license to the complete work. +# +# Copyright (c), Entrust Datacard Corporation, 2019 +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 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. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +import os +import re +import time +import traceback + +from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.six.moves.urllib.parse import urlencode +from ansible.module_utils.six.moves.urllib.error import HTTPError +from ansible.module_utils.urls import Request + +YAML_IMP_ERR = None +try: + import yaml +except ImportError: + YAML_FOUND = False + YAML_IMP_ERR = traceback.format_exc() +else: + YAML_FOUND = True + +valid_file_format = re.compile(r".*(\.)(yml|yaml|json)$") + + +def ecs_client_argument_spec(): + return dict( + entrust_api_user=dict(type='str', required=True), + entrust_api_key=dict(type='str', required=True, no_log=True), + entrust_api_client_cert_path=dict(type='path', required=True), + entrust_api_client_cert_key_path=dict(type='path', required=True, no_log=True), + entrust_api_specification_path=dict(type='path', default='https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml'), + ) + + +class SessionConfigurationException(Exception): + """ Raised if we cannot configure a session with the API """ + + pass + + +class RestOperationException(Exception): + """ Encapsulate a REST API error """ + + def __init__(self, error): + self.status = to_native(error.get("status", None)) + self.errors = [to_native(err.get("message")) for err in error.get("errors", {})] + self.message = to_native(" ".join(self.errors)) + + +def generate_docstring(operation_spec): + """Generate a docstring for an operation defined in operation_spec (swagger)""" + # Description of the operation + docs = operation_spec.get("description", "No Description") + docs += "\n\n" + + # Parameters of the operation + parameters = operation_spec.get("parameters", []) + if len(parameters) != 0: + docs += "\tArguments:\n\n" + for parameter in parameters: + docs += "{0} ({1}:{2}): {3}\n".format( + parameter.get("name"), + parameter.get("type", "No Type"), + "Required" if parameter.get("required", False) else "Not Required", + parameter.get("description"), + ) + + return docs + + +def bind(instance, method, operation_spec): + def binding_scope_fn(*args, **kwargs): + return method(instance, *args, **kwargs) + + # Make sure we don't confuse users; add the proper name and documentation to the function. + # Users can use !help(<function>) to get help on the function from interactive python or pdb + operation_name = operation_spec.get("operationId").split("Using")[0] + binding_scope_fn.__name__ = str(operation_name) + binding_scope_fn.__doc__ = generate_docstring(operation_spec) + + return binding_scope_fn + + +class RestOperation(object): + def __init__(self, session, uri, method, parameters=None): + self.session = session + self.method = method + if parameters is None: + self.parameters = {} + else: + self.parameters = parameters + self.url = "{scheme}://{host}{base_path}{uri}".format(scheme="https", host=session._spec.get("host"), base_path=session._spec.get("basePath"), uri=uri) + + def restmethod(self, *args, **kwargs): + """Do the hard work of making the request here""" + + # gather named path parameters and do substitution on the URL + if self.parameters: + path_parameters = {} + body_parameters = {} + query_parameters = {} + for x in self.parameters: + expected_location = x.get("in") + key_name = x.get("name", None) + key_value = kwargs.get(key_name, None) + if expected_location == "path" and key_name and key_value: + path_parameters.update({key_name: key_value}) + elif expected_location == "body" and key_name and key_value: + body_parameters.update({key_name: key_value}) + elif expected_location == "query" and key_name and key_value: + query_parameters.update({key_name: key_value}) + + if len(body_parameters.keys()) >= 1: + body_parameters = body_parameters.get(list(body_parameters.keys())[0]) + else: + body_parameters = None + else: + path_parameters = {} + query_parameters = {} + body_parameters = None + + # This will fail if we have not set path parameters with a KeyError + url = self.url.format(**path_parameters) + if query_parameters: + # modify the URL to add path parameters + url = url + "?" + urlencode(query_parameters) + + try: + if body_parameters: + body_parameters_json = json.dumps(body_parameters) + response = self.session.request.open(method=self.method, url=url, data=body_parameters_json) + else: + response = self.session.request.open(method=self.method, url=url) + request_error = False + except HTTPError as e: + # An HTTPError has the same methods available as a valid response from request.open + response = e + request_error = True + + # Return the result if JSON and success ({} for empty responses) + # Raise an exception if there was a failure. + try: + result_code = response.getcode() + result = json.loads(response.read()) + except ValueError: + result = {} + + if result or result == {}: + if result_code and result_code < 400: + return result + else: + raise RestOperationException(result) + + # Raise a generic RestOperationException if this fails + raise RestOperationException({"status": result_code, "errors": [{"message": "REST Operation Failed"}]}) + + +class Resource(object): + """ Implement basic CRUD operations against a path. """ + + def __init__(self, session): + self.session = session + self.parameters = {} + + for url in session._spec.get("paths").keys(): + methods = session._spec.get("paths").get(url) + for method in methods.keys(): + operation_spec = methods.get(method) + operation_name = operation_spec.get("operationId", None) + parameters = operation_spec.get("parameters") + + if not operation_name: + if method.lower() == "post": + operation_name = "Create" + elif method.lower() == "get": + operation_name = "Get" + elif method.lower() == "put": + operation_name = "Update" + elif method.lower() == "delete": + operation_name = "Delete" + elif method.lower() == "patch": + operation_name = "Patch" + else: + raise SessionConfigurationException(to_native("Invalid REST method type {0}".format(method))) + + # Get the non-parameter parts of the URL and append to the operation name + # e.g /application/version -> GetApplicationVersion + # e.g. /application/{id} -> GetApplication + # This may lead to duplicates, which we must prevent. + operation_name += re.sub(r"{(.*)}", "", url).replace("/", " ").title().replace(" ", "") + operation_spec["operationId"] = operation_name + + op = RestOperation(session, url, method, parameters) + setattr(self, operation_name, bind(self, op.restmethod, operation_spec)) + + +# Session to encapsulate the connection parameters of the module_utils Request object, the api spec, etc +class ECSSession(object): + def __init__(self, name, **kwargs): + """ + Initialize our session + """ + + self._set_config(name, **kwargs) + + def client(self): + resource = Resource(self) + return resource + + def _set_config(self, name, **kwargs): + headers = { + "Content-Type": "application/json", + "Connection": "keep-alive", + } + self.request = Request(headers=headers, timeout=60) + + configurators = [self._read_config_vars] + for configurator in configurators: + self._config = configurator(name, **kwargs) + if self._config: + break + if self._config is None: + raise SessionConfigurationException(to_native("No Configuration Found.")) + + # set up auth if passed + entrust_api_user = self.get_config("entrust_api_user") + entrust_api_key = self.get_config("entrust_api_key") + if entrust_api_user and entrust_api_key: + self.request.url_username = entrust_api_user + self.request.url_password = entrust_api_key + else: + raise SessionConfigurationException(to_native("User and key must be provided.")) + + # set up client certificate if passed (support all-in one or cert + key) + entrust_api_cert = self.get_config("entrust_api_cert") + entrust_api_cert_key = self.get_config("entrust_api_cert_key") + if entrust_api_cert: + self.request.client_cert = entrust_api_cert + if entrust_api_cert_key: + self.request.client_key = entrust_api_cert_key + else: + raise SessionConfigurationException(to_native("Client certificate for authentication to the API must be provided.")) + + # set up the spec + entrust_api_specification_path = self.get_config("entrust_api_specification_path") + + if not entrust_api_specification_path.startswith("http") and not os.path.isfile(entrust_api_specification_path): + raise SessionConfigurationException(to_native("OpenAPI specification was not found at location {0}.".format(entrust_api_specification_path))) + if not valid_file_format.match(entrust_api_specification_path): + raise SessionConfigurationException(to_native("OpenAPI specification filename must end in .json, .yml or .yaml")) + + self.verify = True + + if entrust_api_specification_path.startswith("http"): + try: + http_response = Request().open(method="GET", url=entrust_api_specification_path) + http_response_contents = http_response.read() + if entrust_api_specification_path.endswith(".json"): + self._spec = json.load(http_response_contents) + elif entrust_api_specification_path.endswith(".yml") or entrust_api_specification_path.endswith(".yaml"): + self._spec = yaml.safe_load(http_response_contents) + except HTTPError as e: + raise SessionConfigurationException(to_native("Error downloading specification from address '{0}', received error code '{1}'".format( + entrust_api_specification_path, e.getcode()))) + else: + with open(entrust_api_specification_path) as f: + if ".json" in entrust_api_specification_path: + self._spec = json.load(f) + elif ".yml" in entrust_api_specification_path or ".yaml" in entrust_api_specification_path: + self._spec = yaml.safe_load(f) + + def get_config(self, item): + return self._config.get(item, None) + + def _read_config_vars(self, name, **kwargs): + """ Read configuration from variables passed to the module. """ + config = {} + + entrust_api_specification_path = kwargs.get("entrust_api_specification_path") + if not entrust_api_specification_path or (not entrust_api_specification_path.startswith("http") and not os.path.isfile(entrust_api_specification_path)): + raise SessionConfigurationException( + to_native( + "Parameter provided for entrust_api_specification_path of value '{0}' was not a valid file path or HTTPS address.".format( + entrust_api_specification_path + ) + ) + ) + + for required_file in ["entrust_api_cert", "entrust_api_cert_key"]: + file_path = kwargs.get(required_file) + if not file_path or not os.path.isfile(file_path): + raise SessionConfigurationException( + to_native("Parameter provided for {0} of value '{1}' was not a valid file path.".format(required_file, file_path)) + ) + + for required_var in ["entrust_api_user", "entrust_api_key"]: + if not kwargs.get(required_var): + raise SessionConfigurationException(to_native("Parameter provided for {0} was missing.".format(required_var))) + + config["entrust_api_cert"] = kwargs.get("entrust_api_cert") + config["entrust_api_cert_key"] = kwargs.get("entrust_api_cert_key") + config["entrust_api_specification_path"] = kwargs.get("entrust_api_specification_path") + config["entrust_api_user"] = kwargs.get("entrust_api_user") + config["entrust_api_key"] = kwargs.get("entrust_api_key") + + return config + + +def ECSClient(entrust_api_user=None, entrust_api_key=None, entrust_api_cert=None, entrust_api_cert_key=None, entrust_api_specification_path=None): + """Create an ECS client""" + + if not YAML_FOUND: + raise SessionConfigurationException(missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) + + if entrust_api_specification_path is None: + entrust_api_specification_path = "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml" + + # Not functionally necessary with current uses of this module_util, but better to be explicit for future use cases + entrust_api_user = to_text(entrust_api_user) + entrust_api_key = to_text(entrust_api_key) + entrust_api_cert_key = to_text(entrust_api_cert_key) + entrust_api_specification_path = to_text(entrust_api_specification_path) + + return ECSSession( + "ecs", + entrust_api_user=entrust_api_user, + entrust_api_key=entrust_api_key, + entrust_api_cert=entrust_api_cert, + entrust_api_cert_key=entrust_api_cert_key, + entrust_api_specification_path=entrust_api_specification_path, + ).client() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/io.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/io.py new file mode 100644 index 00000000..debc499a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/module_utils/io.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# +# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import errno +import os +import tempfile + + +def load_file_if_exists(path, module=None, ignore_errors=False): + ''' + Load the file as a bytes string. If the file does not exist, ``None`` is returned. + + If ``ignore_errors`` is ``True``, will ignore errors. Otherwise, errors are + raised as exceptions if ``module`` is not specified, and result in ``module.fail_json`` + being called when ``module`` is specified. + ''' + try: + with open(path, 'rb') as f: + return f.read() + except EnvironmentError as exc: + if exc.errno == errno.ENOENT: + return None + if ignore_errors: + return None + if module is None: + raise + module.fail_json('Error while loading {0} - {1}'.format(path, str(exc))) + except Exception as exc: + if ignore_errors: + return None + if module is None: + raise + module.fail_json('Error while loading {0} - {1}'.format(path, str(exc))) + + +def write_file(module, content, default_mode=None, path=None): + ''' + Writes content into destination file as securely as possible. + Uses file arguments from module. + ''' + # Find out parameters for file + try: + file_args = module.load_file_common_arguments(module.params, path=path) + except TypeError: + # The path argument is only supported in Ansible 2.10+. Fall back to + # pre-2.10 behavior of module_utils/crypto.py for older Ansible versions. + file_args = module.load_file_common_arguments(module.params) + if path is not None: + file_args['path'] = path + if file_args['mode'] is None: + file_args['mode'] = default_mode + # Create tempfile name + tmp_fd, tmp_name = tempfile.mkstemp(prefix=b'.ansible_tmp') + try: + os.close(tmp_fd) + except Exception: + pass + module.add_cleanup_file(tmp_name) # if we fail, let Ansible try to remove the file + try: + try: + # Create tempfile + file = os.open(tmp_name, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + os.write(file, content) + os.close(file) + except Exception as e: + try: + os.remove(tmp_name) + except Exception: + pass + module.fail_json(msg='Error while writing result into temporary file: {0}'.format(e)) + # Update destination to wanted permissions + if os.path.exists(file_args['path']): + module.set_fs_attributes_if_different(file_args, False) + # Move tempfile to final destination + module.atomic_move(tmp_name, file_args['path']) + # Try to update permissions again + module.set_fs_attributes_if_different(file_args, False) + except Exception as e: + try: + os.remove(tmp_name) + except Exception: + pass + module.fail_json(msg='Error while writing result: {0}'.format(e)) diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account.py new file mode 100644 index 00000000..1d538e3b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_account +author: "Felix Fontein (@felixfontein)" +short_description: Create, modify or delete ACME accounts +description: + - "Allows to create, modify or delete accounts with a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + such as L(Let's Encrypt,https://letsencrypt.org/)." + - "This module only works with the ACME v2 protocol." +notes: + - "The M(community.crypto.acme_certificate) module also allows to do basic account management. + When using both modules, it is recommended to disable account management + for M(community.crypto.acme_certificate). For that, use the C(modify_account) option of + M(community.crypto.acme_certificate)." +seealso: + - name: Automatic Certificate Management Environment (ACME) + description: The specification of the ACME protocol (RFC 8555). + link: https://tools.ietf.org/html/rfc8555 + - module: community.crypto.acme_account_info + description: Retrieves facts about an ACME account. + - module: community.crypto.openssl_privatekey + description: Can be used to create a private account key. + - module: community.crypto.acme_inspect + description: Allows to debug problems. +extends_documentation_fragment: +- community.crypto.acme + +options: + state: + description: + - "The state of the account, to be identified by its account key." + - "If the state is C(absent), the account will either not exist or be + deactivated." + - "If the state is C(changed_key), the account must exist. The account + key will be changed; no other information will be touched." + type: str + required: true + choices: + - present + - absent + - changed_key + allow_creation: + description: + - "Whether account creation is allowed (when state is C(present))." + type: bool + default: yes + contact: + description: + - "A list of contact URLs." + - "Email addresses must be prefixed with C(mailto:)." + - "See U(https://tools.ietf.org/html/rfc8555#section-7.3) + for what is allowed." + - "Must be specified when state is C(present). Will be ignored + if state is C(absent) or C(changed_key)." + type: list + elements: str + default: [] + terms_agreed: + description: + - "Boolean indicating whether you agree to the terms of service document." + - "ACME servers can require this to be true." + type: bool + default: no + new_account_key_src: + description: + - "Path to a file containing the ACME account RSA or Elliptic Curve key to change to." + - "Same restrictions apply as to C(account_key_src)." + - "Mutually exclusive with C(new_account_key_content)." + - "Required if C(new_account_key_content) is not used and state is C(changed_key)." + type: path + new_account_key_content: + description: + - "Content of the ACME account RSA or Elliptic Curve key to change to." + - "Same restrictions apply as to C(account_key_content)." + - "Mutually exclusive with C(new_account_key_src)." + - "Required if C(new_account_key_src) is not used and state is C(changed_key)." + type: str + external_account_binding: + description: + - Allows to provide external account binding data during account creation. + - This is used by CAs like Sectigo to bind a new ACME account to an existing CA-specific + account, to be able to properly identify a customer. + - Only used when creating a new account. Can not be specified for ACME v1. + type: dict + suboptions: + kid: + description: + - The key identifier provided by the CA. + type: str + required: true + alg: + description: + - The MAC algorithm provided by the CA. + - If not specified by the CA, this is probably C(HS256). + type: str + required: true + choices: [ HS256, HS384, HS512 ] + key: + description: + - Base64 URL encoded value of the MAC key provided by the CA. + - Padding (C(=) symbols at the end) can be omitted. + type: str + required: true + version_added: 1.1.0 +''' + +EXAMPLES = ''' +- name: Make sure account exists and has given contacts. We agree to TOS. + community.crypto.acme_account: + account_key_src: /etc/pki/cert/private/account.key + state: present + terms_agreed: yes + contact: + - mailto:me@example.com + - mailto:myself@example.org + +- name: Make sure account has given email address. Don't create account if it doesn't exist + community.crypto.acme_account: + account_key_src: /etc/pki/cert/private/account.key + state: present + allow_creation: no + contact: + - mailto:me@example.com + +- name: Change account's key to the one stored in the variable new_account_key + community.crypto.acme_account: + account_key_src: /etc/pki/cert/private/account.key + new_account_key_content: '{{ new_account_key }}' + state: changed_key + +- name: Delete account (we have to use the new key) + community.crypto.acme_account: + account_key_content: '{{ new_account_key }}' + state: absent +''' + +RETURN = ''' +account_uri: + description: ACME account URI, or None if account does not exist. + returned: always + type: str +''' + +import base64 + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + ACMEAccount, + handle_standard_module_arguments, + get_default_argspec, +) + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + terms_agreed=dict(type='bool', default=False), + state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']), + allow_creation=dict(type='bool', default=True), + contact=dict(type='list', elements='str', default=[]), + new_account_key_src=dict(type='path'), + new_account_key_content=dict(type='str', no_log=True), + external_account_binding=dict(type='dict', options=dict( + kid=dict(type='str', required=True), + alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']), + key=dict(type='str', required=True, no_log=True), + )) + )) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=( + ['account_key_src', 'account_key_content'], + ), + mutually_exclusive=( + ['account_key_src', 'account_key_content'], + ['new_account_key_src', 'new_account_key_content'], + ), + required_if=( + # Make sure that for state == changed_key, one of + # new_account_key_src and new_account_key_content are specified + ['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True], + ), + supports_check_mode=True, + ) + handle_standard_module_arguments(module, needs_acme_v2=True) + + if module.params['external_account_binding']: + # Make sure padding is there + key = module.params['external_account_binding']['key'] + if len(key) % 4 != 0: + key = key + ('=' * (4 - (len(key) % 4))) + # Make sure key is Base64 encoded + try: + base64.urlsafe_b64decode(key) + except Exception as e: + module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e) + module.params['external_account_binding']['key'] = key + + try: + account = ACMEAccount(module) + changed = False + state = module.params.get('state') + diff_before = {} + diff_after = {} + if state == 'absent': + created, account_data = account.setup_account(allow_creation=False) + if account_data: + diff_before = dict(account_data) + diff_before['public_account_key'] = account.key_data['jwk'] + if created: + raise AssertionError('Unwanted account creation') + if account_data is not None: + # Account is not yet deactivated + if not module.check_mode: + # Deactivate it + payload = { + 'status': 'deactivated' + } + result, info = account.send_signed_request(account.uri, payload) + if info['status'] != 200: + raise ModuleFailException('Error deactivating account: {0} {1}'.format(info['status'], result)) + changed = True + elif state == 'present': + allow_creation = module.params.get('allow_creation') + contact = [str(v) for v in module.params.get('contact')] + terms_agreed = module.params.get('terms_agreed') + external_account_binding = module.params.get('external_account_binding') + created, account_data = account.setup_account( + contact, + terms_agreed=terms_agreed, + allow_creation=allow_creation, + external_account_binding=external_account_binding, + ) + if account_data is None: + raise ModuleFailException(msg='Account does not exist or is deactivated.') + if created: + diff_before = {} + else: + diff_before = dict(account_data) + diff_before['public_account_key'] = account.key_data['jwk'] + updated = False + if not created: + updated, account_data = account.update_account(account_data, contact) + changed = created or updated + diff_after = dict(account_data) + diff_after['public_account_key'] = account.key_data['jwk'] + elif state == 'changed_key': + # Parse new account key + error, new_key_data = account.parse_key( + module.params.get('new_account_key_src'), + module.params.get('new_account_key_content') + ) + if error: + raise ModuleFailException("error while parsing account key: %s" % error) + # Verify that the account exists and has not been deactivated + created, account_data = account.setup_account(allow_creation=False) + if created: + raise AssertionError('Unwanted account creation') + if account_data is None: + raise ModuleFailException(msg='Account does not exist or is deactivated.') + diff_before = dict(account_data) + diff_before['public_account_key'] = account.key_data['jwk'] + # Now we can start the account key rollover + if not module.check_mode: + # Compose inner signed message + # https://tools.ietf.org/html/rfc8555#section-7.3.5 + url = account.directory['keyChange'] + protected = { + "alg": new_key_data['alg'], + "jwk": new_key_data['jwk'], + "url": url, + } + payload = { + "account": account.uri, + "newKey": new_key_data['jwk'], # specified in draft 12 and older + "oldKey": account.jwk, # specified in draft 13 and newer + } + data = account.sign_request(protected, payload, new_key_data) + # Send request and verify result + result, info = account.send_signed_request(url, data) + if info['status'] != 200: + raise ModuleFailException('Error account key rollover: {0} {1}'.format(info['status'], result)) + if module._diff: + account.key_data = new_key_data + account.jws_header['alg'] = new_key_data['alg'] + diff_after = account.get_account_data() + elif module._diff: + # Kind of fake diff_after + diff_after = dict(diff_before) + diff_after['public_account_key'] = new_key_data['jwk'] + changed = True + result = { + 'changed': changed, + 'account_uri': account.uri, + } + if module._diff: + result['diff'] = { + 'before': diff_before, + 'after': diff_after, + } + module.exit_json(**result) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_facts.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_facts.py new file mode 100644 index 00000000..47d5981e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_facts.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_account_info +author: "Felix Fontein (@felixfontein)" +short_description: Retrieves information on ACME accounts +description: + - "Allows to retrieve information on accounts a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + such as L(Let's Encrypt,https://letsencrypt.org/)." + - "This module only works with the ACME v2 protocol." +notes: + - "The M(community.crypto.acme_account) module allows to modify, create and delete ACME + accounts." + - "This module was called C(acme_account_facts) before Ansible 2.8. The usage + did not change." + - Supports C(check_mode). +options: + retrieve_orders: + description: + - "Whether to retrieve the list of order URLs or order objects, if provided + by the ACME server." + - "A value of C(ignore) will not fetch the list of orders." + - "Currently, Let's Encrypt does not return orders, so the C(orders) result + will always be empty." + type: str + choices: + - ignore + - url_list + - object_list + default: ignore +seealso: + - module: community.crypto.acme_account + description: Allows to create, modify or delete an ACME account. +extends_documentation_fragment: +- community.crypto.acme + +''' + +EXAMPLES = ''' +- name: Check whether an account with the given account key exists + community.crypto.acme_account_info: + account_key_src: /etc/pki/cert/private/account.key + register: account_data +- name: Verify that account exists + assert: + that: + - account_data.exists +- name: Print account URI + ansible.builtin.debug: + var: account_data.account_uri +- name: Print account contacts + ansible.builtin.debug: + var: account_data.account.contact + +- name: Check whether the account exists and is accessible with the given account key + acme_account_info: + account_key_content: "{{ acme_account_key }}" + account_uri: "{{ acme_account_uri }}" + register: account_data +- name: Verify that account exists + assert: + that: + - account_data.exists +- name: Print account contacts + ansible.builtin.debug: + var: account_data.account.contact +''' + +RETURN = ''' +exists: + description: Whether the account exists. + returned: always + type: bool + +account_uri: + description: ACME account URI, or None if account does not exist. + returned: always + type: str + +account: + description: The account information, as retrieved from the ACME server. + returned: if account exists + type: dict + contains: + contact: + description: the challenge resource that must be created for validation + returned: always + type: list + elements: str + sample: "['mailto:me@example.com', 'tel:00123456789']" + status: + description: the account's status + returned: always + type: str + choices: ['valid', 'deactivated', 'revoked'] + sample: valid + orders: + description: + - A URL where a list of orders can be retrieved for this account. + - Use the I(retrieve_orders) option to query this URL and retrieve the + complete list of orders. + returned: always + type: str + sample: https://example.ca/account/1/orders + public_account_key: + description: the public account key as a L(JSON Web Key,https://tools.ietf.org/html/rfc7517). + returned: always + type: str + sample: '{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}' + +orders: + description: + - "The list of orders." + - "If I(retrieve_orders) is C(url_list), this will be a list of URLs." + - "If I(retrieve_orders) is C(object_list), this will be a list of objects." + type: list + #elements: ... depends on retrieve_orders + returned: if account exists, I(retrieve_orders) is not C(ignore), and server supports order listing + contains: + status: + description: The order's status. + type: str + choices: + - pending + - ready + - processing + - valid + - invalid + expires: + description: + - When the order expires. + - Timestamp should be formatted as described in RFC3339. + - Only required to be included in result when I(status) is C(pending) or C(valid). + type: str + returned: when server gives expiry date + identifiers: + description: + - List of identifiers this order is for. + type: list + elements: dict + contains: + type: + description: Type of identifier. C(dns) or C(ip). + type: str + value: + description: Name of identifier. Hostname or IP address. + type: str + wildcard: + description: "Whether I(value) is actually a wildcard. The wildcard + prefix C(*.) is not included in I(value) if this is C(true)." + type: bool + returned: required to be included if the identifier is wildcarded + notBefore: + description: + - The requested value of the C(notBefore) field in the certificate. + - Date should be formatted as described in RFC3339. + - Server is not required to return this. + type: str + returned: when server returns this + notAfter: + description: + - The requested value of the C(notAfter) field in the certificate. + - Date should be formatted as described in RFC3339. + - Server is not required to return this. + type: str + returned: when server returns this + error: + description: + - In case an error occurred during processing, this contains information about the error. + - The field is structured as a problem document (RFC7807). + type: dict + returned: when an error occurred + authorizations: + description: + - A list of URLs for authorizations for this order. + type: list + elements: str + finalize: + description: + - A URL used for finalizing an ACME order. + type: str + certificate: + description: + - The URL for retrieving the certificate. + type: str + returned: when certificate was issued +''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + ACMEAccount, + handle_standard_module_arguments, + process_links, + get_default_argspec, +) + + +def get_orders_list(module, account, orders_url): + ''' + Retrieves orders list (handles pagination). + ''' + orders = [] + while orders_url: + # Get part of orders list + res, info = account.get_request(orders_url, parse_json_result=True, fail_on_error=True) + if not res.get('orders'): + if orders: + module.warn('When retrieving orders list part {0}, got empty result list'.format(orders_url)) + break + # Add order URLs to result list + orders.extend(res['orders']) + # Extract URL of next part of results list + new_orders_url = [] + + def f(link, relation): + if relation == 'next': + new_orders_url.append(link) + + process_links(info, f) + new_orders_url.append(None) + previous_orders_url, orders_url = orders_url, new_orders_url.pop(0) + if orders_url == previous_orders_url: + # Prevent infinite loop + orders_url = None + return orders + + +def get_order(account, order_url): + ''' + Retrieve order data. + ''' + return account.get_request(order_url, parse_json_result=True, fail_on_error=True)[0] + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']), + )) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=( + ['account_key_src', 'account_key_content'], + ), + mutually_exclusive=( + ['account_key_src', 'account_key_content'], + ), + supports_check_mode=True, + ) + if module._name in ('acme_account_facts', 'community.crypto.acme_account_facts'): + module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'", + version='2.0.0', collection_name='community.crypto') + handle_standard_module_arguments(module, needs_acme_v2=True) + + try: + account = ACMEAccount(module) + # Check whether account exists + created, account_data = account.setup_account( + [], + allow_creation=False, + remove_account_uri_if_not_exists=True, + ) + if created: + raise AssertionError('Unwanted account creation') + result = { + 'changed': False, + 'exists': account.uri is not None, + 'account_uri': account.uri, + } + if account.uri is not None: + # Make sure promised data is there + if 'contact' not in account_data: + account_data['contact'] = [] + account_data['public_account_key'] = account.key_data['jwk'] + result['account'] = account_data + # Retrieve orders list + if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore': + orders = get_orders_list(module, account, account_data['orders']) + if module.params['retrieve_orders'] == 'url_list': + result['orders'] = orders + else: + result['orders'] = [get_order(account, order) for order in orders] + module.exit_json(**result) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_info.py new file mode 100644 index 00000000..47d5981e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_account_info.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_account_info +author: "Felix Fontein (@felixfontein)" +short_description: Retrieves information on ACME accounts +description: + - "Allows to retrieve information on accounts a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + such as L(Let's Encrypt,https://letsencrypt.org/)." + - "This module only works with the ACME v2 protocol." +notes: + - "The M(community.crypto.acme_account) module allows to modify, create and delete ACME + accounts." + - "This module was called C(acme_account_facts) before Ansible 2.8. The usage + did not change." + - Supports C(check_mode). +options: + retrieve_orders: + description: + - "Whether to retrieve the list of order URLs or order objects, if provided + by the ACME server." + - "A value of C(ignore) will not fetch the list of orders." + - "Currently, Let's Encrypt does not return orders, so the C(orders) result + will always be empty." + type: str + choices: + - ignore + - url_list + - object_list + default: ignore +seealso: + - module: community.crypto.acme_account + description: Allows to create, modify or delete an ACME account. +extends_documentation_fragment: +- community.crypto.acme + +''' + +EXAMPLES = ''' +- name: Check whether an account with the given account key exists + community.crypto.acme_account_info: + account_key_src: /etc/pki/cert/private/account.key + register: account_data +- name: Verify that account exists + assert: + that: + - account_data.exists +- name: Print account URI + ansible.builtin.debug: + var: account_data.account_uri +- name: Print account contacts + ansible.builtin.debug: + var: account_data.account.contact + +- name: Check whether the account exists and is accessible with the given account key + acme_account_info: + account_key_content: "{{ acme_account_key }}" + account_uri: "{{ acme_account_uri }}" + register: account_data +- name: Verify that account exists + assert: + that: + - account_data.exists +- name: Print account contacts + ansible.builtin.debug: + var: account_data.account.contact +''' + +RETURN = ''' +exists: + description: Whether the account exists. + returned: always + type: bool + +account_uri: + description: ACME account URI, or None if account does not exist. + returned: always + type: str + +account: + description: The account information, as retrieved from the ACME server. + returned: if account exists + type: dict + contains: + contact: + description: the challenge resource that must be created for validation + returned: always + type: list + elements: str + sample: "['mailto:me@example.com', 'tel:00123456789']" + status: + description: the account's status + returned: always + type: str + choices: ['valid', 'deactivated', 'revoked'] + sample: valid + orders: + description: + - A URL where a list of orders can be retrieved for this account. + - Use the I(retrieve_orders) option to query this URL and retrieve the + complete list of orders. + returned: always + type: str + sample: https://example.ca/account/1/orders + public_account_key: + description: the public account key as a L(JSON Web Key,https://tools.ietf.org/html/rfc7517). + returned: always + type: str + sample: '{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"}' + +orders: + description: + - "The list of orders." + - "If I(retrieve_orders) is C(url_list), this will be a list of URLs." + - "If I(retrieve_orders) is C(object_list), this will be a list of objects." + type: list + #elements: ... depends on retrieve_orders + returned: if account exists, I(retrieve_orders) is not C(ignore), and server supports order listing + contains: + status: + description: The order's status. + type: str + choices: + - pending + - ready + - processing + - valid + - invalid + expires: + description: + - When the order expires. + - Timestamp should be formatted as described in RFC3339. + - Only required to be included in result when I(status) is C(pending) or C(valid). + type: str + returned: when server gives expiry date + identifiers: + description: + - List of identifiers this order is for. + type: list + elements: dict + contains: + type: + description: Type of identifier. C(dns) or C(ip). + type: str + value: + description: Name of identifier. Hostname or IP address. + type: str + wildcard: + description: "Whether I(value) is actually a wildcard. The wildcard + prefix C(*.) is not included in I(value) if this is C(true)." + type: bool + returned: required to be included if the identifier is wildcarded + notBefore: + description: + - The requested value of the C(notBefore) field in the certificate. + - Date should be formatted as described in RFC3339. + - Server is not required to return this. + type: str + returned: when server returns this + notAfter: + description: + - The requested value of the C(notAfter) field in the certificate. + - Date should be formatted as described in RFC3339. + - Server is not required to return this. + type: str + returned: when server returns this + error: + description: + - In case an error occurred during processing, this contains information about the error. + - The field is structured as a problem document (RFC7807). + type: dict + returned: when an error occurred + authorizations: + description: + - A list of URLs for authorizations for this order. + type: list + elements: str + finalize: + description: + - A URL used for finalizing an ACME order. + type: str + certificate: + description: + - The URL for retrieving the certificate. + type: str + returned: when certificate was issued +''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + ACMEAccount, + handle_standard_module_arguments, + process_links, + get_default_argspec, +) + + +def get_orders_list(module, account, orders_url): + ''' + Retrieves orders list (handles pagination). + ''' + orders = [] + while orders_url: + # Get part of orders list + res, info = account.get_request(orders_url, parse_json_result=True, fail_on_error=True) + if not res.get('orders'): + if orders: + module.warn('When retrieving orders list part {0}, got empty result list'.format(orders_url)) + break + # Add order URLs to result list + orders.extend(res['orders']) + # Extract URL of next part of results list + new_orders_url = [] + + def f(link, relation): + if relation == 'next': + new_orders_url.append(link) + + process_links(info, f) + new_orders_url.append(None) + previous_orders_url, orders_url = orders_url, new_orders_url.pop(0) + if orders_url == previous_orders_url: + # Prevent infinite loop + orders_url = None + return orders + + +def get_order(account, order_url): + ''' + Retrieve order data. + ''' + return account.get_request(order_url, parse_json_result=True, fail_on_error=True)[0] + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']), + )) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=( + ['account_key_src', 'account_key_content'], + ), + mutually_exclusive=( + ['account_key_src', 'account_key_content'], + ), + supports_check_mode=True, + ) + if module._name in ('acme_account_facts', 'community.crypto.acme_account_facts'): + module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'", + version='2.0.0', collection_name='community.crypto') + handle_standard_module_arguments(module, needs_acme_v2=True) + + try: + account = ACMEAccount(module) + # Check whether account exists + created, account_data = account.setup_account( + [], + allow_creation=False, + remove_account_uri_if_not_exists=True, + ) + if created: + raise AssertionError('Unwanted account creation') + result = { + 'changed': False, + 'exists': account.uri is not None, + 'account_uri': account.uri, + } + if account.uri is not None: + # Make sure promised data is there + if 'contact' not in account_data: + account_data['contact'] = [] + account_data['public_account_key'] = account.key_data['jwk'] + result['account'] = account_data + # Retrieve orders list + if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore': + orders = get_orders_list(module, account, account_data['orders']) + if module.params['retrieve_orders'] == 'url_list': + result['orders'] = orders + else: + result['orders'] = [get_order(account, order) for order in orders] + module.exit_json(**result) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate.py new file mode 100644 index 00000000..20c0e843 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate.py @@ -0,0 +1,1293 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_certificate +author: "Michael Gruener (@mgruener)" +short_description: Create SSL/TLS certificates with the ACME protocol +description: + - "Create and renew SSL/TLS certificates with a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + such as L(Let's Encrypt,https://letsencrypt.org/) or + L(Buypass,https://www.buypass.com/). The current implementation + supports the C(http-01), C(dns-01) and C(tls-alpn-01) challenges." + - "To use this module, it has to be executed twice. Either as two + different tasks in the same run or during two runs. Note that the output + of the first run needs to be recorded and passed to the second run as the + module argument C(data)." + - "Between these two tasks you have to fulfill the required steps for the + chosen challenge by whatever means necessary. For C(http-01) that means + creating the necessary challenge file on the destination webserver. For + C(dns-01) the necessary dns record has to be created. For C(tls-alpn-01) + the necessary certificate has to be created and served. + It is I(not) the responsibility of this module to perform these steps." + - "For details on how to fulfill these challenges, you might have to read through + L(the main ACME specification,https://tools.ietf.org/html/rfc8555#section-8) + and the L(TLS-ALPN-01 specification,https://www.rfc-editor.org/rfc/rfc8737.html#section-3). + Also, consider the examples provided for this module." + - "The module includes experimental support for IP identifiers according to + the L(RFC 8738,https://www.rfc-editor.org/rfc/rfc8738.html)." +notes: + - "At least one of C(dest) and C(fullchain_dest) must be specified." + - "This module includes basic account management functionality. + If you want to have more control over your ACME account, use the + M(community.crypto.acme_account) module and disable account management + for this module using the C(modify_account) option." + - "This module was called C(letsencrypt) before Ansible 2.6. The usage + did not change." +seealso: + - name: The Let's Encrypt documentation + description: Documentation for the Let's Encrypt Certification Authority. + Provides useful information for example on rate limits. + link: https://letsencrypt.org/docs/ + - name: Buypass Go SSL + description: Documentation for the Buypass Certification Authority. + Provides useful information for example on rate limits. + link: https://www.buypass.com/ssl/products/acme + - name: Automatic Certificate Management Environment (ACME) + description: The specification of the ACME protocol (RFC 8555). + link: https://tools.ietf.org/html/rfc8555 + - name: ACME TLS ALPN Challenge Extension + description: The specification of the C(tls-alpn-01) challenge (RFC 8737). + link: https://www.rfc-editor.org/rfc/rfc8737.html-05 + - module: community.crypto.acme_challenge_cert_helper + description: Helps preparing C(tls-alpn-01) challenges. + - module: community.crypto.openssl_privatekey + description: Can be used to create private keys (both for certificates and accounts). + - module: community.crypto.openssl_csr + description: Can be used to create a Certificate Signing Request (CSR). + - module: community.crypto.certificate_complete_chain + description: Allows to find the root certificate for the returned fullchain. + - module: community.crypto.acme_certificate_revoke + description: Allows to revoke certificates. + - module: community.crypto.acme_account + description: Allows to create, modify or delete an ACME account. + - module: community.crypto.acme_inspect + description: Allows to debug problems. +extends_documentation_fragment: +- community.crypto.acme + +options: + account_email: + description: + - "The email address associated with this account." + - "It will be used for certificate expiration warnings." + - "Note that when C(modify_account) is not set to C(no) and you also + used the M(community.crypto.acme_account) module to specify more than one contact + for your account, this module will update your account and restrict + it to the (at most one) contact email address specified here." + type: str + agreement: + description: + - "URI to a terms of service document you agree to when using the + ACME v1 service at C(acme_directory)." + - Default is latest gathered from C(acme_directory) URL. + - This option will only be used when C(acme_version) is 1. + type: str + terms_agreed: + description: + - "Boolean indicating whether you agree to the terms of service document." + - "ACME servers can require this to be true." + - This option will only be used when C(acme_version) is not 1. + type: bool + default: no + modify_account: + description: + - "Boolean indicating whether the module should create the account if + necessary, and update its contact data." + - "Set to C(no) if you want to use the M(community.crypto.acme_account) module to manage + your account instead, and to avoid accidental creation of a new account + using an old key if you changed the account key with M(community.crypto.acme_account)." + - "If set to C(no), C(terms_agreed) and C(account_email) are ignored." + type: bool + default: yes + challenge: + description: The challenge to be performed. + type: str + default: 'http-01' + choices: [ 'http-01', 'dns-01', 'tls-alpn-01' ] + csr: + description: + - "File containing the CSR for the new certificate." + - "Can be created with C(openssl req ...)." + - "The CSR may contain multiple Subject Alternate Names, but each one + will lead to an individual challenge that must be fulfilled for the + CSR to be signed." + - "I(Note): the private key used to create the CSR I(must not) be the + account key. This is a bad idea from a security point of view, and + the CA should not accept the CSR. The ACME server should return an + error in this case." + - Precisely one of I(csr) or I(csr_content) must be specified. + type: path + aliases: ['src'] + csr_content: + description: + - "Content of the CSR for the new certificate." + - "Can be created with C(openssl req ...)." + - "The CSR may contain multiple Subject Alternate Names, but each one + will lead to an individual challenge that must be fulfilled for the + CSR to be signed." + - "I(Note): the private key used to create the CSR I(must not) be the + account key. This is a bad idea from a security point of view, and + the CA should not accept the CSR. The ACME server should return an + error in this case." + - Precisely one of I(csr) or I(csr_content) must be specified. + type: str + version_added: 1.2.0 + data: + description: + - "The data to validate ongoing challenges. This must be specified for + the second run of the module only." + - "The value that must be used here will be provided by a previous use + of this module. See the examples for more details." + - "Note that for ACME v2, only the C(order_uri) entry of C(data) will + be used. For ACME v1, C(data) must be non-empty to indicate the + second stage is active; all needed data will be taken from the + CSR." + - "I(Note): the C(data) option was marked as C(no_log) up to + Ansible 2.5. From Ansible 2.6 on, it is no longer marked this way + as it causes error messages to be come unusable, and C(data) does + not contain any information which can be used without having + access to the account key or which are not public anyway." + type: dict + dest: + description: + - "The destination file for the certificate." + - "Required if C(fullchain_dest) is not specified." + type: path + aliases: ['cert'] + fullchain_dest: + description: + - "The destination file for the full chain (i.e. certificate followed + by chain of intermediate certificates)." + - "Required if C(dest) is not specified." + type: path + aliases: ['fullchain'] + chain_dest: + description: + - If specified, the intermediate certificate will be written to this file. + type: path + aliases: ['chain'] + remaining_days: + description: + - "The number of days the certificate must have left being valid. + If C(cert_days < remaining_days), then it will be renewed. + If the certificate is not renewed, module return values will not + include C(challenge_data)." + - "To make sure that the certificate is renewed in any case, you can + use the C(force) option." + type: int + default: 10 + deactivate_authzs: + description: + - "Deactivate authentication objects (authz) after issuing a certificate, + or when issuing the certificate failed." + - "Authentication objects are bound to an account key and remain valid + for a certain amount of time, and can be used to issue certificates + without having to re-authenticate the domain. This can be a security + concern." + type: bool + default: no + force: + description: + - Enforces the execution of the challenge and validation, even if an + existing certificate is still valid for more than C(remaining_days). + - This is especially helpful when having an updated CSR e.g. with + additional domains for which a new certificate is desired. + type: bool + default: no + retrieve_all_alternates: + description: + - "When set to C(yes), will retrieve all alternate trust chains offered by the ACME CA. + These will not be written to disk, but will be returned together with the main + chain as C(all_chains). See the documentation for the C(all_chains) return + value for details." + type: bool + default: no + select_chain: + description: + - "Allows to specify criteria by which an (alternate) trust chain can be selected." + - "The list of criteria will be processed one by one until a chain is found + matching a criterium. If such a chain is found, it will be used by the + module instead of the default chain." + - "If a criterium matches multiple chains, the first one matching will be + returned. The order is determined by the ordering of the C(Link) headers + returned by the ACME server and might not be deterministic." + - "Every criterium can consist of multiple different conditions, like I(issuer) + and I(subject). For the criterium to match a chain, all conditions must apply + to the same certificate in the chain." + - "This option can only be used with the C(cryptography) backend." + type: list + elements: dict + version_added: '1.0.0' + suboptions: + test_certificates: + description: + - "Determines which certificates in the chain will be tested." + - "I(all) tests all certificates in the chain (excluding the leaf, which is + identical in all chains)." + - "I(first) only tests the first certificate in the chain, i.e. the one which + signed the leaf." + - "I(last) only tests the last certificate in the chain, i.e. the one furthest + away from the leaf. Its issuer is the root certificate of this chain." + type: str + default: all + choices: [first, last, all] + issuer: + description: + - "Allows to specify parts of the issuer of a certificate in the chain must + have to be selected." + - "If I(issuer) is empty, any certificate will match." + - 'An example value would be C({"commonName": "My Preferred CA Root"}).' + type: dict + subject: + description: + - "Allows to specify parts of the subject of a certificate in the chain must + have to be selected." + - "If I(subject) is empty, any certificate will match." + - 'An example value would be C({"CN": "My Preferred CA Intermediate"})' + type: dict + subject_key_identifier: + description: + - "Checks for the SubjectKeyIdentifier extension. This is an identifier based + on the private key of the intermediate certificate." + - "The identifier must be of the form + C(A8:4A:6A:63:04:7D:DD:BA:E6:D1:39:B7:A6:45:65:EF:F3:A8:EC:A1)." + type: str + authority_key_identifier: + description: + - "Checks for the AuthorityKeyIdentifier extension. This is an identifier based + on the private key of the issuer of the intermediate certificate." + - "The identifier must be of the form + C(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)." + type: str +''' + +EXAMPLES = r''' +### Example with HTTP challenge ### + +- name: Create a challenge for sample.com using a account key from a variable. + community.crypto.acme_certificate: + account_key_content: "{{ account_private_key }}" + csr: /etc/pki/cert/csr/sample.com.csr + dest: /etc/httpd/ssl/sample.com.crt + register: sample_com_challenge + +# Alternative first step: +- name: Create a challenge for sample.com using a account key from hashi vault. + community.crypto.acme_certificate: + account_key_content: "{{ lookup('hashi_vault', 'secret=secret/account_private_key:value') }}" + csr: /etc/pki/cert/csr/sample.com.csr + fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt + register: sample_com_challenge + +# Alternative first step: +- name: Create a challenge for sample.com using a account key file. + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + csr_content: "{{ lookup('file', '/etc/pki/cert/csr/sample.com.csr') }}" + dest: /etc/httpd/ssl/sample.com.crt + fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt + register: sample_com_challenge + +# perform the necessary steps to fulfill the challenge +# for example: +# +# - copy: +# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }} +# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}" +# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data'] +# +# Alternative way: +# +# - copy: +# dest: /var/www/{{ item.key }}/{{ item.value['http-01']['resource'] }} +# content: "{{ item.value['http-01']['resource_value'] }}" +# loop: "{{ sample_com_challenge.challenge_data | dict2items }}" +# when: sample_com_challenge is changed + +- name: Let the challenge be validated and retrieve the cert and intermediate certificate + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + csr: /etc/pki/cert/csr/sample.com.csr + dest: /etc/httpd/ssl/sample.com.crt + fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt + chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt + data: "{{ sample_com_challenge }}" + +### Example with DNS challenge against production ACME server ### + +- name: Create a challenge for sample.com using a account key file. + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + account_email: myself@sample.com + src: /etc/pki/cert/csr/sample.com.csr + cert: /etc/httpd/ssl/sample.com.crt + challenge: dns-01 + acme_directory: https://acme-v01.api.letsencrypt.org/directory + # Renew if the certificate is at least 30 days old + remaining_days: 60 + register: sample_com_challenge + +# perform the necessary steps to fulfill the challenge +# for example: +# +# - community.aws.route53: +# zone: sample.com +# record: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].record }}" +# type: TXT +# ttl: 60 +# state: present +# wait: yes +# # Note: route53 requires TXT entries to be enclosed in quotes +# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}" +# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data +# +# Alternative way: +# +# - community.aws.route53: +# zone: sample.com +# record: "{{ item.key }}" +# type: TXT +# ttl: 60 +# state: present +# wait: yes +# # Note: item.value is a list of TXT entries, and route53 +# # requires every entry to be enclosed in quotes +# value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}" +# loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}" +# when: sample_com_challenge is changed + +- name: Let the challenge be validated and retrieve the cert and intermediate certificate + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + account_email: myself@sample.com + src: /etc/pki/cert/csr/sample.com.csr + cert: /etc/httpd/ssl/sample.com.crt + fullchain: /etc/httpd/ssl/sample.com-fullchain.crt + chain: /etc/httpd/ssl/sample.com-intermediate.crt + challenge: dns-01 + acme_directory: https://acme-v01.api.letsencrypt.org/directory + remaining_days: 60 + data: "{{ sample_com_challenge }}" + when: sample_com_challenge is changed + +# Alternative second step: +- name: Let the challenge be validated and retrieve the cert and intermediate certificate + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + account_email: myself@sample.com + src: /etc/pki/cert/csr/sample.com.csr + cert: /etc/httpd/ssl/sample.com.crt + fullchain: /etc/httpd/ssl/sample.com-fullchain.crt + chain: /etc/httpd/ssl/sample.com-intermediate.crt + challenge: tls-alpn-01 + remaining_days: 60 + data: "{{ sample_com_challenge }}" + # We use Let's Encrypt's ACME v2 endpoint + acme_directory: https://acme-v02.api.letsencrypt.org/directory + acme_version: 2 + # The following makes sure that if a chain with /CN=DST Root CA X3 in its issuer is provided + # as an alternative, it will be selected. These are the roots cross-signed by IdenTrust. + # As long as Let's Encrypt provides alternate chains with the cross-signed root(s) when + # switching to their own ISRG Root X1 root, this will use the chain ending with a cross-signed + # root. This chain is more compatible with older TLS clients. + select_chain: + - test_certificates: last + issuer: + CN: DST Root CA X3 + O: Digital Signature Trust Co. + when: sample_com_challenge is changed +''' + +RETURN = ''' +cert_days: + description: The number of days the certificate remains valid. + returned: success + type: int +challenge_data: + description: + - Per identifier / challenge type challenge data. + - Since Ansible 2.8.5, only challenges which are not yet valid are returned. + returned: changed + type: list + elements: dict + contains: + resource: + description: The challenge resource that must be created for validation. + returned: changed + type: str + sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA + resource_original: + description: + - The original challenge resource including type identifier for C(tls-alpn-01) + challenges. + returned: changed and challenge is C(tls-alpn-01) + type: str + sample: DNS:example.com + resource_value: + description: + - The value the resource has to produce for the validation. + - For C(http-01) and C(dns-01) challenges, the value can be used as-is. + - "For C(tls-alpn-01) challenges, note that this return value contains a + Base64 encoded version of the correct binary blob which has to be put + into the acmeValidation x509 extension; see + U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3) + for details. To do this, you might need the C(b64decode) Jinja filter + to extract the binary blob from this return value." + returned: changed + type: str + sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA + record: + description: The full DNS record's name for the challenge. + returned: changed and challenge is C(dns-01) + type: str + sample: _acme-challenge.example.com +challenge_data_dns: + description: + - List of TXT values per DNS record, in case challenge is C(dns-01). + - Since Ansible 2.8.5, only challenges which are not yet valid are returned. + returned: changed + type: dict +authorizations: + description: + - ACME authorization data. + - Maps an identifier to ACME authorization objects. See U(https://tools.ietf.org/html/rfc8555#section-7.1.4). + returned: changed + type: dict + sample: '{"example.com":{...}}' +order_uri: + description: ACME order URI. + returned: changed + type: str +finalization_uri: + description: ACME finalization URI. + returned: changed + type: str +account_uri: + description: ACME account URI. + returned: changed + type: str +all_chains: + description: + - When I(retrieve_all_alternates) is set to C(yes), the module will query the ACME server + for alternate chains. This return value will contain a list of all chains returned, + the first entry being the main chain returned by the server. + - See L(Section 7.4.2 of RFC8555,https://tools.ietf.org/html/rfc8555#section-7.4.2) for details. + returned: when certificate was retrieved and I(retrieve_all_alternates) is set to C(yes) + type: list + elements: dict + contains: + cert: + description: + - The leaf certificate itself, in PEM format. + type: str + returned: always + chain: + description: + - The certificate chain, excluding the root, as concatenated PEM certificates. + type: str + returned: always + full_chain: + description: + - The certificate chain, excluding the root, but including the leaf certificate, + as concatenated PEM certificates. + type: str + returned: always +''' + +import base64 +import binascii +import hashlib +import os +import re +import textwrap +import time +import traceback + +from datetime import datetime + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_bytes, to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + parse_name_field, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_name_to_oid, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + split_pem_list, +) + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + write_file, + nopad_b64, + pem_to_der, + ACMEAccount, + HAS_CURRENT_CRYPTOGRAPHY, + cryptography_get_csr_identifiers, + openssl_get_csr_identifiers, + cryptography_get_cert_days, + handle_standard_module_arguments, + process_links, + get_default_argspec, +) +from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress + +try: + import cryptography + import cryptography.hazmat.backends + import cryptography.x509 +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +def get_cert_days(module, cert_file): + ''' + Return the days the certificate in cert_file remains valid and -1 + if the file was not found. If cert_file contains more than one + certificate, only the first one will be considered. + ''' + if HAS_CURRENT_CRYPTOGRAPHY: + return cryptography_get_cert_days(module, cert_file) + if not os.path.exists(cert_file): + return -1 + + openssl_bin = module.get_bin_path('openssl', True) + openssl_cert_cmd = [openssl_bin, "x509", "-in", cert_file, "-noout", "-text"] + dummy, out, dummy = module.run_command(openssl_cert_cmd, check_rc=True, encoding=None) + try: + not_after_str = re.search(r"\s+Not After\s*:\s+(.*)", out.decode('utf8')).group(1) + not_after = datetime.fromtimestamp(time.mktime(time.strptime(not_after_str, '%b %d %H:%M:%S %Y %Z'))) + except AttributeError: + raise ModuleFailException("No 'Not after' date found in {0}".format(cert_file)) + except ValueError: + raise ModuleFailException("Failed to parse 'Not after' date of {0}".format(cert_file)) + now = datetime.utcnow() + return (not_after - now).days + + +class ACMEClient(object): + ''' + ACME client class. Uses an ACME account object and a CSR to + start and validate ACME challenges and download the respective + certificates. + ''' + + def __init__(self, module): + self.module = module + self.version = module.params['acme_version'] + self.challenge = module.params['challenge'] + self.csr = module.params['csr'] + self.csr_content = module.params['csr_content'] + self.dest = module.params.get('dest') + self.fullchain_dest = module.params.get('fullchain_dest') + self.chain_dest = module.params.get('chain_dest') + self.account = ACMEAccount(module) + self.directory = self.account.directory + self.data = module.params['data'] + self.authorizations = None + self.cert_days = -1 + self.order_uri = self.data.get('order_uri') if self.data else None + self.finalize_uri = None + + # Make sure account exists + modify_account = module.params['modify_account'] + if modify_account or self.version > 1: + contact = [] + if module.params['account_email']: + contact.append('mailto:' + module.params['account_email']) + created, account_data = self.account.setup_account( + contact, + agreement=module.params.get('agreement'), + terms_agreed=module.params.get('terms_agreed'), + allow_creation=modify_account, + ) + if account_data is None: + raise ModuleFailException(msg='Account does not exist or is deactivated.') + updated = False + if not created and account_data and modify_account: + updated, account_data = self.account.update_account(account_data, contact) + self.changed = created or updated + else: + # This happens if modify_account is False and the ACME v1 + # protocol is used. In this case, we do not call setup_account() + # to avoid accidental creation of an account. This is OK + # since for ACME v1, the account URI is not needed to send a + # signed ACME request. + pass + + if self.csr is not None and not os.path.exists(self.csr): + raise ModuleFailException("CSR %s not found" % (self.csr)) + + self._openssl_bin = module.get_bin_path('openssl', True) + + # Extract list of identifiers from CSR + self.identifiers = self._get_csr_identifiers() + + def _get_csr_identifiers(self): + ''' + Parse the CSR and return the list of requested identifiers + ''' + if HAS_CURRENT_CRYPTOGRAPHY: + return cryptography_get_csr_identifiers(self.module, self.csr, self.csr_content) + else: + return openssl_get_csr_identifiers(self._openssl_bin, self.module, self.csr, self.csr_content) + + def _add_or_update_auth(self, identifier_type, identifier, auth): + ''' + Add or update the given authorization in the global authorizations list. + Return True if the auth was updated/added and False if no change was + necessary. + ''' + if self.authorizations.get(identifier_type + ':' + identifier) == auth: + return False + self.authorizations[identifier_type + ':' + identifier] = auth + return True + + def _new_authz_v1(self, identifier_type, identifier): + ''' + Create a new authorization for the given identifier. + Return the authorization object of the new authorization + https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.4 + ''' + new_authz = { + "resource": "new-authz", + "identifier": {"type": identifier_type, "value": identifier}, + } + + result, info = self.account.send_signed_request(self.directory['new-authz'], new_authz) + if info['status'] not in [200, 201]: + raise ModuleFailException("Error requesting challenges: CODE: {0} RESULT: {1}".format(info['status'], result)) + else: + result['uri'] = info['location'] + return result + + def _get_challenge_data(self, auth, identifier_type, identifier): + ''' + Returns a dict with the data for all proposed (and supported) challenges + of the given authorization. + ''' + + data = {} + # no need to choose a specific challenge here as this module + # is not responsible for fulfilling the challenges. Calculate + # and return the required information for each challenge. + for challenge in auth['challenges']: + challenge_type = challenge['type'] + token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) + keyauthorization = self.account.get_keyauthorization(token) + + if challenge_type == 'http-01': + # https://tools.ietf.org/html/rfc8555#section-8.3 + resource = '.well-known/acme-challenge/' + token + data[challenge_type] = {'resource': resource, 'resource_value': keyauthorization} + elif challenge_type == 'dns-01': + if identifier_type != 'dns': + continue + # https://tools.ietf.org/html/rfc8555#section-8.4 + resource = '_acme-challenge' + value = nopad_b64(hashlib.sha256(to_bytes(keyauthorization)).digest()) + record = (resource + identifier[1:]) if identifier.startswith('*.') else (resource + '.' + identifier) + data[challenge_type] = {'resource': resource, 'resource_value': value, 'record': record} + elif challenge_type == 'tls-alpn-01': + # https://www.rfc-editor.org/rfc/rfc8737.html#section-3 + if identifier_type == 'ip': + # IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596) + resource = compat_ipaddress.ip_address(identifier).reverse_pointer + if not resource.endswith('.'): + resource += '.' + else: + resource = identifier + value = base64.b64encode(hashlib.sha256(to_bytes(keyauthorization)).digest()) + data[challenge_type] = {'resource': resource, 'resource_original': identifier_type + ':' + identifier, 'resource_value': value} + else: + continue + + return data + + def _fail_challenge(self, identifier_type, identifier, auth, error): + ''' + Aborts with a specific error for a challenge. + ''' + error_details = '' + # multiple challenges could have failed at this point, gather error + # details for all of them before failing + for challenge in auth['challenges']: + if challenge['status'] == 'invalid': + error_details += ' CHALLENGE: {0}'.format(challenge['type']) + if 'error' in challenge: + error_details += ' DETAILS: {0};'.format(challenge['error']['detail']) + else: + error_details += ';' + raise ModuleFailException("{0}: {1}".format(error.format(identifier_type + ':' + identifier), error_details)) + + def _validate_challenges(self, identifier_type, identifier, auth): + ''' + Validate the authorization provided in the auth dict. Returns True + when the validation was successful and False when it was not. + ''' + found_challenge = False + for challenge in auth['challenges']: + if self.challenge != challenge['type']: + continue + + uri = challenge['uri'] if self.version == 1 else challenge['url'] + found_challenge = True + + challenge_response = {} + if self.version == 1: + token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) + keyauthorization = self.account.get_keyauthorization(token) + challenge_response["resource"] = "challenge" + challenge_response["keyAuthorization"] = keyauthorization + challenge_response["type"] = self.challenge + result, info = self.account.send_signed_request(uri, challenge_response) + if info['status'] not in [200, 202]: + raise ModuleFailException("Error validating challenge: CODE: {0} RESULT: {1}".format(info['status'], result)) + + if not found_challenge: + raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}:{2}!".format( + self.challenge, identifier_type, identifier)) + + status = '' + + while status not in ['valid', 'invalid', 'revoked']: + result, dummy = self.account.get_request(auth['uri']) + result['uri'] = auth['uri'] + if self._add_or_update_auth(identifier_type, identifier, result): + self.changed = True + # https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1.2 + # "status (required, string): ... + # If this field is missing, then the default value is "pending"." + if self.version == 1 and 'status' not in result: + status = 'pending' + else: + status = result['status'] + time.sleep(2) + + if status == 'invalid': + self._fail_challenge(identifier_type, identifier, result, 'Authorization for {0} returned invalid') + + return status == 'valid' + + def _finalize_cert(self): + ''' + Create a new certificate based on the csr. + Return the certificate object as dict + https://tools.ietf.org/html/rfc8555#section-7.4 + ''' + csr = pem_to_der(self.csr, self.csr_content) + new_cert = { + "csr": nopad_b64(csr), + } + result, info = self.account.send_signed_request(self.finalize_uri, new_cert) + if info['status'] not in [200]: + raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result)) + + status = result['status'] + while status not in ['valid', 'invalid']: + time.sleep(2) + result, dummy = self.account.get_request(self.order_uri) + status = result['status'] + + if status != 'valid': + raise ModuleFailException("Error new cert: CODE: {0} STATUS: {1} RESULT: {2}".format(info['status'], status, result)) + + return result['certificate'] + + def _der_to_pem(self, der_cert): + ''' + Convert the DER format certificate in der_cert to a PEM format + certificate and return it. + ''' + return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format( + "\n".join(textwrap.wrap(base64.b64encode(der_cert).decode('utf8'), 64))) + + def _download_cert(self, url): + ''' + Download and parse the certificate chain. + https://tools.ietf.org/html/rfc8555#section-7.4.2 + ''' + content, info = self.account.get_request(url, parse_json_result=False, headers={'Accept': 'application/pem-certificate-chain'}) + + if not content or not info['content-type'].startswith('application/pem-certificate-chain'): + raise ModuleFailException("Cannot download certificate chain from {0}: {1} (headers: {2})".format(url, content, info)) + + cert = None + chain = [] + + # Parse data + certs = split_pem_list(content.decode('utf-8'), keep_inbetween=True) + if certs: + cert = certs[0] + chain = certs[1:] + + alternates = [] + + def f(link, relation): + if relation == 'up': + # Process link-up headers if there was no chain in reply + if not chain: + chain_result, chain_info = self.account.get_request(link, parse_json_result=False) + if chain_info['status'] in [200, 201]: + chain.append(self._der_to_pem(chain_result)) + elif relation == 'alternate': + alternates.append(link) + + process_links(info, f) + + if cert is None: + raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info)) + return {'cert': cert, 'chain': chain, 'alternates': alternates} + + def _new_cert_v1(self): + ''' + Create a new certificate based on the CSR (ACME v1 protocol). + Return the certificate object as dict + https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5 + ''' + csr = pem_to_der(self.csr, self.csr_content) + new_cert = { + "resource": "new-cert", + "csr": nopad_b64(csr), + } + result, info = self.account.send_signed_request(self.directory['new-cert'], new_cert) + + chain = [] + + def f(link, relation): + if relation == 'up': + chain_result, chain_info = self.account.get_request(link, parse_json_result=False) + if chain_info['status'] in [200, 201]: + del chain[:] + chain.append(self._der_to_pem(chain_result)) + + process_links(info, f) + + if info['status'] not in [200, 201]: + raise ModuleFailException("Error new cert: CODE: {0} RESULT: {1}".format(info['status'], result)) + else: + return {'cert': self._der_to_pem(result), 'uri': info['location'], 'chain': chain} + + def _new_order_v2(self): + ''' + Start a new certificate order (ACME v2 protocol). + https://tools.ietf.org/html/rfc8555#section-7.4 + ''' + identifiers = [] + for identifier_type, identifier in self.identifiers: + identifiers.append({ + 'type': identifier_type, + 'value': identifier, + }) + new_order = { + "identifiers": identifiers + } + result, info = self.account.send_signed_request(self.directory['newOrder'], new_order) + + if info['status'] not in [201]: + raise ModuleFailException("Error new order: CODE: {0} RESULT: {1}".format(info['status'], result)) + + for auth_uri in result['authorizations']: + auth_data, dummy = self.account.get_request(auth_uri) + auth_data['uri'] = auth_uri + identifier_type = auth_data['identifier']['type'] + identifier = auth_data['identifier']['value'] + if auth_data.get('wildcard', False): + identifier = '*.{0}'.format(identifier) + self.authorizations[identifier_type + ':' + identifier] = auth_data + + self.order_uri = info['location'] + self.finalize_uri = result['finalize'] + + def is_first_step(self): + ''' + Return True if this is the first execution of this module, i.e. if a + sufficient data object from a first run has not been provided. + ''' + if self.data is None: + return True + if self.version == 1: + # As soon as self.data is a non-empty object, we are in the second stage. + return not self.data + else: + # We are in the second stage if data.order_uri is given (which has been + # stored in self.order_uri by the constructor). + return self.order_uri is None + + def start_challenges(self): + ''' + Create new authorizations for all identifiers of the CSR, + respectively start a new order for ACME v2. + ''' + self.authorizations = {} + if self.version == 1: + for identifier_type, identifier in self.identifiers: + if identifier_type != 'dns': + raise ModuleFailException('ACME v1 only supports DNS identifiers!') + for identifier_type, identifier in self.identifiers: + new_auth = self._new_authz_v1(identifier_type, identifier) + self._add_or_update_auth(identifier_type, identifier, new_auth) + else: + self._new_order_v2() + self.changed = True + + def get_challenges_data(self, first_step): + ''' + Get challenge details for the chosen challenge type. + Return a tuple of generic challenge details, and specialized DNS challenge details. + ''' + # Get general challenge data + data = {} + for type_identifier, auth in self.authorizations.items(): + identifier_type, identifier = type_identifier.split(':', 1) + auth = self.authorizations[type_identifier] + # Skip valid authentications: their challenges are already valid + # and do not need to be returned + if auth['status'] == 'valid': + continue + # We drop the type from the key to preserve backwards compatibility + data[identifier] = self._get_challenge_data(auth, identifier_type, identifier) + if first_step and self.challenge not in data[identifier]: + raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format( + self.challenge, type_identifier)) + # Get DNS challenge data + data_dns = {} + if self.challenge == 'dns-01': + for identifier, challenges in data.items(): + if self.challenge in challenges: + values = data_dns.get(challenges[self.challenge]['record']) + if values is None: + values = [] + data_dns[challenges[self.challenge]['record']] = values + values.append(challenges[self.challenge]['resource_value']) + return data, data_dns + + def finish_challenges(self): + ''' + Verify challenges for all identifiers of the CSR. + ''' + self.authorizations = {} + + # Step 1: obtain challenge information + if self.version == 1: + # For ACME v1, we attempt to create new authzs. Existing ones + # will be returned instead. + for identifier_type, identifier in self.identifiers: + new_auth = self._new_authz_v1(identifier_type, identifier) + self._add_or_update_auth(identifier_type, identifier, new_auth) + else: + # For ACME v2, we obtain the order object by fetching the + # order URI, and extract the information from there. + result, info = self.account.get_request(self.order_uri) + + if not result: + raise ModuleFailException("Cannot download order from {0}: {1} (headers: {2})".format(self.order_uri, result, info)) + + if info['status'] not in [200]: + raise ModuleFailException("Error on downloading order: CODE: {0} RESULT: {1}".format(info['status'], result)) + + for auth_uri in result['authorizations']: + auth_data, dummy = self.account.get_request(auth_uri) + auth_data['uri'] = auth_uri + identifier_type = auth_data['identifier']['type'] + identifier = auth_data['identifier']['value'] + if auth_data.get('wildcard', False): + identifier = '*.{0}'.format(identifier) + self.authorizations[identifier_type + ':' + identifier] = auth_data + + self.finalize_uri = result['finalize'] + + # Step 2: validate challenges + for type_identifier, auth in self.authorizations.items(): + if auth['status'] == 'pending': + identifier_type, identifier = type_identifier.split(':', 1) + self._validate_challenges(identifier_type, identifier, auth) + + def _chain_matches(self, chain, criterium): + ''' + Check whether an alternate chain matches the specified criterium. + ''' + if criterium['test_certificates'] == 'last': + chain = chain[-1:] + elif criterium['test_certificates'] == 'first': + chain = chain[:1] + for cert in chain: + try: + x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography.hazmat.backends.default_backend()) + matches = True + if criterium['subject']: + for k, v in parse_name_field(criterium['subject']): + oid = cryptography_name_to_oid(k) + value = to_native(v) + found = False + for attribute in x509.subject: + if attribute.oid == oid and value == to_native(attribute.value): + found = True + break + if not found: + matches = False + break + if criterium['issuer']: + for k, v in parse_name_field(criterium['issuer']): + oid = cryptography_name_to_oid(k) + value = to_native(v) + found = False + for attribute in x509.issuer: + if attribute.oid == oid and value == to_native(attribute.value): + found = True + break + if not found: + matches = False + break + if criterium['subject_key_identifier']: + try: + ext = x509.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier) + if criterium['subject_key_identifier'] != ext.value.digest: + matches = False + except cryptography.x509.ExtensionNotFound: + matches = False + if criterium['authority_key_identifier']: + try: + ext = x509.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier) + if criterium['authority_key_identifier'] != ext.value.key_identifier: + matches = False + except cryptography.x509.ExtensionNotFound: + matches = False + if matches: + return True + except Exception as e: + self.module.warn('Error while loading certificate {0}: {1}'.format(cert, e)) + return False + + def get_certificate(self): + ''' + Request a new certificate and write it to the destination file. + First verifies whether all authorizations are valid; if not, aborts + with an error. + ''' + for identifier_type, identifier in self.identifiers: + auth = self.authorizations.get(identifier_type + ':' + identifier) + if auth is None: + raise ModuleFailException('Found no authorization information for "{0}"!'.format(identifier_type + ':' + identifier)) + if 'status' not in auth: + self._fail_challenge(identifier_type, identifier, auth, 'Authorization for {0} returned no status') + if auth['status'] != 'valid': + self._fail_challenge(identifier_type, identifier, auth, 'Authorization for {0} returned status ' + str(auth['status'])) + + if self.version == 1: + cert = self._new_cert_v1() + else: + cert_uri = self._finalize_cert() + cert = self._download_cert(cert_uri) + if self.module.params['retrieve_all_alternates'] or self.module.params['select_chain']: + # Retrieve alternate chains + alternate_chains = [] + for alternate in cert['alternates']: + try: + alt_cert = self._download_cert(alternate) + except ModuleFailException as e: + self.module.warn('Error while downloading alternative certificate {0}: {1}'.format(alternate, e)) + continue + alternate_chains.append(alt_cert) + + # Prepare return value for all alternate chains + if self.module.params['retrieve_all_alternates']: + self.all_chains = [] + + def _append_all_chains(cert_data): + self.all_chains.append(dict( + cert=cert_data['cert'].encode('utf8'), + chain=("\n".join(cert_data.get('chain', []))).encode('utf8'), + full_chain=(cert_data['cert'] + "\n".join(cert_data.get('chain', []))).encode('utf8'), + )) + + _append_all_chains(cert) + for alt_chain in alternate_chains: + _append_all_chains(alt_chain) + + # Try to select alternate chain depending on criteria + if self.module.params['select_chain']: + matching_chain = None + all_chains = [cert] + alternate_chains + for criterium_idx, criterium in enumerate(self.module.params['select_chain']): + for v in ('subject_key_identifier', 'authority_key_identifier'): + if criterium[v]: + try: + criterium[v] = binascii.unhexlify(criterium[v].replace(':', '')) + except Exception: + self.module.warn('Criterium {0} in select_chain has invalid {1} value. ' + 'Ignoring criterium.'.format(criterium_idx, v)) + continue + for alt_chain in all_chains: + if self._chain_matches(alt_chain.get('chain', []), criterium): + self.module.debug('Found matching chain for criterium {0}'.format(criterium_idx)) + matching_chain = alt_chain + break + if matching_chain: + break + if matching_chain: + cert.update(matching_chain) + else: + self.module.debug('Found no matching alternative chain') + + if cert['cert'] is not None: + pem_cert = cert['cert'] + + chain = [link for link in cert.get('chain', [])] + + if self.dest and write_file(self.module, self.dest, pem_cert.encode('utf8')): + self.cert_days = get_cert_days(self.module, self.dest) + self.changed = True + + if self.fullchain_dest and write_file(self.module, self.fullchain_dest, (pem_cert + "\n".join(chain)).encode('utf8')): + self.cert_days = get_cert_days(self.module, self.fullchain_dest) + self.changed = True + + if self.chain_dest and write_file(self.module, self.chain_dest, ("\n".join(chain)).encode('utf8')): + self.changed = True + + def deactivate_authzs(self): + ''' + Deactivates all valid authz's. Does not raise exceptions. + https://community.letsencrypt.org/t/authorization-deactivation/19860/2 + https://tools.ietf.org/html/rfc8555#section-7.5.2 + ''' + authz_deactivate = { + 'status': 'deactivated' + } + if self.version == 1: + authz_deactivate['resource'] = 'authz' + if self.authorizations: + for identifier_type, identifier in self.identifiers: + auth = self.authorizations.get(identifier_type + ':' + identifier) + if auth is None or auth.get('status') != 'valid': + continue + try: + result, info = self.account.send_signed_request(auth['uri'], authz_deactivate) + if 200 <= info['status'] < 300 and result.get('status') == 'deactivated': + auth['status'] = 'deactivated' + except Exception as dummy: + # Ignore errors on deactivating authzs + pass + if auth.get('status') != 'deactivated': + self.module.warn(warning='Could not deactivate authz object {0}.'.format(auth['uri'])) + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + modify_account=dict(type='bool', default=True), + account_email=dict(type='str'), + agreement=dict(type='str'), + terms_agreed=dict(type='bool', default=False), + challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01']), + csr=dict(type='path', aliases=['src']), + csr_content=dict(type='str'), + data=dict(type='dict'), + dest=dict(type='path', aliases=['cert']), + fullchain_dest=dict(type='path', aliases=['fullchain']), + chain_dest=dict(type='path', aliases=['chain']), + remaining_days=dict(type='int', default=10), + deactivate_authzs=dict(type='bool', default=False), + force=dict(type='bool', default=False), + retrieve_all_alternates=dict(type='bool', default=False), + select_chain=dict(type='list', elements='dict', options=dict( + test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']), + issuer=dict(type='dict'), + subject=dict(type='dict'), + subject_key_identifier=dict(type='str'), + authority_key_identifier=dict(type='str'), + )), + )) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=( + ['account_key_src', 'account_key_content'], + ['dest', 'fullchain_dest'], + ['csr', 'csr_content'], + ), + mutually_exclusive=( + ['account_key_src', 'account_key_content'], + ['csr', 'csr_content'], + ), + supports_check_mode=True, + ) + backend = handle_standard_module_arguments(module) + if module.params['select_chain']: + if backend != 'cryptography': + module.fail_json(msg="The 'select_chain' can only be used with the 'cryptography' backend.") + elif not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography')) + + try: + if module.params.get('dest'): + cert_days = get_cert_days(module, module.params['dest']) + else: + cert_days = get_cert_days(module, module.params['fullchain_dest']) + + if module.params['force'] or cert_days < module.params['remaining_days']: + # If checkmode is active, base the changed state solely on the status + # of the certificate file as all other actions (accessing an account, checking + # the authorization status...) would lead to potential changes of the current + # state + if module.check_mode: + module.exit_json(changed=True, authorizations={}, challenge_data={}, cert_days=cert_days) + else: + client = ACMEClient(module) + client.cert_days = cert_days + other = dict() + is_first_step = client.is_first_step() + if is_first_step: + # First run: start challenges / start new order + client.start_challenges() + else: + # Second run: finish challenges, and get certificate + try: + client.finish_challenges() + client.get_certificate() + if module.params['retrieve_all_alternates']: + other['all_chains'] = client.all_chains + finally: + if module.params['deactivate_authzs']: + client.deactivate_authzs() + data, data_dns = client.get_challenges_data(first_step=is_first_step) + auths = dict() + for k, v in client.authorizations.items(): + # Remove "type:" from key + auths[k.split(':', 1)[1]] = v + module.exit_json( + changed=client.changed, + authorizations=auths, + finalize_uri=client.finalize_uri, + order_uri=client.order_uri, + account_uri=client.account.uri, + challenge_data=data, + challenge_data_dns=data_dns, + cert_days=client.cert_days, + **other + ) + else: + module.exit_json(changed=False, cert_days=cert_days) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py new file mode 100644 index 00000000..d9751abd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_certificate_revoke +author: "Felix Fontein (@felixfontein)" +short_description: Revoke certificates with the ACME protocol +description: + - "Allows to revoke certificates issued by a CA supporting the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + such as L(Let's Encrypt,https://letsencrypt.org/)." +notes: + - "Exactly one of C(account_key_src), C(account_key_content), + C(private_key_src) or C(private_key_content) must be specified." + - "Trying to revoke an already revoked certificate + should result in an unchanged status, even if the revocation reason + was different than the one specified here. Also, depending on the + server, it can happen that some other error is returned if the + certificate has already been revoked." + - Does not support C(check_mode). +seealso: + - name: The Let's Encrypt documentation + description: Documentation for the Let's Encrypt Certification Authority. + Provides useful information for example on rate limits. + link: https://letsencrypt.org/docs/ + - name: Automatic Certificate Management Environment (ACME) + description: The specification of the ACME protocol (RFC 8555). + link: https://tools.ietf.org/html/rfc8555 + - module: community.crypto.acme_inspect + description: Allows to debug problems. +extends_documentation_fragment: +- community.crypto.acme + +options: + certificate: + description: + - "Path to the certificate to revoke." + type: path + required: yes + account_key_src: + description: + - "Path to a file containing the ACME account RSA or Elliptic Curve + key." + - "RSA keys can be created with C(openssl rsa ...). Elliptic curve keys can + be created with C(openssl ecparam -genkey ...). Any other tool creating + private keys in PEM format can be used as well." + - "Mutually exclusive with C(account_key_content)." + - "Required if C(account_key_content) is not used." + type: path + account_key_content: + description: + - "Content of the ACME account RSA or Elliptic Curve key." + - "Note that exactly one of C(account_key_src), C(account_key_content), + C(private_key_src) or C(private_key_content) must be specified." + - "I(Warning): the content will be written into a temporary file, which will + be deleted by Ansible when the module completes. Since this is an + important private key — it can be used to change the account key, + or to revoke your certificates without knowing their private keys + —, this might not be acceptable." + - "In case C(cryptography) is used, the content is not written into a + temporary file. It can still happen that it is written to disk by + Ansible in the process of moving the module with its argument to + the node where it is executed." + type: str + private_key_src: + description: + - "Path to the certificate's private key." + - "Note that exactly one of C(account_key_src), C(account_key_content), + C(private_key_src) or C(private_key_content) must be specified." + type: path + private_key_content: + description: + - "Content of the certificate's private key." + - "Note that exactly one of C(account_key_src), C(account_key_content), + C(private_key_src) or C(private_key_content) must be specified." + - "I(Warning): the content will be written into a temporary file, which will + be deleted by Ansible when the module completes. Since this is an + important private key — it can be used to change the account key, + or to revoke your certificates without knowing their private keys + —, this might not be acceptable." + - "In case C(cryptography) is used, the content is not written into a + temporary file. It can still happen that it is written to disk by + Ansible in the process of moving the module with its argument to + the node where it is executed." + type: str + revoke_reason: + description: + - "One of the revocation reasonCodes defined in + L(Section 5.3.1 of RFC5280,https://tools.ietf.org/html/rfc5280#section-5.3.1)." + - "Possible values are C(0) (unspecified), C(1) (keyCompromise), + C(2) (cACompromise), C(3) (affiliationChanged), C(4) (superseded), + C(5) (cessationOfOperation), C(6) (certificateHold), + C(8) (removeFromCRL), C(9) (privilegeWithdrawn), + C(10) (aACompromise)." + type: int +''' + +EXAMPLES = ''' +- name: Revoke certificate with account key + community.crypto.acme_certificate_revoke: + account_key_src: /etc/pki/cert/private/account.key + certificate: /etc/httpd/ssl/sample.com.crt + +- name: Revoke certificate with certificate's private key + community.crypto.acme_certificate_revoke: + private_key_src: /etc/httpd/ssl/sample.com.key + certificate: /etc/httpd/ssl/sample.com.crt +''' + +RETURN = '''#''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + ACMEAccount, + nopad_b64, + pem_to_der, + handle_standard_module_arguments, + get_default_argspec, +) + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + private_key_src=dict(type='path'), + private_key_content=dict(type='str', no_log=True), + certificate=dict(type='path', required=True), + revoke_reason=dict(type='int'), + )) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=( + ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], + ), + mutually_exclusive=( + ['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'], + ), + supports_check_mode=False, + ) + handle_standard_module_arguments(module) + + try: + account = ACMEAccount(module) + # Load certificate + certificate = pem_to_der(module.params.get('certificate')) + certificate = nopad_b64(certificate) + # Construct payload + payload = { + 'certificate': certificate + } + if module.params.get('revoke_reason') is not None: + payload['reason'] = module.params.get('revoke_reason') + # Determine endpoint + if module.params.get('acme_version') == 1: + endpoint = account.directory['revoke-cert'] + payload['resource'] = 'revoke-cert' + else: + endpoint = account.directory['revokeCert'] + # Get hold of private key (if available) and make sure it comes from disk + private_key = module.params.get('private_key_src') + private_key_content = module.params.get('private_key_content') + # Revoke certificate + if private_key or private_key_content: + # Step 1: load and parse private key + error, private_key_data = account.parse_key(private_key, private_key_content) + if error: + raise ModuleFailException("error while parsing private key: %s" % error) + # Step 2: sign revokation request with private key + jws_header = { + "alg": private_key_data['alg'], + "jwk": private_key_data['jwk'], + } + result, info = account.send_signed_request(endpoint, payload, key_data=private_key_data, jws_header=jws_header) + else: + # Step 1: get hold of account URI + created, account_data = account.setup_account(allow_creation=False) + if created: + raise AssertionError('Unwanted account creation') + if account_data is None: + raise ModuleFailException(msg='Account does not exist or is deactivated.') + # Step 2: sign revokation request with account key + result, info = account.send_signed_request(endpoint, payload) + if info['status'] != 200: + already_revoked = False + # Standardized error from draft 14 on (https://tools.ietf.org/html/rfc8555#section-7.6) + if result.get('type') == 'urn:ietf:params:acme:error:alreadyRevoked': + already_revoked = True + else: + # Hack for Boulder errors + if module.params.get('acme_version') == 1: + error_type = 'urn:acme:error:malformed' + else: + error_type = 'urn:ietf:params:acme:error:malformed' + if result.get('type') == error_type and result.get('detail') == 'Certificate already revoked': + # Fallback: boulder returns this in case the certificate was already revoked. + already_revoked = True + # If we know the certificate was already revoked, we don't fail, + # but successfully terminate while indicating no change + if already_revoked: + module.exit_json(changed=False) + raise ModuleFailException('Error revoking certificate: {0} {1}'.format(info['status'], result)) + module.exit_json(changed=True) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py new file mode 100644 index 00000000..344044bc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py @@ -0,0 +1,294 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: acme_challenge_cert_helper +author: "Felix Fontein (@felixfontein)" +short_description: Prepare certificates required for ACME challenges such as C(tls-alpn-01) +description: + - "Prepares certificates for ACME challenges such as C(tls-alpn-01)." + - "The raw data is provided by the M(community.crypto.acme_certificate) module, and needs to be + converted to a certificate to be used for challenge validation. This module + provides a simple way to generate the required certificates." +seealso: + - name: Automatic Certificate Management Environment (ACME) + description: The specification of the ACME protocol (RFC 8555). + link: https://tools.ietf.org/html/rfc8555 + - name: ACME TLS ALPN Challenge Extension + description: The specification of the C(tls-alpn-01) challenge (RFC 8737). + link: https://www.rfc-editor.org/rfc/rfc8737.html +requirements: + - "cryptography >= 1.3" +options: + challenge: + description: + - "The challenge type." + type: str + required: yes + choices: + - tls-alpn-01 + challenge_data: + description: + - "The C(challenge_data) entry provided by M(community.crypto.acme_certificate) for the + challenge." + type: dict + required: yes + private_key_src: + description: + - "Path to a file containing the private key file to use for this challenge + certificate." + - "Mutually exclusive with C(private_key_content)." + type: path + private_key_content: + description: + - "Content of the private key to use for this challenge certificate." + - "Mutually exclusive with C(private_key_src)." + type: str +notes: + - Does not support C(check_mode). +''' + +EXAMPLES = ''' +- name: Create challenges for a given CRT for sample.com + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + challenge: tls-alpn-01 + csr: /etc/pki/cert/csr/sample.com.csr + dest: /etc/httpd/ssl/sample.com.crt + register: sample_com_challenge + +- name: Create certificates for challenges + community.crypto.acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: /etc/pki/cert/key/sample.com.key + loop: "{{ sample_com_challenge.challenge_data | dictsort }}" + register: sample_com_challenge_certs + +- name: Install challenge certificates + # We need to set up HTTPS such that for the domain, + # regular_certificate is delivered for regular connections, + # except if ALPN selects the "acme-tls/1"; then, the + # challenge_certificate must be delivered. + # This can for example be achieved with very new versions + # of NGINX; search for ssl_preread and + # ssl_preread_alpn_protocols for information on how to + # route by ALPN protocol. + ...: + domain: "{{ item.domain }}" + challenge_certificate: "{{ item.challenge_certificate }}" + regular_certificate: "{{ item.regular_certificate }}" + private_key: /etc/pki/cert/key/sample.com.key + loop: "{{ sample_com_challenge_certs.results }}" + +- name: Create certificate for a given CSR for sample.com + community.crypto.acme_certificate: + account_key_src: /etc/pki/cert/private/account.key + challenge: tls-alpn-01 + csr: /etc/pki/cert/csr/sample.com.csr + dest: /etc/httpd/ssl/sample.com.crt + data: "{{ sample_com_challenge }}" +''' + +RETURN = ''' +domain: + description: + - "The domain the challenge is for. The certificate should be provided if + this is specified in the request's the C(Host) header." + returned: always + type: str +identifier_type: + description: + - "The identifier type for the actual resource identifier. Will be C(dns) + or C(ip)." + returned: always + type: str +identifier: + description: + - "The identifier for the actual resource. Will be a domain name if the + type is C(dns), or an IP address if the type is C(ip)." + returned: always + type: str +challenge_certificate: + description: + - "The challenge certificate in PEM format." + returned: always + type: str +regular_certificate: + description: + - "A self-signed certificate for the challenge domain." + - "If no existing certificate exists, can be used to set-up + https in the first place if that is needed for providing + the challenge." + returned: always + type: str +''' + +import base64 +import datetime +import sys +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_bytes, to_text + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + read_file, +) + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.serialization + import cryptography.hazmat.primitives.asymmetric.rsa + import cryptography.hazmat.primitives.asymmetric.ec + import cryptography.hazmat.primitives.asymmetric.padding + import cryptography.hazmat.primitives.hashes + import cryptography.hazmat.primitives.asymmetric.utils + import cryptography.x509 + import cryptography.x509.oid + import ipaddress + from distutils.version import LooseVersion + HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3')) + _cryptography_backend = cryptography.hazmat.backends.default_backend() +except ImportError as dummy: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + HAS_CRYPTOGRAPHY = False + + +# Convert byte string to ASN1 encoded octet string +if sys.version_info[0] >= 3: + def encode_octet_string(octet_string): + if len(octet_string) >= 128: + raise ModuleFailException('Cannot handle octet strings with more than 128 bytes') + return bytes([0x4, len(octet_string)]) + octet_string +else: + def encode_octet_string(octet_string): + if len(octet_string) >= 128: + raise ModuleFailException('Cannot handle octet strings with more than 128 bytes') + return b'\x04' + chr(len(octet_string)) + octet_string + + +def main(): + module = AnsibleModule( + argument_spec=dict( + challenge=dict(type='str', required=True, choices=['tls-alpn-01']), + challenge_data=dict(type='dict', required=True), + private_key_src=dict(type='path'), + private_key_content=dict(type='str', no_log=True), + ), + required_one_of=( + ['private_key_src', 'private_key_content'], + ), + mutually_exclusive=( + ['private_key_src', 'private_key_content'], + ), + ) + if not HAS_CRYPTOGRAPHY: + module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR) + + try: + # Get parameters + challenge = module.params['challenge'] + challenge_data = module.params['challenge_data'] + + # Get hold of private key + private_key_content = module.params.get('private_key_content') + if private_key_content is None: + private_key_content = read_file(module.params['private_key_src']) + else: + private_key_content = to_bytes(private_key_content) + try: + private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(private_key_content, password=None, backend=_cryptography_backend) + except Exception as e: + raise ModuleFailException('Error while loading private key: {0}'.format(e)) + + # Some common attributes + domain = to_text(challenge_data['resource']) + identifier_type, identifier = to_text(challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split(':', 1) + subject = issuer = cryptography.x509.Name([]) + not_valid_before = datetime.datetime.utcnow() + not_valid_after = datetime.datetime.utcnow() + datetime.timedelta(days=10) + if identifier_type == 'dns': + san = cryptography.x509.DNSName(identifier) + elif identifier_type == 'ip': + san = cryptography.x509.IPAddress(ipaddress.ip_address(identifier)) + else: + raise ModuleFailException('Unsupported identifier type "{0}"'.format(identifier_type)) + + # Generate regular self-signed certificate + regular_certificate = cryptography.x509.CertificateBuilder().subject_name( + subject + ).issuer_name( + issuer + ).public_key( + private_key.public_key() + ).serial_number( + cryptography.x509.random_serial_number() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).add_extension( + cryptography.x509.SubjectAlternativeName([san]), + critical=False, + ).sign( + private_key, + cryptography.hazmat.primitives.hashes.SHA256(), + _cryptography_backend + ) + + # Process challenge + if challenge == 'tls-alpn-01': + value = base64.b64decode(challenge_data['resource_value']) + challenge_certificate = cryptography.x509.CertificateBuilder().subject_name( + subject + ).issuer_name( + issuer + ).public_key( + private_key.public_key() + ).serial_number( + cryptography.x509.random_serial_number() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).add_extension( + cryptography.x509.SubjectAlternativeName([san]), + critical=False, + ).add_extension( + cryptography.x509.UnrecognizedExtension( + cryptography.x509.ObjectIdentifier("1.3.6.1.5.5.7.1.31"), + encode_octet_string(value), + ), + critical=True, + ).sign( + private_key, + cryptography.hazmat.primitives.hashes.SHA256(), + _cryptography_backend + ) + + module.exit_json( + changed=True, + domain=domain, + identifier_type=identifier_type, + identifier=identifier, + challenge_certificate=challenge_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM), + regular_certificate=regular_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM) + ) + except ModuleFailException as e: + e.do_fail(module) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_inspect.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_inspect.py new file mode 100644 index 00000000..c22131c4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/acme_inspect.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018 Felix Fontein (@felixfontein) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: acme_inspect +author: "Felix Fontein (@felixfontein)" +short_description: Send direct requests to an ACME server +description: + - "Allows to send direct requests to an ACME server with the + L(ACME protocol,https://tools.ietf.org/html/rfc8555), + which is supported by CAs such as L(Let's Encrypt,https://letsencrypt.org/)." + - "This module can be used to debug failed certificate request attempts, + for example when M(community.crypto.acme_certificate) fails or encounters a problem which + you wish to investigate." + - "The module can also be used to directly access features of an ACME servers + which are not yet supported by the Ansible ACME modules." +notes: + - "The I(account_uri) option must be specified for properly authenticated + ACME v2 requests (except a C(new-account) request)." + - "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute + ACME requests without the need of writing a playbook. For example, the + following command retrieves the ACME account with ID 1 from Let's Encrypt + (assuming C(/path/to/key) is the correct private account key): + C(ansible localhost -m acme_inspect -a \"account_key_src=/path/to/key + acme_directory=https://acme-v02.api.letsencrypt.org/directory acme_version=2 + account_uri=https://acme-v02.api.letsencrypt.org/acme/acct/1 method=get + url=https://acme-v02.api.letsencrypt.org/acme/acct/1\")" +seealso: + - name: Automatic Certificate Management Environment (ACME) + description: The specification of the ACME protocol (RFC 8555). + link: https://tools.ietf.org/html/rfc8555 + - name: ACME TLS ALPN Challenge Extension + description: The specification of the C(tls-alpn-01) challenge (RFC 8737). + link: https://www.rfc-editor.org/rfc/rfc8737.html +extends_documentation_fragment: +- community.crypto.acme + +options: + url: + description: + - "The URL to send the request to." + - "Must be specified if I(method) is not C(directory-only)." + type: str + method: + description: + - "The method to use to access the given URL on the ACME server." + - "The value C(post) executes an authenticated POST request. The content + must be specified in the I(content) option." + - "The value C(get) executes an authenticated POST-as-GET request for ACME v2, + and a regular GET request for ACME v1." + - "The value C(directory-only) only retrieves the directory, without doing + a request." + type: str + default: get + choices: + - get + - post + - directory-only + content: + description: + - "An encoded JSON object which will be sent as the content if I(method) + is C(post)." + - "Required when I(method) is C(post), and not allowed otherwise." + type: str + fail_on_acme_error: + description: + - "If I(method) is C(post) or C(get), make the module fail in case an ACME + error is returned." + type: bool + default: yes +''' + +EXAMPLES = r''' +- name: Get directory + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + method: directory-only + register: directory + +- name: Create an account + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + url: "{{ directory.newAccount}}" + method: post + content: '{"termsOfServiceAgreed":true}' + register: account_creation + # account_creation.headers.location contains the account URI + # if creation was successful + +- name: Get account information + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ account_creation.headers.location }}" + method: get + +- name: Update account contacts + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ account_creation.headers.location }}" + method: post + content: '{{ account_info | to_json }}' + vars: + account_info: + # For valid values, see + # https://tools.ietf.org/html/rfc8555#section-7.3 + contact: + - mailto:me@example.com + +- name: Create certificate order + community.crypto.acme_certificate: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + csr: /etc/pki/cert/csr/sample.com.csr + fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt + challenge: http-01 + register: certificate_request + +# Assume something went wrong. certificate_request.order_uri contains +# the order URI. + +- name: Get order information + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ certificate_request.order_uri }}" + method: get + register: order + +- name: Get first authz for order + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ order.output_json.authorizations[0] }}" + method: get + register: authz + +- name: Get HTTP-01 challenge for authz + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ authz.output_json.challenges | selectattr('type', 'equalto', 'http-01') }}" + method: get + register: http01challenge + +- name: Activate HTTP-01 challenge manually + community.crypto.acme_inspect: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: /etc/pki/cert/private/account.key + account_uri: "{{ account_creation.headers.location }}" + url: "{{ http01challenge.url }}" + method: post + content: '{}' +''' + +RETURN = ''' +directory: + description: The ACME directory's content + returned: always + type: dict + sample: | + { + "a85k3x9f91A4": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417", + "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change", + "meta": { + "caaIdentities": [ + "letsencrypt.org" + ], + "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf", + "website": "https://letsencrypt.org" + }, + "newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct", + "newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce", + "newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order", + "revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert" + } +headers: + description: The request's HTTP headers (with lowercase keys) + returned: always + type: dict + sample: | + { + "boulder-requester": "12345", + "cache-control": "max-age=0, no-cache, no-store", + "connection": "close", + "content-length": "904", + "content-type": "application/json", + "cookies": {}, + "cookies_string": "", + "date": "Wed, 07 Nov 2018 12:34:56 GMT", + "expires": "Wed, 07 Nov 2018 12:44:56 GMT", + "link": "<https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel=\"terms-of-service\"", + "msg": "OK (904 bytes)", + "pragma": "no-cache", + "replay-nonce": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGH", + "server": "nginx", + "status": 200, + "strict-transport-security": "max-age=604800", + "url": "https://acme-v02.api.letsencrypt.org/acme/acct/46161", + "x-frame-options": "DENY" + } +output_text: + description: The raw text output + returned: always + type: str + sample: "{\\n \\\"id\\\": 12345,\\n \\\"key\\\": {\\n \\\"kty\\\": \\\"RSA\\\",\\n ..." +output_json: + description: The output parsed as JSON + returned: if output can be parsed as JSON + type: dict + sample: + - id: 12345 + - key: + - kty: RSA + - ... +''' + +import json + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native, to_bytes, to_text + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + ModuleFailException, + ACMEAccount, + handle_standard_module_arguments, + get_default_argspec, +) + + +def main(): + argument_spec = get_default_argspec() + argument_spec.update(dict( + url=dict(type='str'), + method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'), + content=dict(type='str'), + fail_on_acme_error=dict(type='bool', default=True), + )) + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=( + ['account_key_src', 'account_key_content'], + ), + required_if=( + ['method', 'get', ['url']], + ['method', 'post', ['url', 'content']], + ['method', 'get', ['account_key_src', 'account_key_content'], True], + ['method', 'post', ['account_key_src', 'account_key_content'], True], + ), + ) + handle_standard_module_arguments(module) + + result = dict() + changed = False + try: + # Get hold of ACMEAccount object (includes directory) + account = ACMEAccount(module) + method = module.params['method'] + result['directory'] = account.directory.directory + # Do we have to do more requests? + if method != 'directory-only': + url = module.params['url'] + fail_on_acme_error = module.params['fail_on_acme_error'] + # Do request + if method == 'get': + data, info = account.get_request(url, parse_json_result=False, fail_on_error=False) + elif method == 'post': + changed = True # only POSTs can change + data, info = account.send_signed_request(url, to_bytes(module.params['content']), parse_json_result=False, encode_payload=False) + # Update results + result.update(dict( + headers=info, + output_text=to_native(data), + )) + # See if we can parse the result as JSON + try: + # to_text() is needed only for Python 3.5 (and potentially 3.0 to 3.4 as well) + result['output_json'] = json.loads(to_text(data)) + except Exception as dummy: + pass + # Fail if error was returned + if fail_on_acme_error and info['status'] >= 400: + raise ModuleFailException("ACME request failed: CODE: {0} RESULT: {1}".format(info['status'], data)) + # Done! + module.exit_json(changed=changed, **result) + except ModuleFailException as e: + e.do_fail(module, **result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/certificate_complete_chain.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/certificate_complete_chain.py new file mode 100644 index 00000000..2201e565 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/certificate_complete_chain.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2018, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: certificate_complete_chain +author: "Felix Fontein (@felixfontein)" +short_description: Complete certificate chain given a set of untrusted and root certificates +description: + - "This module completes a given chain of certificates in PEM format by finding + intermediate certificates from a given set of certificates, until it finds a root + certificate in another given set of certificates." + - "This can for example be used to find the root certificate for a certificate chain + returned by M(community.crypto.acme_certificate)." + - "Note that this module does I(not) check for validity of the chains. It only + checks that issuer and subject match, and that the signature is correct. It + ignores validity dates and key usage completely. If you need to verify that a + generated chain is valid, please use C(openssl verify ...)." +requirements: + - "cryptography >= 1.5" +options: + input_chain: + description: + - A concatenated set of certificates in PEM format forming a chain. + - The module will try to complete this chain. + type: str + required: yes + root_certificates: + description: + - "A list of filenames or directories." + - "A filename is assumed to point to a file containing one or more certificates + in PEM format. All certificates in this file will be added to the set of + root certificates." + - "If a directory name is given, all files in the directory and its + subdirectories will be scanned and tried to be parsed as concatenated + certificates in PEM format." + - "Symbolic links will be followed." + type: list + elements: path + required: yes + intermediate_certificates: + description: + - "A list of filenames or directories." + - "A filename is assumed to point to a file containing one or more certificates + in PEM format. All certificates in this file will be added to the set of + root certificates." + - "If a directory name is given, all files in the directory and its + subdirectories will be scanned and tried to be parsed as concatenated + certificates in PEM format." + - "Symbolic links will be followed." + type: list + elements: path + default: [] +''' + + +EXAMPLES = ''' +# Given a leaf certificate for www.ansible.com and one or more intermediate +# certificates, finds the associated root certificate. +- name: Find root certificate + community.crypto.certificate_complete_chain: + input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com-fullchain.pem') }}" + root_certificates: + - /etc/ca-certificates/ + register: www_ansible_com +- name: Write root certificate to disk + copy: + dest: /etc/ssl/csr/www.ansible.com-root.pem + content: "{{ www_ansible_com.root }}" + +# Given a leaf certificate for www.ansible.com, and a list of intermediate +# certificates, finds the associated root certificate. +- name: Find root certificate + community.crypto.certificate_complete_chain: + input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.pem') }}" + intermediate_certificates: + - /etc/ssl/csr/www.ansible.com-chain.pem + root_certificates: + - /etc/ca-certificates/ + register: www_ansible_com +- name: Write complete chain to disk + copy: + dest: /etc/ssl/csr/www.ansible.com-completechain.pem + content: "{{ ''.join(www_ansible_com.complete_chain) }}" +- name: Write root chain (intermediates and root) to disk + copy: + dest: /etc/ssl/csr/www.ansible.com-rootchain.pem + content: "{{ ''.join(www_ansible_com.chain) }}" +''' + + +RETURN = ''' +root: + description: + - "The root certificate in PEM format." + returned: success + type: str +chain: + description: + - "The chain added to the given input chain. Includes the root certificate." + - "Returned as a list of PEM certificates." + returned: success + type: list + elements: str +complete_chain: + description: + - "The completed chain, including leaf, all intermediates, and root." + - "Returned as a list of PEM certificates." + returned: success + type: list + elements: str +''' + +import os +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + split_pem_list, +) + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.serialization + import cryptography.hazmat.primitives.asymmetric.rsa + import cryptography.hazmat.primitives.asymmetric.ec + import cryptography.hazmat.primitives.asymmetric.padding + import cryptography.hazmat.primitives.hashes + import cryptography.hazmat.primitives.asymmetric.utils + import cryptography.x509 + import cryptography.x509.oid + from distutils.version import LooseVersion + HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5')) + _cryptography_backend = cryptography.hazmat.backends.default_backend() +except ImportError as dummy: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + HAS_CRYPTOGRAPHY = False + + +class Certificate(object): + ''' + Stores PEM with parsed certificate. + ''' + def __init__(self, pem, cert): + if not (pem.endswith('\n') or pem.endswith('\r')): + pem = pem + '\n' + self.pem = pem + self.cert = cert + + +def is_parent(module, cert, potential_parent): + ''' + Tests whether the given certificate has been issued by the potential parent certificate. + ''' + # Check issuer + if cert.cert.issuer != potential_parent.cert.subject: + return False + # Check signature + public_key = potential_parent.cert.public_key() + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + public_key.verify( + cert.cert.signature, + cert.cert.tbs_certificate_bytes, + cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15(), + cert.cert.signature_hash_algorithm + ) + elif isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): + public_key.verify( + cert.cert.signature, + cert.cert.tbs_certificate_bytes, + cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cert.cert.signature_hash_algorithm), + ) + else: + # Unknown public key type + module.warn('Unknown public key type "{0}"'.format(public_key)) + return False + return True + except cryptography.exceptions.InvalidSignature as dummy: + return False + except Exception as e: + module.fail_json(msg='Unknown error on signature validation: {0}'.format(e)) + + +def parse_PEM_list(module, text, source, fail_on_error=True): + ''' + Parse concatenated PEM certificates. Return list of ``Certificate`` objects. + ''' + result = [] + for cert_pem in split_pem_list(text): + # Try to load PEM certificate + try: + cert = cryptography.x509.load_pem_x509_certificate(to_bytes(cert_pem), _cryptography_backend) + result.append(Certificate(cert_pem, cert)) + except Exception as e: + msg = 'Cannot parse certificate #{0} from {1}: {2}'.format(len(result) + 1, source, e) + if fail_on_error: + module.fail_json(msg=msg) + else: + module.warn(msg) + return result + + +def load_PEM_list(module, path, fail_on_error=True): + ''' + Load concatenated PEM certificates from file. Return list of ``Certificate`` objects. + ''' + try: + with open(path, "rb") as f: + return parse_PEM_list(module, f.read().decode('utf-8'), source=path, fail_on_error=fail_on_error) + except Exception as e: + msg = 'Cannot read certificate file {0}: {1}'.format(path, e) + if fail_on_error: + module.fail_json(msg=msg) + else: + module.warn(msg) + return [] + + +class CertificateSet(object): + ''' + Stores a set of certificates. Allows to search for parent (issuer of a certificate). + ''' + + def __init__(self, module): + self.module = module + self.certificates = set() + self.certificate_by_issuer = dict() + + def _load_file(self, path): + certs = load_PEM_list(self.module, path, fail_on_error=False) + for cert in certs: + self.certificates.add(cert) + self.certificate_by_issuer[cert.cert.subject] = cert + + def load(self, path): + ''' + Load lists of PEM certificates from a file or a directory. + ''' + b_path = to_bytes(path, errors='surrogate_or_strict') + if os.path.isdir(b_path): + for directory, dummy, files in os.walk(b_path, followlinks=True): + for file in files: + self._load_file(os.path.join(directory, file)) + else: + self._load_file(b_path) + + def find_parent(self, cert): + ''' + Search for the parent (issuer) of a certificate. Return ``None`` if none was found. + ''' + potential_parent = self.certificate_by_issuer.get(cert.cert.issuer) + if potential_parent is not None: + if is_parent(self.module, cert, potential_parent): + return potential_parent + return None + + +def format_cert(cert): + ''' + Return human readable representation of certificate for error messages. + ''' + return str(cert.cert) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + input_chain=dict(type='str', required=True), + root_certificates=dict(type='list', required=True, elements='path'), + intermediate_certificates=dict(type='list', default=[], elements='path'), + ), + supports_check_mode=True, + ) + + if not HAS_CRYPTOGRAPHY: + module.fail_json(msg=missing_required_lib('cryptography >= 1.5'), exception=CRYPTOGRAPHY_IMP_ERR) + + # Load chain + chain = parse_PEM_list(module, module.params['input_chain'], source='input chain') + if len(chain) == 0: + module.fail_json(msg='Input chain must contain at least one certificate') + + # Check chain + for i, parent in enumerate(chain): + if i > 0: + if not is_parent(module, chain[i - 1], parent): + module.fail_json(msg=('Cannot verify input chain: certificate #{2}: {3} is not issuer ' + + 'of certificate #{0}: {1}').format(i, format_cert(chain[i - 1]), i + 1, format_cert(parent))) + + # Load intermediate certificates + intermediates = CertificateSet(module) + for path in module.params['intermediate_certificates']: + intermediates.load(path) + + # Load root certificates + roots = CertificateSet(module) + for path in module.params['root_certificates']: + roots.load(path) + + # Try to complete chain + current = chain[-1] + completed = [] + while current: + root = roots.find_parent(current) + if root: + completed.append(root) + break + intermediate = intermediates.find_parent(current) + if intermediate: + completed.append(intermediate) + current = intermediate + else: + module.fail_json(msg='Cannot complete chain. Stuck at certificate {0}'.format(format_cert(current))) + + # Return results + complete_chain = chain + completed + module.exit_json( + changed=False, + root=complete_chain[-1].pem, + chain=[cert.pem for cert in completed], + complete_chain=[cert.pem for cert in complete_chain], + ) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py new file mode 100644 index 00000000..4507f400 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py @@ -0,0 +1,955 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c), Entrust Datacard Corporation, 2019 +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ecs_certificate +author: + - Chris Trufan (@ctrufan) +short_description: Request SSL/TLS certificates with the Entrust Certificate Services (ECS) API +description: + - Create, reissue, and renew certificates with the Entrust Certificate Services (ECS) API. + - Requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API. + - In order to request a certificate, the domain and organization used in the certificate signing request must be already + validated in the ECS system. It is I(not) the responsibility of this module to perform those steps. +notes: + - C(path) must be specified as the output location of the certificate. +requirements: + - cryptography >= 1.6 +options: + backup: + description: + - Whether a backup should be made for the certificate in I(path). + type: bool + default: false + force: + description: + - If force is used, a certificate is requested regardless of whether I(path) points to an existing valid certificate. + - If C(request_type=renew), a forced renew will fail if the certificate being renewed has been issued within the past 30 days, regardless of the + value of I(remaining_days) or the return value of I(cert_days) - the ECS API does not support the "renew" operation for certificates that are not + at least 30 days old. + type: bool + default: false + path: + description: + - The destination path for the generated certificate as a PEM encoded cert. + - If the certificate at this location is not an Entrust issued certificate, a new certificate will always be requested even if the current + certificate is technically valid. + - If there is already an Entrust certificate at this location, whether it is replaced is depends on the I(remaining_days) calculation. + - If an existing certificate is being replaced (see I(remaining_days), I(force), and I(tracking_id)), whether a new certificate is requested + or the existing certificate is renewed or reissued is based on I(request_type). + type: path + required: true + full_chain_path: + description: + - The destination path for the full certificate chain of the certificate, intermediates, and roots. + type: path + csr: + description: + - Base-64 encoded Certificate Signing Request (CSR). I(csr) is accepted with or without PEM formatting around the Base-64 string. + - If no I(csr) is provided when C(request_type=reissue) or C(request_type=renew), the certificate will be generated with the same public key as + the certificate being renewed or reissued. + - If I(subject_alt_name) is specified, it will override the subject alternate names in the CSR. + - If I(eku) is specified, it will override the extended key usage in the CSR. + - If I(ou) is specified, it will override the organizational units "ou=" present in the subject distinguished name of the CSR, if any. + - The organization "O=" field from the CSR will not be used. It will be replaced in the issued certificate by I(org) if present, and if not present, + the organization tied to I(client_id). + type: str + tracking_id: + description: + - The tracking ID of the certificate to reissue or renew. + - I(tracking_id) is invalid if C(request_type=new) or C(request_type=validate_only). + - If there is a certificate present in I(path) and it is an ECS certificate, I(tracking_id) will be ignored. + - If there is no certificate present in I(path) or there is but it is from another provider, the certificate represented by I(tracking_id) will + be renewed or reissued and saved to I(path). + - If there is no certificate present in I(path) and the I(force) and I(remaining_days) parameters do not indicate a new certificate is needed, + the certificate referenced by I(tracking_id) certificate will be saved to I(path). + - This can be used when a known certificate is not currently present on a server, but you want to renew or reissue it to be managed by an ansible + playbook. For example, if you specify C(request_type=renew), I(tracking_id) of an issued certificate, and I(path) to a file that does not exist, + the first run of a task will download the certificate specified by I(tracking_id) (assuming it is still valid). Future runs of the task will + (if applicable - see I(force) and I(remaining_days)) renew the certificate now present in I(path). + type: int + remaining_days: + description: + - The number of days the certificate must have left being valid. If C(cert_days < remaining_days) then a new certificate will be + obtained using I(request_type). + - If C(request_type=renew), a renewal will fail if the certificate being renewed has been issued within the past 30 days, so do not set a + I(remaining_days) value that is within 30 days of the full lifetime of the certificate being acted upon. (e.g. if you are requesting Certificates + with a 90 day lifetime, do not set remaining_days to a value C(60) or higher). + - The I(force) option may be used to ensure that a new certificate is always obtained. + type: int + default: 30 + request_type: + description: + - The operation performed if I(tracking_id) references a valid certificate to reissue, or there is already a certificate present in I(path) but + either I(force) is specified or C(cert_days < remaining_days). + - Specifying C(request_type=validate_only) means the request will be validated against the ECS API, but no certificate will be issued. + - Specifying C(request_type=new) means a certificate request will always be submitted and a new certificate issued. + - Specifying C(request_type=renew) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be renewed. + If there is no certificate to renew, a new certificate is requested. + - Specifying C(request_type=reissue) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be + reissued. + If there is no certificate to reissue, a new certificate is requested. + - If a certificate was issued within the past 30 days, the 'renew' operation is not a valid operation and will fail. + - Note that C(reissue) is an operation that will result in the revocation of the certificate that is reissued, be cautious with it's use. + - I(check_mode) is only supported if C(request_type=new) + - For example, setting C(request_type=renew) and C(remaining_days=30) and pointing to the same certificate on multiple playbook runs means that on + the first run new certificate will be requested. It will then be left along on future runs until it is within 30 days of expiry, then the + ECS "renew" operation will be performed. + type: str + choices: [ 'new', 'renew', 'reissue', 'validate_only'] + default: new + cert_type: + description: + - Specify the type of certificate requested. + - If a certificate is being reissued or renewed, this parameter is ignored, and the C(cert_type) of the initial certificate is used. + type: str + choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CODE_SIGNING', 'EV_CODE_SIGNING', + 'CDS_INDIVIDUAL', 'CDS_GROUP', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ] + subject_alt_name: + description: + - The subject alternative name identifiers, as an array of values (applies to I(cert_type) with a value of C(STANDARD_SSL), C(ADVANTAGE_SSL), + C(UC_SSL), C(EV_SSL), C(WILDCARD_SSL), C(PRIVATE_SSL), and C(PD_SSL)). + - If you are requesting a new SSL certificate, and you pass a I(subject_alt_name) parameter, any SAN names in the CSR are ignored. + If no subjectAltName parameter is passed, the SAN names in the CSR are used. + - See I(request_type) to understand more about SANs during reissues and renewals. + - In the case of certificates of type C(STANDARD_SSL) certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value + is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted. + type: list + elements: str + eku: + description: + - If specified, overrides the key usage in the I(csr). + type: str + choices: [ SERVER_AUTH, CLIENT_AUTH, SERVER_AND_CLIENT_AUTH ] + ct_log: + description: + - In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice + technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging. + - If I(ct_log) is not specified, the certificate uses the account default. + - If I(ct_log) is specified and the account settings allow it, I(ct_log) overrides the account default. + - If I(ct_log) is set to C(false), but the account settings are set to "always log", the certificate generation will fail. + type: bool + client_id: + description: + - The client ID to submit the Certificate Signing Request under. + - If no client ID is specified, the certificate will be submitted under the primary client with ID of 1. + - When using a client other than the primary client, the I(org) parameter cannot be specified. + - The issued certificate will have an organization value in the subject distinguished name represented by the client. + type: int + default: 1 + org: + description: + - Organization "O=" to include in the certificate. + - If I(org) is not specified, the organization from the client represented by I(client_id) is used. + - Unless the I(cert_type) is C(PD_SSL), this field may not be specified if the value of I(client_id) is not "1" (the primary client). + non-primary clients, certificates may only be issued with the organization of that client. + type: str + ou: + description: + - Organizational unit "OU=" to include in the certificate. + - I(ou) behavior is dependent on whether organizational units are enabled for your account. If organizational unit support is disabled for your + account, organizational units from the I(csr) and the I(ou) parameter are ignored. + - If both I(csr) and I(ou) are specified, the value in I(ou) will override the OU fields present in the subject distinguished name in the I(csr) + - If neither I(csr) nor I(ou) are specified for a renew or reissue operation, the OU fields in the initial certificate are reused. + - An invalid OU from I(csr) is ignored, but any invalid organizational units in I(ou) will result in an error indicating "Unapproved OU". The I(ou) + parameter can be used to force failure if an unapproved organizational unit is provided. + - A maximum of one OU may be specified for current products. Multiple OUs are reserved for future products. + type: list + elements: str + end_user_key_storage_agreement: + description: + - The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure + hardware to be compliant with the Entrust CSP and Subscription agreement. If requesting a certificate of type C(CODE_SIGNING) or + C(EV_CODE_SIGNING), you must set I(end_user_key_storage_agreement) to true if and only if you acknowledge that you will inform the user of this + requirement. + - Applicable only to I(cert_type) of values C(CODE_SIGNING) and C(EV_CODE_SIGNING). + type: bool + tracking_info: + description: Free form tracking information to attach to the record for the certificate. + type: str + requester_name: + description: The requester name to associate with certificate tracking information. + type: str + required: true + requester_email: + description: The requester email to associate with certificate tracking information and receive delivery and expiry notices for the certificate. + type: str + required: true + requester_phone: + description: The requester phone number to associate with certificate tracking information. + type: str + required: true + additional_emails: + description: A list of additional email addresses to receive the delivery notice and expiry notification for the certificate. + type: list + elements: str + custom_fields: + description: + - Mapping of custom fields to associate with the certificate request and certificate. + - Only supported if custom fields are enabled for your account. + - Each custom field specified must be a custom field you have defined for your account. + type: dict + suboptions: + text1: + description: Custom text field (maximum 500 characters) + type: str + text2: + description: Custom text field (maximum 500 characters) + type: str + text3: + description: Custom text field (maximum 500 characters) + type: str + text4: + description: Custom text field (maximum 500 characters) + type: str + text5: + description: Custom text field (maximum 500 characters) + type: str + text6: + description: Custom text field (maximum 500 characters) + type: str + text7: + description: Custom text field (maximum 500 characters) + type: str + text8: + description: Custom text field (maximum 500 characters) + type: str + text9: + description: Custom text field (maximum 500 characters) + type: str + text10: + description: Custom text field (maximum 500 characters) + type: str + text11: + description: Custom text field (maximum 500 characters) + type: str + text12: + description: Custom text field (maximum 500 characters) + type: str + text13: + description: Custom text field (maximum 500 characters) + type: str + text14: + description: Custom text field (maximum 500 characters) + type: str + text15: + description: Custom text field (maximum 500 characters) + type: str + number1: + description: Custom number field. + type: float + number2: + description: Custom number field. + type: float + number3: + description: Custom number field. + type: float + number4: + description: Custom number field. + type: float + number5: + description: Custom number field. + type: float + date1: + description: Custom date field. + type: str + date2: + description: Custom date field. + type: str + date3: + description: Custom date field. + type: str + date4: + description: Custom date field. + type: str + date5: + description: Custom date field. + type: str + email1: + description: Custom email field. + type: str + email2: + description: Custom email field. + type: str + email3: + description: Custom email field. + type: str + email4: + description: Custom email field. + type: str + email5: + description: Custom email field. + type: str + dropdown1: + description: Custom dropdown field. + type: str + dropdown2: + description: Custom dropdown field. + type: str + dropdown3: + description: Custom dropdown field. + type: str + dropdown4: + description: Custom dropdown field. + type: str + dropdown5: + description: Custom dropdown field. + type: str + cert_expiry: + description: + - The date the certificate should be set to expire, in RFC3339 compliant date or date-time format. For example, + C(2020-02-23), C(2020-02-23T15:00:00.05Z). + - I(cert_expiry) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue), + I(cert_expiry) will be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial + certificate. + - A reissued certificate will always have the same expiry as the original certificate. + - Note that only the date (day, month, year) is supported for specifying the expiry date. If you choose to specify an expiry time with the expiry + date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous + day. + - Applies only to accounts with a pooling inventory model. + - Only one of I(cert_expiry) or I(cert_lifetime) may be specified. + type: str + cert_lifetime: + description: + - The lifetime of the certificate. + - Applies to all certificates for accounts with a non-pooling inventory model. + - I(cert_lifetime) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue), I(cert_lifetime) will + be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial certificate. + - Applies to certificates of I(cert_type)=C(CDS_INDIVIDUAL, CDS_GROUP, CDS_ENT_LITE, CDS_ENT_PRO, SMIME_ENT) for accounts with a pooling inventory + model. + - C(P1Y) is a certificate with a 1 year lifetime. + - C(P2Y) is a certificate with a 2 year lifetime. + - C(P3Y) is a certificate with a 3 year lifetime. + - Only one of I(cert_expiry) or I(cert_lifetime) may be specified. + type: str + choices: [ P1Y, P2Y, P3Y ] +seealso: + - module: community.crypto.openssl_privatekey + description: Can be used to create private keys (both for certificates and accounts). + - module: community.crypto.openssl_csr + description: Can be used to create a Certificate Signing Request (CSR). +extends_documentation_fragment: +- community.crypto.ecs_credential + +''' + +EXAMPLES = r''' +- name: Request a new certificate from Entrust with bare minimum parameters. + Will request a new certificate if current one is valid but within 30 + days of expiry. If replacing an existing file in path, will back it up. + community.crypto.ecs_certificate: + backup: true + path: /etc/ssl/crt/ansible.com.crt + full_chain_path: /etc/ssl/crt/ansible.com.chain.crt + csr: /etc/ssl/csr/ansible.com.csr + cert_type: EV_SSL + requester_name: Jo Doe + requester_email: jdoe@ansible.com + requester_phone: 555-555-5555 + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: If there is no certificate present in path, request a new certificate + of type EV_SSL. Otherwise, if there is an Entrust managed certificate + in path and it is within 63 days of expiration, request a renew of that + certificate. + community.crypto.ecs_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr: /etc/ssl/csr/ansible.com.csr + cert_type: EV_SSL + cert_expiry: '2020-08-20' + request_type: renew + remaining_days: 63 + requester_name: Jo Doe + requester_email: jdoe@ansible.com + requester_phone: 555-555-5555 + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: If there is no certificate present in path, download certificate + specified by tracking_id if it is still valid. Otherwise, if the + certificate is within 79 days of expiration, request a renew of that + certificate and save it in path. This can be used to "migrate" a + certificate to be Ansible managed. + community.crypto.ecs_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr: /etc/ssl/csr/ansible.com.csr + tracking_id: 2378915 + request_type: renew + remaining_days: 79 + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Force a reissue of the certificate specified by tracking_id. + community.crypto.ecs_certificate: + path: /etc/ssl/crt/ansible.com.crt + force: true + tracking_id: 2378915 + request_type: reissue + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Request a new certificate with an alternative client. Note that the + issued certificate will have it's Subject Distinguished Name use the + organization details associated with that client, rather than what is + in the CSR. + community.crypto.ecs_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr: /etc/ssl/csr/ansible.com.csr + client_id: 2 + requester_name: Jo Doe + requester_email: jdoe@ansible.com + requester_phone: 555-555-5555 + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Request a new certificate with a number of CSR parameters overridden + and tracking information + community.crypto.ecs_certificate: + path: /etc/ssl/crt/ansible.com.crt + full_chain_path: /etc/ssl/crt/ansible.com.chain.crt + csr: /etc/ssl/csr/ansible.com.csr + subject_alt_name: + - ansible.testcertificates.com + - www.testcertificates.com + eku: SERVER_AND_CLIENT_AUTH + ct_log: true + org: Test Organization Inc. + ou: + - Administration + tracking_info: "Submitted via Ansible" + additional_emails: + - itsupport@testcertificates.com + - jsmith@ansible.com + custom_fields: + text1: Admin + text2: Invoice 25 + number1: 342 + date1: '2018-01-01' + email1: sales@ansible.testcertificates.com + dropdown1: red + cert_expiry: '2020-08-15' + requester_name: Jo Doe + requester_email: jdoe@ansible.com + requester_phone: 555-555-5555 + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +''' + +RETURN = ''' +filename: + description: The destination path for the generated certificate. + returned: changed or success + type: str + sample: /etc/ssl/crt/www.ansible.com.crt +backup_file: + description: Name of backup file created for the certificate. + returned: changed and if I(backup) is C(true) + type: str + sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~ +backup_full_chain_file: + description: Name of the backup file created for the certificate chain. + returned: changed and if I(backup) is C(true) and I(full_chain_path) is set. + type: str + sample: /path/to/ca.chain.crt.2019-03-09@11:22~ +tracking_id: + description: The tracking ID to reference and track the certificate in ECS. + returned: success + type: int + sample: 380079 +serial_number: + description: The serial number of the issued certificate. + returned: success + type: int + sample: 1235262234164342 +cert_days: + description: The number of days the certificate remains valid. + returned: success + type: int + sample: 253 +cert_status: + description: + - The certificate status in ECS. + - 'Current possible values (which may be expanded in the future) are: C(ACTIVE), C(APPROVED), C(DEACTIVATED), C(DECLINED), C(EXPIRED), C(NA), + C(PENDING), C(PENDING_QUORUM), C(READY), C(REISSUED), C(REISSUING), C(RENEWED), C(RENEWING), C(REVOKED), C(SUSPENDED)' + returned: success + type: str + sample: ACTIVE +cert_details: + description: + - The full response JSON from the Get Certificate call of the ECS API. + - 'While the response contents are guaranteed to be forwards compatible with new ECS API releases, Entrust recommends that you do not make any + playbooks take actions based on the content of this field. However it may be useful for debugging, logging, or auditing purposes.' + returned: success + type: dict + +''' + +from ansible_collections.community.crypto.plugins.module_utils.ecs.api import ( + ecs_client_argument_spec, + ECSClient, + RestOperationException, + SessionConfigurationException, +) + +import datetime +import os +import re +import time +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + load_certificate, +) + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' + + +def validate_cert_expiry(cert_expiry): + search_string_partial = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\Z') + search_string_full = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):' + r'([0-5][0-9]|60)(.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))\Z') + if search_string_partial.match(cert_expiry) or search_string_full.match(cert_expiry): + return True + return False + + +def calculate_cert_days(expires_after): + cert_days = 0 + if expires_after: + expires_after_datetime = datetime.datetime.strptime(expires_after, '%Y-%m-%dT%H:%M:%SZ') + cert_days = (expires_after_datetime - datetime.datetime.now()).days + return cert_days + + +# Populate the value of body[dict_param_name] with the JSON equivalent of +# module parameter of param_name if that parameter is present, otherwise leave field +# out of resulting dict +def convert_module_param_to_json_bool(module, dict_param_name, param_name): + body = {} + if module.params[param_name] is not None: + if module.params[param_name]: + body[dict_param_name] = 'true' + else: + body[dict_param_name] = 'false' + return body + + +class EcsCertificate(object): + ''' + Entrust Certificate Services certificate class. + ''' + + def __init__(self, module): + self.path = module.params['path'] + self.full_chain_path = module.params['full_chain_path'] + self.force = module.params['force'] + self.backup = module.params['backup'] + self.request_type = module.params['request_type'] + self.csr = module.params['csr'] + + # All return values + self.changed = False + self.filename = None + self.tracking_id = None + self.cert_status = None + self.serial_number = None + self.cert_days = None + self.cert_details = None + self.backup_file = None + self.backup_full_chain_file = None + + self.cert = None + self.ecs_client = None + if self.path and os.path.exists(self.path): + try: + self.cert = load_certificate(self.path, backend='cryptography') + except Exception as dummy: + self.cert = None + # Instantiate the ECS client and then try a no-op connection to verify credentials are valid + try: + self.ecs_client = ECSClient( + entrust_api_user=module.params['entrust_api_user'], + entrust_api_key=module.params['entrust_api_key'], + entrust_api_cert=module.params['entrust_api_client_cert_path'], + entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], + entrust_api_specification_path=module.params['entrust_api_specification_path'] + ) + except SessionConfigurationException as e: + module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) + try: + self.ecs_client.GetAppVersion() + except RestOperationException as e: + module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message))) + + # Conversion of the fields that go into the 'tracking' parameter of the request object + def convert_tracking_params(self, module): + body = {} + tracking = {} + if module.params['requester_name']: + tracking['requesterName'] = module.params['requester_name'] + if module.params['requester_email']: + tracking['requesterEmail'] = module.params['requester_email'] + if module.params['requester_phone']: + tracking['requesterPhone'] = module.params['requester_phone'] + if module.params['tracking_info']: + tracking['trackingInfo'] = module.params['tracking_info'] + if module.params['custom_fields']: + # Omit custom fields from submitted dict if not present, instead of submitting them with value of 'null' + # The ECS API does technically accept null without error, but it complicates debugging user escalations and is unnecessary bandwidth. + custom_fields = {} + for k, v in module.params['custom_fields'].items(): + if v is not None: + custom_fields[k] = v + tracking['customFields'] = custom_fields + if module.params['additional_emails']: + tracking['additionalEmails'] = module.params['additional_emails'] + body['tracking'] = tracking + return body + + def convert_cert_subject_params(self, module): + body = {} + if module.params['subject_alt_name']: + body['subjectAltName'] = module.params['subject_alt_name'] + if module.params['org']: + body['org'] = module.params['org'] + if module.params['ou']: + body['ou'] = module.params['ou'] + return body + + def convert_general_params(self, module): + body = {} + if module.params['eku']: + body['eku'] = module.params['eku'] + if self.request_type == 'new': + body['certType'] = module.params['cert_type'] + body['clientId'] = module.params['client_id'] + body.update(convert_module_param_to_json_bool(module, 'ctLog', 'ct_log')) + body.update(convert_module_param_to_json_bool(module, 'endUserKeyStorageAgreement', 'end_user_key_storage_agreement')) + return body + + def convert_expiry_params(self, module): + body = {} + if module.params['cert_lifetime']: + body['certLifetime'] = module.params['cert_lifetime'] + elif module.params['cert_expiry']: + body['certExpiryDate'] = module.params['cert_expiry'] + # If neither cerTLifetime or certExpiryDate was specified and the request type is new, default to 365 days + elif self.request_type != 'reissue': + gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())) + expiry = gmt_now + datetime.timedelta(days=365) + body['certExpiryDate'] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") + return body + + def set_tracking_id_by_serial_number(self, module): + try: + # Use serial_number to identify if certificate is an Entrust Certificate + # with an associated tracking ID + serial_number = "{0:X}".format(self.cert.serial_number) + cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {}) + if len(cert_results) == 1: + self.tracking_id = cert_results[0].get('trackingId') + except RestOperationException as dummy: + # If we fail to find a cert by serial number, that's fine, we just don't set self.tracking_id + return + + def set_cert_details(self, module): + try: + self.cert_details = self.ecs_client.GetCertificate(trackingId=self.tracking_id) + self.cert_status = self.cert_details.get('status') + self.serial_number = self.cert_details.get('serialNumber') + self.cert_days = calculate_cert_days(self.cert_details.get('expiresAfter')) + except RestOperationException as e: + module.fail_json('Failed to get details of certificate with tracking_id="{0}", Error: '.format(self.tracking_id), to_native(e.message)) + + def check(self, module): + if self.cert: + # We will only treat a certificate as valid if it is found as a managed entrust cert. + # We will only set updated tracking ID based on certificate in "path" if it is managed by entrust. + self.set_tracking_id_by_serial_number(module) + + if module.params['tracking_id'] and self.tracking_id and module.params['tracking_id'] != self.tracking_id: + module.warn('tracking_id parameter of "{0}" provided, but will be ignored. Valid certificate was present in path "{1}" with ' + 'tracking_id of "{2}".'.format(module.params['tracking_id'], self.path, self.tracking_id)) + + # If we did not end up setting tracking_id based on existing cert, get from module params + if not self.tracking_id: + self.tracking_id = module.params['tracking_id'] + + if not self.tracking_id: + return False + + self.set_cert_details(module) + + if self.cert_status == 'EXPIRED' or self.cert_status == 'SUSPENDED' or self.cert_status == 'REVOKED': + return False + if self.cert_days < module.params['remaining_days']: + return False + + return True + + def request_cert(self, module): + if not self.check(module) or self.force: + body = {} + + # Read the CSR contents + if self.csr and os.path.exists(self.csr): + with open(self.csr, 'r') as csr_file: + body['csr'] = csr_file.read() + + # Check if the path is already a cert + # tracking_id may be set as a parameter or by get_cert_details if an entrust cert is in 'path'. If tracking ID is null + # We will be performing a reissue operation. + if self.request_type != 'new' and not self.tracking_id: + module.warn('No existing Entrust certificate found in path={0} and no tracking_id was provided, setting request_type to "new" for this task' + 'run. Future playbook runs that point to the pathination file in {1} will use request_type={2}' + .format(self.path, self.path, self.request_type)) + self.request_type = 'new' + elif self.request_type == 'new' and self.tracking_id: + module.warn('Existing certificate being acted upon, but request_type is "new", so will be a new certificate issuance rather than a' + 'reissue or renew') + # Use cases where request type is new and no existing certificate, or where request type is reissue/renew and a valid + # existing certificate is found, do not need warnings. + + body.update(self.convert_tracking_params(module)) + body.update(self.convert_cert_subject_params(module)) + body.update(self.convert_general_params(module)) + body.update(self.convert_expiry_params(module)) + + if not module.check_mode: + try: + if self.request_type == 'validate_only': + body['validateOnly'] = 'true' + result = self.ecs_client.NewCertRequest(Body=body) + if self.request_type == 'new': + result = self.ecs_client.NewCertRequest(Body=body) + elif self.request_type == 'renew': + result = self.ecs_client.RenewCertRequest(trackingId=self.tracking_id, Body=body) + elif self.request_type == 'reissue': + result = self.ecs_client.ReissueCertRequest(trackingId=self.tracking_id, Body=body) + self.tracking_id = result.get('trackingId') + self.set_cert_details(module) + except RestOperationException as e: + module.fail_json(msg='Failed to request new certificate from Entrust (ECS) {0}'.format(e.message)) + + if self.request_type != 'validate_only': + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) + if self.full_chain_path and self.cert_details.get('chainCerts'): + if self.backup: + self.backup_full_chain_file = module.backup_local(self.full_chain_path) + chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' + write_file(module, to_bytes(chain_string), path=self.full_chain_path) + self.changed = True + # If there is no certificate present in path but a tracking ID was specified, save it to disk + elif not os.path.exists(self.path) and self.tracking_id: + if not module.check_mode: + write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) + if self.full_chain_path and self.cert_details.get('chainCerts'): + chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' + write_file(module, to_bytes(chain_string), path=self.full_chain_path) + self.changed = True + + def dump(self): + result = { + 'changed': self.changed, + 'filename': self.path, + 'tracking_id': self.tracking_id, + 'cert_status': self.cert_status, + 'serial_number': self.serial_number, + 'cert_days': self.cert_days, + 'cert_details': self.cert_details, + } + if self.backup_file: + result['backup_file'] = self.backup_file + result['backup_full_chain_file'] = self.backup_full_chain_file + return result + + +def custom_fields_spec(): + return dict( + text1=dict(type='str'), + text2=dict(type='str'), + text3=dict(type='str'), + text4=dict(type='str'), + text5=dict(type='str'), + text6=dict(type='str'), + text7=dict(type='str'), + text8=dict(type='str'), + text9=dict(type='str'), + text10=dict(type='str'), + text11=dict(type='str'), + text12=dict(type='str'), + text13=dict(type='str'), + text14=dict(type='str'), + text15=dict(type='str'), + number1=dict(type='float'), + number2=dict(type='float'), + number3=dict(type='float'), + number4=dict(type='float'), + number5=dict(type='float'), + date1=dict(type='str'), + date2=dict(type='str'), + date3=dict(type='str'), + date4=dict(type='str'), + date5=dict(type='str'), + email1=dict(type='str'), + email2=dict(type='str'), + email3=dict(type='str'), + email4=dict(type='str'), + email5=dict(type='str'), + dropdown1=dict(type='str'), + dropdown2=dict(type='str'), + dropdown3=dict(type='str'), + dropdown4=dict(type='str'), + dropdown5=dict(type='str'), + ) + + +def ecs_certificate_argument_spec(): + return dict( + backup=dict(type='bool', default=False), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + full_chain_path=dict(type='path'), + tracking_id=dict(type='int'), + remaining_days=dict(type='int', default=30), + request_type=dict(type='str', default='new', choices=['new', 'renew', 'reissue', 'validate_only']), + cert_type=dict(type='str', choices=['STANDARD_SSL', + 'ADVANTAGE_SSL', + 'UC_SSL', + 'EV_SSL', + 'WILDCARD_SSL', + 'PRIVATE_SSL', + 'PD_SSL', + 'CODE_SIGNING', + 'EV_CODE_SIGNING', + 'CDS_INDIVIDUAL', + 'CDS_GROUP', + 'CDS_ENT_LITE', + 'CDS_ENT_PRO', + 'SMIME_ENT', + ]), + csr=dict(type='str'), + subject_alt_name=dict(type='list', elements='str'), + eku=dict(type='str', choices=['SERVER_AUTH', 'CLIENT_AUTH', 'SERVER_AND_CLIENT_AUTH']), + ct_log=dict(type='bool'), + client_id=dict(type='int', default=1), + org=dict(type='str'), + ou=dict(type='list', elements='str'), + end_user_key_storage_agreement=dict(type='bool'), + tracking_info=dict(type='str'), + requester_name=dict(type='str', required=True), + requester_email=dict(type='str', required=True), + requester_phone=dict(type='str', required=True), + additional_emails=dict(type='list', elements='str'), + custom_fields=dict(type='dict', default=None, options=custom_fields_spec()), + cert_expiry=dict(type='str'), + cert_lifetime=dict(type='str', choices=['P1Y', 'P2Y', 'P3Y']), + ) + + +def main(): + ecs_argument_spec = ecs_client_argument_spec() + ecs_argument_spec.update(ecs_certificate_argument_spec()) + module = AnsibleModule( + argument_spec=ecs_argument_spec, + required_if=( + ['request_type', 'new', ['cert_type']], + ['request_type', 'validate_only', ['cert_type']], + ['cert_type', 'CODE_SIGNING', ['end_user_key_storage_agreement']], + ['cert_type', 'EV_CODE_SIGNING', ['end_user_key_storage_agreement']], + ), + mutually_exclusive=( + ['cert_expiry', 'cert_lifetime'], + ), + supports_check_mode=True, + ) + + if not CRYPTOGRAPHY_FOUND or CRYPTOGRAPHY_VERSION < LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION): + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + + # If validate_only is used, pointing to an existing tracking_id is an invalid operation + if module.params['tracking_id']: + if module.params['request_type'] == 'new' or module.params['request_type'] == 'validate_only': + module.fail_json(msg='The tracking_id field is invalid when request_type="{0}".'.format(module.params['request_type'])) + + # A reissued request can not specify an expiration date or lifetime + if module.params['request_type'] == 'reissue': + if module.params['cert_expiry']: + module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".') + elif module.params['cert_lifetime']: + module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".') + # Only a reissued request can omit the CSR + else: + module_params_csr = module.params['csr'] + if module_params_csr is None: + module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type'])) + elif not os.path.exists(module_params_csr): + module.fail_json(msg='The csr field of {0} was not a valid path. csr is required when request_type={1}'.format( + module_params_csr, module.params['request_type'])) + + if module.params['ou'] and len(module.params['ou']) > 1: + module.fail_json(msg='Multiple "ou" values are not currently supported.') + + if module.params['end_user_key_storage_agreement']: + if module.params['cert_type'] != 'CODE_SIGNING' and module.params['cert_type'] != 'EV_CODE_SIGNING': + module.fail_json(msg='Parameter "end_user_key_storage_agreement" is valid only for cert_types "CODE_SIGNING" and "EV_CODE_SIGNING"') + + if module.params['org'] and module.params['client_id'] != 1 and module.params['cert_type'] != 'PD_SSL': + module.fail_json(msg='The "org" parameter is not supported when client_id parameter is set to a value other than 1, unless cert_type is "PD_SSL".') + + if module.params['cert_expiry']: + if not validate_cert_expiry(module.params['cert_expiry']): + module.fail_json(msg='The "cert_expiry" parameter of "{0}" is not a valid date or date-time'.format(module.params['cert_expiry'])) + + certificate = EcsCertificate(module) + certificate.request_cert(module) + result = certificate.dump() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_domain.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_domain.py new file mode 100644 index 00000000..60ea7781 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/ecs_domain.py @@ -0,0 +1,406 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright 2019 Entrust Datacard Corporation. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: ecs_domain +author: + - Chris Trufan (@ctrufan) +version_added: '1.0.0' +short_description: Request validation of a domain with the Entrust Certificate Services (ECS) API +description: + - Request validation or re-validation of a domain with the Entrust Certificate Services (ECS) API. + - Requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API. + - If the domain is already in the validation process, no new validation will be requested, but the validation data (if applicable) will be returned. + - If the domain is already in the validation process but the I(verification_method) specified is different than the current I(verification_method), + the I(verification_method) will be updated and validation data (if applicable) will be returned. + - If the domain is an active, validated domain, the return value of I(changed) will be false, unless C(domain_status=EXPIRED), in which case a re-validation + will be performed. + - If C(verification_method=dns), details about the required DNS entry will be specified in the return parameters I(dns_contents), I(dns_location), and + I(dns_resource_type). + - If C(verification_method=web_server), details about the required file details will be specified in the return parameters I(file_contents) and + I(file_location). + - If C(verification_method=email), the email address(es) that the validation email(s) were sent to will be in the return parameter I(emails). This is + purely informational. For domains requested using this module, this will always be a list of size 1. +notes: + - There is a small delay (typically about 5 seconds, but can be as long as 60 seconds) before obtaining the random values when requesting a validation + while C(verification_method=dns) or C(verification_method=web_server). Be aware of that if doing many domain validation requests. +options: + client_id: + description: + - The client ID to request the domain be associated with. + - If no client ID is specified, the domain will be added under the primary client with ID of 1. + type: int + default: 1 + domain_name: + description: + - The domain name to be verified or reverified. + type: str + required: true + verification_method: + description: + - The verification method to be used to prove control of the domain. + - If C(verification_method=email) and the value I(verification_email) is specified, that value is used for the email validation. If + I(verification_email) is not provided, the first value present in WHOIS data will be used. An email will be sent to the address in + I(verification_email) with instructions on how to verify control of the domain. + - If C(verification_method=dns), the value I(dns_contents) must be stored in location I(dns_location), with a DNS record type of + I(verification_dns_record_type). To prove domain ownership, update your DNS records so the text string returned by I(dns_contents) is available at + I(dns_location). + - If C(verification_method=web_server), the contents of return value I(file_contents) must be made available on a web server accessible at location + I(file_location). + - If C(verification_method=manual), the domain will be validated with a manual process. This is not recommended. + type: str + choices: [ 'dns', 'email', 'manual', 'web_server'] + required: true + verification_email: + description: + - Email address to be used to verify domain ownership. + - 'Email address must be either an email address present in the WHOIS data for I(domain_name), or one of the following constructed emails: + admin@I(domain_name), administrator@I(domain_name), webmaster@I(domain_name), hostmaster@I(domain_name), postmaster@I(domain_name)' + - 'Note that if I(domain_name) includes subdomains, the top level domain should be used. For example, if requesting validation of + example1.ansible.com, or test.example2.ansible.com, and you want to use the "admin" preconstructed name, the email address should be + admin@ansible.com.' + - If using the email values from the WHOIS data for the domain or it's top level namespace, they must be exact matches. + - If C(verification_method=email) but I(verification_email) is not provided, the first email address found in WHOIS data for the domain will be + used. + - To verify domain ownership, domain owner must follow the instructions in the email they receive. + - Only allowed if C(verification_method=email) + type: str +seealso: + - module: community.crypto.x509_certificate + description: Can be used to request certificates from ECS, with C(provider=entrust). + - module: community.crypto.ecs_certificate + description: Can be used to request a Certificate from ECS using a verified domain. +extends_documentation_fragment: +- community.crypto.ecs_credential + +''' + +EXAMPLES = r''' +- name: Request domain validation using email validation for client ID of 2. + community.crypto.ecs_domain: + domain_name: ansible.com + client_id: 2 + verification_method: email + verification_email: admin@ansible.com + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Request domain validation using DNS. If domain is already valid, + request revalidation if expires within 90 days + community.crypto.ecs_domain: + domain_name: ansible.com + verification_method: dns + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Request domain validation using web server validation, and revalidate + if fewer than 60 days remaining of EV eligibility. + community.crypto.ecs_domain: + domain_name: ansible.com + verification_method: web_server + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key + +- name: Request domain validation using manual validation. + community.crypto.ecs_domain: + domain_name: ansible.com + verification_method: manual + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key +''' + +RETURN = ''' +domain_status: + description: Status of the current domain. Will be one of C(APPROVED), C(DECLINED), C(CANCELLED), C(INITIAL_VERIFICATION), C(DECLINED), C(CANCELLED), + C(RE_VERIFICATION), C(EXPIRED), C(EXPIRING) + returned: changed or success + type: str + sample: APPROVED +verification_method: + description: Verification method used to request the domain validation. If C(changed) will be the same as I(verification_method) input parameter. + returned: changed or success + type: str + sample: dns +file_location: + description: The location that ECS will be expecting to be able to find the file for domain verification, containing the contents of I(file_contents). + returned: I(verification_method) is C(web_server) + type: str + sample: http://ansible.com/.well-known/pki-validation/abcd.txt +file_contents: + description: The contents of the file that ECS will be expecting to find at C(file_location). + returned: I(verification_method) is C(web_server) + type: str + sample: AB23CD41432522FF2526920393982FAB +emails: + description: + - The list of emails used to request validation of this domain. + - Domains requested using this module will only have a list of size 1. + returned: I(verification_method) is C(email) + type: list + sample: [ admin@ansible.com, administrator@ansible.com ] +dns_location: + description: The location that ECS will be expecting to be able to find the DNS entry for domain verification, containing the contents of I(dns_contents). + returned: changed and if I(verification_method) is C(dns) + type: str + sample: _pki-validation.ansible.com +dns_contents: + description: The value that ECS will be expecting to find in the DNS record located at I(dns_location). + returned: changed and if I(verification_method) is C(dns) + type: str + sample: AB23CD41432522FF2526920393982FAB +dns_resource_type: + description: The type of resource record that ECS will be expecting for the DNS record located at I(dns_location). + returned: changed and if I(verification_method) is C(dns) + type: str + sample: TXT +client_id: + description: Client ID that the domain belongs to. If the input value I(client_id) is specified, this will always be the same as I(client_id) + returned: changed or success + type: int + sample: 1 +ov_eligible: + description: Whether the domain is eligible for submission of "OV" certificates. Will never be C(false) if I(ov_eligible) is C(true) + returned: success and I(domain_status) is C(APPROVED), C(RE_VERIFICATION), C(EXPIRING), or C(EXPIRED). + type: bool + sample: true +ov_days_remaining: + description: The number of days the domain remains eligible for submission of "OV" certificates. Will never be less than the value of I(ev_days_remaining) + returned: success and I(ov_eligible) is C(true) and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING). + type: int + sample: 129 +ev_eligible: + description: Whether the domain is eligible for submission of "EV" certificates. Will never be C(true) if I(ov_eligible) is C(false) + returned: success and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING), or C(EXPIRED). + type: bool + sample: true +ev_days_remaining: + description: The number of days the domain remains eligible for submission of "EV" certificates. Will never be greater than the value of + I(ov_days_remaining) + returned: success and I(ev_eligible) is C(true) and I(domain_status) is C(APPROVED), C(RE_VERIFICATION) or C(EXPIRING). + type: int + sample: 94 + +''' + +import datetime +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.ecs.api import ( + ecs_client_argument_spec, + ECSClient, + RestOperationException, + SessionConfigurationException, +) + + +def calculate_days_remaining(expiry_date): + days_remaining = None + if expiry_date: + expiry_datetime = datetime.datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ') + days_remaining = (expiry_datetime - datetime.datetime.now()).days + return days_remaining + + +class EcsDomain(object): + ''' + Entrust Certificate Services domain class. + ''' + + def __init__(self, module): + self.changed = False + self.domain_status = None + self.verification_method = None + self.file_location = None + self.file_contents = None + self.dns_location = None + self.dns_contents = None + self.dns_resource_type = None + self.emails = None + self.ov_eligible = None + self.ov_days_remaining = None + self.ev_eligble = None + self.ev_days_remaining = None + # Note that verification_method is the 'current' verification + # method of the domain, we'll use module.params when requesting a new + # one, in case the verification method has changed. + self.verification_method = None + + self.ecs_client = None + # Instantiate the ECS client and then try a no-op connection to verify credentials are valid + try: + self.ecs_client = ECSClient( + entrust_api_user=module.params['entrust_api_user'], + entrust_api_key=module.params['entrust_api_key'], + entrust_api_cert=module.params['entrust_api_client_cert_path'], + entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], + entrust_api_specification_path=module.params['entrust_api_specification_path'] + ) + except SessionConfigurationException as e: + module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) + try: + self.ecs_client.GetAppVersion() + except RestOperationException as e: + module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message))) + + def set_domain_details(self, domain_details): + if domain_details.get('verificationMethod'): + self.verification_method = domain_details['verificationMethod'].lower() + self.domain_status = domain_details['verificationStatus'] + self.ov_eligible = domain_details.get('ovEligible') + self.ov_days_remaining = calculate_days_remaining(domain_details.get('ovExpiry')) + self.ev_eligible = domain_details.get('evEligible') + self.ev_days_remaining = calculate_days_remaining(domain_details.get('evExpiry')) + self.client_id = domain_details['clientId'] + + if self.verification_method == 'dns' and domain_details.get('dnsMethod'): + self.dns_location = domain_details['dnsMethod']['recordDomain'] + self.dns_resource_type = domain_details['dnsMethod']['recordType'] + self.dns_contents = domain_details['dnsMethod']['recordValue'] + elif self.verification_method == 'web_server' and domain_details.get('webServerMethod'): + self.file_location = domain_details['webServerMethod']['fileLocation'] + self.file_contents = domain_details['webServerMethod']['fileContents'] + elif self.verification_method == 'email' and domain_details.get('emailMethod'): + self.emails = domain_details['emailMethod'] + + def check(self, module): + try: + domain_details = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + self.set_domain_details(domain_details) + if self.domain_status != 'APPROVED' and self.domain_status != 'INITIAL_VERIFICATION' and self.domain_status != 'RE_VERIFICATION': + return False + + # If domain verification is in process, we want to return the random values and treat it as a valid. + if self.domain_status == 'INITIAL_VERIFICATION' or self.domain_status == 'RE_VERIFICATION': + # Unless the verification method has changed, in which case we need to do a reverify request. + if self.verification_method != module.params['verification_method']: + return False + + if self.domain_status == 'EXPIRING': + return False + + return True + except RestOperationException as dummy: + return False + + def request_domain(self, module): + if not self.check(module): + body = {} + + body['verificationMethod'] = module.params['verification_method'].upper() + if module.params['verification_method'] == 'email': + emailMethod = {} + if module.params['verification_email']: + emailMethod['emailSource'] = 'SPECIFIED' + emailMethod['email'] = module.params['verification_email'] + else: + emailMethod['emailSource'] = 'INCLUDE_WHOIS' + body['emailMethod'] = emailMethod + # Only populate domain name in body if it is not an existing domain + if not self.domain_status: + body['domainName'] = module.params['domain_name'] + try: + if not self.domain_status: + self.ecs_client.AddDomain(clientId=module.params['client_id'], Body=body) + else: + self.ecs_client.ReverifyDomain(clientId=module.params['client_id'], domain=module.params['domain_name'], Body=body) + + time.sleep(5) + result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + + # It takes a bit of time before the random values are available + if module.params['verification_method'] == 'dns' or module.params['verification_method'] == 'web_server': + for i in range(4): + # Check both that random values are now available, and that they're different than were populated by previous 'check' + if module.params['verification_method'] == 'dns': + if result.get('dnsMethod') and result['dnsMethod']['recordValue'] != self.dns_contents: + break + elif module.params['verification_method'] == 'web_server': + if result.get('webServerMethod') and result['webServerMethod']['fileContents'] != self.file_contents: + break + time.sleep(10) + result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name']) + self.changed = True + self.set_domain_details(result) + except RestOperationException as e: + module.fail_json(msg='Failed to request domain validation from Entrust (ECS) {0}'.format(e.message)) + + def dump(self): + result = { + 'changed': self.changed, + 'client_id': self.client_id, + 'domain_status': self.domain_status, + } + + if self.verification_method: + result['verification_method'] = self.verification_method + if self.ov_eligible is not None: + result['ov_eligible'] = self.ov_eligible + if self.ov_days_remaining: + result['ov_days_remaining'] = self.ov_days_remaining + if self.ev_eligible is not None: + result['ev_eligible'] = self.ev_eligible + if self.ev_days_remaining: + result['ev_days_remaining'] = self.ev_days_remaining + if self.emails: + result['emails'] = self.emails + + if self.verification_method == 'dns': + result['dns_location'] = self.dns_location + result['dns_contents'] = self.dns_contents + result['dns_resource_type'] = self.dns_resource_type + elif self.verification_method == 'web_server': + result['file_location'] = self.file_location + result['file_contents'] = self.file_contents + elif self.verification_method == 'email': + result['emails'] = self.emails + + return result + + +def ecs_domain_argument_spec(): + return dict( + client_id=dict(type='int', default=1), + domain_name=dict(type='str', required=True), + verification_method=dict(type='str', required=True, choices=['dns', 'email', 'manual', 'web_server']), + verification_email=dict(type='str'), + ) + + +def main(): + ecs_argument_spec = ecs_client_argument_spec() + ecs_argument_spec.update(ecs_domain_argument_spec()) + module = AnsibleModule( + argument_spec=ecs_argument_spec, + supports_check_mode=False, + ) + + if module.params['verification_email'] and module.params['verification_method'] != 'email': + module.fail_json(msg='The verification_email field is invalid when verification_method="{0}".'.format(module.params['verification_method'])) + + domain = EcsDomain(module) + domain.request_domain(module) + result = domain.dump() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/get_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/get_certificate.py new file mode 100644 index 00000000..f0592db7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/get_certificate.py @@ -0,0 +1,388 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: get_certificate +author: "John Westcott IV (@john-westcott-iv)" +short_description: Get a certificate from a host:port +description: + - Makes a secure connection and returns information about the presented certificate + - "The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL + backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0." + - Support SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) only with python >= 2.7. +options: + host: + description: + - The host to get the cert for (IP is fine) + type: str + required: true + ca_cert: + description: + - A PEM file containing one or more root certificates; if present, the cert will be validated against these root certs. + - Note that this only validates the certificate is signed by the chain; not that the cert is valid for the host presenting it. + type: path + port: + description: + - The port to connect to + type: int + required: true + server_name: + description: + - Server name used for SNI (L(Server Name Indication,https://en.wikipedia.org/wiki/Server_Name_Indication)) when hostname + is an IP or is different from server name. + type: str + version_added: 1.4.0 + proxy_host: + description: + - Proxy host used when get a certificate. + type: str + proxy_port: + description: + - Proxy port used when get a certificate. + type: int + default: 8080 + timeout: + description: + - The timeout in seconds + type: int + default: 10 + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +notes: + - When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed. + +requirements: + - "python >= 2.7 when using C(proxy_host)" + - "cryptography >= 1.6 or pyOpenSSL >= 0.15" +''' + +RETURN = ''' +cert: + description: The certificate retrieved from the port + returned: success + type: str +expired: + description: Boolean indicating if the cert is expired + returned: success + type: bool +extensions: + description: Extensions applied to the cert + returned: success + type: list + elements: dict + contains: + critical: + returned: success + type: bool + description: Whether the extension is critical. + asn1_data: + returned: success + type: str + description: The Base64 encoded ASN.1 content of the extnesion. + name: + returned: success + type: str + description: The extension's name. +issuer: + description: Information about the issuer of the cert + returned: success + type: dict +not_after: + description: Expiration date of the cert + returned: success + type: str +not_before: + description: Issue date of the cert + returned: success + type: str +serial_number: + description: The serial number of the cert + returned: success + type: str +signature_algorithm: + description: The algorithm used to sign the cert + returned: success + type: str +subject: + description: Information about the subject of the cert (OU, CN, etc) + returned: success + type: dict +version: + description: The version number of the certificate + returned: success + type: str +''' + +EXAMPLES = ''' +- name: Get the cert from an RDP port + community.crypto.get_certificate: + host: "1.2.3.4" + port: 3389 + delegate_to: localhost + run_once: true + register: cert + +- name: Get a cert from an https port + community.crypto.get_certificate: + host: "www.google.com" + port: 443 + delegate_to: localhost + run_once: true + register: cert + +- name: How many days until cert expires + debug: + msg: "cert expires in: {{ expire_days }} days." + vars: + expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}" +''' + +import atexit +import base64 +import datetime +import traceback + +from distutils.version import LooseVersion +from os.path import isfile +from socket import create_connection, setdefaulttimeout, socket +from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_oid_to_name, + cryptography_get_extensions_from_cert, +) + +MINIMAL_PYOPENSSL_VERSION = '0.15' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' + +CREATE_DEFAULT_CONTEXT_IMP_ERR = None +try: + from ssl import create_default_context +except ImportError: + CREATE_DEFAULT_CONTEXT_IMP_ERR = traceback.format_exc() + HAS_CREATE_DEFAULT_CONTEXT = False +else: + HAS_CREATE_DEFAULT_CONTEXT = True + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.exceptions + import cryptography.x509 + from cryptography.hazmat.backends import default_backend as cryptography_backend + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +def main(): + module = AnsibleModule( + argument_spec=dict( + ca_cert=dict(type='path'), + host=dict(type='str', required=True), + port=dict(type='int', required=True), + proxy_host=dict(type='str'), + proxy_port=dict(type='int', default=8080), + server_name=dict(type='str'), + timeout=dict(type='int', default=10), + select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'), + ), + ) + + ca_cert = module.params.get('ca_cert') + host = module.params.get('host') + port = module.params.get('port') + proxy_host = module.params.get('proxy_host') + proxy_port = module.params.get('proxy_port') + timeout = module.params.get('timeout') + server_name = module.params.get('server_name') + + backend = module.params.get('select_crypto_backend') + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # First try cryptography, then pyOpenSSL + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + + result = dict( + changed=False, + ) + + if timeout: + setdefaulttimeout(timeout) + + if ca_cert: + if not isfile(ca_cert): + module.fail_json(msg="ca_cert file does not exist") + + if not HAS_CREATE_DEFAULT_CONTEXT: + # Python < 2.7.9 + if proxy_host: + module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.', + exception=CREATE_DEFAULT_CONTEXT_IMP_ERR) + try: + # Note: get_server_certificate does not support SNI! + cert = get_server_certificate((host, port), ca_certs=ca_cert) + except Exception as e: + module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e)) + else: + # Python >= 2.7.9 + try: + if proxy_host: + connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port) + sock = socket() + atexit.register(sock.close) + sock.connect((proxy_host, proxy_port)) + sock.send(connect.encode()) + sock.recv(8192) + else: + sock = create_connection((host, port)) + atexit.register(sock.close) + + if ca_cert: + ctx = create_default_context(cafile=ca_cert) + ctx.check_hostname = False + ctx.verify_mode = CERT_REQUIRED + else: + ctx = create_default_context() + ctx.check_hostname = False + ctx.verify_mode = CERT_NONE + + cert = ctx.wrap_socket(sock, server_hostname=server_name or host).getpeercert(True) + cert = DER_cert_to_PEM_cert(cert) + except Exception as e: + if proxy_host: + module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format( + proxy_host, proxy_port, host, port, e)) + else: + module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e)) + + result['cert'] = cert + + if backend == 'pyopenssl': + x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert) + result['subject'] = {} + for component in x509.get_subject().get_components(): + result['subject'][component[0]] = component[1] + + result['expired'] = x509.has_expired() + + result['extensions'] = [] + extension_count = x509.get_extension_count() + for index in range(0, extension_count): + extension = x509.get_extension(index) + result['extensions'].append({ + 'critical': extension.get_critical(), + 'asn1_data': extension.get_data(), + 'name': extension.get_short_name(), + }) + + result['issuer'] = {} + for component in x509.get_issuer().get_components(): + result['issuer'][component[0]] = component[1] + + result['not_after'] = x509.get_notAfter() + result['not_before'] = x509.get_notBefore() + + result['serial_number'] = x509.get_serial_number() + result['signature_algorithm'] = x509.get_signature_algorithm() + + result['version'] = x509.get_version() + + elif backend == 'cryptography': + x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend()) + result['subject'] = {} + for attribute in x509.subject: + result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value + + result['expired'] = x509.not_valid_after < datetime.datetime.utcnow() + + result['extensions'] = [] + for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items(): + oid = cryptography.x509.oid.ObjectIdentifier(dotted_number) + result['extensions'].append({ + 'critical': entry['critical'], + 'asn1_data': base64.b64decode(entry['value']), + 'name': cryptography_oid_to_name(oid, short=True), + }) + + result['issuer'] = {} + for attribute in x509.issuer: + result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value + + result['not_after'] = x509.not_valid_after.strftime('%Y%m%d%H%M%SZ') + result['not_before'] = x509.not_valid_before.strftime('%Y%m%d%H%M%SZ') + + result['serial_number'] = x509.serial_number + result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid) + + # We need the -1 offset to get the same values as pyOpenSSL + if x509.version == cryptography.x509.Version.v1: + result['version'] = 1 - 1 + elif x509.version == cryptography.x509.Version.v3: + result['version'] = 3 - 1 + else: + result['version'] = "unknown" + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/luks_device.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/luks_device.py new file mode 100644 index 00000000..3749e0a9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/luks_device.py @@ -0,0 +1,914 @@ +#!/usr/bin/python +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: luks_device + +short_description: Manage encrypted (LUKS) devices + + +description: + - "Module manages L(LUKS,https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup) + on given device. Supports creating, destroying, opening and closing of + LUKS container and adding or removing new keys and passphrases." + +options: + device: + description: + - "Device to work with (e.g. C(/dev/sda1)). Needed in most cases. + Can be omitted only when I(state=closed) together with I(name) + is provided." + type: str + state: + description: + - "Desired state of the LUKS container. Based on its value creates, + destroys, opens or closes the LUKS container on a given device." + - "I(present) will create LUKS container unless already present. + Requires I(device) and either I(keyfile) or I(passphrase) options + to be provided." + - "I(absent) will remove existing LUKS container if it exists. + Requires I(device) or I(name) to be specified." + - "I(opened) will unlock the LUKS container. If it does not exist + it will be created first. + Requires I(device) and either I(keyfile) or I(passphrase) + to be specified. Use the I(name) option to set the name of + the opened container. Otherwise the name will be + generated automatically and returned as a part of the + result." + - "I(closed) will lock the LUKS container. However if the container + does not exist it will be created. + Requires I(device) and either I(keyfile) or I(passphrase) + options to be provided. If container does already exist + I(device) or I(name) will suffice." + type: str + default: present + choices: [present, absent, opened, closed] + name: + description: + - "Sets container name when I(state=opened). Can be used + instead of I(device) when closing the existing container + (i.e. when I(state=closed))." + type: str + keyfile: + description: + - "Used to unlock the container. Either a I(keyfile) or a + I(passphrase) is needed for most of the operations. Parameter + value is the path to the keyfile with the passphrase." + - "BEWARE that working with keyfiles in plaintext is dangerous. + Make sure that they are protected." + type: path + passphrase: + description: + - "Used to unlock the container. Either a I(passphrase) or a + I(keyfile) is needed for most of the operations. Parameter + value is a string with the passphrase." + type: str + version_added: '1.0.0' + keysize: + description: + - "Sets the key size only if LUKS container does not exist." + type: int + version_added: '1.0.0' + new_keyfile: + description: + - "Adds additional key to given container on I(device). + Needs I(keyfile) or I(passphrase) option for authorization. + LUKS container supports up to 8 keyslots. Parameter value + is the path to the keyfile with the passphrase." + - "NOTE that adding additional keys is idempotent only since + community.crypto 1.4.0. For older versions, a new keyslot + will be used even if another keyslot already exists for this + keyfile." + - "BEWARE that working with keyfiles in plaintext is dangerous. + Make sure that they are protected." + type: path + new_passphrase: + description: + - "Adds additional passphrase to given container on I(device). + Needs I(keyfile) or I(passphrase) option for authorization. LUKS + container supports up to 8 keyslots. Parameter value is a string + with the new passphrase." + - "NOTE that adding additional passphrase is idempotent only since + community.crypto 1.4.0. For older versions, a new keyslot will + be used even if another keyslot already exists for this passphrase." + type: str + version_added: '1.0.0' + remove_keyfile: + description: + - "Removes given key from the container on I(device). Does not + remove the keyfile from filesystem. + Parameter value is the path to the keyfile with the passphrase." + - "NOTE that removing keys is idempotent only since + community.crypto 1.4.0. For older versions, trying to remove + a key which no longer exists results in an error." + - "NOTE that to remove the last key from a LUKS container, the + I(force_remove_last_key) option must be set to C(yes)." + - "BEWARE that working with keyfiles in plaintext is dangerous. + Make sure that they are protected." + type: path + remove_passphrase: + description: + - "Removes given passphrase from the container on I(device). + Parameter value is a string with the passphrase to remove." + - "NOTE that removing passphrases is idempotent only since + community.crypto 1.4.0. For older versions, trying to remove + a passphrase which no longer exists results in an error." + - "NOTE that to remove the last keyslot from a LUKS + container, the I(force_remove_last_key) option must be set + to C(yes)." + type: str + version_added: '1.0.0' + force_remove_last_key: + description: + - "If set to C(yes), allows removing the last key from a container." + - "BEWARE that when the last key has been removed from a container, + the container can no longer be opened!" + type: bool + default: no + label: + description: + - "This option allow the user to create a LUKS2 format container + with label support, respectively to identify the container by + label on later usages." + - "Will only be used on container creation, or when I(device) is + not specified." + - "This cannot be specified if I(type) is set to C(luks1)." + type: str + version_added: '1.0.0' + uuid: + description: + - "With this option user can identify the LUKS container by UUID." + - "Will only be used when I(device) and I(label) are not specified." + type: str + version_added: '1.0.0' + type: + description: + - "This option allow the user explicit define the format of LUKS + container that wants to work with. Options are C(luks1) or C(luks2)" + type: str + choices: [luks1, luks2] + version_added: '1.0.0' + cipher: + description: + - "This option allows the user to define the cipher specification + string for the LUKS container." + - "Will only be used on container creation." + - "For pre-2.6.10 kernels, use C(aes-plain) as they don't understand + the new cipher spec strings. To use ESSIV, use C(aes-cbc-essiv:sha256)." + type: str + version_added: '1.1.0' + hash: + description: + - "This option allows the user to specify the hash function used in LUKS + key setup scheme and volume key digest." + - "Will only be used on container creation." + type: str + version_added: '1.1.0' + pbkdf: + description: + - This option allows the user to configure the Password-Based Key Derivation + Function (PBKDF) used. + - Will only be used on container creation, and when adding keys to an existing + container. + type: dict + version_added: '1.4.0' + suboptions: + iteration_time: + description: + - Specify the iteration time used for the PBKDF. + - Note that this is in B(seconds), not in milliseconds as on the + command line. + - Mutually exclusive with I(iteration_count). + type: float + iteration_count: + description: + - Specify the iteration count used for the PBKDF. + - Mutually exclusive with I(iteration_time). + type: int + algorithm: + description: + - The algorithm to use. + - Only available for the LUKS 2 format. + choices: + - argon2i + - argon2id + - pbkdf2 + type: str + memory: + description: + - The memory cost limit in kilobytes for the PBKDF. + - This is not used for PBKDF2, but only for the Argon PBKDFs. + type: int + parallel: + description: + - The parallel cost for the PBKDF. This is the number of threads that + run in parallel. + - This is not used for PBKDF2, but only for the Argon PBKDFs. + type: int + +requirements: + - "cryptsetup" + - "wipefs (when I(state) is C(absent))" + - "lsblk" + - "blkid (when I(label) or I(uuid) options are used)" + +author: Jan Pokorny (@japokorn) +''' + +EXAMPLES = ''' + +- name: Create LUKS container (remains unchanged if it already exists) + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + keyfile: "/vault/keyfile" + +- name: Create LUKS container with a passphrase + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + passphrase: "foo" + +- name: Create LUKS container with specific encryption + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + cipher: "aes" + hash: "sha256" + +- name: (Create and) open the LUKS container; name it "mycrypt" + community.crypto.luks_device: + device: "/dev/loop0" + state: "opened" + name: "mycrypt" + keyfile: "/vault/keyfile" + +- name: Close the existing LUKS container "mycrypt" + community.crypto.luks_device: + state: "closed" + name: "mycrypt" + +- name: Make sure LUKS container exists and is closed + community.crypto.luks_device: + device: "/dev/loop0" + state: "closed" + keyfile: "/vault/keyfile" + +- name: Create container if it does not exist and add new key to it + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + keyfile: "/vault/keyfile" + new_keyfile: "/vault/keyfile2" + +- name: Add new key to the LUKS container (container has to exist) + community.crypto.luks_device: + device: "/dev/loop0" + keyfile: "/vault/keyfile" + new_keyfile: "/vault/keyfile2" + +- name: Add new passphrase to the LUKS container + community.crypto.luks_device: + device: "/dev/loop0" + keyfile: "/vault/keyfile" + new_passphrase: "foo" + +- name: Remove existing keyfile from the LUKS container + community.crypto.luks_device: + device: "/dev/loop0" + remove_keyfile: "/vault/keyfile2" + +- name: Remove existing passphrase from the LUKS container + community.crypto.luks_device: + device: "/dev/loop0" + remove_passphrase: "foo" + +- name: Completely remove the LUKS container and its contents + community.crypto.luks_device: + device: "/dev/loop0" + state: "absent" + +- name: Create a container with label + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + keyfile: "/vault/keyfile" + label: personalLabelName + +- name: Open the LUKS container based on label without device; name it "mycrypt" + community.crypto.luks_device: + label: "personalLabelName" + state: "opened" + name: "mycrypt" + keyfile: "/vault/keyfile" + +- name: Close container based on UUID + community.crypto.luks_device: + uuid: 03ecd578-fad4-4e6c-9348-842e3e8fa340 + state: "closed" + name: "mycrypt" + +- name: Create a container using luks2 format + community.crypto.luks_device: + device: "/dev/loop0" + state: "present" + keyfile: "/vault/keyfile" + type: luks2 +''' + +RETURN = ''' +name: + description: + When I(state=opened) returns (generated or given) name + of LUKS container. Returns None if no name is supplied. + returned: success + type: str + sample: "luks-c1da9a58-2fde-4256-9d9f-6ab008b4dd1b" +''' + +import os +import re +import stat + +from ansible.module_utils.basic import AnsibleModule + +RETURN_CODE = 0 +STDOUT = 1 +STDERR = 2 + +# used to get <luks-name> out of lsblk output in format 'crypt <luks-name>' +# regex takes care of any possible blank characters +LUKS_NAME_REGEX = re.compile(r'\s*crypt\s+([^\s]*)\s*') +# used to get </luks/device> out of lsblk output +# in format 'device: </luks/device>' +LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*') + + +class Handler(object): + + def __init__(self, module): + self._module = module + self._lsblk_bin = self._module.get_bin_path('lsblk', True) + + def _run_command(self, command, data=None): + return self._module.run_command(command, data=data) + + def get_device_by_uuid(self, uuid): + ''' Returns the device that holds UUID passed by user + ''' + self._blkid_bin = self._module.get_bin_path('blkid', True) + uuid = self._module.params['uuid'] + if uuid is None: + return None + result = self._run_command([self._blkid_bin, '--uuid', uuid]) + if result[RETURN_CODE] != 0: + return None + return result[STDOUT].strip() + + def get_device_by_label(self, label): + ''' Returns the device that holds label passed by user + ''' + self._blkid_bin = self._module.get_bin_path('blkid', True) + label = self._module.params['label'] + if label is None: + return None + result = self._run_command([self._blkid_bin, '--label', label]) + if result[RETURN_CODE] != 0: + return None + return result[STDOUT].strip() + + def generate_luks_name(self, device): + ''' Generate name for luks based on device UUID ('luks-<UUID>'). + Raises ValueError when obtaining of UUID fails. + ''' + result = self._run_command([self._lsblk_bin, '-n', device, '-o', 'UUID']) + + if result[RETURN_CODE] != 0: + raise ValueError('Error while generating LUKS name for %s: %s' + % (device, result[STDERR])) + dev_uuid = result[STDOUT].strip() + return 'luks-%s' % dev_uuid + + +class CryptHandler(Handler): + + def __init__(self, module): + super(CryptHandler, self).__init__(module) + self._cryptsetup_bin = self._module.get_bin_path('cryptsetup', True) + + def get_container_name_by_device(self, device): + ''' obtain LUKS container name based on the device where it is located + return None if not found + raise ValueError if lsblk command fails + ''' + result = self._run_command([self._lsblk_bin, device, '-nlo', 'type,name']) + if result[RETURN_CODE] != 0: + raise ValueError('Error while obtaining LUKS name for %s: %s' + % (device, result[STDERR])) + + m = LUKS_NAME_REGEX.search(result[STDOUT]) + + try: + name = m.group(1) + except AttributeError: + name = None + return name + + def get_container_device_by_name(self, name): + ''' obtain device name based on the LUKS container name + return None if not found + raise ValueError if lsblk command fails + ''' + # apparently each device can have only one LUKS container on it + result = self._run_command([self._cryptsetup_bin, 'status', name]) + if result[RETURN_CODE] != 0: + return None + + m = LUKS_DEVICE_REGEX.search(result[STDOUT]) + device = m.group(1) + return device + + def is_luks(self, device): + ''' check if the LUKS container does exist + ''' + result = self._run_command([self._cryptsetup_bin, 'isLuks', device]) + return result[RETURN_CODE] == 0 + + def _add_pbkdf_options(self, options, pbkdf): + if pbkdf['iteration_time'] is not None: + options.extend(['--iter-time', str(int(pbkdf['iteration_time'] * 1000))]) + if pbkdf['iteration_count'] is not None: + options.extend(['--pbkdf-force-iterations', str(pbkdf['iteration_count'])]) + if pbkdf['algorithm'] is not None: + options.extend(['--pbkdf', pbkdf['algorithm']]) + if pbkdf['memory'] is not None: + options.extend(['--pbkdf-memory', str(pbkdf['memory'])]) + if pbkdf['parallel'] is not None: + options.extend(['--pbkdf-parallel', str(pbkdf['parallel'])]) + + def run_luks_create(self, device, keyfile, passphrase, keysize, cipher, hash_, pbkdf): + # create a new luks container; use batch mode to auto confirm + luks_type = self._module.params['type'] + label = self._module.params['label'] + + options = [] + if keysize is not None: + options.append('--key-size=' + str(keysize)) + if label is not None: + options.extend(['--label', label]) + luks_type = 'luks2' + if luks_type is not None: + options.extend(['--type', luks_type]) + if cipher is not None: + options.extend(['--cipher', cipher]) + if hash_ is not None: + options.extend(['--hash', hash_]) + if pbkdf is not None: + self._add_pbkdf_options(options, pbkdf) + + args = [self._cryptsetup_bin, 'luksFormat'] + args.extend(options) + args.extend(['-q', device]) + if keyfile: + args.append(keyfile) + + result = self._run_command(args, data=passphrase) + if result[RETURN_CODE] != 0: + raise ValueError('Error while creating LUKS on %s: %s' + % (device, result[STDERR])) + + def run_luks_open(self, device, keyfile, passphrase, name): + args = [self._cryptsetup_bin] + if keyfile: + args.extend(['--key-file', keyfile]) + args.extend(['open', '--type', 'luks', device, name]) + + result = self._run_command(args, data=passphrase) + if result[RETURN_CODE] != 0: + raise ValueError('Error while opening LUKS container on %s: %s' + % (device, result[STDERR])) + + def run_luks_close(self, name): + result = self._run_command([self._cryptsetup_bin, 'close', name]) + if result[RETURN_CODE] != 0: + raise ValueError('Error while closing LUKS container %s' % (name)) + + def run_luks_remove(self, device): + wipefs_bin = self._module.get_bin_path('wipefs', True) + + name = self.get_container_name_by_device(device) + if name is not None: + self.run_luks_close(name) + result = self._run_command([wipefs_bin, '--all', device]) + if result[RETURN_CODE] != 0: + raise ValueError('Error while wiping luks container %s: %s' + % (device, result[STDERR])) + + def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile, + new_passphrase, pbkdf): + ''' Add new key from a keyfile or passphrase to given 'device'; + authentication done using 'keyfile' or 'passphrase'. + Raises ValueError when command fails. + ''' + data = [] + args = [self._cryptsetup_bin, 'luksAddKey', device] + if pbkdf is not None: + self._add_pbkdf_options(args, pbkdf) + + if keyfile: + args.extend(['--key-file', keyfile]) + else: + data.append(passphrase) + + if new_keyfile: + args.append(new_keyfile) + else: + data.extend([new_passphrase, new_passphrase]) + + result = self._run_command(args, data='\n'.join(data) or None) + if result[RETURN_CODE] != 0: + raise ValueError('Error while adding new LUKS keyslot to %s: %s' + % (device, result[STDERR])) + + def run_luks_remove_key(self, device, keyfile, passphrase, + force_remove_last_key=False): + ''' Remove key from given device + Raises ValueError when command fails + ''' + if not force_remove_last_key: + result = self._run_command([self._cryptsetup_bin, 'luksDump', device]) + if result[RETURN_CODE] != 0: + raise ValueError('Error while dumping LUKS header from %s' + % (device, )) + keyslot_count = 0 + keyslot_area = False + keyslot_re = re.compile(r'^Key Slot [0-9]+: ENABLED') + for line in result[STDOUT].splitlines(): + if line.startswith('Keyslots:'): + keyslot_area = True + elif line.startswith(' '): + # LUKS2 header dumps use human-readable indented output. + # Thus we have to look out for 'Keyslots:' and count the + # number of indented keyslot numbers. + if keyslot_area and line[2] in '0123456789': + keyslot_count += 1 + elif line.startswith('\t'): + pass + elif keyslot_re.match(line): + # LUKS1 header dumps have one line per keyslot with ENABLED + # or DISABLED in them. We count such lines with ENABLED. + keyslot_count += 1 + else: + keyslot_area = False + if keyslot_count < 2: + self._module.fail_json(msg="LUKS device %s has less than two active keyslots. " + "To be able to remove a key, please set " + "`force_remove_last_key` to `yes`." % device) + + args = [self._cryptsetup_bin, 'luksRemoveKey', device, '-q'] + if keyfile: + args.extend(['--key-file', keyfile]) + result = self._run_command(args, data=passphrase) + if result[RETURN_CODE] != 0: + raise ValueError('Error while removing LUKS key from %s: %s' + % (device, result[STDERR])) + + def luks_test_key(self, device, keyfile, passphrase): + ''' Check whether the keyfile or passphrase works. + Raises ValueError when command fails. + ''' + data = None + args = [self._cryptsetup_bin, 'luksOpen', '--test-passphrase', device] + + if keyfile: + args.extend(['--key-file', keyfile]) + else: + data = passphrase + + result = self._run_command(args, data=data) + if result[RETURN_CODE] == 0: + return True + for output in (STDOUT, STDERR): + if 'No key available with this passphrase' in result[output]: + return False + + raise ValueError('Error while testing whether keyslot exists on %s: %s' + % (device, result[STDERR])) + + +class ConditionsHandler(Handler): + + def __init__(self, module, crypthandler): + super(ConditionsHandler, self).__init__(module) + self._crypthandler = crypthandler + self.device = self.get_device_name() + + def get_device_name(self): + device = self._module.params.get('device') + label = self._module.params.get('label') + uuid = self._module.params.get('uuid') + name = self._module.params.get('name') + + if device is None and label is not None: + device = self.get_device_by_label(label) + elif device is None and uuid is not None: + device = self.get_device_by_uuid(uuid) + elif device is None and name is not None: + device = self._crypthandler.get_container_device_by_name(name) + + return device + + def luks_create(self): + return (self.device is not None and + (self._module.params['keyfile'] is not None or + self._module.params['passphrase'] is not None) and + self._module.params['state'] in ('present', + 'opened', + 'closed') and + not self._crypthandler.is_luks(self.device)) + + def opened_luks_name(self): + ''' If luks is already opened, return its name. + If 'name' parameter is specified and differs + from obtained value, fail. + Return None otherwise + ''' + if self._module.params['state'] != 'opened': + return None + + # try to obtain luks name - it may be already opened + name = self._crypthandler.get_container_name_by_device(self.device) + + if name is None: + # container is not open + return None + + if self._module.params['name'] is None: + # container is already opened + return name + + if name != self._module.params['name']: + # the container is already open but with different name: + # suspicious. back off + self._module.fail_json(msg="LUKS container is already opened " + "under different name '%s'." % name) + + # container is opened and the names match + return name + + def luks_open(self): + if ((self._module.params['keyfile'] is None and + self._module.params['passphrase'] is None) or + self.device is None or + self._module.params['state'] != 'opened'): + # conditions for open not fulfilled + return False + + name = self.opened_luks_name() + + if name is None: + return True + return False + + def luks_close(self): + if ((self._module.params['name'] is None and self.device is None) or + self._module.params['state'] != 'closed'): + # conditions for close not fulfilled + return False + + if self.device is not None: + name = self._crypthandler.get_container_name_by_device(self.device) + # successfully getting name based on device means that luks is open + luks_is_open = name is not None + + if self._module.params['name'] is not None: + self.device = self._crypthandler.get_container_device_by_name( + self._module.params['name']) + # successfully getting device based on name means that luks is open + luks_is_open = self.device is not None + + return luks_is_open + + def luks_add_key(self): + if (self.device is None or + (self._module.params['keyfile'] is None and + self._module.params['passphrase'] is None) or + (self._module.params['new_keyfile'] is None and + self._module.params['new_passphrase'] is None)): + # conditions for adding a key not fulfilled + return False + + if self._module.params['state'] == 'absent': + self._module.fail_json(msg="Contradiction in setup: Asking to " + "add a key to absent LUKS.") + + return not self._crypthandler.luks_test_key(self.device, self._module.params['new_keyfile'], self._module.params['new_passphrase']) + + def luks_remove_key(self): + if (self.device is None or + (self._module.params['remove_keyfile'] is None and + self._module.params['remove_passphrase'] is None)): + # conditions for removing a key not fulfilled + return False + + if self._module.params['state'] == 'absent': + self._module.fail_json(msg="Contradiction in setup: Asking to " + "remove a key from absent LUKS.") + + return self._crypthandler.luks_test_key(self.device, self._module.params['remove_keyfile'], self._module.params['remove_passphrase']) + + def luks_remove(self): + return (self.device is not None and + self._module.params['state'] == 'absent' and + self._crypthandler.is_luks(self.device)) + + +def run_module(): + # available arguments/parameters that a user can pass + module_args = dict( + state=dict(type='str', default='present', choices=['present', 'absent', 'opened', 'closed']), + device=dict(type='str'), + name=dict(type='str'), + keyfile=dict(type='path'), + new_keyfile=dict(type='path'), + remove_keyfile=dict(type='path'), + passphrase=dict(type='str', no_log=True), + new_passphrase=dict(type='str', no_log=True), + remove_passphrase=dict(type='str', no_log=True), + force_remove_last_key=dict(type='bool', default=False), + keysize=dict(type='int'), + label=dict(type='str'), + uuid=dict(type='str'), + type=dict(type='str', choices=['luks1', 'luks2']), + cipher=dict(type='str'), + hash=dict(type='str'), + pbkdf=dict( + type='dict', + options=dict( + iteration_time=dict(type='float'), + iteration_count=dict(type='int'), + algorithm=dict(type='str', choices=['argon2i', 'argon2id', 'pbkdf2']), + memory=dict(type='int'), + parallel=dict(type='int'), + ), + mutually_exclusive=[('iteration_time', 'iteration_count')], + ), + ) + + mutually_exclusive = [ + ('keyfile', 'passphrase'), + ('new_keyfile', 'new_passphrase'), + ('remove_keyfile', 'remove_passphrase') + ] + + # seed the result dict in the object + result = dict( + changed=False, + name=None + ) + + module = AnsibleModule(argument_spec=module_args, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive) + + if module.params['device'] is not None: + try: + statinfo = os.stat(module.params['device']) + mode = statinfo.st_mode + if not stat.S_ISBLK(mode) and not stat.S_ISCHR(mode): + raise Exception('{0} is not a device'.format(module.params['device'])) + except Exception as e: + module.fail_json(msg=str(e)) + + crypt = CryptHandler(module) + conditions = ConditionsHandler(module, crypt) + + # conditions not allowed to run + if module.params['label'] is not None and module.params['type'] == 'luks1': + module.fail_json(msg='You cannot combine type luks1 with the label option.') + + # The conditions are in order to allow more operations in one run. + # (e.g. create luks and add a key to it) + + # luks create + if conditions.luks_create(): + if not module.check_mode: + try: + crypt.run_luks_create(conditions.device, + module.params['keyfile'], + module.params['passphrase'], + module.params['keysize'], + module.params['cipher'], + module.params['hash'], + module.params['pbkdf'], + ) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # luks open + + name = conditions.opened_luks_name() + if name is not None: + result['name'] = name + + if conditions.luks_open(): + name = module.params['name'] + if name is None: + try: + name = crypt.generate_luks_name(conditions.device) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + if not module.check_mode: + try: + crypt.run_luks_open(conditions.device, + module.params['keyfile'], + module.params['passphrase'], + name) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['name'] = name + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # luks close + if conditions.luks_close(): + if conditions.device is not None: + try: + name = crypt.get_container_name_by_device( + conditions.device) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + else: + name = module.params['name'] + if not module.check_mode: + try: + crypt.run_luks_close(name) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['name'] = name + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # luks add key + if conditions.luks_add_key(): + if not module.check_mode: + try: + crypt.run_luks_add_key(conditions.device, + module.params['keyfile'], + module.params['passphrase'], + module.params['new_keyfile'], + module.params['new_passphrase'], + module.params['pbkdf']) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # luks remove key + if conditions.luks_remove_key(): + if not module.check_mode: + try: + last_key = module.params['force_remove_last_key'] + crypt.run_luks_remove_key(conditions.device, + module.params['remove_keyfile'], + module.params['remove_passphrase'], + force_remove_last_key=last_key) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # luks remove + if conditions.luks_remove(): + if not module.check_mode: + try: + crypt.run_luks_remove(conditions.device) + except ValueError as e: + module.fail_json(msg="luks_device error: %s" % e) + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + # Success - return result + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_cert.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_cert.py new file mode 100644 index 00000000..a293ba48 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_cert.py @@ -0,0 +1,636 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, David Kainz <dkainz@mgit.at> <dave.jokain@gmx.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: openssh_cert +author: "David Kainz (@lolcube)" +short_description: Generate OpenSSH host or user certificates. +description: + - Generate and regenerate OpenSSH host or user certificates. +requirements: + - "ssh-keygen" +options: + state: + description: + - Whether the host or user certificate should exist or not, taking action if the state is different from what is stated. + type: str + default: "present" + choices: [ 'present', 'absent' ] + type: + description: + - Whether the module should generate a host or a user certificate. + - Required if I(state) is C(present). + type: str + choices: ['host', 'user'] + force: + description: + - Should the certificate be regenerated even if it already exists and is valid. + type: bool + default: false + path: + description: + - Path of the file containing the certificate. + type: path + required: true + signing_key: + description: + - The path to the private openssh key that is used for signing the public key in order to generate the certificate. + - If the private key is on a PKCS#11 token (I(pkcs11_provider)), set this to the path to the public key instead. + - Required if I(state) is C(present). + type: path + pkcs11_provider: + description: + - To use a signing key that resides on a PKCS#11 token, set this to the name (or full path) of the shared library to use with the token. + Usually C(libpkcs11.so). + - If this is set, I(signing_key) needs to point to a file containing the public key of the CA. + type: str + version_added: 1.1.0 + use_agent: + description: + - Should the ssh-keygen use a CA key residing in a ssh-agent. + type: bool + default: false + version_added: 1.3.0 + public_key: + description: + - The path to the public key that will be signed with the signing key in order to generate the certificate. + - Required if I(state) is C(present). + type: path + valid_from: + description: + - "The point in time the certificate is valid from. Time can be specified either as relative time or as absolute timestamp. + Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | always) + where timespec can be an integer + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + Note that if using relative time this module is NOT idempotent." + - Required if I(state) is C(present). + type: str + valid_to: + description: + - "The point in time the certificate is valid to. Time can be specified either as relative time or as absolute timestamp. + Time will always be interpreted as UTC. Valid formats are: C([+-]timespec | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS | YYYY-MM-DD HH:MM:SS | forever) + where timespec can be an integer + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + Note that if using relative time this module is NOT idempotent." + - Required if I(state) is C(present). + type: str + valid_at: + description: + - "Check if the certificate is valid at a certain point in time. If it is not the certificate will be regenerated. + Time will always be interpreted as UTC. Mainly to be used with relative timespec for I(valid_from) and / or I(valid_to). + Note that if using relative time this module is NOT idempotent." + type: str + principals: + description: + - "Certificates may be limited to be valid for a set of principal (user/host) names. + By default, generated certificates are valid for all users or hosts." + type: list + elements: str + options: + description: + - "Specify certificate options when signing a key. The option that are valid for user certificates are:" + - "C(clear): Clear all enabled permissions. This is useful for clearing the default set of permissions so permissions may be added individually." + - "C(force-command=command): Forces the execution of command instead of any shell or + command specified by the user when the certificate is used for authentication." + - "C(no-agent-forwarding): Disable ssh-agent forwarding (permitted by default)." + - "C(no-port-forwarding): Disable port forwarding (permitted by default)." + - "C(no-pty): Disable PTY allocation (permitted by default)." + - "C(no-user-rc): Disable execution of C(~/.ssh/rc) by sshd (permitted by default)." + - "C(no-x11-forwarding): Disable X11 forwarding (permitted by default)" + - "C(permit-agent-forwarding): Allows ssh-agent forwarding." + - "C(permit-port-forwarding): Allows port forwarding." + - "C(permit-pty): Allows PTY allocation." + - "C(permit-user-rc): Allows execution of C(~/.ssh/rc) by sshd." + - "C(permit-x11-forwarding): Allows X11 forwarding." + - "C(source-address=address_list): Restrict the source addresses from which the certificate is considered valid. + The C(address_list) is a comma-separated list of one or more address/netmask pairs in CIDR format." + - "At present, no options are valid for host keys." + type: list + elements: str + identifier: + description: + - Specify the key identity when signing a public key. The identifier that is logged by the server when the certificate is used for authentication. + type: str + serial_number: + description: + - "Specify the certificate serial number. + The serial number is logged by the server when the certificate is used for authentication. + The certificate serial number may be used in a KeyRevocationList. + The serial number may be omitted for checks, but must be specified again for a new certificate. + Note: The default value set by ssh-keygen is 0." + type: int + +extends_documentation_fragment: files +''' + +EXAMPLES = ''' +- name: Generate an OpenSSH user certificate that is valid forever and for all users + community.crypto.openssh_cert: + type: user + signing_key: /path/to/private_key + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: always + valid_to: forever + +# Generate an OpenSSH host certificate that is valid for 32 weeks from now and will be regenerated +# if it is valid for less than 2 weeks from the time the module is being run +- name: Generate an OpenSSH host certificate with valid_from, valid_to and valid_at parameters + community.crypto.openssh_cert: + type: host + signing_key: /path/to/private_key + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: +0s + valid_to: +32w + valid_at: +2w + +- name: Generate an OpenSSH host certificate that is valid forever and only for example.com and examplehost + community.crypto.openssh_cert: + type: host + signing_key: /path/to/private_key + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: always + valid_to: forever + principals: + - example.com + - examplehost + +- name: Generate an OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019 + community.crypto.openssh_cert: + type: host + signing_key: /path/to/private_key + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: "2001-01-21" + valid_to: "2019-01-21" + +- name: Generate an OpenSSH user Certificate with clear and force-command option + community.crypto.openssh_cert: + type: user + signing_key: /path/to/private_key + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: always + valid_to: forever + options: + - "clear" + - "force-command=/tmp/bla/foo" + +- name: Generate an OpenSSH user certificate using a PKCS#11 token + community.crypto.openssh_cert: + type: user + signing_key: /path/to/ca_public_key.pub + pkcs11_provider: libpkcs11.so + public_key: /path/to/public_key.pub + path: /path/to/certificate + valid_from: always + valid_to: forever + +''' + +RETURN = ''' +type: + description: type of the certificate (host or user) + returned: changed or success + type: str + sample: host +filename: + description: path to the certificate + returned: changed or success + type: str + sample: /tmp/certificate-cert.pub +info: + description: Information about the certificate. Output of C(ssh-keygen -L -f). + returned: change or success + type: list + elements: str + +''' + +import errno +import os +import re +import tempfile + +from datetime import datetime +from datetime import MINYEAR, MAXYEAR +from distutils.version import LooseVersion +from shutil import copy2, rmtree + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import convert_relative_to_datetime +from ansible_collections.community.crypto.plugins.module_utils.crypto.openssh import parse_openssh_version + + +class CertificateError(Exception): + pass + + +class Certificate(object): + + def __init__(self, module): + self.state = module.params['state'] + self.force = module.params['force'] + self.type = module.params['type'] + self.signing_key = module.params['signing_key'] + self.use_agent = module.params['use_agent'] + self.pkcs11_provider = module.params['pkcs11_provider'] + self.public_key = module.params['public_key'] + self.path = module.params['path'] + self.identifier = module.params['identifier'] + self.serial_number = module.params['serial_number'] + self.valid_from = module.params['valid_from'] + self.valid_to = module.params['valid_to'] + self.valid_at = module.params['valid_at'] + self.principals = module.params['principals'] + self.options = module.params['options'] + self.changed = False + self.check_mode = module.check_mode + self.cert_info = {} + + if self.state == 'present': + + if self.options and self.type == "host": + module.fail_json(msg="Options can only be used with user certificates.") + + if self.valid_at: + self.valid_at = self.valid_at.lstrip() + + self.valid_from = self.valid_from.lstrip() + self.valid_to = self.valid_to.lstrip() + + self.ssh_keygen = module.get_bin_path('ssh-keygen', True) + + def generate(self, module): + + if not self.is_valid(module, perms_required=False) or self.force: + args = [ + self.ssh_keygen, + '-s', self.signing_key + ] + + if self.pkcs11_provider: + args.extend(['-D', self.pkcs11_provider]) + + if self.use_agent: + args.extend(['-U']) + + validity = "" + + if not (self.valid_from == "always" and self.valid_to == "forever"): + + if not self.valid_from == "always": + timeobj = self.convert_to_datetime(module, self.valid_from) + validity += ( + str(timeobj.year).zfill(4) + + str(timeobj.month).zfill(2) + + str(timeobj.day).zfill(2) + + str(timeobj.hour).zfill(2) + + str(timeobj.minute).zfill(2) + + str(timeobj.second).zfill(2) + ) + else: + validity += "19700101010101" + + validity += ":" + + if self.valid_to == "forever": + # on ssh-keygen versions that have the year 2038 bug this will cause the datetime to be 2038-01-19T04:14:07 + timeobj = datetime(MAXYEAR, 12, 31) + else: + timeobj = self.convert_to_datetime(module, self.valid_to) + + validity += ( + str(timeobj.year).zfill(4) + + str(timeobj.month).zfill(2) + + str(timeobj.day).zfill(2) + + str(timeobj.hour).zfill(2) + + str(timeobj.minute).zfill(2) + + str(timeobj.second).zfill(2) + ) + + args.extend(["-V", validity]) + + if self.type == 'host': + args.extend(['-h']) + + if self.identifier: + args.extend(['-I', self.identifier]) + else: + args.extend(['-I', ""]) + + if self.serial_number is not None: + args.extend(['-z', str(self.serial_number)]) + + if self.principals: + args.extend(['-n', ','.join(self.principals)]) + + if self.options: + for option in self.options: + args.extend(['-O']) + args.extend([option]) + + args.extend(['-P', '']) + + try: + temp_directory = tempfile.mkdtemp() + copy2(self.public_key, temp_directory) + args.extend([temp_directory + "/" + os.path.basename(self.public_key)]) + module.run_command(args, environ_update=dict(TZ="UTC"), check_rc=True) + copy2(temp_directory + "/" + os.path.splitext(os.path.basename(self.public_key))[0] + "-cert.pub", self.path) + rmtree(temp_directory, ignore_errors=True) + proc = module.run_command([self.ssh_keygen, '-L', '-f', self.path]) + self.cert_info = proc[1].split() + self.changed = True + except Exception as e: + try: + self.remove() + rmtree(temp_directory, ignore_errors=True) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise CertificateError(exc) + else: + pass + module.fail_json(msg="%s" % to_native(e)) + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def convert_to_datetime(self, module, timestring): + + if self.is_relative(timestring): + result = convert_relative_to_datetime(timestring) + if result is None: + module.fail_json( + msg="'%s' is not a valid time format." % timestring) + else: + return result + else: + formats = ["%Y-%m-%d", + "%Y-%m-%d %H:%M:%S", + "%Y-%m-%dT%H:%M:%S", + ] + for fmt in formats: + try: + return datetime.strptime(timestring, fmt) + except ValueError: + pass + module.fail_json(msg="'%s' is not a valid time format" % timestring) + + def is_relative(self, timestr): + if timestr.startswith("+") or timestr.startswith("-"): + return True + return False + + def is_same_datetime(self, datetime_one, datetime_two): + + # This function is for backwards compatibility only because .total_seconds() is new in python2.7 + def timedelta_total_seconds(time_delta): + return (time_delta.microseconds + 0.0 + (time_delta.seconds + time_delta.days * 24 * 3600) * 10 ** 6) / 10 ** 6 + # try to use .total_ seconds() from python2.7 + try: + return (datetime_one - datetime_two).total_seconds() == 0.0 + except AttributeError: + return timedelta_total_seconds(datetime_one - datetime_two) == 0.0 + + def is_valid(self, module, perms_required=True): + + def _check_state(): + return os.path.exists(self.path) + + if _check_state(): + proc = module.run_command([self.ssh_keygen, '-L', '-f', self.path], environ_update=dict(TZ="UTC"), check_rc=False) + if proc[0] != 0: + return False + self.cert_info = proc[1].split() + principals = re.findall("(?<=Principals:)(.*)(?=Critical)", proc[1], re.S)[0].split() + principals = list(map(str.strip, principals)) + if principals == ["(none)"]: + principals = None + cert_type = re.findall("( user | host )", proc[1])[0].strip() + serial_number = re.search(r"Serial: (\d+)", proc[1]).group(1) + validity = re.findall("(from (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}) to (\\d{4}-\\d{2}-\\d{2}T\\d{2}(:\\d{2}){2}))", proc[1]) + if validity: + if validity[0][1]: + cert_valid_from = self.convert_to_datetime(module, validity[0][1]) + if self.is_same_datetime(cert_valid_from, self.convert_to_datetime(module, "1970-01-01 01:01:01")): + cert_valid_from = datetime(MINYEAR, 1, 1) + else: + cert_valid_from = datetime(MINYEAR, 1, 1) + + if validity[0][3]: + cert_valid_to = self.convert_to_datetime(module, validity[0][3]) + if self.is_same_datetime(cert_valid_to, self.convert_to_datetime(module, "2038-01-19 03:14:07")): + cert_valid_to = datetime(MAXYEAR, 12, 31) + else: + cert_valid_to = datetime(MAXYEAR, 12, 31) + else: + cert_valid_from = datetime(MINYEAR, 1, 1) + cert_valid_to = datetime(MAXYEAR, 12, 31) + else: + return False + + def _check_perms(module): + file_args = module.load_file_common_arguments(module.params) + return not module.set_fs_attributes_if_different(file_args, False) + + def _check_serial_number(): + if self.serial_number is None: + return True + return self.serial_number == int(serial_number) + + def _check_type(): + return self.type == cert_type + + def _check_principals(): + if not principals or not self.principals: + return self.principals == principals + return set(self.principals) == set(principals) + + def _check_validity(module): + if self.valid_from == "always": + earliest_time = datetime(MINYEAR, 1, 1) + elif self.is_relative(self.valid_from): + earliest_time = None + else: + earliest_time = self.convert_to_datetime(module, self.valid_from) + + if self.valid_to == "forever": + last_time = datetime(MAXYEAR, 12, 31) + elif self.is_relative(self.valid_to): + last_time = None + else: + last_time = self.convert_to_datetime(module, self.valid_to) + + if earliest_time: + if not self.is_same_datetime(earliest_time, cert_valid_from): + return False + if last_time: + if not self.is_same_datetime(last_time, cert_valid_to): + return False + + if self.valid_at: + if cert_valid_from <= self.convert_to_datetime(module, self.valid_at) <= cert_valid_to: + return True + + if earliest_time and last_time: + return True + + return False + + if perms_required and not _check_perms(module): + return False + + return _check_type() and _check_principals() and _check_validity(module) and _check_serial_number() + + def dump(self): + + """Serialize the object into a dictionary.""" + + def filter_keywords(arr, keywords): + concated = [] + string = "" + for word in arr: + if word in keywords: + concated.append(string) + string = word + else: + string += " " + word + concated.append(string) + # drop the certificate path + concated.pop(0) + return concated + + def format_cert_info(): + return filter_keywords(self.cert_info, [ + "Type:", + "Public", + "Signing", + "Key", + "Serial:", + "Valid:", + "Principals:", + "Critical", + "Extensions:"]) + + if self.state == 'present': + result = { + 'changed': self.changed, + 'type': self.type, + 'filename': self.path, + 'info': format_cert_info(), + } + else: + result = { + 'changed': self.changed, + } + + return result + + def remove(self): + """Remove the resource from the filesystem.""" + + try: + os.remove(self.path) + self.changed = True + except OSError as exc: + if exc.errno != errno.ENOENT: + raise CertificateError(exc) + else: + pass + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + force=dict(type='bool', default=False), + type=dict(type='str', choices=['host', 'user']), + signing_key=dict(type='path'), + use_agent=dict(type='bool', default=False), + pkcs11_provider=dict(type='str'), + public_key=dict(type='path'), + path=dict(type='path', required=True), + identifier=dict(type='str'), + serial_number=dict(type='int'), + valid_from=dict(type='str'), + valid_to=dict(type='str'), + valid_at=dict(type='str'), + principals=dict(type='list', elements='str'), + options=dict(type='list', elements='str'), + ), + supports_check_mode=True, + add_file_common_args=True, + required_if=[('state', 'present', ['type', 'signing_key', 'public_key', 'valid_from', 'valid_to'])], + ) + + if module.params['use_agent']: + ssh = module.get_bin_path('ssh', True) + proc = module.run_command([ssh, '-Vq']) + ssh_version_string = proc[2].strip() + ssh_version = parse_openssh_version(ssh_version_string) + if ssh_version is None: + module.fail_json(msg="Failed to parse ssh version") + elif LooseVersion(ssh_version) < LooseVersion("7.6"): + module.fail_json( + msg=( + "Signing with CA key in ssh agent requires ssh 7.6 or newer." + " Your version is: %s" + ) % ssh_version_string + ) + + def isBaseDir(path): + base_dir = os.path.dirname(path) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + if module.params['state'] == "present": + isBaseDir(module.params['signing_key']) + isBaseDir(module.params['public_key']) + + isBaseDir(module.params['path']) + + certificate = Certificate(module) + + if certificate.state == 'present': + + if module.check_mode: + certificate.changed = module.params['force'] or not certificate.is_valid(module) + else: + try: + certificate.generate(module) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + else: + + if module.check_mode: + certificate.changed = os.path.exists(module.params['path']) + if certificate.changed: + certificate.cert_info = {} + else: + try: + certificate.remove() + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + result = certificate.dump() + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_keypair.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_keypair.py new file mode 100644 index 00000000..a104045a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssh_keypair.py @@ -0,0 +1,487 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2018, David Kainz <dkainz@mgit.at> <dave.jokain@gmx.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: openssh_keypair +author: "David Kainz (@lolcube)" +short_description: Generate OpenSSH private and public keys +description: + - "This module allows one to (re)generate OpenSSH private and public keys. It uses + ssh-keygen to generate keys. One can generate C(rsa), C(dsa), C(rsa1), C(ed25519) + or C(ecdsa) private keys." +requirements: + - "ssh-keygen" +options: + state: + description: + - Whether the private and public keys should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ present, absent ] + size: + description: + - "Specifies the number of bits in the private key to create. For RSA keys, the minimum size is 1024 bits and the default is 4096 bits. + Generally, 2048 bits is considered sufficient. DSA keys must be exactly 1024 bits as specified by FIPS 186-2. + For ECDSA keys, size determines the key length by selecting from one of three elliptic curve sizes: 256, 384 or 521 bits. + Attempting to use bit lengths other than these three values for ECDSA keys will cause this module to fail. + Ed25519 keys have a fixed length and the size will be ignored." + type: int + type: + description: + - "The algorithm used to generate the SSH private key. C(rsa1) is for protocol version 1. + C(rsa1) is deprecated and may not be supported by every version of ssh-keygen." + type: str + default: rsa + choices: ['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519'] + force: + description: + - Should the key be regenerated even if it already exists + type: bool + default: false + path: + description: + - Name of the files containing the public and private key. The file containing the public key will have the extension C(.pub). + type: path + required: true + comment: + description: + - Provides a new comment to the public key. + type: str + regenerate: + description: + - Allows to configure in which situations the module is allowed to regenerate private keys. + The module will always generate a new key if the destination file does not exist. + - By default, the key will be regenerated when it does not match the module's options, + except when the key cannot be read or the passphrase does not match. Please note that + this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence) + is specified. + - If set to C(never), the module will fail if the key cannot be read or the passphrase + isn't matching, and will never regenerate an existing key. + - If set to C(fail), the module will fail if the key does not correspond to the module's + options. + - If set to C(partial_idempotence), the key will be regenerated if it does not conform to + the module's options. The key is B(not) regenerated if it cannot be read (broken file), + the key is protected by an unknown passphrase, or when they key is not protected by a + passphrase, but a passphrase is specified. + - If set to C(full_idempotence), the key will be regenerated if it does not conform to the + module's options. This is also the case if the key cannot be read (broken file), the key + is protected by an unknown passphrase, or when they key is not protected by a passphrase, + but a passphrase is specified. Make sure you have a B(backup) when using this option! + - If set to C(always), the module will always regenerate the key. This is equivalent to + setting I(force) to C(yes). + - Note that adjusting the comment and the permissions can be changed without regeneration. + Therefore, even for C(never), the task can result in changed. + type: str + choices: + - never + - fail + - partial_idempotence + - full_idempotence + - always + default: partial_idempotence + version_added: '1.0.0' +notes: + - In case the ssh key is broken or password protected, the module will fail. + Set the I(force) option to C(yes) if you want to regenerate the keypair. + - Supports C(check_mode). + +extends_documentation_fragment: files +''' + +EXAMPLES = ''' +- name: Generate an OpenSSH keypair with the default values (4096 bits, rsa) + community.crypto.openssh_keypair: + path: /tmp/id_ssh_rsa + +- name: Generate an OpenSSH rsa keypair with a different size (2048 bits) + community.crypto.openssh_keypair: + path: /tmp/id_ssh_rsa + size: 2048 + +- name: Force regenerate an OpenSSH keypair if it already exists + community.crypto.openssh_keypair: + path: /tmp/id_ssh_rsa + force: True + +- name: Generate an OpenSSH keypair with a different algorithm (dsa) + community.crypto.openssh_keypair: + path: /tmp/id_ssh_dsa + type: dsa +''' + +RETURN = ''' +size: + description: Size (in bits) of the SSH private key. + returned: changed or success + type: int + sample: 4096 +type: + description: Algorithm used to generate the SSH private key. + returned: changed or success + type: str + sample: rsa +filename: + description: Path to the generated SSH private key file. + returned: changed or success + type: str + sample: /tmp/id_ssh_rsa +fingerprint: + description: The fingerprint of the key. + returned: changed or success + type: str + sample: SHA256:r4YCZxihVjedH2OlfjVGI6Y5xAYtdCwk8VxKyzVyYfM +public_key: + description: The public key of the generated SSH private key. + returned: changed or success + type: str + sample: ssh-rsa AAAAB3Nza(...omitted...)veL4E3Xcw== test_key +comment: + description: The comment of the generated key. + returned: changed or success + type: str + sample: test@comment +''' + +import errno +import os +import stat + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +class KeypairError(Exception): + pass + + +class Keypair(object): + + def __init__(self, module): + self.path = module.params['path'] + self.state = module.params['state'] + self.force = module.params['force'] + self.size = module.params['size'] + self.type = module.params['type'] + self.comment = module.params['comment'] + self.changed = False + self.check_mode = module.check_mode + self.privatekey = None + self.fingerprint = {} + self.public_key = {} + self.regenerate = module.params['regenerate'] + if self.regenerate == 'always': + self.force = True + + if self.type in ('rsa', 'rsa1'): + self.size = 4096 if self.size is None else self.size + if self.size < 1024: + module.fail_json(msg=('For RSA keys, the minimum size is 1024 bits and the default is 4096 bits. ' + 'Attempting to use bit lengths under 1024 will cause the module to fail.')) + + if self.type == 'dsa': + self.size = 1024 if self.size is None else self.size + if self.size != 1024: + module.fail_json(msg=('DSA keys must be exactly 1024 bits as specified by FIPS 186-2.')) + + if self.type == 'ecdsa': + self.size = 256 if self.size is None else self.size + if self.size not in (256, 384, 521): + module.fail_json(msg=('For ECDSA keys, size determines the key length by selecting from ' + 'one of three elliptic curve sizes: 256, 384 or 521 bits. ' + 'Attempting to use bit lengths other than these three values for ' + 'ECDSA keys will cause this module to fail. ')) + if self.type == 'ed25519': + self.size = 256 + + def generate(self, module): + # generate a keypair + if self.force or not self.isPrivateKeyValid(module, perms_required=False): + args = [ + module.get_bin_path('ssh-keygen', True), + '-q', + '-N', '', + '-b', str(self.size), + '-t', self.type, + '-f', self.path, + ] + + if self.comment: + args.extend(['-C', self.comment]) + else: + args.extend(['-C', ""]) + + try: + if os.path.exists(self.path) and not os.access(self.path, os.W_OK): + os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR) + self.changed = True + stdin_data = None + if os.path.exists(self.path): + stdin_data = 'y' + module.run_command(args, data=stdin_data) + proc = module.run_command([module.get_bin_path('ssh-keygen', True), '-lf', self.path]) + self.fingerprint = proc[1].split() + pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path]) + self.public_key = pubkey[1].strip('\n') + except Exception as e: + self.remove() + module.fail_json(msg="%s" % to_native(e)) + + elif not self.isPublicKeyValid(module, perms_required=False): + pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path]) + pubkey = pubkey[1].strip('\n') + try: + self.changed = True + with open(self.path + ".pub", "w") as pubkey_f: + pubkey_f.write(pubkey + '\n') + os.chmod(self.path + ".pub", stat.S_IWUSR + stat.S_IRUSR + stat.S_IRGRP + stat.S_IROTH) + except IOError: + module.fail_json( + msg='The public key is missing or does not match the private key. ' + 'Unable to regenerate the public key.') + self.public_key = pubkey + + if self.comment: + try: + if os.path.exists(self.path) and not os.access(self.path, os.W_OK): + os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR) + args = [module.get_bin_path('ssh-keygen', True), + '-q', '-o', '-c', '-C', self.comment, '-f', self.path] + module.run_command(args) + except IOError: + module.fail_json( + msg='Unable to update the comment for the public key.') + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + file_args['path'] = file_args['path'] + '.pub' + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def _check_pass_protected_or_broken_key(self, module): + key_state = module.run_command([module.get_bin_path('ssh-keygen', True), + '-P', '', '-yf', self.path], check_rc=False) + if key_state[0] == 255 or 'is not a public key file' in key_state[2]: + return True + if 'incorrect passphrase' in key_state[2] or 'load failed' in key_state[2]: + return True + return False + + def isPrivateKeyValid(self, module, perms_required=True): + + # check if the key is correct + def _check_state(): + return os.path.exists(self.path) + + if not _check_state(): + return False + + if self._check_pass_protected_or_broken_key(module): + if self.regenerate in ('full_idempotence', 'always'): + return False + module.fail_json(msg='Unable to read the key. The key is protected with a passphrase or broken.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `full_idempotence` or `always`, or with `force=yes`.') + + proc = module.run_command([module.get_bin_path('ssh-keygen', True), '-lf', self.path], check_rc=False) + if not proc[0] == 0: + if os.path.isdir(self.path): + module.fail_json(msg='%s is a directory. Please specify a path to a file.' % (self.path)) + + if self.regenerate in ('full_idempotence', 'always'): + return False + module.fail_json(msg='Unable to read the key. The key is protected with a passphrase or broken.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `full_idempotence` or `always`, or with `force=yes`.') + + fingerprint = proc[1].split() + keysize = int(fingerprint[0]) + keytype = fingerprint[-1][1:-1].lower() + + self.fingerprint = fingerprint + + if self.regenerate == 'never': + return True + + def _check_type(): + return self.type == keytype + + def _check_size(): + return self.size == keysize + + if not (_check_type() and _check_size()): + if self.regenerate in ('partial_idempotence', 'full_idempotence', 'always'): + return False + module.fail_json(msg='Key has wrong type and/or size.' + ' Will not proceed. To force regeneration, call the module with `generate`' + ' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.') + + def _check_perms(module): + file_args = module.load_file_common_arguments(module.params) + return not module.set_fs_attributes_if_different(file_args, False) + + return not perms_required or _check_perms(module) + + def isPublicKeyValid(self, module, perms_required=True): + + def _get_pubkey_content(): + if os.path.exists(self.path + ".pub"): + with open(self.path + ".pub", "r") as pubkey_f: + present_pubkey = pubkey_f.read().strip(' \n') + return present_pubkey + else: + return False + + def _parse_pubkey(pubkey_content): + if pubkey_content: + parts = pubkey_content.split(' ', 2) + if len(parts) < 2: + return False + return parts[0], parts[1], '' if len(parts) <= 2 else parts[2] + return False + + def _pubkey_valid(pubkey): + if pubkey_parts and _parse_pubkey(pubkey): + return pubkey_parts[:2] == _parse_pubkey(pubkey)[:2] + return False + + def _comment_valid(): + if pubkey_parts: + return pubkey_parts[2] == self.comment + return False + + def _check_perms(module): + file_args = module.load_file_common_arguments(module.params) + file_args['path'] = file_args['path'] + '.pub' + return not module.set_fs_attributes_if_different(file_args, False) + + pubkey_parts = _parse_pubkey(_get_pubkey_content()) + + pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path]) + pubkey = pubkey[1].strip('\n') + if _pubkey_valid(pubkey): + self.public_key = pubkey + else: + return False + + if self.comment: + if not _comment_valid(): + return False + + if perms_required: + if not _check_perms(module): + return False + + return True + + def dump(self): + # return result as a dict + + """Serialize the object into a dictionary.""" + result = { + 'changed': self.changed, + 'size': self.size, + 'type': self.type, + 'filename': self.path, + # On removal this has no value + 'fingerprint': self.fingerprint[1] if self.fingerprint else '', + 'public_key': self.public_key, + 'comment': self.comment if self.comment else '', + } + + return result + + def remove(self): + """Remove the resource from the filesystem.""" + + try: + os.remove(self.path) + self.changed = True + except OSError as exc: + if exc.errno != errno.ENOENT: + raise KeypairError(exc) + else: + pass + + if os.path.exists(self.path + ".pub"): + try: + os.remove(self.path + ".pub") + self.changed = True + except OSError as exc: + if exc.errno != errno.ENOENT: + raise KeypairError(exc) + else: + pass + + +def main(): + + # Define Ansible Module + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + size=dict(type='int'), + type=dict(type='str', default='rsa', choices=['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519']), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + comment=dict(type='str'), + regenerate=dict( + type='str', + default='partial_idempotence', + choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always'] + ), + ), + supports_check_mode=True, + add_file_common_args=True, + ) + + # Check if Path exists + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + keypair = Keypair(module) + + if keypair.state == 'present': + + if module.check_mode: + result = keypair.dump() + result['changed'] = keypair.force or not keypair.isPrivateKeyValid(module) or not keypair.isPublicKeyValid(module) + module.exit_json(**result) + + try: + keypair.generate(module) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + else: + + if module.check_mode: + keypair.changed = os.path.exists(module.params['path']) + if keypair.changed: + keypair.fingerprint = {} + result = keypair.dump() + module.exit_json(**result) + + try: + keypair.remove() + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + result = keypair.dump() + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate.py new file mode 100644 index 00000000..fb6fc2f5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate.py @@ -0,0 +1,555 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_certificate +short_description: Generate and/or check OpenSSL certificates +description: + - It implements a notion of provider (ie. C(selfsigned), C(ownca), C(acme), C(assertonly), C(entrust)) + for your certificate. + - "Please note that the module regenerates existing certificate if it does not match the module's + options, or if it seems to be corrupt. If you are concerned that this could overwrite + your existing certificate, consider using the I(backup) option." + - Note that this module was called C(openssl_certificate) when included directly in Ansible up to version 2.9. + When moved to the collection C(community.crypto), it was renamed to + M(community.crypto.x509_certificate). From Ansible 2.10 on, it can still be used by the + old short name (or by C(ansible.builtin.openssl_certificate)), which redirects to + C(community.crypto.x509_certificate). When using FQCNs or when using the + L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook) + keyword, the new name M(community.crypto.x509_certificate) should be used to avoid + a deprecation warning. +author: + - Yanis Guenane (@Spredzy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + state: + description: + - Whether the certificate should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + + path: + description: + - Remote absolute path where the generated certificate file should be created or is already located. + type: path + required: true + + provider: + description: + - Name of the provider to use to generate/retrieve the OpenSSL certificate. + - The C(assertonly) provider will not generate files and fail if the certificate file is missing. + - The C(assertonly) provider has been deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. + Please see the examples on how to emulate it with + M(community.crypto.x509_certificate_info), M(community.crypto.openssl_csr_info), + M(community.crypto.openssl_privatekey_info) and M(ansible.builtin.assert). + - "The C(entrust) provider was added for Ansible 2.9 and requires credentials for the + L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API." + - Required if I(state) is C(present). + type: str + choices: [ acme, assertonly, entrust, ownca, selfsigned ] + + return_content: + description: + - If set to C(yes), will return the (current or generated) certificate's content as I(certificate). + type: bool + default: no + version_added: '1.0.0' + + backup: + description: + - Create a backup file including a timestamp so you can get the original + certificate back if you overwrote it with a new one by accident. + - This is not used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + + csr_content: + version_added: '1.0.0' + privatekey_content: + version_added: '1.0.0' + acme_directory: + version_added: '1.0.0' + ownca_content: + version_added: '1.0.0' + ownca_privatekey_content: + version_added: '1.0.0' + +notes: +- Supports C(check_mode). + +seealso: +- module: community.crypto.x509_certificate_pipe + +extends_documentation_fragment: + - ansible.builtin.files + - community.crypto.module_certificate + - community.crypto.module_certificate.backend_acme_documentation + - community.crypto.module_certificate.backend_assertonly_documentation + - community.crypto.module_certificate.backend_entrust_documentation + - community.crypto.module_certificate.backend_ownca_documentation + - community.crypto.module_certificate.backend_selfsigned_documentation +''' + +EXAMPLES = r''' +- name: Generate a Self Signed OpenSSL certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + privatekey_path: /etc/ssl/private/ansible.com.pem + csr_path: /etc/ssl/csr/ansible.com.csr + provider: selfsigned + +- name: Generate an OpenSSL certificate signed with your own CA certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + ownca_path: /etc/ssl/crt/ansible_CA.crt + ownca_privatekey_path: /etc/ssl/private/ansible_CA.pem + provider: ownca + +- name: Generate a Let's Encrypt Certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: acme + acme_accountkey_path: /etc/ssl/private/ansible.com.pem + acme_challenge_path: /etc/ssl/challenges/ansible.com/ + +- name: Force (re-)generate a new Let's Encrypt Certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: acme + acme_accountkey_path: /etc/ssl/private/ansible.com.pem + acme_challenge_path: /etc/ssl/challenges/ansible.com/ + force: yes + +- name: Generate an Entrust certificate via the Entrust Certificate Services (ECS) API + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: entrust + entrust_requester_name: Jo Doe + entrust_requester_email: jdoe@ansible.com + entrust_requester_phone: 555-555-5555 + entrust_cert_type: STANDARD_SSL + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-key.crt + entrust_api_specification_path: /etc/ssl/entrust/api-docs/cms-api-2.1.0.yaml + +# The following example shows one assertonly usage using all existing options for +# assertonly, and shows how to emulate the behavior with the x509_certificate_info, +# openssl_csr_info, openssl_privatekey_info and assert modules: +- name: Usage of assertonly with all existing options + community.crypto.x509_certificate: + provider: assertonly + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + privatekey_path: /etc/ssl/csr/ansible.com.key + signature_algorithms: + - sha256WithRSAEncryption + - sha512WithRSAEncryption + subject: + commonName: ansible.com + subject_strict: yes + issuer: + commonName: ansible.com + issuer_strict: yes + has_expired: no + version: 3 + key_usage: + - Data Encipherment + key_usage_strict: yes + extended_key_usage: + - DVCS + extended_key_usage_strict: yes + subject_alt_name: + - dns:ansible.com + subject_alt_name_strict: yes + not_before: 20190331202428Z + not_after: 20190413202428Z + valid_at: "+1d10h" + invalid_at: 20200331202428Z + valid_in: 10 # in ten seconds + +- name: Get certificate information + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + # for valid_at, invalid_at and valid_in + valid_at: + one_day_ten_hours: "+1d10h" + fixed_timestamp: 20200331202428Z + ten_seconds: "+10" + register: result + +- name: Get CSR information + community.crypto.openssl_csr_info: + # Verifies that the CSR signature is valid; module will fail if not + path: /etc/ssl/csr/ansible.com.csr + register: result_csr + +- name: Get private key information + community.crypto.openssl_privatekey_info: + path: /etc/ssl/csr/ansible.com.key + register: result_privatekey + +- assert: + that: + # When private key is specified for assertonly, this will be checked: + - result.public_key == result_privatekey.public_key + # When CSR is specified for assertonly, this will be checked: + - result.public_key == result_csr.public_key + - result.subject_ordered == result_csr.subject_ordered + - result.extensions_by_oid == result_csr.extensions_by_oid + # signature_algorithms check + - "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha512WithRSAEncryption'" + # subject and subject_strict + - "result.subject.commonName == 'ansible.com'" + - "result.subject | length == 1" # the number must be the number of entries you check for + # issuer and issuer_strict + - "result.issuer.commonName == 'ansible.com'" + - "result.issuer | length == 1" # the number must be the number of entries you check for + # has_expired + - not result.expired + # version + - result.version == 3 + # key_usage and key_usage_strict + - "'Data Encipherment' in result.key_usage" + - "result.key_usage | length == 1" # the number must be the number of entries you check for + # extended_key_usage and extended_key_usage_strict + - "'DVCS' in result.extended_key_usage" + - "result.extended_key_usage | length == 1" # the number must be the number of entries you check for + # subject_alt_name and subject_alt_name_strict + - "'dns:ansible.com' in result.subject_alt_name" + - "result.subject_alt_name | length == 1" # the number must be the number of entries you check for + # not_before and not_after + - "result.not_before == '20190331202428Z'" + - "result.not_after == '20190413202428Z'" + # valid_at, invalid_at and valid_in + - "result.valid_at.one_day_ten_hours" # for valid_at + - "not result.valid_at.fixed_timestamp" # for invalid_at + - "result.valid_at.ten_seconds" # for valid_in + +# Examples for some checks one could use the assertonly provider for: +# (Please note that assertonly has been deprecated!) + +# How to use the assertonly provider to implement and trigger your own custom certificate generation workflow: +- name: Check if a certificate is currently still valid, ignoring failures + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + has_expired: no + ignore_errors: yes + register: validity_check + +- name: Run custom task(s) to get a new, valid certificate in case the initial check failed + command: superspecialSSL recreate /etc/ssl/crt/example.com.crt + when: validity_check.failed + +- name: Check the new certificate again for validity with the same parameters, this time failing the play if it is still invalid + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + has_expired: no + when: validity_check.failed + +# Some other checks that assertonly could be used for: +- name: Verify that an existing certificate was issued by the Let's Encrypt CA and is currently still valid + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + issuer: + O: Let's Encrypt + has_expired: no + +- name: Ensure that a certificate uses a modern signature algorithm (no SHA1, MD5 or DSA) + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + signature_algorithms: + - sha224WithRSAEncryption + - sha256WithRSAEncryption + - sha384WithRSAEncryption + - sha512WithRSAEncryption + - sha224WithECDSAEncryption + - sha256WithECDSAEncryption + - sha384WithECDSAEncryption + - sha512WithECDSAEncryption + +- name: Ensure that the existing certificate belongs to the specified private key + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + privatekey_path: /etc/ssl/private/example.com.pem + provider: assertonly + +- name: Ensure that the existing certificate is still valid at the winter solstice 2017 + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + valid_at: 20171221162800Z + +- name: Ensure that the existing certificate is still valid 2 weeks (1209600 seconds) from now + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + valid_in: 1209600 + +- name: Ensure that the existing certificate is only used for digital signatures and encrypting other keys + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + key_usage: + - digitalSignature + - keyEncipherment + key_usage_strict: true + +- name: Ensure that the existing certificate can be used for client authentication + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + extended_key_usage: + - clientAuth + +- name: Ensure that the existing certificate can only be used for client authentication and time stamping + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + extended_key_usage: + - clientAuth + - 1.3.6.1.5.5.7.3.8 + extended_key_usage_strict: true + +- name: Ensure that the existing certificate has a certain domain in its subjectAltName + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + subject_alt_name: + - www.example.com + - test.example.com +''' + +RETURN = r''' +filename: + description: Path to the generated certificate. + returned: changed or success + type: str + sample: /etc/ssl/crt/www.ansible.com.crt +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~ +certificate: + description: The (current or generated) certificate's content. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: '1.0.0' +''' + + +import os + +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + select_backend, + get_certificate_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_acme import ( + AcmeCertificateProvider, + add_acme_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_assertonly import ( + AssertOnlyCertificateProvider, + add_assertonly_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_entrust import ( + EntrustCertificateProvider, + add_entrust_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_ownca import ( + OwnCACertificateProvider, + add_ownca_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_selfsigned import ( + SelfSignedCertificateProvider, + add_selfsigned_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, +) + + +class CertificateAbsent(OpenSSLObject): + def __init__(self, module): + super(CertificateAbsent, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.module = module + self.return_content = module.params['return_content'] + self.backup = module.params['backup'] + self.backup_file = None + + def generate(self, module): + pass + + def remove(self, module): + if self.backup: + self.backup_file = module.backup_local(self.path) + super(CertificateAbsent, self).remove(module) + + def dump(self, check_mode=False): + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.module.params['privatekey_path'], + 'csr': self.module.params['csr_path'] + } + if self.backup_file: + result['backup_file'] = self.backup_file + if self.return_content: + result['certificate'] = None + + return result + + +class GenericCertificate(OpenSSLObject): + """Retrieve a certificate using the given module backend.""" + def __init__(self, module, module_backend): + super(GenericCertificate, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.module = module + self.return_content = module.params['return_content'] + self.backup = module.params['backup'] + self.backup_file = None + + self.module_backend = module_backend + self.module_backend.set_existing(load_file_if_exists(self.path, module)) + + def generate(self, module): + if self.module_backend.needs_regeneration(): + if not self.check_mode: + self.module_backend.generate_certificate() + result = self.module_backend.get_certificate_data() + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, result) + self.changed = True + + file_args = module.load_file_common_arguments(module.params) + self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + return super(GenericCertificate, self).check(module, perms_required) and not self.module_backend.needs_regeneration() + + def dump(self, check_mode=False): + result = self.module_backend.dump(include_certificate=self.return_content) + result.update({ + 'changed': self.changed, + 'filename': self.path, + }) + if self.backup_file: + result['backup_file'] = self.backup_file + return result + + +def main(): + argument_spec = get_certificate_argument_spec() + add_acme_provider_to_argument_spec(argument_spec) + add_assertonly_provider_to_argument_spec(argument_spec) + add_entrust_provider_to_argument_spec(argument_spec) + add_ownca_provider_to_argument_spec(argument_spec) + add_selfsigned_provider_to_argument_spec(argument_spec) + argument_spec.argument_spec.update(dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + path=dict(type='path', required=True), + backup=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + )) + argument_spec.required_if.append(['state', 'present', ['provider']]) + module = argument_spec.create_ansible_module( + add_file_common_args=True, + supports_check_mode=True, + ) + + if module._name == 'community.crypto.openssl_certificate': + module.deprecate("The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'", + version='2.0.0', collection_name='community.crypto') + + try: + if module.params['state'] == 'absent': + certificate = CertificateAbsent(module) + + if module.check_mode: + result = certificate.dump(check_mode=True) + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + certificate.remove(module) + + else: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + provider = module.params['provider'] + provider_map = { + 'acme': AcmeCertificateProvider, + 'assertonly': AssertOnlyCertificateProvider, + 'entrust': EntrustCertificateProvider, + 'ownca': OwnCACertificateProvider, + 'selfsigned': SelfSignedCertificateProvider, + } + + backend = module.params['select_crypto_backend'] + module_backend = select_backend(module, backend, provider_map[provider]()) + certificate = GenericCertificate(module, module_backend) + certificate.generate(module) + + result = certificate.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate_info.py new file mode 100644 index 00000000..3e7bfc71 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_certificate_info.py @@ -0,0 +1,904 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_certificate_info +short_description: Provide information of OpenSSL X.509 certificates +description: + - This module allows one to query information on OpenSSL certificates. + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the + cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with + C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 + and will be removed in community.crypto 2.0.0. + - Note that this module was called C(openssl_certificate_info) when included directly in Ansible + up to version 2.9. When moved to the collection C(community.crypto), it was renamed to + M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the + old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to + C(community.crypto.x509_certificate_info). When using FQCNs or when using the + L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook) + keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid + a deprecation warning. +requirements: + - PyOpenSSL >= 0.15 or cryptography >= 1.6 +author: + - Felix Fontein (@felixfontein) + - Yanis Guenane (@Spredzy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + path: + description: + - Remote absolute path where the certificate file is loaded from. + - Either I(path) or I(content) must be specified, but not both. + type: path + content: + description: + - Content of the X.509 certificate in PEM format. + - Either I(path) or I(content) must be specified, but not both. + type: str + version_added: '1.0.0' + valid_at: + description: + - A dict of names mapping to time specifications. Every time specified here + will be checked whether the certificate is valid at this point. See the + C(valid_at) return value for informations on the result. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (in other words, pattern C(YYYYMMDDHHMMSSZ)). + Note that all timestamps will be treated as being in UTC. + type: dict + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +notes: + - All timestamp values are provided in ASN.1 TIME format, in other words, following the C(YYYYMMDDHHMMSSZ) pattern. + They are all in UTC. + - Supports C(check_mode). +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.x509_certificate_pipe +''' + +EXAMPLES = r''' +- name: Generate a Self Signed OpenSSL certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + privatekey_path: /etc/ssl/private/ansible.com.pem + csr_path: /etc/ssl/csr/ansible.com.csr + provider: selfsigned + + +# Get information on the certificate + +- name: Get information on generated certificate + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + register: result + +- name: Dump information + ansible.builtin.debug: + var: result + + +# Check whether the certificate is valid or not valid at certain times, fail +# if this is not the case. The first task (x509_certificate_info) collects +# the information, and the second task (assert) validates the result and +# makes the playbook fail in case something is not as expected. + +- name: Test whether that certificate is valid tomorrow and/or in three weeks + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + valid_at: + point_1: "+1d" + point_2: "+3w" + register: result + +- name: Validate that certificate is valid tomorrow, but not in three weeks + assert: + that: + - result.valid_at.point_1 # valid in one day + - not result.valid_at.point_2 # not valid in three weeks +''' + +RETURN = r''' +expired: + description: Whether the certificate is expired (in other words, C(notAfter) is in the past). + returned: success + type: bool +basic_constraints: + description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[CA:TRUE, pathlen:1]" +basic_constraints_critical: + description: Whether the C(basic_constraints) extension is critical. + returned: success + type: bool +extended_key_usage: + description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[Biometric Info, DVCS, Time Stamping]" +extended_key_usage_critical: + description: Whether the C(extended_key_usage) extension is critical. + returned: success + type: bool +extensions_by_oid: + description: Returns a dictionary for every extension OID. + returned: success + type: dict + contains: + critical: + description: Whether the extension is critical. + returned: success + type: bool + value: + description: The Base64 encoded value (in DER format) of the extension. + returned: success + type: str + sample: "MAMCAQU=" + sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' +key_usage: + description: Entries in the C(key_usage) extension, or C(none) if extension is not present. + returned: success + type: str + sample: "[Key Agreement, Data Encipherment]" +key_usage_critical: + description: Whether the C(key_usage) extension is critical. + returned: success + type: bool +subject_alt_name: + description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +subject_alt_name_critical: + description: Whether the C(subject_alt_name) extension is critical. + returned: success + type: bool +ocsp_must_staple: + description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise. + returned: success + type: bool +ocsp_must_staple_critical: + description: Whether the C(ocsp_must_staple) extension is critical. + returned: success + type: bool +issuer: + description: + - The certificate's issuer. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' +issuer_ordered: + description: The certificate's issuer as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' +subject: + description: + - The certificate's subject as a dictionary. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' +subject_ordered: + description: The certificate's subject as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' +not_after: + description: C(notAfter) date as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +not_before: + description: C(notBefore) date as ASN.1 TIME. + returned: success + type: str + sample: 20190331202428Z +public_key: + description: Certificate's public key in PEM format. + returned: success + type: str + sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." +public_key_fingerprints: + description: + - Fingerprints of certificate's public key. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." +fingerprints: + description: + - Fingerprints of the DER-encoded form of the whole certificate. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." + version_added: 1.2.0 +signature_algorithm: + description: The signature algorithm used to sign the certificate. + returned: success + type: str + sample: sha256WithRSAEncryption +serial_number: + description: The certificate's serial number. + returned: success + type: int + sample: 1234 +version: + description: The certificate version. + returned: success + type: int + sample: 3 +valid_at: + description: For every time stamp provided in the I(valid_at) option, a + boolean whether the certificate is valid at that point in time + or not. + returned: success + type: dict +subject_key_identifier: + description: + - The certificate's subject key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(SubjectKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_key_identifier: + description: + - The certificate's authority key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_cert_issuer: + description: + - The certificate's authority cert issuer as a list of general names. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +authority_cert_serial_number: + description: + - The certificate's authority cert serial number. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: int + sample: '12345' +ocsp_uri: + description: The OCSP responder URI, if included in the certificate. Will be + C(none) if no OCSP responder URI is included. + returned: success + type: str +''' + + +import abc +import binascii +import datetime +import os +import re +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_native, to_text, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + get_relative_time_option, + load_certificate, + get_fingerprint_of_bytes, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_decode_name, + cryptography_get_extensions_from_cert, + cryptography_oid_to_name, + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( + pyopenssl_get_extensions_from_cert, + pyopenssl_normalize_name, + pyopenssl_normalize_name_attribute, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_PYOPENSSL_VERSION = '0.15' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # OpenSSL 1.1.0 or newer + OPENSSL_MUST_STAPLE_NAME = b"tlsfeature" + OPENSSL_MUST_STAPLE_VALUE = b"status_request" + else: + # OpenSSL 1.0.x or older + OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" + OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.primitives import serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" + + +class CertificateInfo(OpenSSLObject): + def __init__(self, module, backend): + super(CertificateInfo, self).__init__( + module.params['path'] or '', + 'present', + False, + module.check_mode, + ) + self.backend = backend + self.module = module + self.content = module.params['content'] + if self.content is not None: + self.content = self.content.encode('utf-8') + + self.valid_at = module.params['valid_at'] + if self.valid_at: + for k, v in self.valid_at.items(): + if not isinstance(v, string_types): + self.module.fail_json( + msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v)) + ) + self.valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k)) + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + @abc.abstractmethod + def _get_der_bytes(self): + pass + + @abc.abstractmethod + def _get_signature_algorithm(self): + pass + + @abc.abstractmethod + def _get_subject_ordered(self): + pass + + @abc.abstractmethod + def _get_issuer_ordered(self): + pass + + @abc.abstractmethod + def _get_version(self): + pass + + @abc.abstractmethod + def _get_key_usage(self): + pass + + @abc.abstractmethod + def _get_extended_key_usage(self): + pass + + @abc.abstractmethod + def _get_basic_constraints(self): + pass + + @abc.abstractmethod + def _get_ocsp_must_staple(self): + pass + + @abc.abstractmethod + def _get_subject_alt_name(self): + pass + + @abc.abstractmethod + def _get_not_before(self): + pass + + @abc.abstractmethod + def _get_not_after(self): + pass + + @abc.abstractmethod + def _get_public_key(self, binary): + pass + + @abc.abstractmethod + def _get_subject_key_identifier(self): + pass + + @abc.abstractmethod + def _get_authority_key_identifier(self): + pass + + @abc.abstractmethod + def _get_serial_number(self): + pass + + @abc.abstractmethod + def _get_all_extensions(self): + pass + + @abc.abstractmethod + def _get_ocsp_uri(self): + pass + + def get_info(self): + result = dict() + self.cert = load_certificate(self.path, content=self.content, backend=self.backend) + + result['signature_algorithm'] = self._get_signature_algorithm() + subject = self._get_subject_ordered() + issuer = self._get_issuer_ordered() + result['subject'] = dict() + for k, v in subject: + result['subject'][k] = v + result['subject_ordered'] = subject + result['issuer'] = dict() + for k, v in issuer: + result['issuer'][k] = v + result['issuer_ordered'] = issuer + result['version'] = self._get_version() + result['key_usage'], result['key_usage_critical'] = self._get_key_usage() + result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage() + result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints() + result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple() + result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name() + + not_before = self._get_not_before() + not_after = self._get_not_after() + result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT) + result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT) + result['expired'] = not_after < datetime.datetime.utcnow() + + result['valid_at'] = dict() + if self.valid_at: + for k, v in self.valid_at.items(): + result['valid_at'][k] = not_before <= v <= not_after + + result['public_key'] = self._get_public_key(binary=False) + pk = self._get_public_key(binary=True) + result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + + result['fingerprints'] = get_fingerprint_of_bytes(self._get_der_bytes()) + + if self.backend != 'pyopenssl': + ski = self._get_subject_key_identifier() + if ski is not None: + ski = to_native(binascii.hexlify(ski)) + ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)]) + result['subject_key_identifier'] = ski + + aki, aci, acsn = self._get_authority_key_identifier() + if aki is not None: + aki = to_native(binascii.hexlify(aki)) + aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)]) + result['authority_key_identifier'] = aki + result['authority_cert_issuer'] = aci + result['authority_cert_serial_number'] = acsn + + result['serial_number'] = self._get_serial_number() + result['extensions_by_oid'] = self._get_all_extensions() + result['ocsp_uri'] = self._get_ocsp_uri() + + return result + + +class CertificateInfoCryptography(CertificateInfo): + """Validate the supplied cert, using the cryptography backend""" + def __init__(self, module): + super(CertificateInfoCryptography, self).__init__(module, 'cryptography') + + def _get_der_bytes(self): + return self.cert.public_bytes(serialization.Encoding.DER) + + def _get_signature_algorithm(self): + return cryptography_oid_to_name(self.cert.signature_algorithm_oid) + + def _get_subject_ordered(self): + result = [] + for attribute in self.cert.subject: + result.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + return result + + def _get_issuer_ordered(self): + result = [] + for attribute in self.cert.issuer: + result.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + return result + + def _get_version(self): + if self.cert.version == x509.Version.v1: + return 1 + if self.cert.version == x509.Version.v3: + return 3 + return "unknown" + + def _get_key_usage(self): + try: + current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage) + current_key_usage = current_key_ext.value + key_usage = dict( + digital_signature=current_key_usage.digital_signature, + content_commitment=current_key_usage.content_commitment, + key_encipherment=current_key_usage.key_encipherment, + data_encipherment=current_key_usage.data_encipherment, + key_agreement=current_key_usage.key_agreement, + key_cert_sign=current_key_usage.key_cert_sign, + crl_sign=current_key_usage.crl_sign, + encipher_only=False, + decipher_only=False, + ) + if key_usage['key_agreement']: + key_usage.update(dict( + encipher_only=current_key_usage.encipher_only, + decipher_only=current_key_usage.decipher_only + )) + + key_usage_names = dict( + digital_signature='Digital Signature', + content_commitment='Non Repudiation', + key_encipherment='Key Encipherment', + data_encipherment='Data Encipherment', + key_agreement='Key Agreement', + key_cert_sign='Certificate Sign', + crl_sign='CRL Sign', + encipher_only='Encipher Only', + decipher_only='Decipher Only', + ) + return sorted([ + key_usage_names[name] for name, value in key_usage.items() if value + ]), current_key_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_extended_key_usage(self): + try: + ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + return sorted([ + cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value + ]), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_basic_constraints(self): + try: + ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints) + result = [] + result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE')) + if ext_keyusage_ext.value.path_length is not None: + result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length)) + return sorted(result), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_ocsp_must_staple(self): + try: + try: + # This only works with cryptography >= 2.1 + tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature) + value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value + except AttributeError as dummy: + # Fallback for cryptography < 2.1 + oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24") + tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid) + value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05" + return value, tlsfeature_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_subject_alt_name(self): + try: + san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) + result = [cryptography_decode_name(san) for san in san_ext.value] + return result, san_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_not_before(self): + return self.cert.not_valid_before + + def _get_not_after(self): + return self.cert.not_valid_after + + def _get_public_key(self, binary): + return self.cert.public_key().public_bytes( + serialization.Encoding.DER if binary else serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def _get_subject_key_identifier(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + return ext.value.digest + except cryptography.x509.ExtensionNotFound: + return None + + def _get_authority_key_identifier(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) + issuer = None + if ext.value.authority_cert_issuer is not None: + issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer] + return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number + except cryptography.x509.ExtensionNotFound: + return None, None, None + + def _get_serial_number(self): + return cryptography_serial_number_of_cert(self.cert) + + def _get_all_extensions(self): + return cryptography_get_extensions_from_cert(self.cert) + + def _get_ocsp_uri(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + for desc in ext.value: + if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP: + if isinstance(desc.access_location, x509.UniformResourceIdentifier): + return desc.access_location.value + except x509.ExtensionNotFound as dummy: + pass + return None + + +class CertificateInfoPyOpenSSL(CertificateInfo): + """validate the supplied certificate.""" + + def __init__(self, module): + super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl') + + def _get_der_bytes(self): + return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert) + + def _get_signature_algorithm(self): + return to_text(self.cert.get_signature_algorithm()) + + def __get_name(self, name): + result = [] + for sub in name.get_components(): + result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])]) + return result + + def _get_subject_ordered(self): + return self.__get_name(self.cert.get_subject()) + + def _get_issuer_ordered(self): + return self.__get_name(self.cert.get_issuer()) + + def _get_version(self): + # Version numbers in certs are off by one: + # v1: 0, v2: 1, v3: 2 ... + return self.cert.get_version() + 1 + + def _get_extension(self, short_name): + for extension_idx in range(0, self.cert.get_extension_count()): + extension = self.cert.get_extension(extension_idx) + if extension.get_short_name() == short_name: + result = [ + pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',') + ] + return sorted(result), bool(extension.get_critical()) + return None, False + + def _get_key_usage(self): + return self._get_extension(b'keyUsage') + + def _get_extended_key_usage(self): + return self._get_extension(b'extendedKeyUsage') + + def _get_basic_constraints(self): + return self._get_extension(b'basicConstraints') + + def _get_ocsp_must_staple(self): + extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())] + oms_ext = [ + ext for ext in extensions + if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE + ] + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: + # Older versions of libssl don't know about OCSP Must Staple + oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05']) + if oms_ext: + return True, bool(oms_ext[0].get_critical()) + else: + return None, False + + def _get_subject_alt_name(self): + for extension_idx in range(0, self.cert.get_extension_count()): + extension = self.cert.get_extension(extension_idx) + if extension.get_short_name() == b'subjectAltName': + result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in + to_text(extension, errors='surrogate_or_strict').split(', ')] + return result, bool(extension.get_critical()) + return None, False + + def _get_not_before(self): + time_string = to_native(self.cert.get_notBefore()) + return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + + def _get_not_after(self): + time_string = to_native(self.cert.get_notAfter()) + return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + + def _get_public_key(self, binary): + try: + return crypto.dump_publickey( + crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, + self.cert.get_pubkey() + ) + except AttributeError: + try: + # pyOpenSSL < 16.0: + bio = crypto._new_mem_buf() + if binary: + rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey) + else: + rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey) + if rc != 1: + crypto._raise_current_error() + return crypto._bio_to_string(bio) + except AttributeError: + self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' + 'Please upgrade to version 16.0 or newer, or use the cryptography backend.') + + def _get_subject_key_identifier(self): + # Won't be implemented + return None + + def _get_authority_key_identifier(self): + # Won't be implemented + return None, None, None + + def _get_serial_number(self): + return self.cert.get_serial_number() + + def _get_all_extensions(self): + return pyopenssl_get_extensions_from_cert(self.cert) + + def _get_ocsp_uri(self): + for i in range(self.cert.get_extension_count()): + ext = self.cert.get_extension(i) + if ext.get_short_name() == b'authorityInfoAccess': + v = str(ext) + m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE) + if m: + return m.group(1) + return None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='path'), + content=dict(type='str'), + valid_at=dict(type='dict'), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + ), + required_one_of=( + ['path', 'content'], + ), + mutually_exclusive=( + ['path', 'content'], + ), + supports_check_mode=True, + ) + if module._name == 'community.crypto.openssl_certificate_info': + module.deprecate("The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'", + version='2.0.0', collection_name='community.crypto') + + try: + if module.params['path'] is not None: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') + + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + certificate = CertificateInfoPyOpenSSL(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + certificate = CertificateInfoCryptography(module) + + result = certificate.get_info() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr.py new file mode 100644 index 00000000..e9d375ac --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr.py @@ -0,0 +1,345 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_csr +short_description: Generate OpenSSL Certificate Signing Request (CSR) +description: + - "Please note that the module regenerates an existing CSR if it doesn't match the module's + options, or if it seems to be corrupt. If you are concerned that this could overwrite + your existing CSR, consider using the I(backup) option." +author: +- Yanis Guenane (@Spredzy) +- Felix Fontein (@felixfontein) +options: + state: + description: + - Whether the certificate signing request should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + force: + description: + - Should the certificate signing request be forced regenerated by this ansible module. + type: bool + default: no + path: + description: + - The name of the file into which the generated OpenSSL certificate signing request will be written. + type: path + required: true + backup: + description: + - Create a backup file including a timestamp so you can get the original + CSR back if you overwrote it with a new one by accident. + type: bool + default: no + return_content: + description: + - If set to C(yes), will return the (current or generated) CSR's content as I(csr). + type: bool + default: no + version_added: "1.0.0" + privatekey_content: + version_added: "1.0.0" + name_constraints_permitted: + version_added: 1.1.0 + name_constraints_excluded: + version_added: 1.1.0 + name_constraints_critical: + version_added: 1.1.0 +extends_documentation_fragment: +- ansible.builtin.files +- community.crypto.module_csr +seealso: +- module: community.crypto.openssl_csr_pipe +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL Certificate Signing Request + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + +- name: Generate an OpenSSL Certificate Signing Request with an inline key + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_content: "{{ private_key_content }}" + common_name: www.ansible.com + +- name: Generate an OpenSSL Certificate Signing Request with a passphrase protected private key + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + privatekey_passphrase: ansible + common_name: www.ansible.com + +- name: Generate an OpenSSL Certificate Signing Request with Subject information + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + country_name: FR + organization_name: Ansible + email_address: jdoe@ansible.com + common_name: www.ansible.com + +- name: Generate an OpenSSL Certificate Signing Request with subjectAltName extension + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + subject_alt_name: 'DNS:www.ansible.com,DNS:m.ansible.com' + +- name: Generate an OpenSSL CSR with subjectAltName extension with dynamic list + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}" + with_dict: + dns_server: + - www.ansible.com + - m.ansible.com + +- name: Force regenerate an OpenSSL Certificate Signing Request + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + force: yes + common_name: www.ansible.com + +- name: Generate an OpenSSL Certificate Signing Request with special key usages + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + key_usage: + - digitalSignature + - keyAgreement + extended_key_usage: + - clientAuth + +- name: Generate an OpenSSL Certificate Signing Request with OCSP Must Staple + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + ocsp_must_staple: yes + +- name: Generate an OpenSSL Certificate Signing Request for WinRM Certificate authentication + community.crypto.openssl_csr: + path: /etc/ssl/csr/winrm.auth.csr + privatekey_path: /etc/ssl/private/winrm.auth.pem + common_name: username + extended_key_usage: + - clientAuth + subject_alt_name: otherName:1.3.6.1.4.1.311.20.2.3;UTF8:username@localhost + +- name: Generate an OpenSSL Certificate Signing Request with a CRL distribution point + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + crl_distribution_points: + - full_name: + - "URI:https://ca.example.com/revocations.crl" + crl_issuer: + - "URI:https://ca.example.com/" + reasons: + - key_compromise + - ca_compromise + - cessation_of_operation +''' + +RETURN = r''' +privatekey: + description: + - Path to the TLS/SSL private key the CSR was generated for + - Will be C(none) if the private key has been provided in I(privatekey_content). + returned: changed or success + type: str + sample: /etc/ssl/private/ansible.com.pem +filename: + description: Path to the generated Certificate Signing Request + returned: changed or success + type: str + sample: /etc/ssl/csr/www.ansible.com.csr +subject: + description: A list of the subject tuples attached to the CSR + returned: changed or success + type: list + elements: list + sample: "[('CN', 'www.ansible.com'), ('O', 'Ansible')]" +subjectAltName: + description: The alternative names this CSR is valid for + returned: changed or success + type: list + elements: str + sample: [ 'DNS:www.ansible.com', 'DNS:m.ansible.com' ] +keyUsage: + description: Purpose for which the public key may be used + returned: changed or success + type: list + elements: str + sample: [ 'digitalSignature', 'keyAgreement' ] +extendedKeyUsage: + description: Additional restriction on the public key purposes + returned: changed or success + type: list + elements: str + sample: [ 'clientAuth' ] +basicConstraints: + description: Indicates if the certificate belongs to a CA + returned: changed or success + type: list + elements: str + sample: ['CA:TRUE', 'pathLenConstraint:0'] +ocsp_must_staple: + description: Indicates whether the certificate has the OCSP + Must Staple feature enabled + returned: changed or success + type: bool + sample: false +name_constraints_permitted: + description: List of permitted subtrees to sign certificates for. + returned: changed or success + type: list + elements: str + sample: ['email:.somedomain.com'] + version_added: 1.1.0 +name_constraints_excluded: + description: List of excluded subtrees the CA cannot sign certificates for. + returned: changed or success + type: list + elements: str + sample: ['email:.com'] + version_added: 1.1.0 +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/www.ansible.com.csr.2019-03-09@11:22~ +csr: + description: The (current or generated) CSR's content. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: "1.0.0" +''' + +import os + +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr import ( + select_backend, + get_csr_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, +) + + +class CertificateSigningRequestModule(OpenSSLObject): + + def __init__(self, module, module_backend): + super(CertificateSigningRequestModule, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.module_backend = module_backend + self.return_content = module.params['return_content'] + + self.backup = module.params['backup'] + self.backup_file = None + + self.module_backend.set_existing(load_file_if_exists(self.path, module)) + + def generate(self, module): + '''Generate the certificate signing request.''' + if self.force or self.module_backend.needs_regeneration(): + if not self.check_mode: + self.module_backend.generate_csr() + result = self.module_backend.get_csr_data() + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, result) + self.changed = True + + file_args = module.load_file_common_arguments(module.params) + self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + + def remove(self, module): + self.module_backend.set_existing(None) + if self.backup and not self.check_mode: + self.backup_file = module.backup_local(self.path) + super(CertificateSigningRequestModule, self).remove(module) + + def dump(self): + '''Serialize the object into a dictionary.''' + result = self.module_backend.dump(include_csr=self.return_content) + result.update({ + 'filename': self.path, + 'changed': self.changed, + }) + if self.backup_file: + result['backup_file'] = self.backup_file + return result + + +def main(): + argument_spec = get_csr_argument_spec() + argument_spec.argument_spec.update(dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + backup=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + )) + argument_spec.required_if.extend([('state', 'present', rof, True) for rof in argument_spec.required_one_of]) + argument_spec.required_one_of = [] + module = argument_spec.create_ansible_module( + add_file_common_args=True, + supports_check_mode=True, + ) + + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir) + + backend = module.params['select_crypto_backend'] + backend, module_backend = select_backend(module, backend) + try: + csr = CertificateSigningRequestModule(module, module_backend) + if module.params['state'] == 'present': + csr.generate(module) + else: + csr.remove(module) + + result = csr.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_info.py new file mode 100644 index 00000000..0e2a1ed2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_info.py @@ -0,0 +1,720 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_csr_info +short_description: Provide information of OpenSSL Certificate Signing Requests (CSR) +description: + - This module allows one to query information on OpenSSL Certificate Signing Requests (CSR). + - In case the CSR signature cannot be validated, the module will fail. In this case, all return + variables are still returned. + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the + cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with + C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 + and will be removed in community.crypto 2.0.0. +requirements: + - PyOpenSSL >= 0.15 or cryptography >= 1.3 +author: + - Felix Fontein (@felixfontein) + - Yanis Guenane (@Spredzy) +options: + path: + description: + - Remote absolute path where the CSR file is loaded from. + - Either I(path) or I(content) must be specified, but not both. + type: path + content: + description: + - Content of the CSR file. + - Either I(path) or I(content) must be specified, but not both. + type: str + version_added: "1.0.0" + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +seealso: +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_csr_pipe +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL Certificate Signing Request + community.crypto.openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + +- name: Get information on the CSR + community.crypto.openssl_csr_info: + path: /etc/ssl/csr/www.ansible.com.csr + register: result + +- name: Dump information + debug: + var: result +''' + +RETURN = r''' +signature_valid: + description: + - Whether the CSR's signature is valid. + - In case the check returns C(no), the module will fail. + returned: success + type: bool +basic_constraints: + description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[CA:TRUE, pathlen:1]" +basic_constraints_critical: + description: Whether the C(basic_constraints) extension is critical. + returned: success + type: bool +extended_key_usage: + description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[Biometric Info, DVCS, Time Stamping]" +extended_key_usage_critical: + description: Whether the C(extended_key_usage) extension is critical. + returned: success + type: bool +extensions_by_oid: + description: Returns a dictionary for every extension OID + returned: success + type: dict + contains: + critical: + description: Whether the extension is critical. + returned: success + type: bool + value: + description: The Base64 encoded value (in DER format) of the extension + returned: success + type: str + sample: "MAMCAQU=" + sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' +key_usage: + description: Entries in the C(key_usage) extension, or C(none) if extension is not present. + returned: success + type: str + sample: "[Key Agreement, Data Encipherment]" +key_usage_critical: + description: Whether the C(key_usage) extension is critical. + returned: success + type: bool +subject_alt_name: + description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +subject_alt_name_critical: + description: Whether the C(subject_alt_name) extension is critical. + returned: success + type: bool +ocsp_must_staple: + description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise. + returned: success + type: bool +ocsp_must_staple_critical: + description: Whether the C(ocsp_must_staple) extension is critical. + returned: success + type: bool +name_constraints_permitted: + description: List of permitted subtrees to sign certificates for. + returned: success + type: list + elements: str + sample: ['email:.somedomain.com'] + version_added: 1.1.0 +name_constraints_excluded: + description: + - List of excluded subtrees the CA cannot sign certificates for. + - Is C(none) if extension is not present. + returned: success + type: list + elements: str + sample: ['email:.com'] + version_added: 1.1.0 +name_constraints_critical: + description: + - Whether the C(name_constraints) extension is critical. + - Is C(none) if extension is not present. + returned: success + type: bool + version_added: 1.1.0 +subject: + description: + - The CSR's subject as a dictionary. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' +subject_ordered: + description: The CSR's subject as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' +public_key: + description: CSR's public key in PEM format + returned: success + type: str + sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." +public_key_fingerprints: + description: + - Fingerprints of CSR's public key. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." +subject_key_identifier: + description: + - The CSR's subject key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(SubjectKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_key_identifier: + description: + - The CSR's authority key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_cert_issuer: + description: + - The CSR's authority cert issuer as a list of general names. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +authority_cert_serial_number: + description: + - The CSR's authority cert serial number. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: int + sample: '12345' +''' + + +import abc +import binascii +import os +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native, to_text, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_certificate_request, + get_fingerprint_of_bytes, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_decode_name, + cryptography_get_extensions_from_csr, + cryptography_oid_to_name, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( + pyopenssl_get_extensions_from_csr, + pyopenssl_normalize_name, + pyopenssl_normalize_name_attribute, + pyopenssl_parse_name_constraints, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.3' +MINIMAL_PYOPENSSL_VERSION = '0.15' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # OpenSSL 1.1.0 or newer + OPENSSL_MUST_STAPLE_NAME = b"tlsfeature" + OPENSSL_MUST_STAPLE_VALUE = b"status_request" + else: + # OpenSSL 1.0.x or older + OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" + OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.primitives import serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" + + +class CertificateSigningRequestInfo(OpenSSLObject): + def __init__(self, module, backend): + super(CertificateSigningRequestInfo, self).__init__( + module.params['path'] or '', + 'present', + False, + module.check_mode, + ) + self.backend = backend + self.module = module + self.content = module.params['content'] + if self.content is not None: + self.content = self.content.encode('utf-8') + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + @abc.abstractmethod + def _get_subject_ordered(self): + pass + + @abc.abstractmethod + def _get_key_usage(self): + pass + + @abc.abstractmethod + def _get_extended_key_usage(self): + pass + + @abc.abstractmethod + def _get_basic_constraints(self): + pass + + @abc.abstractmethod + def _get_ocsp_must_staple(self): + pass + + @abc.abstractmethod + def _get_subject_alt_name(self): + pass + + @abc.abstractmethod + def _get_name_constraints(self): + pass + + @abc.abstractmethod + def _get_public_key(self, binary): + pass + + @abc.abstractmethod + def _get_subject_key_identifier(self): + pass + + @abc.abstractmethod + def _get_authority_key_identifier(self): + pass + + @abc.abstractmethod + def _get_all_extensions(self): + pass + + @abc.abstractmethod + def _is_signature_valid(self): + pass + + def get_info(self): + result = dict() + self.csr = load_certificate_request(self.path, content=self.content, backend=self.backend) + + subject = self._get_subject_ordered() + result['subject'] = dict() + for k, v in subject: + result['subject'][k] = v + result['subject_ordered'] = subject + result['key_usage'], result['key_usage_critical'] = self._get_key_usage() + result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage() + result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints() + result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple() + result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name() + ( + result['name_constraints_permitted'], + result['name_constraints_excluded'], + result['name_constraints_critical'], + ) = self._get_name_constraints() + + result['public_key'] = self._get_public_key(binary=False) + pk = self._get_public_key(binary=True) + result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + + if self.backend != 'pyopenssl': + ski = self._get_subject_key_identifier() + if ski is not None: + ski = to_native(binascii.hexlify(ski)) + ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)]) + result['subject_key_identifier'] = ski + + aki, aci, acsn = self._get_authority_key_identifier() + if aki is not None: + aki = to_native(binascii.hexlify(aki)) + aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)]) + result['authority_key_identifier'] = aki + result['authority_cert_issuer'] = aci + result['authority_cert_serial_number'] = acsn + + result['extensions_by_oid'] = self._get_all_extensions() + + result['signature_valid'] = self._is_signature_valid() + if not result['signature_valid']: + self.module.fail_json( + msg='CSR signature is invalid!', + **result + ) + return result + + +class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo): + """Validate the supplied CSR, using the cryptography backend""" + def __init__(self, module): + super(CertificateSigningRequestInfoCryptography, self).__init__(module, 'cryptography') + + def _get_subject_ordered(self): + result = [] + for attribute in self.csr.subject: + result.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + return result + + def _get_key_usage(self): + try: + current_key_ext = self.csr.extensions.get_extension_for_class(x509.KeyUsage) + current_key_usage = current_key_ext.value + key_usage = dict( + digital_signature=current_key_usage.digital_signature, + content_commitment=current_key_usage.content_commitment, + key_encipherment=current_key_usage.key_encipherment, + data_encipherment=current_key_usage.data_encipherment, + key_agreement=current_key_usage.key_agreement, + key_cert_sign=current_key_usage.key_cert_sign, + crl_sign=current_key_usage.crl_sign, + encipher_only=False, + decipher_only=False, + ) + if key_usage['key_agreement']: + key_usage.update(dict( + encipher_only=current_key_usage.encipher_only, + decipher_only=current_key_usage.decipher_only + )) + + key_usage_names = dict( + digital_signature='Digital Signature', + content_commitment='Non Repudiation', + key_encipherment='Key Encipherment', + data_encipherment='Data Encipherment', + key_agreement='Key Agreement', + key_cert_sign='Certificate Sign', + crl_sign='CRL Sign', + encipher_only='Encipher Only', + decipher_only='Decipher Only', + ) + return sorted([ + key_usage_names[name] for name, value in key_usage.items() if value + ]), current_key_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_extended_key_usage(self): + try: + ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + return sorted([ + cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value + ]), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_basic_constraints(self): + try: + ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.BasicConstraints) + result = [] + result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE')) + if ext_keyusage_ext.value.path_length is not None: + result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length)) + return sorted(result), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_ocsp_must_staple(self): + try: + try: + # This only works with cryptography >= 2.1 + tlsfeature_ext = self.csr.extensions.get_extension_for_class(x509.TLSFeature) + value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value + except AttributeError as dummy: + # Fallback for cryptography < 2.1 + oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24") + tlsfeature_ext = self.csr.extensions.get_extension_for_oid(oid) + value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05" + return value, tlsfeature_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_subject_alt_name(self): + try: + san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName) + result = [cryptography_decode_name(san) for san in san_ext.value] + return result, san_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_name_constraints(self): + try: + nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints) + permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []] + excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []] + return permitted, excluded, nc_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, None, False + + def _get_public_key(self, binary): + return self.csr.public_key().public_bytes( + serialization.Encoding.DER if binary else serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def _get_subject_key_identifier(self): + try: + ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + return ext.value.digest + except cryptography.x509.ExtensionNotFound: + return None + + def _get_authority_key_identifier(self): + try: + ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) + issuer = None + if ext.value.authority_cert_issuer is not None: + issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer] + return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number + except cryptography.x509.ExtensionNotFound: + return None, None, None + + def _get_all_extensions(self): + return cryptography_get_extensions_from_csr(self.csr) + + def _is_signature_valid(self): + return self.csr.is_signature_valid + + +class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo): + """validate the supplied CSR.""" + + def __init__(self, module): + super(CertificateSigningRequestInfoPyOpenSSL, self).__init__(module, 'pyopenssl') + + def __get_name(self, name): + result = [] + for sub in name.get_components(): + result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])]) + return result + + def _get_subject_ordered(self): + return self.__get_name(self.csr.get_subject()) + + def _get_extension(self, short_name): + for extension in self.csr.get_extensions(): + if extension.get_short_name() == short_name: + result = [ + pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',') + ] + return sorted(result), bool(extension.get_critical()) + return None, False + + def _get_key_usage(self): + return self._get_extension(b'keyUsage') + + def _get_extended_key_usage(self): + return self._get_extension(b'extendedKeyUsage') + + def _get_basic_constraints(self): + return self._get_extension(b'basicConstraints') + + def _get_ocsp_must_staple(self): + extensions = self.csr.get_extensions() + oms_ext = [ + ext for ext in extensions + if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE + ] + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: + # Older versions of libssl don't know about OCSP Must Staple + oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05']) + if oms_ext: + return True, bool(oms_ext[0].get_critical()) + else: + return None, False + + def _get_subject_alt_name(self): + for extension in self.csr.get_extensions(): + if extension.get_short_name() == b'subjectAltName': + result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in + to_text(extension, errors='surrogate_or_strict').split(', ')] + return result, bool(extension.get_critical()) + return None, False + + def _get_name_constraints(self): + for extension in self.csr.get_extensions(): + if extension.get_short_name() == b'nameConstraints': + permitted, excluded = pyopenssl_parse_name_constraints(extension) + return permitted, excluded, bool(extension.get_critical()) + return None, None, False + + def _get_public_key(self, binary): + try: + return crypto.dump_publickey( + crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, + self.csr.get_pubkey() + ) + except AttributeError: + try: + bio = crypto._new_mem_buf() + if binary: + rc = crypto._lib.i2d_PUBKEY_bio(bio, self.csr.get_pubkey()._pkey) + else: + rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey) + if rc != 1: + crypto._raise_current_error() + return crypto._bio_to_string(bio) + except AttributeError: + self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' + 'Please upgrade to version 16.0 or newer, or use the cryptography backend.') + + def _get_subject_key_identifier(self): + # Won't be implemented + return None + + def _get_authority_key_identifier(self): + # Won't be implemented + return None, None, None + + def _get_all_extensions(self): + return pyopenssl_get_extensions_from_csr(self.csr) + + def _is_signature_valid(self): + try: + return bool(self.csr.verify(self.csr.get_pubkey())) + except crypto.Error: + # OpenSSL error means that key is not consistent + return False + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='path'), + content=dict(type='str'), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + ), + required_one_of=( + ['path', 'content'], + ), + mutually_exclusive=( + ['path', 'content'], + ), + supports_check_mode=True, + ) + + try: + if module.params['path'] is not None: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') + + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + certificate = CertificateSigningRequestInfoPyOpenSSL(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + certificate = CertificateSigningRequestInfoCryptography(module) + + result = certificate.get_info() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_pipe.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_pipe.py new file mode 100644 index 00000000..b789c25a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_csr_pipe.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_csr_pipe +short_description: Generate OpenSSL Certificate Signing Request (CSR) +version_added: 1.3.0 +description: + - "Please note that the module regenerates an existing CSR if it doesn't match the module's + options, or if it seems to be corrupt." +author: +- Yanis Guenane (@Spredzy) +- Felix Fontein (@felixfontein) +options: + content: + description: + - The existing CSR. + type: str +extends_documentation_fragment: +- community.crypto.module_csr +seealso: +- module: community.crypto.openssl_csr +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL Certificate Signing Request + community.crypto.openssl_csr_pipe: + privatekey_path: /etc/ssl/private/ansible.com.pem + common_name: www.ansible.com + register: result +- debug: + var: result.csr + +- name: Generate an OpenSSL Certificate Signing Request with an inline CSR + community.crypto.openssl_csr: + content: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.csr') }}" + privatekey_content: "{{ private_key_content }}" + common_name: www.ansible.com + register: result +- name: Store CSR + ansible.builtin.copy: + dest: /etc/ssl/csr/www.ansible.com.csr + content: "{{ result.csr }}" + when: result is changed +''' + +RETURN = r''' +privatekey: + description: + - Path to the TLS/SSL private key the CSR was generated for + - Will be C(none) if the private key has been provided in I(privatekey_content). + returned: changed or success + type: str + sample: /etc/ssl/private/ansible.com.pem +subject: + description: A list of the subject tuples attached to the CSR + returned: changed or success + type: list + elements: list + sample: "[('CN', 'www.ansible.com'), ('O', 'Ansible')]" +subjectAltName: + description: The alternative names this CSR is valid for + returned: changed or success + type: list + elements: str + sample: [ 'DNS:www.ansible.com', 'DNS:m.ansible.com' ] +keyUsage: + description: Purpose for which the public key may be used + returned: changed or success + type: list + elements: str + sample: [ 'digitalSignature', 'keyAgreement' ] +extendedKeyUsage: + description: Additional restriction on the public key purposes + returned: changed or success + type: list + elements: str + sample: [ 'clientAuth' ] +basicConstraints: + description: Indicates if the certificate belongs to a CA + returned: changed or success + type: list + elements: str + sample: ['CA:TRUE', 'pathLenConstraint:0'] +ocsp_must_staple: + description: Indicates whether the certificate has the OCSP + Must Staple feature enabled + returned: changed or success + type: bool + sample: false +name_constraints_permitted: + description: List of permitted subtrees to sign certificates for. + returned: changed or success + type: list + elements: str + sample: ['email:.somedomain.com'] +name_constraints_excluded: + description: List of excluded subtrees the CA cannot sign certificates for. + returned: changed or success + type: list + elements: str + sample: ['email:.com'] +csr: + description: The (current or generated) CSR's content. + returned: changed or success + type: str +''' + +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr import ( + select_backend, + get_csr_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + + +class CertificateSigningRequestModule(object): + def __init__(self, module, module_backend): + self.check_mode = module.check_mode + self.module_backend = module_backend + self.changed = False + if module.params['content'] is not None: + self.module_backend.set_existing(module.params['content'].encode('utf-8')) + + def generate(self, module): + '''Generate the certificate signing request.''' + if self.module_backend.needs_regeneration(): + if not self.check_mode: + self.module_backend.generate_csr() + self.changed = True + + def dump(self): + '''Serialize the object into a dictionary.''' + result = self.module_backend.dump(include_csr=True) + result.update({ + 'changed': self.changed, + }) + return result + + +def main(): + argument_spec = get_csr_argument_spec() + argument_spec.argument_spec.update(dict( + content=dict(type='str'), + )) + module = argument_spec.create_ansible_module( + supports_check_mode=True, + ) + + backend = module.params['select_crypto_backend'] + backend, module_backend = select_backend(module, backend) + try: + csr = CertificateSigningRequestModule(module, module_backend) + csr.generate(module) + result = csr.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_dhparam.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_dhparam.py new file mode 100644 index 00000000..484f7fbb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_dhparam.py @@ -0,0 +1,423 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Thom Wiggers <ansible@thomwiggers.nl> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_dhparam +short_description: Generate OpenSSL Diffie-Hellman Parameters +description: + - This module allows one to (re)generate OpenSSL DH-params. + - This module uses file common arguments to specify generated file permissions. + - "Please note that the module regenerates existing DH params if they do not + match the module's options. If you are concerned that this could overwrite + your existing DH params, consider using the I(backup) option." + - The module can use the cryptography Python library, or the C(openssl) executable. + By default, it tries to detect which one is available. This can be overridden + with the I(select_crypto_backend) option. +requirements: + - Either cryptography >= 2.0 + - Or OpenSSL binary C(openssl) +author: + - Thom Wiggers (@thomwiggers) +options: + state: + description: + - Whether the parameters should exist or not, + taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + size: + description: + - Size (in bits) of the generated DH-params. + type: int + default: 4096 + force: + description: + - Should the parameters be regenerated even it it already exists. + type: bool + default: no + path: + description: + - Name of the file in which the generated parameters will be saved. + type: path + required: true + backup: + description: + - Create a backup file including a timestamp so you can get the original + DH params back if you overwrote them with new ones by accident. + type: bool + default: no + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(openssl). + - If set to C(openssl), will try to use the OpenSSL C(openssl) executable. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, openssl ] + version_added: "1.0.0" + return_content: + description: + - If set to C(yes), will return the (current or generated) DH params' content as I(dhparams). + type: bool + default: no + version_added: "1.0.0" +notes: +- Supports C(check_mode). +extends_documentation_fragment: +- files +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_pkcs12 +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_publickey +''' + +EXAMPLES = r''' +- name: Generate Diffie-Hellman parameters with the default size (4096 bits) + community.crypto.openssl_dhparam: + path: /etc/ssl/dhparams.pem + +- name: Generate DH Parameters with a different size (2048 bits) + community.crypto.openssl_dhparam: + path: /etc/ssl/dhparams.pem + size: 2048 + +- name: Force regenerate an DH parameters if they already exist + community.crypto.openssl_dhparam: + path: /etc/ssl/dhparams.pem + force: yes +''' + +RETURN = r''' +size: + description: Size (in bits) of the Diffie-Hellman parameters. + returned: changed or success + type: int + sample: 4096 +filename: + description: Path to the generated Diffie-Hellman parameters. + returned: changed or success + type: str + sample: /etc/ssl/dhparams.pem +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/dhparams.pem.2019-03-09@11:22~ +dhparams: + description: The (current or generated) DH params' content. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: "1.0.0" +''' + +import abc +import os +import re +import tempfile +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.math import ( + count_bits, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '2.0' + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.exceptions + import cryptography.hazmat.backends + import cryptography.hazmat.primitives.asymmetric.dh + import cryptography.hazmat.primitives.serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class DHParameterError(Exception): + pass + + +class DHParameterBase(object): + + def __init__(self, module): + self.state = module.params['state'] + self.path = module.params['path'] + self.size = module.params['size'] + self.force = module.params['force'] + self.changed = False + self.return_content = module.params['return_content'] + + self.backup = module.params['backup'] + self.backup_file = None + + @abc.abstractmethod + def _do_generate(self, module): + """Actually generate the DH params.""" + pass + + def generate(self, module): + """Generate DH params.""" + changed = False + + # ony generate when necessary + if self.force or not self._check_params_valid(module): + self._do_generate(module) + changed = True + + # fix permissions (checking force not necessary as done above) + if not self._check_fs_attributes(module): + # Fix done implicitly by + # AnsibleModule.set_fs_attributes_if_different + changed = True + + self.changed = changed + + def remove(self, module): + if self.backup: + self.backup_file = module.backup_local(self.path) + try: + os.remove(self.path) + self.changed = True + except OSError as exc: + module.fail_json(msg=to_native(exc)) + + def check(self, module): + """Ensure the resource is in its desired state.""" + if self.force: + return False + return self._check_params_valid(module) and self._check_fs_attributes(module) + + @abc.abstractmethod + def _check_params_valid(self, module): + """Check if the params are in the correct state""" + pass + + def _check_fs_attributes(self, module): + """Checks (and changes if not in check mode!) fs attributes""" + file_args = module.load_file_common_arguments(module.params) + attrs_changed = module.set_fs_attributes_if_different(file_args, False) + + return not attrs_changed + + def dump(self): + """Serialize the object into a dictionary.""" + + result = { + 'size': self.size, + 'filename': self.path, + 'changed': self.changed, + } + if self.backup_file: + result['backup_file'] = self.backup_file + if self.return_content: + content = load_file_if_exists(self.path, ignore_errors=True) + result['dhparams'] = content.decode('utf-8') if content else None + + return result + + +class DHParameterAbsent(DHParameterBase): + + def __init__(self, module): + super(DHParameterAbsent, self).__init__(module) + + def _do_generate(self, module): + """Actually generate the DH params.""" + pass + + def _check_params_valid(self, module): + """Check if the params are in the correct state""" + pass + + +class DHParameterOpenSSL(DHParameterBase): + + def __init__(self, module): + super(DHParameterOpenSSL, self).__init__(module) + self.openssl_bin = module.get_bin_path('openssl', True) + + def _do_generate(self, module): + """Actually generate the DH params.""" + # create a tempfile + fd, tmpsrc = tempfile.mkstemp() + os.close(fd) + module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit + # openssl dhparam -out <path> <bits> + command = [self.openssl_bin, 'dhparam', '-out', tmpsrc, str(self.size)] + rc, dummy, err = module.run_command(command, check_rc=False) + if rc != 0: + raise DHParameterError(to_native(err)) + if self.backup: + self.backup_file = module.backup_local(self.path) + try: + module.atomic_move(tmpsrc, self.path) + except Exception as e: + module.fail_json(msg="Failed to write to file %s: %s" % (self.path, str(e))) + + def _check_params_valid(self, module): + """Check if the params are in the correct state""" + command = [self.openssl_bin, 'dhparam', '-check', '-text', '-noout', '-in', self.path] + rc, out, err = module.run_command(command, check_rc=False) + result = to_native(out) + if rc != 0: + # If the call failed the file probably doesn't exist or is + # unreadable + return False + # output contains "(xxxx bit)" + match = re.search(r"Parameters:\s+\((\d+) bit\).*", result) + if not match: + return False # No "xxxx bit" in output + + bits = int(match.group(1)) + + # if output contains "WARNING" we've got a problem + if "WARNING" in result or "WARNING" in to_native(err): + return False + + return bits == self.size + + +class DHParameterCryptography(DHParameterBase): + + def __init__(self, module): + super(DHParameterCryptography, self).__init__(module) + self.crypto_backend = cryptography.hazmat.backends.default_backend() + + def _do_generate(self, module): + """Actually generate the DH params.""" + # Generate parameters + params = cryptography.hazmat.primitives.asymmetric.dh.generate_parameters( + generator=2, + key_size=self.size, + backend=self.crypto_backend, + ) + # Serialize parameters + result = params.parameter_bytes( + encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM, + format=cryptography.hazmat.primitives.serialization.ParameterFormat.PKCS3, + ) + # Write result + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, result) + + def _check_params_valid(self, module): + """Check if the params are in the correct state""" + # Load parameters + try: + with open(self.path, 'rb') as f: + data = f.read() + params = self.crypto_backend.load_pem_parameters(data) + except Exception as dummy: + return False + # Check parameters + bits = count_bits(params.parameter_numbers().p) + return bits == self.size + + +def main(): + """Main function""" + + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + size=dict(type='int', default=4096), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + backup=dict(type='bool', default=False), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'openssl']), + return_content=dict(type='bool', default=False), + ), + supports_check_mode=True, + add_file_common_args=True, + ) + + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg="The directory '%s' does not exist or the file is not a directory" % base_dir + ) + + if module.params['state'] == 'present': + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_openssl = module.get_bin_path('openssl', False) is not None + + # First try cryptography, then OpenSSL + if can_use_cryptography: + backend = 'cryptography' + elif can_use_openssl: + backend = 'openssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect either the required Python library cryptography (>= {0}) " + "or the OpenSSL binary openssl").format(MINIMAL_CRYPTOGRAPHY_VERSION)) + + if backend == 'openssl': + dhparam = DHParameterOpenSSL(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + dhparam = DHParameterCryptography(module) + + if module.check_mode: + result = dhparam.dump() + result['changed'] = module.params['force'] or not dhparam.check(module) + module.exit_json(**result) + + try: + dhparam.generate(module) + except DHParameterError as exc: + module.fail_json(msg=to_native(exc)) + else: + dhparam = DHParameterAbsent(module) + + if module.check_mode: + result = dhparam.dump() + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + if os.path.exists(module.params['path']): + try: + dhparam.remove(module) + except Exception as exc: + module.fail_json(msg=to_native(exc)) + + result = dhparam.dump() + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_pkcs12.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_pkcs12.py new file mode 100644 index 00000000..16939887 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_pkcs12.py @@ -0,0 +1,543 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Guillaume Delpierre <gde@llew.me> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_pkcs12 +author: +- Guillaume Delpierre (@gdelpierre) +short_description: Generate OpenSSL PKCS#12 archive +description: + - This module allows one to (re-)generate PKCS#12. +requirements: + - python-pyOpenSSL +options: + action: + description: + - C(export) or C(parse) a PKCS#12. + type: str + default: export + choices: [ export, parse ] + other_certificates: + description: + - List of other certificates to include. Pre Ansible 2.8 this parameter was called I(ca_certificates). + - Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates, + set I(other_certificates_parse_all) to C(true). + type: list + elements: path + aliases: [ ca_certificates ] + other_certificates_parse_all: + description: + - If set to C(true), assumes that the files mentioned in I(other_certificates) can contain more than one + certificate per file (or even none per file). + type: bool + default: false + version_added: 1.4.0 + certificate_path: + description: + - The path to read certificates and private keys from. + - Must be in PEM format. + type: path + force: + description: + - Should the file be regenerated even if it already exists. + type: bool + default: no + friendly_name: + description: + - Specifies the friendly name for the certificate and private key. + type: str + aliases: [ name ] + iter_size: + description: + - Number of times to repeat the encryption step. + type: int + default: 2048 + maciter_size: + description: + - Number of times to repeat the MAC step. + type: int + default: 1 + passphrase: + description: + - The PKCS#12 password. + type: str + path: + description: + - Filename to write the PKCS#12 file to. + type: path + required: true + privatekey_passphrase: + description: + - Passphrase source to decrypt any input private keys with. + type: str + privatekey_path: + description: + - File to read private key from. + type: path + state: + description: + - Whether the file should exist or not. + All parameters except C(path) are ignored when state is C(absent). + choices: [ absent, present ] + default: present + type: str + src: + description: + - PKCS#12 file path to parse. + type: path + backup: + description: + - Create a backup file including a timestamp so you can get the original + output file back if you overwrote it with a new one by accident. + type: bool + default: no + return_content: + description: + - If set to C(yes), will return the (current or generated) PKCS#12's content as I(pkcs12). + type: bool + default: no + version_added: "1.0.0" +extends_documentation_fragment: + - files +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_dhparam +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_publickey +''' + +EXAMPLES = r''' +- name: Generate PKCS#12 file + community.crypto.openssl_pkcs12: + action: export + path: /opt/certs/ansible.p12 + friendly_name: raclette + privatekey_path: /opt/certs/keys/key.pem + certificate_path: /opt/certs/cert.pem + other_certificates: /opt/certs/ca.pem + # Note that if /opt/certs/ca.pem contains multiple certificates, + # only the first one will be used. See the other_certificates_parse_all + # option for changing this behavior. + state: present + +- name: Generate PKCS#12 file + community.crypto.openssl_pkcs12: + action: export + path: /opt/certs/ansible.p12 + friendly_name: raclette + privatekey_path: /opt/certs/keys/key.pem + certificate_path: /opt/certs/cert.pem + other_certificates_parse_all: true + other_certificates: + - /opt/certs/ca_bundle.pem + # Since we set other_certificates_parse_all to true, all + # certificates in the CA bundle are included and not just + # the first one. + - /opt/certs/intermediate.pem + # In case this file has multiple certificates in it, + # all will be included as well. + state: present + +- name: Change PKCS#12 file permission + community.crypto.openssl_pkcs12: + action: export + path: /opt/certs/ansible.p12 + friendly_name: raclette + privatekey_path: /opt/certs/keys/key.pem + certificate_path: /opt/certs/cert.pem + other_certificates: /opt/certs/ca.pem + state: present + mode: '0600' + +- name: Regen PKCS#12 file + community.crypto.openssl_pkcs12: + action: export + src: /opt/certs/ansible.p12 + path: /opt/certs/ansible.p12 + friendly_name: raclette + privatekey_path: /opt/certs/keys/key.pem + certificate_path: /opt/certs/cert.pem + other_certificates: /opt/certs/ca.pem + state: present + mode: '0600' + force: yes + +- name: Dump/Parse PKCS#12 file + community.crypto.openssl_pkcs12: + action: parse + src: /opt/certs/ansible.p12 + path: /opt/certs/ansible.pem + state: present + +- name: Remove PKCS#12 file + community.crypto.openssl_pkcs12: + path: /opt/certs/ansible.p12 + state: absent +''' + +RETURN = r''' +filename: + description: Path to the generate PKCS#12 file. + returned: changed or success + type: str + sample: /opt/certs/ansible.p12 +privatekey: + description: Path to the TLS/SSL private key the public key was generated from. + returned: changed or success + type: str + sample: /etc/ssl/private/ansible.com.pem +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/ansible.com.pem.2019-03-09@11:22~ +pkcs12: + description: The (current or generated) PKCS#12's content Base64 encoded. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: "1.0.0" +''' + +import base64 +import os +import stat +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_bytes, to_native + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_privatekey, + load_certificate, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + split_pem_list, +) + +PYOPENSSL_IMP_ERR = None +try: + from OpenSSL import crypto +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + pyopenssl_found = False +else: + pyopenssl_found = True + + +def load_certificate_set(filename): + ''' + Load list of concatenated PEM files, and return a list of parsed certificates. + ''' + with open(filename, 'rb') as f: + data = f.read().decode('utf-8') + return [load_certificate(None, content=cert) for cert in split_pem_list(data)] + + +class PkcsError(OpenSSLObjectError): + pass + + +class Pkcs(OpenSSLObject): + + def __init__(self, module): + super(Pkcs, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.action = module.params['action'] + self.other_certificates = module.params['other_certificates'] + self.other_certificates_parse_all = module.params['other_certificates_parse_all'] + self.certificate_path = module.params['certificate_path'] + self.friendly_name = module.params['friendly_name'] + self.iter_size = module.params['iter_size'] + self.maciter_size = module.params['maciter_size'] + self.passphrase = module.params['passphrase'] + self.pkcs12 = None + self.privatekey_passphrase = module.params['privatekey_passphrase'] + self.privatekey_path = module.params['privatekey_path'] + self.pkcs12_bytes = None + self.return_content = module.params['return_content'] + self.src = module.params['src'] + + if module.params['mode'] is None: + module.params['mode'] = '0400' + + self.backup = module.params['backup'] + self.backup_file = None + + if self.other_certificates: + if self.other_certificates_parse_all: + filenames = list(self.other_certificates) + self.other_certificates = [] + for other_cert_bundle in filenames: + self.other_certificates.extend(load_certificate_set(other_cert_bundle)) + else: + self.other_certificates = [ + load_certificate(other_cert) for other_cert in self.other_certificates + ] + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + + state_and_perms = super(Pkcs, self).check(module, perms_required) + + def _check_pkey_passphrase(): + if self.privatekey_passphrase: + try: + load_privatekey(self.privatekey_path, self.privatekey_passphrase) + except crypto.Error: + return False + except OpenSSLBadPassphraseError: + return False + return True + + if not state_and_perms: + return state_and_perms + + if os.path.exists(self.path) and module.params['action'] == 'export': + dummy = self.generate(module) + self.src = self.path + try: + pkcs12_privatekey, pkcs12_certificate, pkcs12_other_certificates, pkcs12_friendly_name = self.parse() + except crypto.Error: + return False + if (pkcs12_privatekey is not None) and (self.privatekey_path is not None): + expected_pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, + self.pkcs12.get_privatekey()) + if pkcs12_privatekey != expected_pkey: + return False + elif bool(pkcs12_privatekey) != bool(self.privatekey_path): + return False + + if (pkcs12_certificate is not None) and (self.certificate_path is not None): + + expected_cert = crypto.dump_certificate(crypto.FILETYPE_PEM, + self.pkcs12.get_certificate()) + if pkcs12_certificate != expected_cert: + return False + elif bool(pkcs12_certificate) != bool(self.certificate_path): + return False + + if (pkcs12_other_certificates is not None) and (self.other_certificates is not None): + expected_other_certs = [crypto.dump_certificate(crypto.FILETYPE_PEM, + other_cert) for other_cert in self.pkcs12.get_ca_certificates()] + if set(pkcs12_other_certificates) != set(expected_other_certs): + return False + elif bool(pkcs12_other_certificates) != bool(self.other_certificates): + return False + + if pkcs12_privatekey: + # This check is required because pyOpenSSL will not return a friendly name + # if the private key is not set in the file + if ((self.pkcs12.get_friendlyname() is not None) and (pkcs12_friendly_name is not None)): + if self.pkcs12.get_friendlyname() != pkcs12_friendly_name: + return False + elif bool(self.pkcs12.get_friendlyname()) != bool(pkcs12_friendly_name): + return False + elif module.params['action'] == 'parse' and os.path.exists(self.src) and os.path.exists(self.path): + try: + pkey, cert, other_certs, friendly_name = self.parse() + except crypto.Error: + return False + expected_content = to_bytes( + ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) + ) + dumped_content = load_file_if_exists(self.path, ignore_errors=True) + if expected_content != dumped_content: + return False + else: + return False + + return _check_pkey_passphrase() + + def dump(self): + """Serialize the object into a dictionary.""" + + result = { + 'filename': self.path, + } + if self.privatekey_path: + result['privatekey_path'] = self.privatekey_path + if self.backup_file: + result['backup_file'] = self.backup_file + if self.return_content: + if self.pkcs12_bytes is None: + self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True) + result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None + + return result + + def generate(self, module): + """Generate PKCS#12 file archive.""" + self.pkcs12 = crypto.PKCS12() + + if self.other_certificates: + self.pkcs12.set_ca_certificates(self.other_certificates) + + if self.certificate_path: + self.pkcs12.set_certificate(load_certificate(self.certificate_path)) + + if self.friendly_name: + self.pkcs12.set_friendlyname(to_bytes(self.friendly_name)) + + if self.privatekey_path: + try: + self.pkcs12.set_privatekey(load_privatekey(self.privatekey_path, self.privatekey_passphrase)) + except OpenSSLBadPassphraseError as exc: + raise PkcsError(exc) + + return self.pkcs12.export(self.passphrase, self.iter_size, self.maciter_size) + + def remove(self, module): + if self.backup: + self.backup_file = module.backup_local(self.path) + super(Pkcs, self).remove(module) + + def parse(self): + """Read PKCS#12 file.""" + + try: + with open(self.src, 'rb') as pkcs12_fh: + pkcs12_content = pkcs12_fh.read() + p12 = crypto.load_pkcs12(pkcs12_content, + self.passphrase) + pkey = p12.get_privatekey() + if pkey is not None: + pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) + crt = p12.get_certificate() + if crt is not None: + crt = crypto.dump_certificate(crypto.FILETYPE_PEM, crt) + other_certs = [] + if p12.get_ca_certificates() is not None: + other_certs = [crypto.dump_certificate(crypto.FILETYPE_PEM, + other_cert) for other_cert in p12.get_ca_certificates()] + + friendly_name = p12.get_friendlyname() + + return (pkey, crt, other_certs, friendly_name) + + except IOError as exc: + raise PkcsError(exc) + + def write(self, module, content, mode=None): + """Write the PKCS#12 file.""" + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, content, mode) + if self.return_content: + self.pkcs12_bytes = content + + +def main(): + argument_spec = dict( + action=dict(type='str', default='export', choices=['export', 'parse']), + other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']), + other_certificates_parse_all=dict(type='bool', default=False), + certificate_path=dict(type='path'), + force=dict(type='bool', default=False), + friendly_name=dict(type='str', aliases=['name']), + iter_size=dict(type='int', default=2048), + maciter_size=dict(type='int', default=1), + passphrase=dict(type='str', no_log=True), + path=dict(type='path', required=True), + privatekey_passphrase=dict(type='str', no_log=True), + privatekey_path=dict(type='path'), + state=dict(type='str', default='present', choices=['absent', 'present']), + src=dict(type='path'), + backup=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + ) + + required_if = [ + ['action', 'parse', ['src']], + ] + + module = AnsibleModule( + add_file_common_args=True, + argument_spec=argument_spec, + required_if=required_if, + supports_check_mode=True, + ) + + if not pyopenssl_found: + module.fail_json(msg=missing_required_lib('pyOpenSSL'), exception=PYOPENSSL_IMP_ERR) + + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg="The directory '%s' does not exist or the path is not a directory" % base_dir + ) + + try: + pkcs12 = Pkcs(module) + changed = False + + if module.params['state'] == 'present': + if module.check_mode: + result = pkcs12.dump() + result['changed'] = module.params['force'] or not pkcs12.check(module) + module.exit_json(**result) + + if not pkcs12.check(module, perms_required=False) or module.params['force']: + if module.params['action'] == 'export': + if not module.params['friendly_name']: + module.fail_json(msg='Friendly_name is required') + pkcs12_content = pkcs12.generate(module) + pkcs12.write(module, pkcs12_content, 0o600) + changed = True + else: + pkey, cert, other_certs, friendly_name = pkcs12.parse() + dump_content = ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None]) + pkcs12.write(module, to_bytes(dump_content)) + changed = True + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, changed): + changed = True + else: + if module.check_mode: + result = pkcs12.dump() + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + if os.path.exists(module.params['path']): + pkcs12.remove(module) + changed = True + + result = pkcs12.dump() + result['changed'] = changed + if os.path.exists(module.params['path']): + file_mode = "%04o" % stat.S_IMODE(os.stat(module.params['path']).st_mode) + result['mode'] = file_mode + + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey.py new file mode 100644 index 00000000..017f8cba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_privatekey +short_description: Generate OpenSSL private keys +description: + - This module allows one to (re)generate OpenSSL private keys. +author: + - Yanis Guenane (@Spredzy) + - Felix Fontein (@felixfontein) +options: + state: + description: + - Whether the private key should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + force: + description: + - Should the key be regenerated even if it already exists. + type: bool + default: no + path: + description: + - Name of the file in which the generated TLS/SSL private key will be written. It will have C(0600) mode + if I(mode) is not explicitly set. + type: path + required: true + format: + version_added: '1.0.0' + format_mismatch: + version_added: '1.0.0' + backup: + description: + - Create a backup file including a timestamp so you can get + the original private key back if you overwrote it with a new one by accident. + type: bool + default: no + return_content: + description: + - If set to C(yes), will return the (current or generated) private key's content as I(privatekey). + - Note that especially if the private key is not encrypted, you have to make sure that the returned + value is treated appropriately and not accidentally written to logs etc.! Use with care! + - Use Ansible's I(no_log) task option to avoid the output being shown. See also + U(https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-keep-secret-data-in-my-playbook). + type: bool + default: no + version_added: '1.0.0' + regenerate: + version_added: '1.0.0' +extends_documentation_fragment: +- ansible.builtin.files +- community.crypto.module_privatekey +seealso: +- module: community.crypto.openssl_privatekey_pipe +- module: community.crypto.openssl_privatekey_info +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + +- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) and a passphrase + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + passphrase: ansible + cipher: aes256 + +- name: Generate an OpenSSL private key with a different size (2048 bits) + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + size: 2048 + +- name: Force regenerate an OpenSSL private key if it already exists + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + force: yes + +- name: Generate an OpenSSL private key with a different algorithm (DSA) + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + type: DSA +''' + +RETURN = r''' +size: + description: Size (in bits) of the TLS/SSL private key. + returned: changed or success + type: int + sample: 4096 +type: + description: Algorithm used to generate the TLS/SSL private key. + returned: changed or success + type: str + sample: RSA +curve: + description: Elliptic curve used to generate the TLS/SSL private key. + returned: changed or success, and I(type) is C(ECC) + type: str + sample: secp256r1 +filename: + description: Path to the generated TLS/SSL private key file. + returned: changed or success + type: str + sample: /etc/ssl/private/ansible.com.pem +fingerprint: + description: + - The fingerprint of the public key. Fingerprint will be generated for each C(hashlib.algorithms) available. + - The PyOpenSSL backend requires PyOpenSSL >= 16.0 for meaningful output. + returned: changed or success + type: dict + sample: + md5: "84:75:71:72:8d:04:b5:6c:4d:37:6d:66:83:f5:4c:29" + sha1: "51:cc:7c:68:5d:eb:41:43:88:7e:1a:ae:c7:f8:24:72:ee:71:f6:10" + sha224: "b1:19:a6:6c:14:ac:33:1d:ed:18:50:d3:06:5c:b2:32:91:f1:f1:52:8c:cb:d5:75:e9:f5:9b:46" + sha256: "41:ab:c7:cb:d5:5f:30:60:46:99:ac:d4:00:70:cf:a1:76:4f:24:5d:10:24:57:5d:51:6e:09:97:df:2f:de:c7" + sha384: "85:39:50:4e:de:d9:19:33:40:70:ae:10:ab:59:24:19:51:c3:a2:e4:0b:1c:b1:6e:dd:b3:0c:d9:9e:6a:46:af:da:18:f8:ef:ae:2e:c0:9a:75:2c:9b:b3:0f:3a:5f:3d" + sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b" +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/privatekey.pem.2019-03-09@11:22~ +privatekey: + description: + - The (current or generated) private key's content. + - Will be Base64-encoded if the key is in raw format. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: '1.0.0' +''' + +import os + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.privatekey import ( + select_backend, + get_privatekey_argument_spec, +) + + +class PrivateKeyModule(OpenSSLObject): + + def __init__(self, module, module_backend): + super(PrivateKeyModule, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode, + ) + self.module_backend = module_backend + self.return_content = module.params['return_content'] + if self.force: + module_backend.regenerate = 'always' + + self.backup = module.params['backup'] + self.backup_file = None + + if module.params['mode'] is None: + module.params['mode'] = '0600' + + module_backend.set_existing(load_file_if_exists(self.path, module)) + + def generate(self, module): + """Generate a keypair.""" + + if self.module_backend.needs_regeneration(): + # Regenerate + if not self.check_mode: + if self.backup: + self.backup_file = module.backup_local(self.path) + self.module_backend.generate_private_key() + privatekey_data = self.module_backend.get_private_key_data() + if self.return_content: + self.privatekey_bytes = privatekey_data + write_file(module, privatekey_data, 0o600) + self.changed = True + elif self.module_backend.needs_conversion(): + # Convert + if not self.check_mode: + if self.backup: + self.backup_file = module.backup_local(self.path) + self.module_backend.convert_private_key() + privatekey_data = self.module_backend.get_private_key_data() + if self.return_content: + self.privatekey_bytes = privatekey_data + write_file(module, privatekey_data, 0o600) + self.changed = True + + file_args = module.load_file_common_arguments(module.params) + self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + + def remove(self, module): + self.module_backend.set_existing(None) + if self.backup and not self.check_mode: + self.backup_file = module.backup_local(self.path) + super(PrivateKeyModule, self).remove(module) + + def dump(self): + """Serialize the object into a dictionary.""" + + result = self.module_backend.dump(include_key=self.return_content) + result['filename'] = self.path + result['changed'] = self.changed + if self.backup_file: + result['backup_file'] = self.backup_file + + return result + + +def main(): + + argument_spec = get_privatekey_argument_spec() + argument_spec.argument_spec.update(dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + backup=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + )) + module = argument_spec.create_ansible_module( + supports_check_mode=True, + add_file_common_args=True, + ) + + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + backend, module_backend = select_backend( + module=module, + backend=module.params['select_crypto_backend'], + ) + + try: + private_key = PrivateKeyModule(module, module_backend) + + if private_key.state == 'present': + private_key.generate(module) + else: + private_key.remove(module) + + result = private_key.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_info.py new file mode 100644 index 00000000..648c3d85 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_info.py @@ -0,0 +1,652 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_privatekey_info +short_description: Provide information for OpenSSL private keys +description: + - This module allows one to query information on OpenSSL private keys. + - In case the key consistency checks fail, the module will fail as this indicates a faked + private key. In this case, all return variables are still returned. Note that key consistency + checks are not available all key types; if none is available, C(none) is returned for + C(key_is_consistent). + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the + cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with + C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 + and will be removed in community.crypto 2.0.0. +requirements: + - PyOpenSSL >= 0.15 or cryptography >= 1.2.3 +author: + - Felix Fontein (@felixfontein) + - Yanis Guenane (@Spredzy) +options: + path: + description: + - Remote absolute path where the private key file is loaded from. + type: path + content: + description: + - Content of the private key file. + - Either I(path) or I(content) must be specified, but not both. + type: str + version_added: '1.0.0' + passphrase: + description: + - The passphrase for the private key. + type: str + return_private_key_data: + description: + - Whether to return private key data. + - Only set this to C(yes) when you want private information about this key to + leave the remote machine. + - "WARNING: you have to make sure that private key data isn't accidentally logged!" + type: bool + default: no + + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +notes: +- Supports C(check_mode). + +seealso: +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_privatekey_pipe +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) + community.crypto.openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + +- name: Get information on generated key + community.crypto.openssl_privatekey_info: + path: /etc/ssl/private/ansible.com.pem + register: result + +- name: Dump information + ansible.builtin.debug: + var: result +''' + +RETURN = r''' +can_load_key: + description: Whether the module was able to load the private key from disk. + returned: always + type: bool +can_parse_key: + description: Whether the module was able to parse the private key. + returned: always + type: bool +key_is_consistent: + description: + - Whether the key is consistent. Can also return C(none) next to C(yes) and + C(no), to indicate that consistency could not be checked. + - In case the check returns C(no), the module will fail. + returned: always + type: bool +public_key: + description: Private key's public key in PEM format. + returned: success + type: str + sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." +public_key_fingerprints: + description: + - Fingerprints of private key's public key. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." +type: + description: + - The key's type. + - One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448). + - Will start with C(unknown) if the key type cannot be determined. + returned: success + type: str + sample: RSA +public_data: + description: + - Public key data. Depends on key type. + returned: success + type: dict +private_data: + description: + - Private key data. Depends on key type. + returned: success and when I(return_private_key_data) is set to C(yes) + type: dict +''' + + +import abc +import os +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + CRYPTOGRAPHY_HAS_X25519, + CRYPTOGRAPHY_HAS_X448, + CRYPTOGRAPHY_HAS_ED25519, + CRYPTOGRAPHY_HAS_ED448, + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_privatekey, + get_fingerprint_of_bytes, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.math import ( + binary_exp_mod, + quick_is_not_prime, +) + + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' +MINIMAL_PYOPENSSL_VERSION = '0.15' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography.hazmat.primitives import serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + +SIGNATURE_TEST_DATA = b'1234' + + +def _get_cryptography_key_info(key): + key_public_data = dict() + key_private_data = dict() + if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + key_type = 'RSA' + key_public_data['size'] = key.key_size + key_public_data['modulus'] = key.public_key().public_numbers().n + key_public_data['exponent'] = key.public_key().public_numbers().e + key_private_data['p'] = key.private_numbers().p + key_private_data['q'] = key.private_numbers().q + key_private_data['exponent'] = key.private_numbers().d + elif isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey): + key_type = 'DSA' + key_public_data['size'] = key.key_size + key_public_data['p'] = key.parameters().parameter_numbers().p + key_public_data['q'] = key.parameters().parameter_numbers().q + key_public_data['g'] = key.parameters().parameter_numbers().g + key_public_data['y'] = key.public_key().public_numbers().y + key_private_data['x'] = key.private_numbers().x + elif CRYPTOGRAPHY_HAS_X25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey): + key_type = 'X25519' + elif CRYPTOGRAPHY_HAS_X448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey): + key_type = 'X448' + elif CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + key_type = 'Ed25519' + elif CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + key_type = 'Ed448' + elif isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + key_type = 'ECC' + key_public_data['curve'] = key.public_key().curve.name + key_public_data['x'] = key.public_key().public_numbers().x + key_public_data['y'] = key.public_key().public_numbers().y + key_public_data['exponent_size'] = key.public_key().curve.key_size + key_private_data['multiplier'] = key.private_numbers().private_value + else: + key_type = 'unknown ({0})'.format(type(key)) + return key_type, key_public_data, key_private_data + + +def _check_dsa_consistency(key_public_data, key_private_data): + # Get parameters + p = key_public_data.get('p') + q = key_public_data.get('q') + g = key_public_data.get('g') + y = key_public_data.get('y') + x = key_private_data.get('x') + for v in (p, q, g, y, x): + if v is None: + return None + # Make sure that g is not 0, 1 or -1 in Z/pZ + if g < 2 or g >= p - 1: + return False + # Make sure that x is in range + if x < 1 or x >= q: + return False + # Check whether q divides p-1 + if (p - 1) % q != 0: + return False + # Check that g**q mod p == 1 + if binary_exp_mod(g, q, p) != 1: + return False + # Check whether g**x mod p == y + if binary_exp_mod(g, x, p) != y: + return False + # Check (quickly) whether p or q are not primes + if quick_is_not_prime(q) or quick_is_not_prime(p): + return False + return True + + +def _is_cryptography_key_consistent(key, key_public_data, key_private_data): + if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + return bool(key._backend._lib.RSA_check_key(key._rsa_cdata)) + if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey): + result = _check_dsa_consistency(key_public_data, key_private_data) + if result is not None: + return result + try: + signature = key.sign(SIGNATURE_TEST_DATA, cryptography.hazmat.primitives.hashes.SHA256()) + except AttributeError: + # sign() was added in cryptography 1.5, but we support older versions + return None + try: + key.public_key().verify( + signature, + SIGNATURE_TEST_DATA, + cryptography.hazmat.primitives.hashes.SHA256() + ) + return True + except cryptography.exceptions.InvalidSignature: + return False + if isinstance(key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + try: + signature = key.sign( + SIGNATURE_TEST_DATA, + cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cryptography.hazmat.primitives.hashes.SHA256()) + ) + except AttributeError: + # sign() was added in cryptography 1.5, but we support older versions + return None + try: + key.public_key().verify( + signature, + SIGNATURE_TEST_DATA, + cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cryptography.hazmat.primitives.hashes.SHA256()) + ) + return True + except cryptography.exceptions.InvalidSignature: + return False + has_simple_sign_function = False + if CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + has_simple_sign_function = True + if CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + has_simple_sign_function = True + if has_simple_sign_function: + signature = key.sign(SIGNATURE_TEST_DATA) + try: + key.public_key().verify(signature, SIGNATURE_TEST_DATA) + return True + except cryptography.exceptions.InvalidSignature: + return False + # For X25519 and X448, there's no test yet. + return None + + +class PrivateKeyInfo(OpenSSLObject): + def __init__(self, module, backend): + super(PrivateKeyInfo, self).__init__( + module.params['path'] or '', + 'present', + False, + module.check_mode, + ) + self.backend = backend + self.module = module + self.content = module.params['content'] + + self.passphrase = module.params['passphrase'] + self.return_private_key_data = module.params['return_private_key_data'] + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + @abc.abstractmethod + def _get_public_key(self, binary): + pass + + @abc.abstractmethod + def _get_key_info(self): + pass + + @abc.abstractmethod + def _is_key_consistent(self, key_public_data, key_private_data): + pass + + def get_info(self): + result = dict( + can_load_key=False, + can_parse_key=False, + key_is_consistent=None, + ) + if self.content is not None: + priv_key_detail = self.content.encode('utf-8') + result['can_load_key'] = True + else: + try: + with open(self.path, 'rb') as b_priv_key_fh: + priv_key_detail = b_priv_key_fh.read() + result['can_load_key'] = True + except (IOError, OSError) as exc: + self.module.fail_json(msg=to_native(exc), **result) + try: + self.key = load_privatekey( + path=None, + content=priv_key_detail, + passphrase=to_bytes(self.passphrase) if self.passphrase is not None else self.passphrase, + backend=self.backend + ) + result['can_parse_key'] = True + except OpenSSLObjectError as exc: + self.module.fail_json(msg=to_native(exc), **result) + + result['public_key'] = self._get_public_key(binary=False) + pk = self._get_public_key(binary=True) + result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + + key_type, key_public_data, key_private_data = self._get_key_info() + result['type'] = key_type + result['public_data'] = key_public_data + if self.return_private_key_data: + result['private_data'] = key_private_data + + result['key_is_consistent'] = self._is_key_consistent(key_public_data, key_private_data) + if result['key_is_consistent'] is False: + # Only fail when it is False, to avoid to fail on None (which means "we don't know") + result['key_is_consistent'] = False + self.module.fail_json( + msg="Private key is not consistent! (See " + "https://blog.hboeck.de/archives/888-How-I-tricked-Symantec-with-a-Fake-Private-Key.html)", + **result + ) + return result + + +class PrivateKeyInfoCryptography(PrivateKeyInfo): + """Validate the supplied private key, using the cryptography backend""" + def __init__(self, module): + super(PrivateKeyInfoCryptography, self).__init__(module, 'cryptography') + + def _get_public_key(self, binary): + return self.key.public_key().public_bytes( + serialization.Encoding.DER if binary else serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def _get_key_info(self): + return _get_cryptography_key_info(self.key) + + def _is_key_consistent(self, key_public_data, key_private_data): + return _is_cryptography_key_consistent(self.key, key_public_data, key_private_data) + + +class PrivateKeyInfoPyOpenSSL(PrivateKeyInfo): + """validate the supplied private key.""" + + def __init__(self, module): + super(PrivateKeyInfoPyOpenSSL, self).__init__(module, 'pyopenssl') + + def _get_public_key(self, binary): + try: + return crypto.dump_publickey( + crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, + self.key + ) + except AttributeError: + try: + # pyOpenSSL < 16.0: + bio = crypto._new_mem_buf() + if binary: + rc = crypto._lib.i2d_PUBKEY_bio(bio, self.key._pkey) + else: + rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.key._pkey) + if rc != 1: + crypto._raise_current_error() + return crypto._bio_to_string(bio) + except AttributeError: + self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' + 'Please upgrade to version 16.0 or newer, or use the cryptography backend.') + + def bigint_to_int(self, bn): + '''Convert OpenSSL BIGINT to Python integer''' + if bn == OpenSSL._util.ffi.NULL: + return None + hexstr = OpenSSL._util.lib.BN_bn2hex(bn) + try: + return int(OpenSSL._util.ffi.string(hexstr), 16) + finally: + OpenSSL._util.lib.OPENSSL_free(hexstr) + + def _get_key_info(self): + key_public_data = dict() + key_private_data = dict() + openssl_key_type = self.key.type() + try_fallback = True + if crypto.TYPE_RSA == openssl_key_type: + key_type = 'RSA' + key_public_data['size'] = self.key.bits() + + try: + # Use OpenSSL directly to extract key data + key = OpenSSL._util.lib.EVP_PKEY_get1_RSA(self.key._pkey) + key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.RSA_free) + # OpenSSL 1.1 and newer have functions to extract the parameters + # from the EVP PKEY data structures. Older versions didn't have + # these getters, and it was common use to simply access the values + # directly. Since there's no guarantee that these data structures + # will still be accessible in the future, we use the getters for + # 1.1 and later, and directly access the values for 1.0.x and + # earlier. + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # Get modulus and exponents + n = OpenSSL._util.ffi.new("BIGNUM **") + e = OpenSSL._util.ffi.new("BIGNUM **") + d = OpenSSL._util.ffi.new("BIGNUM **") + OpenSSL._util.lib.RSA_get0_key(key, n, e, d) + key_public_data['modulus'] = self.bigint_to_int(n[0]) + key_public_data['exponent'] = self.bigint_to_int(e[0]) + key_private_data['exponent'] = self.bigint_to_int(d[0]) + # Get factors + p = OpenSSL._util.ffi.new("BIGNUM **") + q = OpenSSL._util.ffi.new("BIGNUM **") + OpenSSL._util.lib.RSA_get0_factors(key, p, q) + key_private_data['p'] = self.bigint_to_int(p[0]) + key_private_data['q'] = self.bigint_to_int(q[0]) + else: + # Get modulus and exponents + key_public_data['modulus'] = self.bigint_to_int(key.n) + key_public_data['exponent'] = self.bigint_to_int(key.e) + key_private_data['exponent'] = self.bigint_to_int(key.d) + # Get factors + key_private_data['p'] = self.bigint_to_int(key.p) + key_private_data['q'] = self.bigint_to_int(key.q) + try_fallback = False + except AttributeError: + # Use fallback if available + pass + elif crypto.TYPE_DSA == openssl_key_type: + key_type = 'DSA' + key_public_data['size'] = self.key.bits() + + try: + # Use OpenSSL directly to extract key data + key = OpenSSL._util.lib.EVP_PKEY_get1_DSA(self.key._pkey) + key = OpenSSL._util.ffi.gc(key, OpenSSL._util.lib.DSA_free) + # OpenSSL 1.1 and newer have functions to extract the parameters + # from the EVP PKEY data structures. Older versions didn't have + # these getters, and it was common use to simply access the values + # directly. Since there's no guarantee that these data structures + # will still be accessible in the future, we use the getters for + # 1.1 and later, and directly access the values for 1.0.x and + # earlier. + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # Get public parameters (primes and group element) + p = OpenSSL._util.ffi.new("BIGNUM **") + q = OpenSSL._util.ffi.new("BIGNUM **") + g = OpenSSL._util.ffi.new("BIGNUM **") + OpenSSL._util.lib.DSA_get0_pqg(key, p, q, g) + key_public_data['p'] = self.bigint_to_int(p[0]) + key_public_data['q'] = self.bigint_to_int(q[0]) + key_public_data['g'] = self.bigint_to_int(g[0]) + # Get public and private key exponents + y = OpenSSL._util.ffi.new("BIGNUM **") + x = OpenSSL._util.ffi.new("BIGNUM **") + OpenSSL._util.lib.DSA_get0_key(key, y, x) + key_public_data['y'] = self.bigint_to_int(y[0]) + key_private_data['x'] = self.bigint_to_int(x[0]) + else: + # Get public parameters (primes and group element) + key_public_data['p'] = self.bigint_to_int(key.p) + key_public_data['q'] = self.bigint_to_int(key.q) + key_public_data['g'] = self.bigint_to_int(key.g) + # Get public and private key exponents + key_public_data['y'] = self.bigint_to_int(key.pub_key) + key_private_data['x'] = self.bigint_to_int(key.priv_key) + try_fallback = False + except AttributeError: + # Use fallback if available + pass + else: + # Return 'unknown' + key_type = 'unknown ({0})'.format(self.key.type()) + # If needed and if possible, fall back to cryptography + if try_fallback and PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND: + return _get_cryptography_key_info(self.key.to_cryptography_key()) + return key_type, key_public_data, key_private_data + + def _is_key_consistent(self, key_public_data, key_private_data): + openssl_key_type = self.key.type() + if crypto.TYPE_RSA == openssl_key_type: + try: + return self.key.check() + except crypto.Error: + # OpenSSL error means that key is not consistent + return False + if crypto.TYPE_DSA == openssl_key_type: + result = _check_dsa_consistency(key_public_data, key_private_data) + if result is not None: + return result + signature = crypto.sign(self.key, SIGNATURE_TEST_DATA, 'sha256') + # Verify wants a cert (where it can get the public key from) + cert = crypto.X509() + cert.set_pubkey(self.key) + try: + crypto.verify(cert, signature, SIGNATURE_TEST_DATA, 'sha256') + return True + except crypto.Error: + return False + # If needed and if possible, fall back to cryptography + if PYOPENSSL_VERSION >= LooseVersion('16.1.0') and CRYPTOGRAPHY_FOUND: + return _is_cryptography_key_consistent(self.key.to_cryptography_key(), key_public_data, key_private_data) + return None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='path'), + content=dict(type='str', no_log=True), + passphrase=dict(type='str', no_log=True), + return_private_key_data=dict(type='bool', default=False), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + ), + required_one_of=( + ['path', 'content'], + ), + mutually_exclusive=( + ['path', 'content'], + ), + supports_check_mode=True, + ) + + try: + if module.params['path'] is not None: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + privatekey = PrivateKeyInfoPyOpenSSL(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + privatekey = PrivateKeyInfoCryptography(module) + + result = privatekey.get_info() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_pipe.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_pipe.py new file mode 100644 index 00000000..2e1279ad --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_privatekey_pipe.py @@ -0,0 +1,118 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_privatekey_pipe +short_description: Generate OpenSSL private keys without disk access +version_added: 1.3.0 +description: + - This module allows one to (re)generate OpenSSL private keys without disk access. + - This allows to read and write keys to vaults without having to write intermediate versions to disk. + - Make sure to not write the result of this module into logs or to the console, as it contains private key data! Use the I(no_log) task option to be sure. + - Note that this module is implemented as an L(action plugin,https://docs.ansible.com/ansible/latest/plugins/action.html) + and will always be executed on the controller. +author: + - Yanis Guenane (@Spredzy) + - Felix Fontein (@felixfontein) +options: + content: + description: + - The current private key data. + - Needed for idempotency. If not provided, the module will always return a change, and all idempotence-related + options are ignored. + type: str + content_base64: + description: + - Set to C(true) if the content is base64 encoded. + type: bool + default: false + return_current_key: + description: + - Set to C(true) to return the current private key when the module did not generate a new one. + - Note that in case of check mode, when this option is not set to C(true), the module always returns the + current key (if it was provided) and Ansible will replace it by C(VALUE_SPECIFIED_IN_NO_LOG_PARAMETER). + type: bool + default: false +extends_documentation_fragment: +- community.crypto.module_privatekey +seealso: +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_privatekey_info +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) + community.crypto.openssl_privatekey_pipe: + path: /etc/ssl/private/ansible.com.pem + register: output + no_log: true # make sure that private key data is not accidentally revealed in logs! +- name: Show generated key + debug: + msg: "{{ output.privatekey }}" + # DO NOT OUTPUT KEY MATERIAL TO CONSOLE OR LOGS IN PRODUCTION! + +- block: + - name: Update sops-encrypted key with the community.sops collection + community.crypto.openssl_privatekey_pipe: + content: "{{ lookup('community.sops.sops', 'private_key.pem.sops') }}" + size: 2048 + register: output + no_log: true # make sure that private key data is not accidentally revealed in logs! + + - name: Update encrypted key when openssl_privatekey_pipe reported a change + community.sops.encrypt_sops: + path: private_key.pem.sops + content_text: "{{ output.privatekey }}" + when: output is changed + always: + - name: Make sure that output (which contains the private key) is overwritten + set_fact: + output: '' +''' + +RETURN = r''' +size: + description: Size (in bits) of the TLS/SSL private key. + returned: changed or success + type: int + sample: 4096 +type: + description: Algorithm used to generate the TLS/SSL private key. + returned: changed or success + type: str + sample: RSA +curve: + description: Elliptic curve used to generate the TLS/SSL private key. + returned: changed or success, and I(type) is C(ECC) + type: str + sample: secp256r1 +fingerprint: + description: + - The fingerprint of the public key. Fingerprint will be generated for each C(hashlib.algorithms) available. + - The PyOpenSSL backend requires PyOpenSSL >= 16.0 for meaningful output. + returned: changed or success + type: dict + sample: + md5: "84:75:71:72:8d:04:b5:6c:4d:37:6d:66:83:f5:4c:29" + sha1: "51:cc:7c:68:5d:eb:41:43:88:7e:1a:ae:c7:f8:24:72:ee:71:f6:10" + sha224: "b1:19:a6:6c:14:ac:33:1d:ed:18:50:d3:06:5c:b2:32:91:f1:f1:52:8c:cb:d5:75:e9:f5:9b:46" + sha256: "41:ab:c7:cb:d5:5f:30:60:46:99:ac:d4:00:70:cf:a1:76:4f:24:5d:10:24:57:5d:51:6e:09:97:df:2f:de:c7" + sha384: "85:39:50:4e:de:d9:19:33:40:70:ae:10:ab:59:24:19:51:c3:a2:e4:0b:1c:b1:6e:dd:b3:0c:d9:9e:6a:46:af:da:18:f8:ef:ae:2e:c0:9a:75:2c:9b:b3:0f:3a:5f:3d" + sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b" +privatekey: + description: + - The generated private key's content. + - Please note that if the result is not changed, the current private key will only be returned + if the I(return_current_key) option is set to C(true). + - Will be Base64-encoded if the key is in raw format. + returned: changed, or I(return_current_key) is C(true) + type: str +''' diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_publickey.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_publickey.py new file mode 100644 index 00000000..524e870c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_publickey.py @@ -0,0 +1,490 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_publickey +short_description: Generate an OpenSSL public key from its private key. +description: + - This module allows one to (re)generate OpenSSL public keys from their private keys. + - Keys are generated in PEM or OpenSSH format. + - "The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. When I(format) is C(OpenSSH), + the C(cryptography) backend has to be used. Please note that the PyOpenSSL backend + was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0." +requirements: + - Either cryptography >= 1.2.3 (older versions might work as well) + - Or pyOpenSSL >= 16.0.0 + - Needs cryptography >= 1.4 if I(format) is C(OpenSSH) +author: + - Yanis Guenane (@Spredzy) + - Felix Fontein (@felixfontein) +options: + state: + description: + - Whether the public key should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + force: + description: + - Should the key be regenerated even it it already exists. + type: bool + default: no + format: + description: + - The format of the public key. + type: str + default: PEM + choices: [ OpenSSH, PEM ] + path: + description: + - Name of the file in which the generated TLS/SSL public key will be written. + type: path + required: true + privatekey_path: + description: + - Path to the TLS/SSL private key from which to generate the public key. + - Either I(privatekey_path) or I(privatekey_content) must be specified, but not both. + If I(state) is C(present), one of them is required. + type: path + privatekey_content: + description: + - The content of the TLS/SSL private key from which to generate the public key. + - Either I(privatekey_path) or I(privatekey_content) must be specified, but not both. + If I(state) is C(present), one of them is required. + type: str + version_added: '1.0.0' + privatekey_passphrase: + description: + - The passphrase for the private key. + type: str + backup: + description: + - Create a backup file including a timestamp so you can get the original + public key back if you overwrote it with a different one by accident. + type: bool + default: no + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + return_content: + description: + - If set to C(yes), will return the (current or generated) public key's content as I(publickey). + type: bool + default: no + version_added: '1.0.0' +extends_documentation_fragment: +- files +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.x509_certificate_pipe +- module: community.crypto.openssl_csr +- module: community.crypto.openssl_csr_pipe +- module: community.crypto.openssl_dhparam +- module: community.crypto.openssl_pkcs12 +- module: community.crypto.openssl_privatekey +- module: community.crypto.openssl_privatekey_pipe +''' + +EXAMPLES = r''' +- name: Generate an OpenSSL public key in PEM format + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_path: /etc/ssl/private/ansible.com.pem + +- name: Generate an OpenSSL public key in PEM format from an inline key + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_content: "{{ private_key_content }}" + +- name: Generate an OpenSSL public key in OpenSSH v2 format + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_path: /etc/ssl/private/ansible.com.pem + format: OpenSSH + +- name: Generate an OpenSSL public key with a passphrase protected private key + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_path: /etc/ssl/private/ansible.com.pem + privatekey_passphrase: ansible + +- name: Force regenerate an OpenSSL public key if it already exists + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_path: /etc/ssl/private/ansible.com.pem + force: yes + +- name: Remove an OpenSSL public key + community.crypto.openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + state: absent +''' + +RETURN = r''' +privatekey: + description: + - Path to the TLS/SSL private key the public key was generated from. + - Will be C(none) if the private key has been provided in I(privatekey_content). + returned: changed or success + type: str + sample: /etc/ssl/private/ansible.com.pem +format: + description: The format of the public key (PEM, OpenSSH, ...). + returned: changed or success + type: str + sample: PEM +filename: + description: Path to the generated TLS/SSL public key file. + returned: changed or success + type: str + sample: /etc/ssl/public/ansible.com.pem +fingerprint: + description: + - The fingerprint of the public key. Fingerprint will be generated for each hashlib.algorithms available. + - Requires PyOpenSSL >= 16.0 for meaningful output. + returned: changed or success + type: dict + sample: + md5: "84:75:71:72:8d:04:b5:6c:4d:37:6d:66:83:f5:4c:29" + sha1: "51:cc:7c:68:5d:eb:41:43:88:7e:1a:ae:c7:f8:24:72:ee:71:f6:10" + sha224: "b1:19:a6:6c:14:ac:33:1d:ed:18:50:d3:06:5c:b2:32:91:f1:f1:52:8c:cb:d5:75:e9:f5:9b:46" + sha256: "41:ab:c7:cb:d5:5f:30:60:46:99:ac:d4:00:70:cf:a1:76:4f:24:5d:10:24:57:5d:51:6e:09:97:df:2f:de:c7" + sha384: "85:39:50:4e:de:d9:19:33:40:70:ae:10:ab:59:24:19:51:c3:a2:e4:0b:1c:b1:6e:dd:b3:0c:d9:9e:6a:46:af:da:18:f8:ef:ae:2e:c0:9a:75:2c:9b:b3:0f:3a:5f:3d" + sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b" +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/publickey.pem.2019-03-09@11:22~ +publickey: + description: The (current or generated) public key's content. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: '1.0.0' +''' + +import os +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_privatekey, + get_fingerprint, +) + +MINIMAL_PYOPENSSL_VERSION = '16.0.0' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3' +MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '1.4' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import serialization as crypto_serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class PublicKeyError(OpenSSLObjectError): + pass + + +class PublicKey(OpenSSLObject): + + def __init__(self, module, backend): + super(PublicKey, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.format = module.params['format'] + self.privatekey_path = module.params['privatekey_path'] + self.privatekey_content = module.params['privatekey_content'] + if self.privatekey_content is not None: + self.privatekey_content = self.privatekey_content.encode('utf-8') + self.privatekey_passphrase = module.params['privatekey_passphrase'] + self.privatekey = None + self.publickey_bytes = None + self.return_content = module.params['return_content'] + self.fingerprint = {} + self.backend = backend + + self.backup = module.params['backup'] + self.backup_file = None + + def _create_publickey(self, module): + self.privatekey = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend + ) + if self.backend == 'cryptography': + if self.format == 'OpenSSH': + return self.privatekey.public_key().public_bytes( + crypto_serialization.Encoding.OpenSSH, + crypto_serialization.PublicFormat.OpenSSH + ) + else: + return self.privatekey.public_key().public_bytes( + crypto_serialization.Encoding.PEM, + crypto_serialization.PublicFormat.SubjectPublicKeyInfo + ) + else: + try: + return crypto.dump_publickey(crypto.FILETYPE_PEM, self.privatekey) + except AttributeError as dummy: + raise PublicKeyError('You need to have PyOpenSSL>=16.0.0 to generate public keys') + + def generate(self, module): + """Generate the public key.""" + + if self.privatekey_content is None and not os.path.exists(self.privatekey_path): + raise PublicKeyError( + 'The private key %s does not exist' % self.privatekey_path + ) + + if not self.check(module, perms_required=False) or self.force: + try: + publickey_content = self._create_publickey(module) + if self.return_content: + self.publickey_bytes = publickey_content + + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, publickey_content) + + self.changed = True + except OpenSSLBadPassphraseError as exc: + raise PublicKeyError(exc) + except (IOError, OSError) as exc: + raise PublicKeyError(exc) + + self.fingerprint = get_fingerprint( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + + state_and_perms = super(PublicKey, self).check(module, perms_required) + + def _check_privatekey(): + if self.privatekey_content is None and not os.path.exists(self.privatekey_path): + return False + + try: + with open(self.path, 'rb') as public_key_fh: + publickey_content = public_key_fh.read() + if self.return_content: + self.publickey_bytes = publickey_content + if self.backend == 'cryptography': + if self.format == 'OpenSSH': + # Read and dump public key. Makes sure that the comment is stripped off. + current_publickey = crypto_serialization.load_ssh_public_key(publickey_content, backend=default_backend()) + publickey_content = current_publickey.public_bytes( + crypto_serialization.Encoding.OpenSSH, + crypto_serialization.PublicFormat.OpenSSH + ) + else: + current_publickey = crypto_serialization.load_pem_public_key(publickey_content, backend=default_backend()) + publickey_content = current_publickey.public_bytes( + crypto_serialization.Encoding.PEM, + crypto_serialization.PublicFormat.SubjectPublicKeyInfo + ) + else: + publickey_content = crypto.dump_publickey( + crypto.FILETYPE_PEM, + crypto.load_publickey(crypto.FILETYPE_PEM, publickey_content) + ) + except Exception as dummy: + return False + + try: + desired_publickey = self._create_publickey(module) + except OpenSSLBadPassphraseError as exc: + raise PublicKeyError(exc) + + return publickey_content == desired_publickey + + if not state_and_perms: + return state_and_perms + + return _check_privatekey() + + def remove(self, module): + if self.backup: + self.backup_file = module.backup_local(self.path) + super(PublicKey, self).remove(module) + + def dump(self): + """Serialize the object into a dictionary.""" + + result = { + 'privatekey': self.privatekey_path, + 'filename': self.path, + 'format': self.format, + 'changed': self.changed, + 'fingerprint': self.fingerprint, + } + if self.backup_file: + result['backup_file'] = self.backup_file + if self.return_content: + if self.publickey_bytes is None: + self.publickey_bytes = load_file_if_exists(self.path, ignore_errors=True) + result['publickey'] = self.publickey_bytes.decode('utf-8') if self.publickey_bytes else None + + return result + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + force=dict(type='bool', default=False), + path=dict(type='path', required=True), + privatekey_path=dict(type='path'), + privatekey_content=dict(type='str', no_log=True), + format=dict(type='str', default='PEM', choices=['OpenSSH', 'PEM']), + privatekey_passphrase=dict(type='str', no_log=True), + backup=dict(type='bool', default=False), + select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'), + return_content=dict(type='bool', default=False), + ), + supports_check_mode=True, + add_file_common_args=True, + required_if=[('state', 'present', ['privatekey_path', 'privatekey_content'], True)], + mutually_exclusive=( + ['privatekey_path', 'privatekey_content'], + ), + ) + + minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION + if module.params['format'] == 'OpenSSH': + minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(minimal_cryptography_version) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # Decision + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + if module.params['format'] == 'OpenSSH': + module.fail_json( + msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH)), + exception=CRYPTOGRAPHY_IMP_ERR + ) + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + minimal_cryptography_version, + MINIMAL_PYOPENSSL_VERSION)) + + if module.params['format'] == 'OpenSSH' and backend != 'cryptography': + module.fail_json(msg="Format OpenSSH requires the cryptography backend.") + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(minimal_cryptography_version)), + exception=CRYPTOGRAPHY_IMP_ERR) + + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg="The directory '%s' does not exist or the file is not a directory" % base_dir + ) + + try: + public_key = PublicKey(module, backend) + + if public_key.state == 'present': + if module.check_mode: + result = public_key.dump() + result['changed'] = module.params['force'] or not public_key.check(module) + module.exit_json(**result) + + public_key.generate(module) + else: + if module.check_mode: + result = public_key.dump() + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + public_key.remove(module) + + result = public_key.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature.py new file mode 100644 index 00000000..57bbdc5a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_signature +version_added: 1.1.0 +short_description: Sign data with openssl +description: + - This module allows one to sign data using a private key. + - The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend + was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. +requirements: + - Either cryptography >= 1.4 (some key types require newer versions) + - Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend) +author: + - Patrick Pichler (@aveexy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + privatekey_path: + description: + - The path to the private key to use when signing. + - Either I(privatekey_path) or I(privatekey_content) must be specified, but not both. + type: path + privatekey_content: + description: + - The content of the private key to use when signing the certificate signing request. + - Either I(privatekey_path) or I(privatekey_content) must be specified, but not both. + type: str + privatekey_passphrase: + description: + - The passphrase for the private key. + - This is required if the private key is password protected. + type: str + path: + description: + - The file to sign. + - This file will only be read and not modified. + type: path + required: true + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] +notes: + - | + When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version: + RSA keys: C(cryptography) >= 1.4 + DSA and ECDSA keys: C(cryptography) >= 1.5 + ed448 and ed25519 keys: C(cryptography) >= 2.6 +seealso: + - module: community.crypto.openssl_signature_info + - module: community.crypto.openssl_privatekey +''' + +EXAMPLES = r''' +- name: Sign example file + community.crypto.openssl_signature: + privatekey_path: private.key + path: /tmp/example_file + register: sig + +- name: Verify signature of example file + community.crypto.openssl_signature_info: + certificate_path: cert.pem + path: /tmp/example_file + signature: "{{ sig.signature }}" + register: verify + +- name: Make sure the signature is valid + assert: + that: + - verify.valid +''' + +RETURN = r''' +signature: + description: Base64 encoded signature. + returned: success + type: str +''' + +import os +import traceback +from distutils.version import LooseVersion +import base64 + +MINIMAL_PYOPENSSL_VERSION = '0.11' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.hazmat.primitives.asymmetric.padding + import cryptography.hazmat.primitives.hashes + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + CRYPTOGRAPHY_HAS_DSA_SIGN, + CRYPTOGRAPHY_HAS_EC_SIGN, + CRYPTOGRAPHY_HAS_ED25519_SIGN, + CRYPTOGRAPHY_HAS_ED448_SIGN, + CRYPTOGRAPHY_HAS_RSA_SIGN, + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_privatekey, +) + +from ansible.module_utils._text import to_native, to_bytes +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + + +class SignatureBase(OpenSSLObject): + + def __init__(self, module, backend): + super(SignatureBase, self).__init__( + path=module.params['path'], + state='present', + force=False, + check_mode=module.check_mode + ) + + self.backend = backend + + self.privatekey_path = module.params['privatekey_path'] + self.privatekey_content = module.params['privatekey_content'] + if self.privatekey_content is not None: + self.privatekey_content = self.privatekey_content.encode('utf-8') + self.privatekey_passphrase = module.params['privatekey_passphrase'] + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + +# Implementation with using pyOpenSSL +class SignaturePyOpenSSL(SignatureBase): + + def __init__(self, module, backend): + super(SignaturePyOpenSSL, self).__init__(module, backend) + + def run(self): + + result = dict() + + try: + with open(self.path, "rb") as f: + _in = f.read() + + private_key = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + + signature = OpenSSL.crypto.sign(private_key, _in, "sha256") + result['signature'] = base64.b64encode(signature) + return result + except Exception as e: + raise OpenSSLObjectError(e) + + +# Implementation with using cryptography +class SignatureCryptography(SignatureBase): + + def __init__(self, module, backend): + super(SignatureCryptography, self).__init__(module, backend) + + def run(self): + _padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15() + _hash = cryptography.hazmat.primitives.hashes.SHA256() + + result = dict() + + try: + with open(self.path, "rb") as f: + _in = f.read() + + private_key = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend=self.backend, + ) + + signature = None + + if CRYPTOGRAPHY_HAS_DSA_SIGN: + if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey): + signature = private_key.sign(_in, _hash) + + if CRYPTOGRAPHY_HAS_EC_SIGN: + if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey): + signature = private_key.sign(_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash)) + + if CRYPTOGRAPHY_HAS_ED25519_SIGN: + if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey): + signature = private_key.sign(_in) + + if CRYPTOGRAPHY_HAS_ED448_SIGN: + if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey): + signature = private_key.sign(_in) + + if CRYPTOGRAPHY_HAS_RSA_SIGN: + if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey): + signature = private_key.sign(_in, _padding, _hash) + + if signature is None: + self.module.fail_json( + msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION) + ) + + result['signature'] = base64.b64encode(signature) + return result + + except Exception as e: + raise OpenSSLObjectError(e) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + privatekey_path=dict(type='path'), + privatekey_content=dict(type='str', no_log=True), + privatekey_passphrase=dict(type='str', no_log=True), + path=dict(type='path', required=True), + select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'), + ), + mutually_exclusive=( + ['privatekey_path', 'privatekey_content'], + ), + required_one_of=( + ['privatekey_path', 'privatekey_content'], + ), + supports_check_mode=True, + ) + + if not os.path.isfile(module.params['path']): + module.fail_json( + name=module.params['path'], + msg='The file {0} does not exist'.format(module.params['path']) + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # Decision + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + try: + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + _sign = SignaturePyOpenSSL(module, backend) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + _sign = SignatureCryptography(module, backend) + + result = _sign.run() + + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature_info.py new file mode 100644 index 00000000..d7f44106 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/openssl_signature_info.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: openssl_signature_info +version_added: 1.1.0 +short_description: Verify signatures with openssl +description: + - This module allows one to verify a signature for a file by a certificate. + - The module can use the cryptography Python library, or the pyOpenSSL Python + library. By default, it tries to detect which one is available. This can be + overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend + was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. +requirements: + - Either cryptography >= 1.4 (some key types require newer versions) + - Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend) +author: + - Patrick Pichler (@aveexy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + path: + description: + - The signed file to verify. + - This file will only be read and not modified. + type: path + required: true + certificate_path: + description: + - The path to the certificate used to verify the signature. + - Either I(certificate_path) or I(certificate_content) must be specified, but not both. + type: path + certificate_content: + description: + - The content of the certificate used to verify the signature. + - Either I(certificate_path) or I(certificate_content) must be specified, but not both. + type: str + signature: + description: Base64 encoded signature. + type: str + required: true + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] +notes: + - | + When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version: + RSA keys: C(cryptography) >= 1.4 + DSA and ECDSA keys: C(cryptography) >= 1.5 + ed448 and ed25519 keys: C(cryptography) >= 2.6 + - Supports C(check_mode). +seealso: + - module: community.crypto.openssl_signature + - module: community.crypto.x509_certificate +''' + +EXAMPLES = r''' +- name: Sign example file + community.crypto.openssl_signature: + privatekey_path: private.key + path: /tmp/example_file + register: sig + +- name: Verify signature of example file + community.crypto.openssl_signature_info: + certificate_path: cert.pem + path: /tmp/example_file + signature: "{{ sig.signature }}" + register: verify + +- name: Make sure the signature is valid + assert: + that: + - verify.valid +''' + +RETURN = r''' +valid: + description: C(true) means the signature was valid for the given file, C(false) means it was not. + returned: success + type: bool +''' + +import os +import traceback +from distutils.version import LooseVersion +import base64 + +MINIMAL_PYOPENSSL_VERSION = '0.11' +MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + import cryptography.hazmat.primitives.asymmetric.padding + import cryptography.hazmat.primitives.hashes + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + CRYPTOGRAPHY_HAS_DSA_SIGN, + CRYPTOGRAPHY_HAS_EC_SIGN, + CRYPTOGRAPHY_HAS_ED25519_SIGN, + CRYPTOGRAPHY_HAS_ED448_SIGN, + CRYPTOGRAPHY_HAS_RSA_SIGN, + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_certificate, +) + +from ansible.module_utils._text import to_native, to_bytes +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + + +class SignatureInfoBase(OpenSSLObject): + + def __init__(self, module, backend): + super(SignatureInfoBase, self).__init__( + path=module.params['path'], + state='present', + force=False, + check_mode=module.check_mode + ) + + self.backend = backend + + self.signature = module.params['signature'] + self.certificate_path = module.params['certificate_path'] + self.certificate_content = module.params['certificate_content'] + if self.certificate_content is not None: + self.certificate_content = self.certificate_content.encode('utf-8') + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + +# Implementation with using pyOpenSSL +class SignatureInfoPyOpenSSL(SignatureInfoBase): + + def __init__(self, module, backend): + super(SignatureInfoPyOpenSSL, self).__init__(module, backend) + + def run(self): + + result = dict() + + try: + with open(self.path, "rb") as f: + _in = f.read() + + _signature = base64.b64decode(self.signature) + certificate = load_certificate( + path=self.certificate_path, + content=self.certificate_content, + backend=self.backend, + ) + + try: + OpenSSL.crypto.verify(certificate, _signature, _in, 'sha256') + result['valid'] = True + except Exception: + result['valid'] = False + return result + except Exception as e: + raise OpenSSLObjectError(e) + + +# Implementation with using cryptography +class SignatureInfoCryptography(SignatureInfoBase): + + def __init__(self, module, backend): + super(SignatureInfoCryptography, self).__init__(module, backend) + + def run(self): + _padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15() + _hash = cryptography.hazmat.primitives.hashes.SHA256() + + result = dict() + + try: + with open(self.path, "rb") as f: + _in = f.read() + + _signature = base64.b64decode(self.signature) + certificate = load_certificate( + path=self.certificate_path, + content=self.certificate_content, + backend=self.backend, + ) + public_key = certificate.public_key() + verified = False + valid = False + + if CRYPTOGRAPHY_HAS_DSA_SIGN: + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey): + public_key.verify(_signature, _in, _hash) + verified = True + valid = True + except cryptography.exceptions.InvalidSignature: + verified = True + valid = False + + if CRYPTOGRAPHY_HAS_EC_SIGN: + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey): + public_key.verify(_signature, _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash)) + verified = True + valid = True + except cryptography.exceptions.InvalidSignature: + verified = True + valid = False + + if CRYPTOGRAPHY_HAS_ED25519_SIGN: + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey): + public_key.verify(_signature, _in) + verified = True + valid = True + except cryptography.exceptions.InvalidSignature: + verified = True + valid = False + + if CRYPTOGRAPHY_HAS_ED448_SIGN: + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey): + public_key.verify(_signature, _in) + verified = True + valid = True + except cryptography.exceptions.InvalidSignature: + verified = True + valid = False + + if CRYPTOGRAPHY_HAS_RSA_SIGN: + try: + if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey): + public_key.verify(_signature, _in, _padding, _hash) + verified = True + valid = True + except cryptography.exceptions.InvalidSignature: + verified = True + valid = False + + if not verified: + self.module.fail_json( + msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION) + ) + result['valid'] = valid + return result + + except Exception as e: + raise OpenSSLObjectError(e) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + certificate_path=dict(type='path'), + certificate_content=dict(type='str'), + path=dict(type='path', required=True), + signature=dict(type='str', required=True), + select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'), + ), + mutually_exclusive=( + ['certificate_path', 'certificate_content'], + ), + required_one_of=( + ['certificate_path', 'certificate_content'], + ), + supports_check_mode=True, + ) + + if not os.path.isfile(module.params['path']): + module.fail_json( + name=module.params['path'], + msg='The file {0} does not exist'.format(module.params['path']) + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detection what is possible + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # Decision + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Success? + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + try: + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + _sign = SignatureInfoPyOpenSSL(module, backend) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + _sign = SignatureInfoCryptography(module, backend) + + result = _sign.run() + + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate.py new file mode 100644 index 00000000..fb6fc2f5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate.py @@ -0,0 +1,555 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_certificate +short_description: Generate and/or check OpenSSL certificates +description: + - It implements a notion of provider (ie. C(selfsigned), C(ownca), C(acme), C(assertonly), C(entrust)) + for your certificate. + - "Please note that the module regenerates existing certificate if it does not match the module's + options, or if it seems to be corrupt. If you are concerned that this could overwrite + your existing certificate, consider using the I(backup) option." + - Note that this module was called C(openssl_certificate) when included directly in Ansible up to version 2.9. + When moved to the collection C(community.crypto), it was renamed to + M(community.crypto.x509_certificate). From Ansible 2.10 on, it can still be used by the + old short name (or by C(ansible.builtin.openssl_certificate)), which redirects to + C(community.crypto.x509_certificate). When using FQCNs or when using the + L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook) + keyword, the new name M(community.crypto.x509_certificate) should be used to avoid + a deprecation warning. +author: + - Yanis Guenane (@Spredzy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + state: + description: + - Whether the certificate should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + + path: + description: + - Remote absolute path where the generated certificate file should be created or is already located. + type: path + required: true + + provider: + description: + - Name of the provider to use to generate/retrieve the OpenSSL certificate. + - The C(assertonly) provider will not generate files and fail if the certificate file is missing. + - The C(assertonly) provider has been deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. + Please see the examples on how to emulate it with + M(community.crypto.x509_certificate_info), M(community.crypto.openssl_csr_info), + M(community.crypto.openssl_privatekey_info) and M(ansible.builtin.assert). + - "The C(entrust) provider was added for Ansible 2.9 and requires credentials for the + L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API." + - Required if I(state) is C(present). + type: str + choices: [ acme, assertonly, entrust, ownca, selfsigned ] + + return_content: + description: + - If set to C(yes), will return the (current or generated) certificate's content as I(certificate). + type: bool + default: no + version_added: '1.0.0' + + backup: + description: + - Create a backup file including a timestamp so you can get the original + certificate back if you overwrote it with a new one by accident. + - This is not used by the C(assertonly) provider. + - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0. + For alternatives, see the example on replacing C(assertonly). + type: bool + default: no + + csr_content: + version_added: '1.0.0' + privatekey_content: + version_added: '1.0.0' + acme_directory: + version_added: '1.0.0' + ownca_content: + version_added: '1.0.0' + ownca_privatekey_content: + version_added: '1.0.0' + +notes: +- Supports C(check_mode). + +seealso: +- module: community.crypto.x509_certificate_pipe + +extends_documentation_fragment: + - ansible.builtin.files + - community.crypto.module_certificate + - community.crypto.module_certificate.backend_acme_documentation + - community.crypto.module_certificate.backend_assertonly_documentation + - community.crypto.module_certificate.backend_entrust_documentation + - community.crypto.module_certificate.backend_ownca_documentation + - community.crypto.module_certificate.backend_selfsigned_documentation +''' + +EXAMPLES = r''' +- name: Generate a Self Signed OpenSSL certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + privatekey_path: /etc/ssl/private/ansible.com.pem + csr_path: /etc/ssl/csr/ansible.com.csr + provider: selfsigned + +- name: Generate an OpenSSL certificate signed with your own CA certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + ownca_path: /etc/ssl/crt/ansible_CA.crt + ownca_privatekey_path: /etc/ssl/private/ansible_CA.pem + provider: ownca + +- name: Generate a Let's Encrypt Certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: acme + acme_accountkey_path: /etc/ssl/private/ansible.com.pem + acme_challenge_path: /etc/ssl/challenges/ansible.com/ + +- name: Force (re-)generate a new Let's Encrypt Certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: acme + acme_accountkey_path: /etc/ssl/private/ansible.com.pem + acme_challenge_path: /etc/ssl/challenges/ansible.com/ + force: yes + +- name: Generate an Entrust certificate via the Entrust Certificate Services (ECS) API + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + provider: entrust + entrust_requester_name: Jo Doe + entrust_requester_email: jdoe@ansible.com + entrust_requester_phone: 555-555-5555 + entrust_cert_type: STANDARD_SSL + entrust_api_user: apiusername + entrust_api_key: a^lv*32!cd9LnT + entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt + entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-key.crt + entrust_api_specification_path: /etc/ssl/entrust/api-docs/cms-api-2.1.0.yaml + +# The following example shows one assertonly usage using all existing options for +# assertonly, and shows how to emulate the behavior with the x509_certificate_info, +# openssl_csr_info, openssl_privatekey_info and assert modules: +- name: Usage of assertonly with all existing options + community.crypto.x509_certificate: + provider: assertonly + path: /etc/ssl/crt/ansible.com.crt + csr_path: /etc/ssl/csr/ansible.com.csr + privatekey_path: /etc/ssl/csr/ansible.com.key + signature_algorithms: + - sha256WithRSAEncryption + - sha512WithRSAEncryption + subject: + commonName: ansible.com + subject_strict: yes + issuer: + commonName: ansible.com + issuer_strict: yes + has_expired: no + version: 3 + key_usage: + - Data Encipherment + key_usage_strict: yes + extended_key_usage: + - DVCS + extended_key_usage_strict: yes + subject_alt_name: + - dns:ansible.com + subject_alt_name_strict: yes + not_before: 20190331202428Z + not_after: 20190413202428Z + valid_at: "+1d10h" + invalid_at: 20200331202428Z + valid_in: 10 # in ten seconds + +- name: Get certificate information + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + # for valid_at, invalid_at and valid_in + valid_at: + one_day_ten_hours: "+1d10h" + fixed_timestamp: 20200331202428Z + ten_seconds: "+10" + register: result + +- name: Get CSR information + community.crypto.openssl_csr_info: + # Verifies that the CSR signature is valid; module will fail if not + path: /etc/ssl/csr/ansible.com.csr + register: result_csr + +- name: Get private key information + community.crypto.openssl_privatekey_info: + path: /etc/ssl/csr/ansible.com.key + register: result_privatekey + +- assert: + that: + # When private key is specified for assertonly, this will be checked: + - result.public_key == result_privatekey.public_key + # When CSR is specified for assertonly, this will be checked: + - result.public_key == result_csr.public_key + - result.subject_ordered == result_csr.subject_ordered + - result.extensions_by_oid == result_csr.extensions_by_oid + # signature_algorithms check + - "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha512WithRSAEncryption'" + # subject and subject_strict + - "result.subject.commonName == 'ansible.com'" + - "result.subject | length == 1" # the number must be the number of entries you check for + # issuer and issuer_strict + - "result.issuer.commonName == 'ansible.com'" + - "result.issuer | length == 1" # the number must be the number of entries you check for + # has_expired + - not result.expired + # version + - result.version == 3 + # key_usage and key_usage_strict + - "'Data Encipherment' in result.key_usage" + - "result.key_usage | length == 1" # the number must be the number of entries you check for + # extended_key_usage and extended_key_usage_strict + - "'DVCS' in result.extended_key_usage" + - "result.extended_key_usage | length == 1" # the number must be the number of entries you check for + # subject_alt_name and subject_alt_name_strict + - "'dns:ansible.com' in result.subject_alt_name" + - "result.subject_alt_name | length == 1" # the number must be the number of entries you check for + # not_before and not_after + - "result.not_before == '20190331202428Z'" + - "result.not_after == '20190413202428Z'" + # valid_at, invalid_at and valid_in + - "result.valid_at.one_day_ten_hours" # for valid_at + - "not result.valid_at.fixed_timestamp" # for invalid_at + - "result.valid_at.ten_seconds" # for valid_in + +# Examples for some checks one could use the assertonly provider for: +# (Please note that assertonly has been deprecated!) + +# How to use the assertonly provider to implement and trigger your own custom certificate generation workflow: +- name: Check if a certificate is currently still valid, ignoring failures + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + has_expired: no + ignore_errors: yes + register: validity_check + +- name: Run custom task(s) to get a new, valid certificate in case the initial check failed + command: superspecialSSL recreate /etc/ssl/crt/example.com.crt + when: validity_check.failed + +- name: Check the new certificate again for validity with the same parameters, this time failing the play if it is still invalid + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + has_expired: no + when: validity_check.failed + +# Some other checks that assertonly could be used for: +- name: Verify that an existing certificate was issued by the Let's Encrypt CA and is currently still valid + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + issuer: + O: Let's Encrypt + has_expired: no + +- name: Ensure that a certificate uses a modern signature algorithm (no SHA1, MD5 or DSA) + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + signature_algorithms: + - sha224WithRSAEncryption + - sha256WithRSAEncryption + - sha384WithRSAEncryption + - sha512WithRSAEncryption + - sha224WithECDSAEncryption + - sha256WithECDSAEncryption + - sha384WithECDSAEncryption + - sha512WithECDSAEncryption + +- name: Ensure that the existing certificate belongs to the specified private key + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + privatekey_path: /etc/ssl/private/example.com.pem + provider: assertonly + +- name: Ensure that the existing certificate is still valid at the winter solstice 2017 + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + valid_at: 20171221162800Z + +- name: Ensure that the existing certificate is still valid 2 weeks (1209600 seconds) from now + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + valid_in: 1209600 + +- name: Ensure that the existing certificate is only used for digital signatures and encrypting other keys + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + key_usage: + - digitalSignature + - keyEncipherment + key_usage_strict: true + +- name: Ensure that the existing certificate can be used for client authentication + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + extended_key_usage: + - clientAuth + +- name: Ensure that the existing certificate can only be used for client authentication and time stamping + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + extended_key_usage: + - clientAuth + - 1.3.6.1.5.5.7.3.8 + extended_key_usage_strict: true + +- name: Ensure that the existing certificate has a certain domain in its subjectAltName + community.crypto.x509_certificate: + path: /etc/ssl/crt/example.com.crt + provider: assertonly + subject_alt_name: + - www.example.com + - test.example.com +''' + +RETURN = r''' +filename: + description: Path to the generated certificate. + returned: changed or success + type: str + sample: /etc/ssl/crt/www.ansible.com.crt +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~ +certificate: + description: The (current or generated) certificate's content. + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str + version_added: '1.0.0' +''' + + +import os + +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + select_backend, + get_certificate_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_acme import ( + AcmeCertificateProvider, + add_acme_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_assertonly import ( + AssertOnlyCertificateProvider, + add_assertonly_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_entrust import ( + EntrustCertificateProvider, + add_entrust_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_ownca import ( + OwnCACertificateProvider, + add_ownca_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_selfsigned import ( + SelfSignedCertificateProvider, + add_selfsigned_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + load_file_if_exists, + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, +) + + +class CertificateAbsent(OpenSSLObject): + def __init__(self, module): + super(CertificateAbsent, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.module = module + self.return_content = module.params['return_content'] + self.backup = module.params['backup'] + self.backup_file = None + + def generate(self, module): + pass + + def remove(self, module): + if self.backup: + self.backup_file = module.backup_local(self.path) + super(CertificateAbsent, self).remove(module) + + def dump(self, check_mode=False): + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.module.params['privatekey_path'], + 'csr': self.module.params['csr_path'] + } + if self.backup_file: + result['backup_file'] = self.backup_file + if self.return_content: + result['certificate'] = None + + return result + + +class GenericCertificate(OpenSSLObject): + """Retrieve a certificate using the given module backend.""" + def __init__(self, module, module_backend): + super(GenericCertificate, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + self.module = module + self.return_content = module.params['return_content'] + self.backup = module.params['backup'] + self.backup_file = None + + self.module_backend = module_backend + self.module_backend.set_existing(load_file_if_exists(self.path, module)) + + def generate(self, module): + if self.module_backend.needs_regeneration(): + if not self.check_mode: + self.module_backend.generate_certificate() + result = self.module_backend.get_certificate_data() + if self.backup: + self.backup_file = module.backup_local(self.path) + write_file(module, result) + self.changed = True + + file_args = module.load_file_common_arguments(module.params) + self.changed = module.set_fs_attributes_if_different(file_args, self.changed) + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + return super(GenericCertificate, self).check(module, perms_required) and not self.module_backend.needs_regeneration() + + def dump(self, check_mode=False): + result = self.module_backend.dump(include_certificate=self.return_content) + result.update({ + 'changed': self.changed, + 'filename': self.path, + }) + if self.backup_file: + result['backup_file'] = self.backup_file + return result + + +def main(): + argument_spec = get_certificate_argument_spec() + add_acme_provider_to_argument_spec(argument_spec) + add_assertonly_provider_to_argument_spec(argument_spec) + add_entrust_provider_to_argument_spec(argument_spec) + add_ownca_provider_to_argument_spec(argument_spec) + add_selfsigned_provider_to_argument_spec(argument_spec) + argument_spec.argument_spec.update(dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + path=dict(type='path', required=True), + backup=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + )) + argument_spec.required_if.append(['state', 'present', ['provider']]) + module = argument_spec.create_ansible_module( + add_file_common_args=True, + supports_check_mode=True, + ) + + if module._name == 'community.crypto.openssl_certificate': + module.deprecate("The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'", + version='2.0.0', collection_name='community.crypto') + + try: + if module.params['state'] == 'absent': + certificate = CertificateAbsent(module) + + if module.check_mode: + result = certificate.dump(check_mode=True) + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + certificate.remove(module) + + else: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + provider = module.params['provider'] + provider_map = { + 'acme': AcmeCertificateProvider, + 'assertonly': AssertOnlyCertificateProvider, + 'entrust': EntrustCertificateProvider, + 'ownca': OwnCACertificateProvider, + 'selfsigned': SelfSignedCertificateProvider, + } + + backend = module.params['select_crypto_backend'] + module_backend = select_backend(module, backend, provider_map[provider]()) + certificate = GenericCertificate(module, module_backend) + certificate.generate(module) + + result = certificate.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py new file mode 100644 index 00000000..3e7bfc71 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py @@ -0,0 +1,904 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_certificate_info +short_description: Provide information of OpenSSL X.509 certificates +description: + - This module allows one to query information on OpenSSL certificates. + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the + cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with + C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 + and will be removed in community.crypto 2.0.0. + - Note that this module was called C(openssl_certificate_info) when included directly in Ansible + up to version 2.9. When moved to the collection C(community.crypto), it was renamed to + M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the + old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to + C(community.crypto.x509_certificate_info). When using FQCNs or when using the + L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook) + keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid + a deprecation warning. +requirements: + - PyOpenSSL >= 0.15 or cryptography >= 1.6 +author: + - Felix Fontein (@felixfontein) + - Yanis Guenane (@Spredzy) + - Markus Teufelberger (@MarkusTeufelberger) +options: + path: + description: + - Remote absolute path where the certificate file is loaded from. + - Either I(path) or I(content) must be specified, but not both. + type: path + content: + description: + - Content of the X.509 certificate in PEM format. + - Either I(path) or I(content) must be specified, but not both. + type: str + version_added: '1.0.0' + valid_at: + description: + - A dict of names mapping to time specifications. Every time specified here + will be checked whether the certificate is valid at this point. See the + C(valid_at) return value for informations on the result. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (in other words, pattern C(YYYYMMDDHHMMSSZ)). + Note that all timestamps will be treated as being in UTC. + type: dict + select_crypto_backend: + description: + - Determines which crypto backend to use. + - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). + - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. + - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. + - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0. + From that point on, only the C(cryptography) backend will be available. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + +notes: + - All timestamp values are provided in ASN.1 TIME format, in other words, following the C(YYYYMMDDHHMMSSZ) pattern. + They are all in UTC. + - Supports C(check_mode). +seealso: +- module: community.crypto.x509_certificate +- module: community.crypto.x509_certificate_pipe +''' + +EXAMPLES = r''' +- name: Generate a Self Signed OpenSSL certificate + community.crypto.x509_certificate: + path: /etc/ssl/crt/ansible.com.crt + privatekey_path: /etc/ssl/private/ansible.com.pem + csr_path: /etc/ssl/csr/ansible.com.csr + provider: selfsigned + + +# Get information on the certificate + +- name: Get information on generated certificate + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + register: result + +- name: Dump information + ansible.builtin.debug: + var: result + + +# Check whether the certificate is valid or not valid at certain times, fail +# if this is not the case. The first task (x509_certificate_info) collects +# the information, and the second task (assert) validates the result and +# makes the playbook fail in case something is not as expected. + +- name: Test whether that certificate is valid tomorrow and/or in three weeks + community.crypto.x509_certificate_info: + path: /etc/ssl/crt/ansible.com.crt + valid_at: + point_1: "+1d" + point_2: "+3w" + register: result + +- name: Validate that certificate is valid tomorrow, but not in three weeks + assert: + that: + - result.valid_at.point_1 # valid in one day + - not result.valid_at.point_2 # not valid in three weeks +''' + +RETURN = r''' +expired: + description: Whether the certificate is expired (in other words, C(notAfter) is in the past). + returned: success + type: bool +basic_constraints: + description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[CA:TRUE, pathlen:1]" +basic_constraints_critical: + description: Whether the C(basic_constraints) extension is critical. + returned: success + type: bool +extended_key_usage: + description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[Biometric Info, DVCS, Time Stamping]" +extended_key_usage_critical: + description: Whether the C(extended_key_usage) extension is critical. + returned: success + type: bool +extensions_by_oid: + description: Returns a dictionary for every extension OID. + returned: success + type: dict + contains: + critical: + description: Whether the extension is critical. + returned: success + type: bool + value: + description: The Base64 encoded value (in DER format) of the extension. + returned: success + type: str + sample: "MAMCAQU=" + sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' +key_usage: + description: Entries in the C(key_usage) extension, or C(none) if extension is not present. + returned: success + type: str + sample: "[Key Agreement, Data Encipherment]" +key_usage_critical: + description: Whether the C(key_usage) extension is critical. + returned: success + type: bool +subject_alt_name: + description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present. + returned: success + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +subject_alt_name_critical: + description: Whether the C(subject_alt_name) extension is critical. + returned: success + type: bool +ocsp_must_staple: + description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise. + returned: success + type: bool +ocsp_must_staple_critical: + description: Whether the C(ocsp_must_staple) extension is critical. + returned: success + type: bool +issuer: + description: + - The certificate's issuer. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' +issuer_ordered: + description: The certificate's issuer as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' +subject: + description: + - The certificate's subject as a dictionary. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' +subject_ordered: + description: The certificate's subject as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' +not_after: + description: C(notAfter) date as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +not_before: + description: C(notBefore) date as ASN.1 TIME. + returned: success + type: str + sample: 20190331202428Z +public_key: + description: Certificate's public key in PEM format. + returned: success + type: str + sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." +public_key_fingerprints: + description: + - Fingerprints of certificate's public key. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." +fingerprints: + description: + - Fingerprints of the DER-encoded form of the whole certificate. + - For every hash algorithm available, the fingerprint is computed. + returned: success + type: dict + sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', + 'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." + version_added: 1.2.0 +signature_algorithm: + description: The signature algorithm used to sign the certificate. + returned: success + type: str + sample: sha256WithRSAEncryption +serial_number: + description: The certificate's serial number. + returned: success + type: int + sample: 1234 +version: + description: The certificate version. + returned: success + type: int + sample: 3 +valid_at: + description: For every time stamp provided in the I(valid_at) option, a + boolean whether the certificate is valid at that point in time + or not. + returned: success + type: dict +subject_key_identifier: + description: + - The certificate's subject key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(SubjectKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_key_identifier: + description: + - The certificate's authority key identifier. + - The identifier is returned in hexadecimal, with C(:) used to separate bytes. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: str + sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33' +authority_cert_issuer: + description: + - The certificate's authority cert issuer as a list of general names. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: list + elements: str + sample: "[DNS:www.ansible.com, IP:1.2.3.4]" +authority_cert_serial_number: + description: + - The certificate's authority cert serial number. + - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. + returned: success and if the pyOpenSSL backend is I(not) used + type: int + sample: '12345' +ocsp_uri: + description: The OCSP responder URI, if included in the certificate. Will be + C(none) if no OCSP responder URI is included. + returned: success + type: str +''' + + +import abc +import binascii +import datetime +import os +import re +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_native, to_text, to_bytes + +from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + get_relative_time_option, + load_certificate, + get_fingerprint_of_bytes, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_decode_name, + cryptography_get_extensions_from_cert, + cryptography_oid_to_name, + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import ( + pyopenssl_get_extensions_from_cert, + pyopenssl_normalize_name, + pyopenssl_normalize_name_attribute, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' +MINIMAL_PYOPENSSL_VERSION = '0.15' + +PYOPENSSL_IMP_ERR = None +try: + import OpenSSL + from OpenSSL import crypto + PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000: + # OpenSSL 1.1.0 or newer + OPENSSL_MUST_STAPLE_NAME = b"tlsfeature" + OPENSSL_MUST_STAPLE_VALUE = b"status_request" + else: + # OpenSSL 1.0.x or older + OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" + OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" +except ImportError: + PYOPENSSL_IMP_ERR = traceback.format_exc() + PYOPENSSL_FOUND = False +else: + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.primitives import serialization + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" + + +class CertificateInfo(OpenSSLObject): + def __init__(self, module, backend): + super(CertificateInfo, self).__init__( + module.params['path'] or '', + 'present', + False, + module.check_mode, + ) + self.backend = backend + self.module = module + self.content = module.params['content'] + if self.content is not None: + self.content = self.content.encode('utf-8') + + self.valid_at = module.params['valid_at'] + if self.valid_at: + for k, v in self.valid_at.items(): + if not isinstance(v, string_types): + self.module.fail_json( + msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v)) + ) + self.valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k)) + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + @abc.abstractmethod + def _get_der_bytes(self): + pass + + @abc.abstractmethod + def _get_signature_algorithm(self): + pass + + @abc.abstractmethod + def _get_subject_ordered(self): + pass + + @abc.abstractmethod + def _get_issuer_ordered(self): + pass + + @abc.abstractmethod + def _get_version(self): + pass + + @abc.abstractmethod + def _get_key_usage(self): + pass + + @abc.abstractmethod + def _get_extended_key_usage(self): + pass + + @abc.abstractmethod + def _get_basic_constraints(self): + pass + + @abc.abstractmethod + def _get_ocsp_must_staple(self): + pass + + @abc.abstractmethod + def _get_subject_alt_name(self): + pass + + @abc.abstractmethod + def _get_not_before(self): + pass + + @abc.abstractmethod + def _get_not_after(self): + pass + + @abc.abstractmethod + def _get_public_key(self, binary): + pass + + @abc.abstractmethod + def _get_subject_key_identifier(self): + pass + + @abc.abstractmethod + def _get_authority_key_identifier(self): + pass + + @abc.abstractmethod + def _get_serial_number(self): + pass + + @abc.abstractmethod + def _get_all_extensions(self): + pass + + @abc.abstractmethod + def _get_ocsp_uri(self): + pass + + def get_info(self): + result = dict() + self.cert = load_certificate(self.path, content=self.content, backend=self.backend) + + result['signature_algorithm'] = self._get_signature_algorithm() + subject = self._get_subject_ordered() + issuer = self._get_issuer_ordered() + result['subject'] = dict() + for k, v in subject: + result['subject'][k] = v + result['subject_ordered'] = subject + result['issuer'] = dict() + for k, v in issuer: + result['issuer'][k] = v + result['issuer_ordered'] = issuer + result['version'] = self._get_version() + result['key_usage'], result['key_usage_critical'] = self._get_key_usage() + result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage() + result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints() + result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple() + result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name() + + not_before = self._get_not_before() + not_after = self._get_not_after() + result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT) + result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT) + result['expired'] = not_after < datetime.datetime.utcnow() + + result['valid_at'] = dict() + if self.valid_at: + for k, v in self.valid_at.items(): + result['valid_at'][k] = not_before <= v <= not_after + + result['public_key'] = self._get_public_key(binary=False) + pk = self._get_public_key(binary=True) + result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict() + + result['fingerprints'] = get_fingerprint_of_bytes(self._get_der_bytes()) + + if self.backend != 'pyopenssl': + ski = self._get_subject_key_identifier() + if ski is not None: + ski = to_native(binascii.hexlify(ski)) + ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)]) + result['subject_key_identifier'] = ski + + aki, aci, acsn = self._get_authority_key_identifier() + if aki is not None: + aki = to_native(binascii.hexlify(aki)) + aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)]) + result['authority_key_identifier'] = aki + result['authority_cert_issuer'] = aci + result['authority_cert_serial_number'] = acsn + + result['serial_number'] = self._get_serial_number() + result['extensions_by_oid'] = self._get_all_extensions() + result['ocsp_uri'] = self._get_ocsp_uri() + + return result + + +class CertificateInfoCryptography(CertificateInfo): + """Validate the supplied cert, using the cryptography backend""" + def __init__(self, module): + super(CertificateInfoCryptography, self).__init__(module, 'cryptography') + + def _get_der_bytes(self): + return self.cert.public_bytes(serialization.Encoding.DER) + + def _get_signature_algorithm(self): + return cryptography_oid_to_name(self.cert.signature_algorithm_oid) + + def _get_subject_ordered(self): + result = [] + for attribute in self.cert.subject: + result.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + return result + + def _get_issuer_ordered(self): + result = [] + for attribute in self.cert.issuer: + result.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + return result + + def _get_version(self): + if self.cert.version == x509.Version.v1: + return 1 + if self.cert.version == x509.Version.v3: + return 3 + return "unknown" + + def _get_key_usage(self): + try: + current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage) + current_key_usage = current_key_ext.value + key_usage = dict( + digital_signature=current_key_usage.digital_signature, + content_commitment=current_key_usage.content_commitment, + key_encipherment=current_key_usage.key_encipherment, + data_encipherment=current_key_usage.data_encipherment, + key_agreement=current_key_usage.key_agreement, + key_cert_sign=current_key_usage.key_cert_sign, + crl_sign=current_key_usage.crl_sign, + encipher_only=False, + decipher_only=False, + ) + if key_usage['key_agreement']: + key_usage.update(dict( + encipher_only=current_key_usage.encipher_only, + decipher_only=current_key_usage.decipher_only + )) + + key_usage_names = dict( + digital_signature='Digital Signature', + content_commitment='Non Repudiation', + key_encipherment='Key Encipherment', + data_encipherment='Data Encipherment', + key_agreement='Key Agreement', + key_cert_sign='Certificate Sign', + crl_sign='CRL Sign', + encipher_only='Encipher Only', + decipher_only='Decipher Only', + ) + return sorted([ + key_usage_names[name] for name, value in key_usage.items() if value + ]), current_key_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_extended_key_usage(self): + try: + ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) + return sorted([ + cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value + ]), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_basic_constraints(self): + try: + ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints) + result = [] + result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE')) + if ext_keyusage_ext.value.path_length is not None: + result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length)) + return sorted(result), ext_keyusage_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_ocsp_must_staple(self): + try: + try: + # This only works with cryptography >= 2.1 + tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature) + value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value + except AttributeError as dummy: + # Fallback for cryptography < 2.1 + oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24") + tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid) + value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05" + return value, tlsfeature_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_subject_alt_name(self): + try: + san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) + result = [cryptography_decode_name(san) for san in san_ext.value] + return result, san_ext.critical + except cryptography.x509.ExtensionNotFound: + return None, False + + def _get_not_before(self): + return self.cert.not_valid_before + + def _get_not_after(self): + return self.cert.not_valid_after + + def _get_public_key(self, binary): + return self.cert.public_key().public_bytes( + serialization.Encoding.DER if binary else serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def _get_subject_key_identifier(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + return ext.value.digest + except cryptography.x509.ExtensionNotFound: + return None + + def _get_authority_key_identifier(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) + issuer = None + if ext.value.authority_cert_issuer is not None: + issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer] + return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number + except cryptography.x509.ExtensionNotFound: + return None, None, None + + def _get_serial_number(self): + return cryptography_serial_number_of_cert(self.cert) + + def _get_all_extensions(self): + return cryptography_get_extensions_from_cert(self.cert) + + def _get_ocsp_uri(self): + try: + ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess) + for desc in ext.value: + if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP: + if isinstance(desc.access_location, x509.UniformResourceIdentifier): + return desc.access_location.value + except x509.ExtensionNotFound as dummy: + pass + return None + + +class CertificateInfoPyOpenSSL(CertificateInfo): + """validate the supplied certificate.""" + + def __init__(self, module): + super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl') + + def _get_der_bytes(self): + return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert) + + def _get_signature_algorithm(self): + return to_text(self.cert.get_signature_algorithm()) + + def __get_name(self, name): + result = [] + for sub in name.get_components(): + result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])]) + return result + + def _get_subject_ordered(self): + return self.__get_name(self.cert.get_subject()) + + def _get_issuer_ordered(self): + return self.__get_name(self.cert.get_issuer()) + + def _get_version(self): + # Version numbers in certs are off by one: + # v1: 0, v2: 1, v3: 2 ... + return self.cert.get_version() + 1 + + def _get_extension(self, short_name): + for extension_idx in range(0, self.cert.get_extension_count()): + extension = self.cert.get_extension(extension_idx) + if extension.get_short_name() == short_name: + result = [ + pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',') + ] + return sorted(result), bool(extension.get_critical()) + return None, False + + def _get_key_usage(self): + return self._get_extension(b'keyUsage') + + def _get_extended_key_usage(self): + return self._get_extension(b'extendedKeyUsage') + + def _get_basic_constraints(self): + return self._get_extension(b'basicConstraints') + + def _get_ocsp_must_staple(self): + extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())] + oms_ext = [ + ext for ext in extensions + if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE + ] + if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000: + # Older versions of libssl don't know about OCSP Must Staple + oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05']) + if oms_ext: + return True, bool(oms_ext[0].get_critical()) + else: + return None, False + + def _get_subject_alt_name(self): + for extension_idx in range(0, self.cert.get_extension_count()): + extension = self.cert.get_extension(extension_idx) + if extension.get_short_name() == b'subjectAltName': + result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in + to_text(extension, errors='surrogate_or_strict').split(', ')] + return result, bool(extension.get_critical()) + return None, False + + def _get_not_before(self): + time_string = to_native(self.cert.get_notBefore()) + return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + + def _get_not_after(self): + time_string = to_native(self.cert.get_notAfter()) + return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") + + def _get_public_key(self, binary): + try: + return crypto.dump_publickey( + crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM, + self.cert.get_pubkey() + ) + except AttributeError: + try: + # pyOpenSSL < 16.0: + bio = crypto._new_mem_buf() + if binary: + rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey) + else: + rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey) + if rc != 1: + crypto._raise_current_error() + return crypto._bio_to_string(bio) + except AttributeError: + self.module.warn('Your pyOpenSSL version does not support dumping public keys. ' + 'Please upgrade to version 16.0 or newer, or use the cryptography backend.') + + def _get_subject_key_identifier(self): + # Won't be implemented + return None + + def _get_authority_key_identifier(self): + # Won't be implemented + return None, None, None + + def _get_serial_number(self): + return self.cert.get_serial_number() + + def _get_all_extensions(self): + return pyopenssl_get_extensions_from_cert(self.cert) + + def _get_ocsp_uri(self): + for i in range(self.cert.get_extension_count()): + ext = self.cert.get_extension(i) + if ext.get_short_name() == b'authorityInfoAccess': + v = str(ext) + m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE) + if m: + return m.group(1) + return None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='path'), + content=dict(type='str'), + valid_at=dict(type='dict'), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), + ), + required_one_of=( + ['path', 'content'], + ), + mutually_exclusive=( + ['path', 'content'], + ), + supports_check_mode=True, + ) + if module._name == 'community.crypto.openssl_certificate_info': + module.deprecate("The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'", + version='2.0.0', collection_name='community.crypto') + + try: + if module.params['path'] is not None: + base_dir = os.path.dirname(module.params['path']) or '.' + if not os.path.isdir(base_dir): + module.fail_json( + name=base_dir, + msg='The directory %s does not exist or the file is not a directory' % base_dir + ) + + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect any of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), + exception=PYOPENSSL_IMP_ERR) + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') + + module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', + version='2.0.0', collection_name='community.crypto') + certificate = CertificateInfoPyOpenSSL(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + certificate = CertificateInfoCryptography(module) + + result = certificate.get_info() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_pipe.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_pipe.py new file mode 100644 index 00000000..07369798 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_certificate_pipe.py @@ -0,0 +1,210 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> +# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> +# Copyright: (2) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_certificate_pipe +short_description: Generate and/or check OpenSSL certificates +version_added: 1.3.0 +description: + - It implements a notion of provider (ie. C(selfsigned), C(ownca), C(entrust)) + for your certificate. + - "Please note that the module regenerates an existing certificate if it does not match the module's + options, or if it seems to be corrupt. If you are concerned that this could overwrite + your existing certificate, consider using the I(backup) option." +author: + - Yanis Guenane (@Spredzy) + - Markus Teufelberger (@MarkusTeufelberger) + - Felix Fontein (@felixfontein) +options: + provider: + description: + - Name of the provider to use to generate/retrieve the OpenSSL certificate. + - "The C(entrust) provider requires credentials for the + L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API." + type: str + choices: [ entrust, ownca, selfsigned ] + required: true + + content: + description: + - The existing certificate. + type: str + +seealso: +- module: community.crypto.x509_certificate + +notes: +- Supports C(check_mode). + +extends_documentation_fragment: + - community.crypto.module_certificate + - community.crypto.module_certificate.backend_entrust_documentation + - community.crypto.module_certificate.backend_ownca_documentation + - community.crypto.module_certificate.backend_selfsigned_documentation +''' + +EXAMPLES = r''' +- name: Generate a Self Signed OpenSSL certificate + community.crypto.x509_certificate_pipe: + provider: selfsigned + privatekey_path: /etc/ssl/private/ansible.com.pem + csr_path: /etc/ssl/csr/ansible.com.csr + register: result +- name: Print the certificate + ansible.builtin.debug: + var: result.certificate + +# In the following example, both CSR and certificate file are stored on the +# machine where ansible-playbook is executed, while the OwnCA data (certificate, +# private key) are stored on the remote machine. + +- name: (1/2) Generate an OpenSSL Certificate with the CSR provided inline + community.crypto.x509_certificate_pipe: + provider: ownca + content: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.crt') }}" + csr_content: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.csr') }}" + ownca_cert: /path/to/ca_cert.crt + ownca_privatekey: /path/to/ca_cert.key + ownca_privatekey_passphrase: hunter2 + register: result + +- name: (2/2) Store certificate + ansible.builtin.copy: + dest: /etc/ssl/csr/www.ansible.com.crt + content: "{{ result.certificate }}" + delegate_to: localhost + when: result is changed + +# In the following example, the certificate from another machine is signed by +# our OwnCA whose private key and certificate are only available on this +# machine (where ansible-playbook is executed), without having to write +# the certificate file to disk on localhost. The CSR could have been +# provided by community.crypto.openssl_csr_pipe earlier, or also have been +# read from the remote machine. + +- name: (1/3) Read certificate's contents from remote machine + ansible.builtin.slurp: + src: /etc/ssl/csr/www.ansible.com.crt + register: certificate_content + +- name: (2/3) Generate an OpenSSL Certificate with the CSR provided inline + community.crypto.x509_certificate_pipe: + provider: ownca + content: "{{ certificate_content.content | b64decode }}" + csr_content: "{{ the_csr }}" + ownca_cert: /path/to/ca_cert.crt + ownca_privatekey: /path/to/ca_cert.key + ownca_privatekey_passphrase: hunter2 + delegate_to: localhost + register: result + +- name: (3/3) Store certificate + ansible.builtin.copy: + dest: /etc/ssl/csr/www.ansible.com.crt + content: "{{ result.certificate }}" + when: result is changed +''' + +RETURN = r''' +certificate: + description: The (current or generated) certificate's content. + returned: changed or success + type: str +''' + + +import os + +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( + select_backend, + get_certificate_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_entrust import ( + EntrustCertificateProvider, + add_entrust_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_ownca import ( + OwnCACertificateProvider, + add_ownca_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_selfsigned import ( + SelfSignedCertificateProvider, + add_selfsigned_provider_to_argument_spec, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + + +class GenericCertificate(object): + """Retrieve a certificate using the given module backend.""" + def __init__(self, module, module_backend): + self.check_mode = module.check_mode + self.module_backend = module_backend + self.changed = False + if module.params['content'] is not None: + self.module_backend.set_existing(module.params['content'].encode('utf-8')) + + def generate(self, module): + if self.module_backend.needs_regeneration(): + if not self.check_mode: + self.module_backend.generate_certificate() + self.changed = True + + def dump(self, check_mode=False): + result = self.module_backend.dump(include_certificate=True) + result.update({ + 'changed': self.changed, + }) + return result + + +def main(): + argument_spec = get_certificate_argument_spec() + argument_spec.argument_spec['provider']['required'] = True + add_entrust_provider_to_argument_spec(argument_spec) + add_ownca_provider_to_argument_spec(argument_spec) + add_selfsigned_provider_to_argument_spec(argument_spec) + argument_spec.argument_spec.update(dict( + content=dict(type='str'), + )) + module = argument_spec.create_ansible_module( + supports_check_mode=True, + ) + + try: + provider = module.params['provider'] + provider_map = { + 'entrust': EntrustCertificateProvider, + 'ownca': OwnCACertificateProvider, + 'selfsigned': SelfSignedCertificateProvider, + } + + backend = module.params['select_crypto_backend'] + module_backend = select_backend(module, backend, provider_map[provider]()) + certificate = GenericCertificate(module, module_backend) + certificate.generate(module) + result = certificate.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl.py new file mode 100644 index 00000000..62286c14 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl.py @@ -0,0 +1,832 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_crl +version_added: '1.0.0' +short_description: Generate Certificate Revocation Lists (CRLs) +description: + - This module allows one to (re)generate or update Certificate Revocation Lists (CRLs). + - Certificates on the revocation list can be either specified by serial number and (optionally) their issuer, + or as a path to a certificate file in PEM format. +requirements: + - cryptography >= 1.2 +author: + - Felix Fontein (@felixfontein) +options: + state: + description: + - Whether the CRL file should exist or not, taking action if the state is different from what is stated. + type: str + default: present + choices: [ absent, present ] + + mode: + description: + - Defines how to process entries of existing CRLs. + - If set to C(generate), makes sure that the CRL has the exact set of revoked certificates + as specified in I(revoked_certificates). + - If set to C(update), makes sure that the CRL contains the revoked certificates from + I(revoked_certificates), but can also contain other revoked certificates. If the CRL file + already exists, all entries from the existing CRL will also be included in the new CRL. + When using C(update), you might be interested in setting I(ignore_timestamps) to C(yes). + type: str + default: generate + choices: [ generate, update ] + + force: + description: + - Should the CRL be forced to be regenerated. + type: bool + default: no + + backup: + description: + - Create a backup file including a timestamp so you can get the original + CRL back if you overwrote it with a new one by accident. + type: bool + default: no + + path: + description: + - Remote absolute path where the generated CRL file should be created or is already located. + type: path + required: yes + + format: + description: + - Whether the CRL file should be in PEM or DER format. + - If an existing CRL file does match everything but I(format), it will be converted to the correct format + instead of regenerated. + type: str + choices: [pem, der] + default: pem + + privatekey_path: + description: + - Path to the CA's private key to use when signing the CRL. + - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. + type: path + + privatekey_content: + description: + - The content of the CA's private key to use when signing the CRL. + - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. + type: str + + privatekey_passphrase: + description: + - The passphrase for the I(privatekey_path). + - This is required if the private key is password protected. + type: str + + issuer: + description: + - Key/value pairs that will be present in the issuer name field of the CRL. + - If you need to specify more than one value with the same key, use a list as value. + - Required if I(state) is C(present). + type: dict + + last_update: + description: + - The point in time from which this CRL can be trusted. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent, except when + I(ignore_timestamps) is set to C(yes). + type: str + default: "+0s" + + next_update: + description: + - "The absolute latest point in time by which this I(issuer) is expected to have issued + another CRL. Many clients will treat a CRL as expired once I(next_update) occurs." + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent, except when + I(ignore_timestamps) is set to C(yes). + - Required if I(state) is C(present). + type: str + + digest: + description: + - Digest algorithm to be used when signing the CRL. + type: str + default: sha256 + + revoked_certificates: + description: + - List of certificates to be revoked. + - Required if I(state) is C(present). + type: list + elements: dict + suboptions: + path: + description: + - Path to a certificate in PEM format. + - The serial number and issuer will be extracted from the certificate. + - Mutually exclusive with I(content) and I(serial_number). One of these three options + must be specified. + type: path + content: + description: + - Content of a certificate in PEM format. + - The serial number and issuer will be extracted from the certificate. + - Mutually exclusive with I(path) and I(serial_number). One of these three options + must be specified. + type: str + serial_number: + description: + - Serial number of the certificate. + - Mutually exclusive with I(path) and I(content). One of these three options must + be specified. + type: int + revocation_date: + description: + - The point in time the certificate was revoked. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent, except when + I(ignore_timestamps) is set to C(yes). + type: str + default: "+0s" + issuer: + description: + - The certificate's issuer. + - "Example: C(DNS:ca.example.org)" + type: list + elements: str + issuer_critical: + description: + - Whether the certificate issuer extension should be critical. + type: bool + default: no + reason: + description: + - The value for the revocation reason extension. + type: str + choices: + - unspecified + - key_compromise + - ca_compromise + - affiliation_changed + - superseded + - cessation_of_operation + - certificate_hold + - privilege_withdrawn + - aa_compromise + - remove_from_crl + reason_critical: + description: + - Whether the revocation reason extension should be critical. + type: bool + default: no + invalidity_date: + description: + - The point in time it was known/suspected that the private key was compromised + or that the certificate otherwise became invalid. + - Time can be specified either as relative time or as absolute timestamp. + - Time will always be interpreted as UTC. + - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer + + C([w | d | h | m | s]) (e.g. C(+32w1d2h). + - Note that if using relative time this module is NOT idempotent. This will NOT + change when I(ignore_timestamps) is set to C(yes). + type: str + invalidity_date_critical: + description: + - Whether the invalidity date extension should be critical. + type: bool + default: no + + ignore_timestamps: + description: + - Whether the timestamps I(last_update), I(next_update) and I(revocation_date) (in + I(revoked_certificates)) should be ignored for idempotency checks. The timestamp + I(invalidity_date) in I(revoked_certificates) will never be ignored. + - Use this in combination with relative timestamps for these values to get idempotency. + type: bool + default: no + + return_content: + description: + - If set to C(yes), will return the (current or generated) CRL's content as I(crl). + type: bool + default: no + +extends_documentation_fragment: + - files + +notes: + - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. + - Date specified should be UTC. Minutes and seconds are mandatory. + - Supports C(check_mode). +''' + +EXAMPLES = r''' +- name: Generate a CRL + community.crypto.x509_crl: + path: /etc/ssl/my-ca.crl + privatekey_path: /etc/ssl/private/my-ca.pem + issuer: + CN: My CA + last_update: "+0s" + next_update: "+7d" + revoked_certificates: + - serial_number: 1234 + revocation_date: 20190331202428Z + issuer: + CN: My CA + - serial_number: 2345 + revocation_date: 20191013152910Z + reason: affiliation_changed + invalidity_date: 20191001000000Z + - path: /etc/ssl/crt/revoked-cert.pem + revocation_date: 20191010010203Z +''' + +RETURN = r''' +filename: + description: Path to the generated CRL. + returned: changed or success + type: str + sample: /path/to/my-ca.crl +backup_file: + description: Name of backup file created. + returned: changed and if I(backup) is C(yes) + type: str + sample: /path/to/my-ca.crl.2019-03-09@11:22~ +privatekey: + description: Path to the private CA key. + returned: changed or success + type: str + sample: /path/to/my-ca.pem +format: + description: + - Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)). + returned: success + type: str + sample: pem +issuer: + description: + - The CRL's issuer. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' +issuer_ordered: + description: The CRL's issuer as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' +last_update: + description: The point in time from which this CRL can be trusted as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +next_update: + description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +digest: + description: The signature algorithm used to sign the CRL. + returned: success + type: str + sample: sha256WithRSAEncryption +revoked_certificates: + description: List of certificates to be revoked. + returned: success + type: list + elements: dict + contains: + serial_number: + description: Serial number of the certificate. + type: int + sample: 1234 + revocation_date: + description: The point in time the certificate was revoked as ASN.1 TIME. + type: str + sample: 20190413202428Z + issuer: + description: The certificate's issuer. + type: list + elements: str + sample: '["DNS:ca.example.org"]' + issuer_critical: + description: Whether the certificate issuer extension is critical. + type: bool + sample: no + reason: + description: + - The value for the revocation reason extension. + - One of C(unspecified), C(key_compromise), C(ca_compromise), C(affiliation_changed), C(superseded), + C(cessation_of_operation), C(certificate_hold), C(privilege_withdrawn), C(aa_compromise), and + C(remove_from_crl). + type: str + sample: key_compromise + reason_critical: + description: Whether the revocation reason extension is critical. + type: bool + sample: no + invalidity_date: + description: | + The point in time it was known/suspected that the private key was compromised + or that the certificate otherwise became invalid as ASN.1 TIME. + type: str + sample: 20190413202428Z + invalidity_date_critical: + description: Whether the invalidity date extension is critical. + type: bool + sample: no +crl: + description: + - The (current or generated) CRL's content. + - Will be the CRL itself if I(format) is C(pem), and Base64 of the + CRL if I(format) is C(der). + returned: if I(state) is C(present) and I(return_content) is C(yes) + type: str +''' + + +import base64 +import os +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native, to_text + +from ansible_collections.community.crypto.plugins.module_utils.io import ( + write_file, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, + OpenSSLBadPassphraseError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, + load_privatekey, + load_certificate, + parse_name_field, + get_relative_time_option, + select_message_digest, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_get_name, + cryptography_name_to_oid, + cryptography_oid_to_name, + cryptography_serial_number_of_cert, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import ( + REVOCATION_REASON_MAP, + TIMESTAMP_FORMAT, + cryptography_decode_revoked_certificate, + cryptography_dump_revoked, + cryptography_get_signature_algorithm_oid_from_crl, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + identify_pem_format, +) + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.2' + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.serialization import Encoding + from cryptography.x509 import ( + CertificateRevocationListBuilder, + RevokedCertificateBuilder, + NameAttribute, + Name, + ) + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class CRLError(OpenSSLObjectError): + pass + + +class CRL(OpenSSLObject): + + def __init__(self, module): + super(CRL, self).__init__( + module.params['path'], + module.params['state'], + module.params['force'], + module.check_mode + ) + + self.format = module.params['format'] + + self.update = module.params['mode'] == 'update' + self.ignore_timestamps = module.params['ignore_timestamps'] + self.return_content = module.params['return_content'] + self.crl_content = None + + self.privatekey_path = module.params['privatekey_path'] + self.privatekey_content = module.params['privatekey_content'] + if self.privatekey_content is not None: + self.privatekey_content = self.privatekey_content.encode('utf-8') + self.privatekey_passphrase = module.params['privatekey_passphrase'] + + self.issuer = parse_name_field(module.params['issuer']) + self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]] + + self.last_update = get_relative_time_option(module.params['last_update'], 'last_update') + self.next_update = get_relative_time_option(module.params['next_update'], 'next_update') + + self.digest = select_message_digest(module.params['digest']) + if self.digest is None: + raise CRLError('The digest "{0}" is not supported'.format(module.params['digest'])) + + self.revoked_certificates = [] + for i, rc in enumerate(module.params['revoked_certificates']): + result = { + 'serial_number': None, + 'revocation_date': None, + 'issuer': None, + 'issuer_critical': False, + 'reason': None, + 'reason_critical': False, + 'invalidity_date': None, + 'invalidity_date_critical': False, + } + path_prefix = 'revoked_certificates[{0}].'.format(i) + if rc['path'] is not None or rc['content'] is not None: + # Load certificate from file or content + try: + if rc['content'] is not None: + rc['content'] = rc['content'].encode('utf-8') + cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography') + result['serial_number'] = cryptography_serial_number_of_cert(cert) + except OpenSSLObjectError as e: + if rc['content'] is not None: + module.fail_json( + msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e)) + ) + else: + module.fail_json( + msg='Cannot read certificate "{1}" from {0}path: {2}'.format(path_prefix, rc['path'], to_native(e)) + ) + else: + # Specify serial_number (and potentially issuer) directly + result['serial_number'] = rc['serial_number'] + # All other options + if rc['issuer']: + result['issuer'] = [cryptography_get_name(issuer, 'issuer') for issuer in rc['issuer']] + result['issuer_critical'] = rc['issuer_critical'] + result['revocation_date'] = get_relative_time_option( + rc['revocation_date'], + path_prefix + 'revocation_date' + ) + if rc['reason']: + result['reason'] = REVOCATION_REASON_MAP[rc['reason']] + result['reason_critical'] = rc['reason_critical'] + if rc['invalidity_date']: + result['invalidity_date'] = get_relative_time_option( + rc['invalidity_date'], + path_prefix + 'invalidity_date' + ) + result['invalidity_date_critical'] = rc['invalidity_date_critical'] + self.revoked_certificates.append(result) + + self.module = module + + self.backup = module.params['backup'] + self.backup_file = None + + try: + self.privatekey = load_privatekey( + path=self.privatekey_path, + content=self.privatekey_content, + passphrase=self.privatekey_passphrase, + backend='cryptography' + ) + except OpenSSLBadPassphraseError as exc: + raise CRLError(exc) + + self.crl = None + try: + with open(self.path, 'rb') as f: + data = f.read() + self.actual_format = 'pem' if identify_pem_format(data) else 'der' + if self.actual_format == 'pem': + self.crl = x509.load_pem_x509_crl(data, default_backend()) + if self.return_content: + self.crl_content = data + else: + self.crl = x509.load_der_x509_crl(data, default_backend()) + if self.return_content: + self.crl_content = base64.b64encode(data) + except Exception as dummy: + self.crl_content = None + self.actual_format = self.format + + def remove(self): + if self.backup: + self.backup_file = self.module.backup_local(self.path) + super(CRL, self).remove(self.module) + + def _compress_entry(self, entry): + if self.ignore_timestamps: + # Throw out revocation_date + return ( + entry['serial_number'], + tuple(entry['issuer']) if entry['issuer'] is not None else None, + entry['issuer_critical'], + entry['reason'], + entry['reason_critical'], + entry['invalidity_date'], + entry['invalidity_date_critical'], + ) + else: + return ( + entry['serial_number'], + entry['revocation_date'], + tuple(entry['issuer']) if entry['issuer'] is not None else None, + entry['issuer_critical'], + entry['reason'], + entry['reason_critical'], + entry['invalidity_date'], + entry['invalidity_date_critical'], + ) + + def check(self, perms_required=True, ignore_conversion=True): + """Ensure the resource is in its desired state.""" + + state_and_perms = super(CRL, self).check(self.module, perms_required) + + if not state_and_perms: + return False + + if self.crl is None: + return False + + if self.last_update != self.crl.last_update and not self.ignore_timestamps: + return False + if self.next_update != self.crl.next_update and not self.ignore_timestamps: + return False + if self.digest.name != self.crl.signature_hash_algorithm.name: + return False + + want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer] + if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]: + return False + + old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl] + new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates] + if self.update: + # We don't simply use a set so that duplicate entries are treated correctly + for entry in new_entries: + try: + old_entries.remove(entry) + except ValueError: + return False + else: + if old_entries != new_entries: + return False + + if self.format != self.actual_format and not ignore_conversion: + return False + + return True + + def _generate_crl(self): + backend = default_backend() + crl = CertificateRevocationListBuilder() + + try: + crl = crl.issuer_name(Name([ + NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) + for entry in self.issuer + ])) + except ValueError as e: + raise CRLError(e) + + crl = crl.last_update(self.last_update) + crl = crl.next_update(self.next_update) + + if self.update and self.crl: + new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates]) + for entry in self.crl: + decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry)) + if decoded_entry not in new_entries: + crl = crl.add_revoked_certificate(entry) + for entry in self.revoked_certificates: + revoked_cert = RevokedCertificateBuilder() + revoked_cert = revoked_cert.serial_number(entry['serial_number']) + revoked_cert = revoked_cert.revocation_date(entry['revocation_date']) + if entry['issuer'] is not None: + revoked_cert = revoked_cert.add_extension( + x509.CertificateIssuer([ + cryptography_get_name(name, 'issuer') for name in entry['issuer'] + ]), + entry['issuer_critical'] + ) + if entry['reason'] is not None: + revoked_cert = revoked_cert.add_extension( + x509.CRLReason(entry['reason']), + entry['reason_critical'] + ) + if entry['invalidity_date'] is not None: + revoked_cert = revoked_cert.add_extension( + x509.InvalidityDate(entry['invalidity_date']), + entry['invalidity_date_critical'] + ) + crl = crl.add_revoked_certificate(revoked_cert.build(backend)) + + self.crl = crl.sign(self.privatekey, self.digest, backend=backend) + if self.format == 'pem': + return self.crl.public_bytes(Encoding.PEM) + else: + return self.crl.public_bytes(Encoding.DER) + + def generate(self): + result = None + if not self.check(perms_required=False, ignore_conversion=True) or self.force: + result = self._generate_crl() + elif not self.check(perms_required=False, ignore_conversion=False) and self.crl: + if self.format == 'pem': + result = self.crl.public_bytes(Encoding.PEM) + else: + result = self.crl.public_bytes(Encoding.DER) + + if result is not None: + if self.return_content: + if self.format == 'pem': + self.crl_content = result + else: + self.crl_content = base64.b64encode(result) + if self.backup: + self.backup_file = self.module.backup_local(self.path) + write_file(self.module, result) + self.changed = True + + file_args = self.module.load_file_common_arguments(self.module.params) + if self.module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def dump(self, check_mode=False): + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.privatekey_path, + 'format': self.format, + 'last_update': None, + 'next_update': None, + 'digest': None, + 'issuer_ordered': None, + 'issuer': None, + 'revoked_certificates': [], + } + if self.backup_file: + result['backup_file'] = self.backup_file + + if check_mode: + result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT) + result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT) + # result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid) + result['digest'] = self.module.params['digest'] + result['issuer_ordered'] = self.issuer + result['issuer'] = {} + for k, v in self.issuer: + result['issuer'][k] = v + result['revoked_certificates'] = [] + for entry in self.revoked_certificates: + result['revoked_certificates'].append(cryptography_dump_revoked(entry)) + elif self.crl: + result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT) + result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT) + result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl)) + issuer = [] + for attribute in self.crl.issuer: + issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + result['issuer_ordered'] = issuer + result['issuer'] = {} + for k, v in issuer: + result['issuer'][k] = v + result['revoked_certificates'] = [] + for cert in self.crl: + entry = cryptography_decode_revoked_certificate(cert) + result['revoked_certificates'].append(cryptography_dump_revoked(entry)) + + if self.return_content: + result['crl'] = self.crl_content + + return result + + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + mode=dict(type='str', default='generate', choices=['generate', 'update']), + force=dict(type='bool', default=False), + backup=dict(type='bool', default=False), + path=dict(type='path', required=True), + format=dict(type='str', default='pem', choices=['pem', 'der']), + privatekey_path=dict(type='path'), + privatekey_content=dict(type='str', no_log=True), + privatekey_passphrase=dict(type='str', no_log=True), + issuer=dict(type='dict'), + last_update=dict(type='str', default='+0s'), + next_update=dict(type='str'), + digest=dict(type='str', default='sha256'), + ignore_timestamps=dict(type='bool', default=False), + return_content=dict(type='bool', default=False), + revoked_certificates=dict( + type='list', + elements='dict', + options=dict( + path=dict(type='path'), + content=dict(type='str'), + serial_number=dict(type='int'), + revocation_date=dict(type='str', default='+0s'), + issuer=dict(type='list', elements='str'), + issuer_critical=dict(type='bool', default=False), + reason=dict( + type='str', + choices=[ + 'unspecified', 'key_compromise', 'ca_compromise', 'affiliation_changed', + 'superseded', 'cessation_of_operation', 'certificate_hold', + 'privilege_withdrawn', 'aa_compromise', 'remove_from_crl' + ] + ), + reason_critical=dict(type='bool', default=False), + invalidity_date=dict(type='str'), + invalidity_date_critical=dict(type='bool', default=False), + ), + required_one_of=[['path', 'content', 'serial_number']], + mutually_exclusive=[['path', 'content', 'serial_number']], + ), + ), + required_if=[ + ('state', 'present', ['privatekey_path', 'privatekey_content'], True), + ('state', 'present', ['issuer', 'next_update', 'revoked_certificates'], False), + ], + mutually_exclusive=( + ['privatekey_path', 'privatekey_content'], + ), + supports_check_mode=True, + add_file_common_args=True, + ) + + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + + try: + crl = CRL(module) + + if module.params['state'] == 'present': + if module.check_mode: + result = crl.dump(check_mode=True) + result['changed'] = module.params['force'] or not crl.check() or not crl.check(ignore_conversion=False) + module.exit_json(**result) + + crl.generate() + else: + if module.check_mode: + result = crl.dump(check_mode=True) + result['changed'] = os.path.exists(module.params['path']) + module.exit_json(**result) + + crl.remove() + + result = crl.dump() + module.exit_json(**result) + except OpenSSLObjectError as exc: + module.fail_json(msg=to_native(exc)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl_info.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl_info.py new file mode 100644 index 00000000..94ae95b1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/modules/x509_crl_info.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: x509_crl_info +version_added: '1.0.0' +short_description: Retrieve information on Certificate Revocation Lists (CRLs) +description: + - This module allows one to retrieve information on Certificate Revocation Lists (CRLs). +requirements: + - cryptography >= 1.2 +author: + - Felix Fontein (@felixfontein) +options: + path: + description: + - Remote absolute path where the generated CRL file should be created or is already located. + - Either I(path) or I(content) must be specified, but not both. + type: path + content: + description: + - Content of the X.509 CRL in PEM format, or Base64-encoded X.509 CRL. + - Either I(path) or I(content) must be specified, but not both. + type: str + +notes: + - All timestamp values are provided in ASN.1 TIME format, in other words, following the C(YYYYMMDDHHMMSSZ) pattern. + They are all in UTC. + - Supports C(check_mode). +seealso: + - module: community.crypto.x509_crl +''' + +EXAMPLES = r''' +- name: Get information on CRL + community.crypto.x509_crl_info: + path: /etc/ssl/my-ca.crl + register: result + +- name: Print the information + ansible.builtin.debug: + msg: "{{ result }}" +''' + +RETURN = r''' +format: + description: + - Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)). + returned: success + type: str + sample: pem +issuer: + description: + - The CRL's issuer. + - Note that for repeated values, only the last one will be returned. + returned: success + type: dict + sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' +issuer_ordered: + description: The CRL's issuer as an ordered list of tuples. + returned: success + type: list + elements: list + sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' +last_update: + description: The point in time from which this CRL can be trusted as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +next_update: + description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME. + returned: success + type: str + sample: 20190413202428Z +digest: + description: The signature algorithm used to sign the CRL. + returned: success + type: str + sample: sha256WithRSAEncryption +revoked_certificates: + description: List of certificates to be revoked. + returned: success + type: list + elements: dict + contains: + serial_number: + description: Serial number of the certificate. + type: int + sample: 1234 + revocation_date: + description: The point in time the certificate was revoked as ASN.1 TIME. + type: str + sample: 20190413202428Z + issuer: + description: The certificate's issuer. + type: list + elements: str + sample: '["DNS:ca.example.org"]' + issuer_critical: + description: Whether the certificate issuer extension is critical. + type: bool + sample: no + reason: + description: + - The value for the revocation reason extension. + - One of C(unspecified), C(key_compromise), C(ca_compromise), C(affiliation_changed), C(superseded), + C(cessation_of_operation), C(certificate_hold), C(privilege_withdrawn), C(aa_compromise), and + C(remove_from_crl). + type: str + sample: key_compromise + reason_critical: + description: Whether the revocation reason extension is critical. + type: bool + sample: no + invalidity_date: + description: | + The point in time it was known/suspected that the private key was compromised + or that the certificate otherwise became invalid as ASN.1 TIME. + type: str + sample: 20190413202428Z + invalidity_date_critical: + description: Whether the invalidity date extension is critical. + type: bool + sample: no +''' + + +import base64 +import traceback + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( + OpenSSLObject, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_oid_to_name, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import ( + TIMESTAMP_FORMAT, + cryptography_decode_revoked_certificate, + cryptography_dump_revoked, + cryptography_get_signature_algorithm_oid_from_crl, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( + identify_pem_format, +) + +# crypto_utils + +MINIMAL_CRYPTOGRAPHY_VERSION = '1.2' + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True + + +class CRLError(OpenSSLObjectError): + pass + + +class CRLInfo(OpenSSLObject): + """The main module implementation.""" + + def __init__(self, module): + super(CRLInfo, self).__init__( + module.params['path'] or '', + 'present', + False, + module.check_mode + ) + + self.content = module.params['content'] + + self.module = module + + self.crl = None + if self.content is None: + try: + with open(self.path, 'rb') as f: + data = f.read() + except Exception as e: + self.module.fail_json(msg='Error while reading CRL file from disk: {0}'.format(e)) + else: + data = self.content.encode('utf-8') + if not identify_pem_format(data): + data = base64.b64decode(self.content) + + self.crl_pem = identify_pem_format(data) + try: + if self.crl_pem: + self.crl = x509.load_pem_x509_crl(data, default_backend()) + else: + self.crl = x509.load_der_x509_crl(data, default_backend()) + except Exception as e: + self.module.fail_json(msg='Error while decoding CRL: {0}'.format(e)) + + def get_info(self): + result = { + 'changed': False, + 'format': 'pem' if self.crl_pem else 'der', + 'last_update': None, + 'next_update': None, + 'digest': None, + 'issuer_ordered': None, + 'issuer': None, + 'revoked_certificates': [], + } + + result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT) + result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT) + result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl)) + issuer = [] + for attribute in self.crl.issuer: + issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value]) + result['issuer_ordered'] = issuer + result['issuer'] = {} + for k, v in issuer: + result['issuer'][k] = v + result['revoked_certificates'] = [] + for cert in self.crl: + entry = cryptography_decode_revoked_certificate(cert) + result['revoked_certificates'].append(cryptography_dump_revoked(entry)) + + return result + + def generate(self): + # Empty method because OpenSSLObject wants this + pass + + def dump(self): + # Empty method because OpenSSLObject wants this + pass + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(type='path'), + content=dict(type='str'), + ), + required_one_of=( + ['path', 'content'], + ), + mutually_exclusive=( + ['path', 'content'], + ), + supports_check_mode=True, + ) + + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), + exception=CRYPTOGRAPHY_IMP_ERR) + + try: + crl = CRLInfo(module) + result = crl.get_info() + module.exit_json(**result) + except OpenSSLObjectError as e: + module.fail_json(msg=to_native(e)) + + +if __name__ == "__main__": + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/plugins/plugin_utils/action_module.py b/collections-debian-merged/ansible_collections/community/crypto/plugins/plugin_utils/action_module.py new file mode 100644 index 00000000..f8ca2af5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/plugins/plugin_utils/action_module.py @@ -0,0 +1,735 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2012-2013 Michael DeHaan <michael.dehaan@gmail.com> +# Copyright (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com> +# Copyright (c) 2019 Ansible Project +# Copyright (c) 2020 Felix Fontein <felix@fontein.de> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Parts taken from ansible.module_utils.basic and ansible.module_utils.common.warnings. + +# NOTE: THIS MUST NOT BE USED BY A MODULE! THIS IS ONLY FOR ACTION PLUGINS! + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import abc +import copy +import traceback + +from ansible import constants as C +from ansible.errors import AnsibleError +from ansible.module_utils import six +from ansible.module_utils.basic import AnsibleFallbackNotFound, SEQUENCETYPE, remove_values +from ansible.module_utils.common._collections_compat import ( + Mapping +) +from ansible.module_utils.common.parameters import ( + handle_aliases, + list_deprecations, + list_no_log_values, + PASS_VARS, + PASS_BOOLS, +) +from ansible.module_utils.common.validation import ( + check_mutually_exclusive, + check_required_arguments, + check_required_by, + check_required_if, + check_required_one_of, + check_required_together, + count_terms, + check_type_bool, + check_type_bits, + check_type_bytes, + check_type_float, + check_type_int, + check_type_jsonarg, + check_type_list, + check_type_dict, + check_type_path, + check_type_raw, + check_type_str, + safe_eval, +) +from ansible.module_utils.common.text.formatters import ( + lenient_lowercase, +) +from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE, BOOLEANS_TRUE +from ansible.module_utils.six import ( + binary_type, + string_types, + text_type, +) +from ansible.module_utils._text import to_native, to_text +from ansible.plugins.action import ActionBase + + +class _ModuleExitException(Exception): + def __init__(self, result): + super(_ModuleExitException, self).__init__() + self.result = result + + +class AnsibleActionModule(object): + def __init__(self, action_plugin, argument_spec, bypass_checks=False, + mutually_exclusive=None, required_together=None, + required_one_of=None, supports_check_mode=False, + required_if=None, required_by=None): + # Internal data + self.__action_plugin = action_plugin + self.__warnings = [] + self.__deprecations = [] + + # AnsibleModule data + self._name = self.__action_plugin._task.action + self.argument_spec = argument_spec + self.supports_check_mode = supports_check_mode + self.check_mode = self.__action_plugin._play_context.check_mode + self.bypass_checks = bypass_checks + self.no_log = self.__action_plugin._play_context.no_log + + self.mutually_exclusive = mutually_exclusive + self.required_together = required_together + self.required_one_of = required_one_of + self.required_if = required_if + self.required_by = required_by + self._diff = self.__action_plugin._play_context.diff + self._verbosity = self.__action_plugin._display.verbosity + self._string_conversion_action = C.STRING_CONVERSION_ACTION + + self.aliases = {} + self._legal_inputs = [] + self._options_context = list() + + self.params = copy.deepcopy(action_plugin._task.args) + self._set_fallbacks() + + # append to legal_inputs and then possibly check against them + try: + self.aliases = self._handle_aliases() + except (ValueError, TypeError) as e: + # Use exceptions here because it isn't safe to call fail_json until no_log is processed + raise _ModuleExitException(dict(failed=True, msg="Module alias error: %s" % to_native(e))) + + # Save parameter values that should never be logged + self.no_log_values = set() + self._handle_no_log_values() + + self._check_arguments() + + # check exclusive early + if not bypass_checks: + self._check_mutually_exclusive(mutually_exclusive) + + self._set_defaults(pre=True) + + self._CHECK_ARGUMENT_TYPES_DISPATCHER = { + 'str': self._check_type_str, + 'list': self._check_type_list, + 'dict': self._check_type_dict, + 'bool': self._check_type_bool, + 'int': self._check_type_int, + 'float': self._check_type_float, + 'path': self._check_type_path, + 'raw': self._check_type_raw, + 'jsonarg': self._check_type_jsonarg, + 'json': self._check_type_jsonarg, + 'bytes': self._check_type_bytes, + 'bits': self._check_type_bits, + } + if not bypass_checks: + self._check_required_arguments() + self._check_argument_types() + self._check_argument_values() + self._check_required_together(required_together) + self._check_required_one_of(required_one_of) + self._check_required_if(required_if) + self._check_required_by(required_by) + + self._set_defaults(pre=False) + + # deal with options sub-spec + self._handle_options() + + def _handle_aliases(self, spec=None, param=None, option_prefix=''): + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + + # this uses exceptions as it happens before we can safely call fail_json + alias_warnings = [] + alias_results, self._legal_inputs = handle_aliases(spec, param, alias_warnings=alias_warnings) + for option, alias in alias_warnings: + self.warn('Both option %s and its alias %s are set.' % (option_prefix + option, option_prefix + alias)) + + deprecated_aliases = [] + for i in spec.keys(): + if 'deprecated_aliases' in spec[i].keys(): + for alias in spec[i]['deprecated_aliases']: + deprecated_aliases.append(alias) + + for deprecation in deprecated_aliases: + if deprecation['name'] in param.keys(): + self.deprecate("Alias '%s' is deprecated. See the module docs for more information" % deprecation['name'], + version=deprecation.get('version'), date=deprecation.get('date'), + collection_name=deprecation.get('collection_name')) + return alias_results + + def _handle_no_log_values(self, spec=None, param=None): + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + + try: + self.no_log_values.update(list_no_log_values(spec, param)) + except TypeError as te: + self.fail_json(msg="Failure when processing no_log parameters. Module invocation will be hidden. " + "%s" % to_native(te), invocation={'module_args': 'HIDDEN DUE TO FAILURE'}) + + for message in list_deprecations(spec, param): + self.deprecate(message['msg'], version=message.get('version'), date=message.get('date'), + collection_name=message.get('collection_name')) + + def _check_arguments(self, spec=None, param=None, legal_inputs=None): + self._syslog_facility = 'LOG_USER' + unsupported_parameters = set() + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + if legal_inputs is None: + legal_inputs = self._legal_inputs + + for k in list(param.keys()): + + if k not in legal_inputs: + unsupported_parameters.add(k) + + for k in PASS_VARS: + # handle setting internal properties from internal ansible vars + param_key = '_ansible_%s' % k + if param_key in param: + if k in PASS_BOOLS: + setattr(self, PASS_VARS[k][0], self.boolean(param[param_key])) + else: + setattr(self, PASS_VARS[k][0], param[param_key]) + + # clean up internal top level params: + if param_key in self.params: + del self.params[param_key] + else: + # use defaults if not already set + if not hasattr(self, PASS_VARS[k][0]): + setattr(self, PASS_VARS[k][0], PASS_VARS[k][1]) + + if unsupported_parameters: + msg = "Unsupported parameters for (%s) module: %s" % (self._name, ', '.join(sorted(list(unsupported_parameters)))) + if self._options_context: + msg += " found in %s." % " -> ".join(self._options_context) + supported_parameters = list() + for key in sorted(spec.keys()): + if 'aliases' in spec[key] and spec[key]['aliases']: + supported_parameters.append("%s (%s)" % (key, ', '.join(sorted(spec[key]['aliases'])))) + else: + supported_parameters.append(key) + msg += " Supported parameters include: %s" % (', '.join(supported_parameters)) + self.fail_json(msg=msg) + if self.check_mode and not self.supports_check_mode: + self.exit_json(skipped=True, msg="action module (%s) does not support check mode" % self._name) + + def _count_terms(self, check, param=None): + if param is None: + param = self.params + return count_terms(check, param) + + def _check_mutually_exclusive(self, spec, param=None): + if param is None: + param = self.params + + try: + check_mutually_exclusive(spec, param) + except TypeError as e: + msg = to_native(e) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def _check_required_one_of(self, spec, param=None): + if spec is None: + return + + if param is None: + param = self.params + + try: + check_required_one_of(spec, param) + except TypeError as e: + msg = to_native(e) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def _check_required_together(self, spec, param=None): + if spec is None: + return + if param is None: + param = self.params + + try: + check_required_together(spec, param) + except TypeError as e: + msg = to_native(e) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def _check_required_by(self, spec, param=None): + if spec is None: + return + if param is None: + param = self.params + + try: + check_required_by(spec, param) + except TypeError as e: + self.fail_json(msg=to_native(e)) + + def _check_required_arguments(self, spec=None, param=None): + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + + try: + check_required_arguments(spec, param) + except TypeError as e: + msg = to_native(e) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def _check_required_if(self, spec, param=None): + ''' ensure that parameters which conditionally required are present ''' + if spec is None: + return + if param is None: + param = self.params + + try: + check_required_if(spec, param) + except TypeError as e: + msg = to_native(e) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def _check_argument_values(self, spec=None, param=None): + ''' ensure all arguments have the requested values, and there are no stray arguments ''' + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + for (k, v) in spec.items(): + choices = v.get('choices', None) + if choices is None: + continue + if isinstance(choices, SEQUENCETYPE) and not isinstance(choices, (binary_type, text_type)): + if k in param: + # Allow one or more when type='list' param with choices + if isinstance(param[k], list): + diff_list = ", ".join([item for item in param[k] if item not in choices]) + if diff_list: + choices_str = ", ".join([to_native(c) for c in choices]) + msg = "value of %s must be one or more of: %s. Got no match for: %s" % (k, choices_str, diff_list) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + elif param[k] not in choices: + # PyYaml converts certain strings to bools. If we can unambiguously convert back, do so before checking + # the value. If we can't figure this out, module author is responsible. + lowered_choices = None + if param[k] == 'False': + lowered_choices = lenient_lowercase(choices) + overlap = BOOLEANS_FALSE.intersection(choices) + if len(overlap) == 1: + # Extract from a set + (param[k],) = overlap + + if param[k] == 'True': + if lowered_choices is None: + lowered_choices = lenient_lowercase(choices) + overlap = BOOLEANS_TRUE.intersection(choices) + if len(overlap) == 1: + (param[k],) = overlap + + if param[k] not in choices: + choices_str = ", ".join([to_native(c) for c in choices]) + msg = "value of %s must be one of: %s, got: %s" % (k, choices_str, param[k]) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + else: + msg = "internal error: choices for argument %s are not iterable: %s" % (k, choices) + if self._options_context: + msg += " found in %s" % " -> ".join(self._options_context) + self.fail_json(msg=msg) + + def safe_eval(self, value, locals=None, include_exceptions=False): + return safe_eval(value, locals, include_exceptions) + + def _check_type_str(self, value, param=None, prefix=''): + opts = { + 'error': False, + 'warn': False, + 'ignore': True + } + + # Ignore, warn, or error when converting to a string. + allow_conversion = opts.get(self._string_conversion_action, True) + try: + return check_type_str(value, allow_conversion) + except TypeError: + common_msg = 'quote the entire value to ensure it does not change.' + from_msg = '{0!r}'.format(value) + to_msg = '{0!r}'.format(to_text(value)) + + if param is not None: + if prefix: + param = '{0}{1}'.format(prefix, param) + + from_msg = '{0}: {1!r}'.format(param, value) + to_msg = '{0}: {1!r}'.format(param, to_text(value)) + + if self._string_conversion_action == 'error': + msg = common_msg.capitalize() + raise TypeError(to_native(msg)) + elif self._string_conversion_action == 'warn': + msg = ('The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). ' + 'If this does not look like what you expect, {3}').format(from_msg, value, to_msg, common_msg) + self.warn(to_native(msg)) + return to_native(value, errors='surrogate_or_strict') + + def _check_type_list(self, value): + return check_type_list(value) + + def _check_type_dict(self, value): + return check_type_dict(value) + + def _check_type_bool(self, value): + return check_type_bool(value) + + def _check_type_int(self, value): + return check_type_int(value) + + def _check_type_float(self, value): + return check_type_float(value) + + def _check_type_path(self, value): + return check_type_path(value) + + def _check_type_jsonarg(self, value): + return check_type_jsonarg(value) + + def _check_type_raw(self, value): + return check_type_raw(value) + + def _check_type_bytes(self, value): + return check_type_bytes(value) + + def _check_type_bits(self, value): + return check_type_bits(value) + + def _handle_options(self, argument_spec=None, params=None, prefix=''): + ''' deal with options to create sub spec ''' + if argument_spec is None: + argument_spec = self.argument_spec + if params is None: + params = self.params + + for (k, v) in argument_spec.items(): + wanted = v.get('type', None) + if wanted == 'dict' or (wanted == 'list' and v.get('elements', '') == 'dict'): + spec = v.get('options', None) + if v.get('apply_defaults', False): + if spec is not None: + if params.get(k) is None: + params[k] = {} + else: + continue + elif spec is None or k not in params or params[k] is None: + continue + + self._options_context.append(k) + + if isinstance(params[k], dict): + elements = [params[k]] + else: + elements = params[k] + + for idx, param in enumerate(elements): + if not isinstance(param, dict): + self.fail_json(msg="value of %s must be of type dict or list of dict" % k) + + new_prefix = prefix + k + if wanted == 'list': + new_prefix += '[%d]' % idx + new_prefix += '.' + + self._set_fallbacks(spec, param) + options_aliases = self._handle_aliases(spec, param, option_prefix=new_prefix) + + options_legal_inputs = list(spec.keys()) + list(options_aliases.keys()) + + self._check_arguments(spec, param, options_legal_inputs) + + # check exclusive early + if not self.bypass_checks: + self._check_mutually_exclusive(v.get('mutually_exclusive', None), param) + + self._set_defaults(pre=True, spec=spec, param=param) + + if not self.bypass_checks: + self._check_required_arguments(spec, param) + self._check_argument_types(spec, param, new_prefix) + self._check_argument_values(spec, param) + + self._check_required_together(v.get('required_together', None), param) + self._check_required_one_of(v.get('required_one_of', None), param) + self._check_required_if(v.get('required_if', None), param) + self._check_required_by(v.get('required_by', None), param) + + self._set_defaults(pre=False, spec=spec, param=param) + + # handle multi level options (sub argspec) + self._handle_options(spec, param, new_prefix) + self._options_context.pop() + + def _get_wanted_type(self, wanted, k): + if not callable(wanted): + if wanted is None: + # Mostly we want to default to str. + # For values set to None explicitly, return None instead as + # that allows a user to unset a parameter + wanted = 'str' + try: + type_checker = self._CHECK_ARGUMENT_TYPES_DISPATCHER[wanted] + except KeyError: + self.fail_json(msg="implementation error: unknown type %s requested for %s" % (wanted, k)) + else: + # set the type_checker to the callable, and reset wanted to the callable's name (or type if it doesn't have one, ala MagicMock) + type_checker = wanted + wanted = getattr(wanted, '__name__', to_native(type(wanted))) + + return type_checker, wanted + + def _handle_elements(self, wanted, param, values): + type_checker, wanted_name = self._get_wanted_type(wanted, param) + validated_params = [] + # Get param name for strings so we can later display this value in a useful error message if needed + # Only pass 'kwargs' to our checkers and ignore custom callable checkers + kwargs = {} + if wanted_name == 'str' and isinstance(wanted, string_types): + if isinstance(param, string_types): + kwargs['param'] = param + elif isinstance(param, dict): + kwargs['param'] = list(param.keys())[0] + for value in values: + try: + validated_params.append(type_checker(value, **kwargs)) + except (TypeError, ValueError) as e: + msg = "Elements value for option %s" % param + if self._options_context: + msg += " found in '%s'" % " -> ".join(self._options_context) + msg += " is of type %s and we were unable to convert to %s: %s" % (type(value), wanted_name, to_native(e)) + self.fail_json(msg=msg) + return validated_params + + def _check_argument_types(self, spec=None, param=None, prefix=''): + ''' ensure all arguments have the requested type ''' + + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + + for (k, v) in spec.items(): + wanted = v.get('type', None) + if k not in param: + continue + + value = param[k] + if value is None: + continue + + type_checker, wanted_name = self._get_wanted_type(wanted, k) + # Get param name for strings so we can later display this value in a useful error message if needed + # Only pass 'kwargs' to our checkers and ignore custom callable checkers + kwargs = {} + if wanted_name == 'str' and isinstance(type_checker, string_types): + kwargs['param'] = list(param.keys())[0] + + # Get the name of the parent key if this is a nested option + if prefix: + kwargs['prefix'] = prefix + + try: + param[k] = type_checker(value, **kwargs) + wanted_elements = v.get('elements', None) + if wanted_elements: + if wanted != 'list' or not isinstance(param[k], list): + msg = "Invalid type %s for option '%s'" % (wanted_name, param) + if self._options_context: + msg += " found in '%s'." % " -> ".join(self._options_context) + msg += ", elements value check is supported only with 'list' type" + self.fail_json(msg=msg) + param[k] = self._handle_elements(wanted_elements, k, param[k]) + + except (TypeError, ValueError) as e: + msg = "argument %s is of type %s" % (k, type(value)) + if self._options_context: + msg += " found in '%s'." % " -> ".join(self._options_context) + msg += " and we were unable to convert to %s: %s" % (wanted_name, to_native(e)) + self.fail_json(msg=msg) + + def _set_defaults(self, pre=True, spec=None, param=None): + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + for (k, v) in spec.items(): + default = v.get('default', None) + if pre is True: + # this prevents setting defaults on required items + if default is not None and k not in param: + param[k] = default + else: + # make sure things without a default still get set None + if k not in param: + param[k] = default + + def _set_fallbacks(self, spec=None, param=None): + if spec is None: + spec = self.argument_spec + if param is None: + param = self.params + + for (k, v) in spec.items(): + fallback = v.get('fallback', (None,)) + fallback_strategy = fallback[0] + fallback_args = [] + fallback_kwargs = {} + if k not in param and fallback_strategy is not None: + for item in fallback[1:]: + if isinstance(item, dict): + fallback_kwargs = item + else: + fallback_args = item + try: + param[k] = fallback_strategy(*fallback_args, **fallback_kwargs) + except AnsibleFallbackNotFound: + continue + + def warn(self, warning): + # Copied from ansible.module_utils.common.warnings: + if isinstance(warning, string_types): + self.__warnings.append(warning) + else: + raise TypeError("warn requires a string not a %s" % type(warning)) + + def deprecate(self, msg, version=None, date=None, collection_name=None): + if version is not None and date is not None: + raise AssertionError("implementation error -- version and date must not both be set") + + # Copied from ansible.module_utils.common.warnings: + if isinstance(msg, string_types): + # For compatibility, we accept that neither version nor date is set, + # and treat that the same as if version would haven been set + if date is not None: + self.__deprecations.append({'msg': msg, 'date': date, 'collection_name': collection_name}) + else: + self.__deprecations.append({'msg': msg, 'version': version, 'collection_name': collection_name}) + else: + raise TypeError("deprecate requires a string not a %s" % type(msg)) + + def _return_formatted(self, kwargs): + if 'invocation' not in kwargs: + kwargs['invocation'] = {'module_args': self.params} + + if 'warnings' in kwargs: + if isinstance(kwargs['warnings'], list): + for w in kwargs['warnings']: + self.warn(w) + else: + self.warn(kwargs['warnings']) + + if self.__warnings: + kwargs['warnings'] = self.__warnings + + if 'deprecations' in kwargs: + if isinstance(kwargs['deprecations'], list): + for d in kwargs['deprecations']: + if isinstance(d, SEQUENCETYPE) and len(d) == 2: + self.deprecate(d[0], version=d[1]) + elif isinstance(d, Mapping): + self.deprecate(d['msg'], version=d.get('version'), date=d.get('date'), + collection_name=d.get('collection_name')) + else: + self.deprecate(d) # pylint: disable=ansible-deprecated-no-version + else: + self.deprecate(kwargs['deprecations']) # pylint: disable=ansible-deprecated-no-version + + if self.__deprecations: + kwargs['deprecations'] = self.__deprecations + + kwargs = remove_values(kwargs, self.no_log_values) + raise _ModuleExitException(kwargs) + + def exit_json(self, **kwargs): + result = dict(kwargs) + if 'failed' not in result: + result['failed'] = False + self._return_formatted(result) + + def fail_json(self, msg, **kwargs): + result = dict(kwargs) + result['failed'] = True + result['msg'] = msg + self._return_formatted(result) + + +@six.add_metaclass(abc.ABCMeta) +class ActionModuleBase(ActionBase): + @abc.abstractmethod + def setup_module(self): + """Return pair (ArgumentSpec, kwargs).""" + pass + + @abc.abstractmethod + def run_module(self, module): + """Run module code""" + module.fail_json(msg='Not implemented.') + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModuleBase, self).run(tmp, task_vars) + del tmp # tmp no longer has any effect + + try: + argument_spec, kwargs = self.setup_module() + module = argument_spec.create_ansible_module_helper(AnsibleActionModule, (self, ), **kwargs) + self.run_module(module) + raise AnsibleError('Internal error: action module did not call module.exit_json()') + except _ModuleExitException as mee: + result.update(mee.result) + return result + except Exception as dummy: + result['failed'] = True + result['msg'] = 'MODULE FAILURE' + result['exception'] = traceback.format_exc() + return result diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/.gitignore b/collections-debian-merged/ansible_collections/community/crypto/tests/.gitignore new file mode 100644 index 00000000..ea1472ec --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/.gitignore @@ -0,0 +1 @@ +output/ diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/impl.yml new file mode 100644 index 00000000..4a0ff616 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/impl.yml @@ -0,0 +1,283 @@ +- name: Generate account keys + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ item }}.pem" + loop: + - accountkey + - accountkey2 + - accountkey3 + - accountkey4 + - accountkey5 + +- name: Parse account keys (to ease debugging some test failures) + command: "{{ openssl_binary }} ec -in {{ output_dir }}/{{ item }}.pem -noout -text" + loop: + - accountkey + - accountkey2 + - accountkey3 + - accountkey4 + - accountkey5 + +- name: Do not try to create account + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: no + ignore_errors: yes + register: account_not_created + +- name: Create it now (check mode, diff) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + check_mode: yes + diff: yes + register: account_created_check + +- name: Create it now + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + register: account_created + +- name: Create it now (idempotent) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + register: account_created_idempotent + +- name: Change email address (check mode, diff) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_content: "{{ lookup('file', output_dir ~ '/accountkey.pem') }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: + - mailto:example@example.com + check_mode: yes + diff: yes + register: account_modified_check + +- name: Change email address + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_content: "{{ lookup('file', output_dir ~ '/accountkey.pem') }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: + - mailto:example@example.com + register: account_modified + +- name: Change email address (idempotent) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_created.account_uri }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: + - mailto:example@example.com + register: account_modified_idempotent + +- name: Cannot access account with wrong URI + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_created.account_uri ~ '12345thisdoesnotexist' }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + contact: [] + ignore_errors: yes + register: account_modified_wrong_uri + +- name: Clear contact email addresses (check mode, diff) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: [] + check_mode: yes + diff: yes + register: account_modified_2_check + +- name: Clear contact email addresses + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: [] + register: account_modified_2 + +- name: Clear contact email addresses (idempotent) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + # allow_creation: no + contact: [] + register: account_modified_2_idempotent + +- name: Change account key (check mode, diff) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + new_account_key_src: "{{ output_dir }}/accountkey2.pem" + state: changed_key + contact: + - mailto:example@example.com + check_mode: yes + diff: yes + register: account_change_key_check + +- name: Change account key + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + new_account_key_src: "{{ output_dir }}/accountkey2.pem" + state: changed_key + contact: + - mailto:example@example.com + register: account_change_key + +- name: Deactivate account (check mode, diff) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: absent + check_mode: yes + diff: yes + register: account_deactivate_check + +- name: Deactivate account + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: absent + register: account_deactivate + +- name: Deactivate account (idempotent) + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: absent + register: account_deactivate_idempotent + +- name: Do not try to create account II + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: no + ignore_errors: yes + register: account_not_created_2 + +- name: Do not try to create account III + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: no + ignore_errors: yes + register: account_not_created_3 + +- name: Create account with External Account Binding + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/{{ item.account }}.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + external_account_binding: + kid: "{{ item.kid }}" + alg: "{{ item.alg }}" + key: "{{ item.key }}" + register: account_created_eab + ignore_errors: yes + loop: + - account: accountkey3 + kid: kid-1 + alg: HS256 + key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W + - account: accountkey4 + kid: kid-2 + alg: HS384 + key: b10lLJs8l1GPIzsLP0s6pMt8O0XVGnfTaCeROxQM0BIt2XrJMDHJZBM5NuQmQJQH + - account: accountkey5 + kid: kid-3 + alg: HS512 + key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W +- debug: var=account_created_eab diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/main.yml new file mode 100644 index 00000000..b7c7452a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tasks/main.yml @@ -0,0 +1,36 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tests/validate.yml new file mode 100644 index 00000000..95140f13 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account/tests/validate.yml @@ -0,0 +1,137 @@ +--- +- name: Validate that account wasn't created in the first step + assert: + that: + - account_not_created is failed + - account_not_created.msg == 'Account does not exist or is deactivated.' + +- name: Validate that account was created in the second step (check mode) + assert: + that: + - account_created_check is changed + - account_created_check.account_uri is none + - "'diff' in account_created_check" + - "account_created_check.diff.before == {}" + - "'after' in account_created_check.diff" + - account_created_check.diff.after.contact | length == 1 + - account_created_check.diff.after.contact[0] == 'mailto:example@example.org' + +- name: Validate that account was created in the second step + assert: + that: + - account_created is changed + - account_created.account_uri is not none + +- name: Validate that account was created in the second step (idempotency) + assert: + that: + - account_created_idempotent is not changed + - account_created_idempotent.account_uri is not none + +- name: Validate that email address was changed (check mode) + assert: + that: + - account_modified_check is changed + - account_modified_check.account_uri is not none + - "'diff' in account_modified_check" + - account_modified_check.diff.before.contact | length == 1 + - account_modified_check.diff.before.contact[0] == 'mailto:example@example.org' + - account_modified_check.diff.after.contact | length == 1 + - account_modified_check.diff.after.contact[0] == 'mailto:example@example.com' + +- name: Validate that email address was changed + assert: + that: + - account_modified is changed + - account_modified.account_uri is not none + +- name: Validate that email address was not changed a second time (idempotency) + assert: + that: + - account_modified_idempotent is not changed + - account_modified_idempotent.account_uri is not none + +- name: Make sure that with the wrong account URI, the account cannot be changed + assert: + that: + - account_modified_wrong_uri is failed + +- name: Validate that email address was cleared (check mode) + assert: + that: + - account_modified_2_check is changed + - account_modified_2_check.account_uri is not none + - "'diff' in account_modified_2_check" + - account_modified_2_check.diff.before.contact | length == 1 + - account_modified_2_check.diff.before.contact[0] == 'mailto:example@example.com' + - account_modified_2_check.diff.after.contact | length == 0 + +- name: Validate that email address was cleared + assert: + that: + - account_modified_2 is changed + - account_modified_2.account_uri is not none + +- name: Validate that email address was not cleared a second time (idempotency) + assert: + that: + - account_modified_2_idempotent is not changed + - account_modified_2_idempotent.account_uri is not none + +- name: Validate that the account key was changed (check mode) + assert: + that: + - account_change_key_check is changed + - account_change_key_check.account_uri is not none + - "'diff' in account_change_key_check" + - account_change_key_check.diff.before.public_account_key != account_change_key_check.diff.after.public_account_key + +- name: Validate that the account key was changed + assert: + that: + - account_change_key is changed + - account_change_key.account_uri is not none + +- name: Validate that the account was deactivated (check mode) + assert: + that: + - account_deactivate_check is changed + - account_deactivate_check.account_uri is not none + - "'diff' in account_deactivate_check" + - "account_deactivate_check.diff.before != {}" + - "account_deactivate_check.diff.after == {}" + +- name: Validate that the account was deactivated + assert: + that: + - account_deactivate is changed + - account_deactivate.account_uri is not none + +- name: Validate that the account was really deactivated (idempotency) + assert: + that: + - account_deactivate_idempotent is not changed + # The next condition should be true for all conforming ACME servers. + # In case it is not true, it could be both an error in acme_account + # and in the ACME server. + - account_deactivate_idempotent.account_uri is none + +- name: Validate that the account is gone (new account key) + assert: + that: + - account_not_created_2 is failed + - account_not_created_2.msg == 'Account does not exist or is deactivated.' + +- name: Validate that the account is gone (old account key) + assert: + that: + - account_not_created_3 is failed + - account_not_created_3.msg == 'Account does not exist or is deactivated.' + +- name: Validate that the account with External Account Binding has been created + assert: + that: + - account_created_eab.results[0] is changed + - account_created_eab.results[1] is changed + - account_created_eab.results[2] is failed + - "'HS512 key must be at least 64 bytes long' in account_created_eab.results[2].msg" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/impl.yml new file mode 100644 index 00000000..552fc0b2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/impl.yml @@ -0,0 +1,82 @@ +--- +- name: Generate account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem" + +- name: Generate second account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey2.pem" + +- name: Parse account key (to ease debugging some test failures) + command: "{{ openssl_binary }} ec -in {{ output_dir }}/accountkey.pem -noout -text" + +- name: Check that account does not exist + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + register: account_not_created + +- name: Create it now + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + +- name: Check that account exists + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + register: account_created + +- name: Clear email address + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_content: "{{ lookup('file', output_dir ~ '/accountkey.pem') }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + state: present + allow_creation: no + contact: [] + +- name: Check that account was modified + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_uri: "{{ account_created.account_uri }}" + register: account_modified + +- name: Check with wrong account URI + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_uri: "{{ account_created.account_uri }}test1234doesnotexists" + register: account_not_exist + +- name: Check with wrong account key + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/accountkey2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_uri: "{{ account_created.account_uri }}" + ignore_errors: yes + register: account_wrong_key diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/main.yml new file mode 100644 index 00000000..b7c7452a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tasks/main.yml @@ -0,0 +1,36 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tests/validate.yml new file mode 100644 index 00000000..5863a8c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_account_info/tests/validate.yml @@ -0,0 +1,40 @@ +--- +- name: Validate that account wasn't there + assert: + that: + - not account_not_created.exists + - account_not_created.account_uri is none + - "'account' not in account_not_created" + +- name: Validate that account was created + assert: + that: + - account_created.exists + - account_created.account_uri is not none + - "'account' in account_created" + - "'contact' in account_created.account" + - "'public_account_key' in account_created.account" + - account_created.account.contact | length == 1 + - "account_created.account.contact[0] == 'mailto:example@example.org'" + +- name: Validate that account email was removed + assert: + that: + - account_modified.exists + - account_modified.account_uri is not none + - "'account' in account_modified" + - "'contact' in account_modified.account" + - "'public_account_key' in account_modified.account" + - account_modified.account.contact | length == 0 + +- name: Validate that account does not exist with wrong account URI + assert: + that: + - not account_not_exist.exists + - account_not_exist.account_uri is none + - "'account' not in account_not_exist" + +- name: Validate that account cannot be accessed with wrong key + assert: + that: + - account_wrong_key is failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/impl.yml new file mode 100644 index 00000000..8547245d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/impl.yml @@ -0,0 +1,462 @@ +--- +## SET UP ACCOUNT KEYS ######################################################################## +- name: Create ECC256 account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/account-ec256.pem" +- name: Create ECC384 account key + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/account-ec384.pem" +- name: Create RSA account key + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/account-rsa.pem {{ default_rsa_key_size }}" +## SET UP ACCOUNTS ############################################################################ +- name: Make sure ECC256 account hasn't been created yet + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key_src: "{{ output_dir }}/account-ec256.pem" + state: absent +- name: Create ECC384 account + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key_content: "{{ lookup('file', output_dir ~ '/account-ec384.pem') }}" + state: present + allow_creation: yes + terms_agreed: yes + contact: + - mailto:example@example.org + - mailto:example@example.com +- name: Create RSA account + acme_account: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key_src: "{{ output_dir }}/account-rsa.pem" + state: present + allow_creation: yes + terms_agreed: yes + contact: [] +## OBTAIN CERTIFICATES ######################################################################## +- name: Obtain cert 1 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 1 + certificate_name: cert-1 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.com" + subject_alt_name_critical: no + account_key: account-ec256 + challenge: http-01 + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" + retrieve_all_alternates: yes + acme_expected_root_number: 1 + select_chain: + - test_certificates: last + issuer: "{{ acme_roots[1].subject }}" + use_csr_content: true +- name: Store obtain results for cert 1 + set_fact: + cert_1_obtain_results: "{{ certificate_obtain_result }}" + cert_1_alternate: "{{ 1 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 2 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 2 + certificate_name: cert-2 + key_type: ec256 + subject_alt_name: "DNS:*.example.com,DNS:example.com" + subject_alt_name_critical: yes + account_key: account-ec384 + challenge: dns-01 + modify_account: no + deactivate_authzs: yes + force: no + remaining_days: 10 + terms_agreed: no + account_email: "" + acme_expected_root_number: 0 + retrieve_all_alternates: yes + select_chain: + # All intermediates have the same subject, so always the first + # chain will be found, and we need a second condition to make sure + # that the first condition actually works. (The second condition + # has been tested above.) + - test_certificates: all + subject: "{{ acme_intermediates[0].subject }}" + - test_certificates: all + issuer: "{{ acme_roots[2].subject }}" + use_csr_content: false +- name: Store obtain results for cert 2 + set_fact: + cert_2_obtain_results: "{{ certificate_obtain_result }}" + cert_2_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 3 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 3 + certificate_name: cert-3 + key_type: ec384 + subject_alt_name: "DNS:*.example.com,DNS:example.org,DNS:t1.example.com" + subject_alt_name_critical: no + account_key_content: "{{ lookup('file', output_dir ~ '/account-rsa.pem') }}" + challenge: dns-01 + modify_account: no + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: no + account_email: "" + acme_expected_root_number: 0 + retrieve_all_alternates: yes + select_chain: + - test_certificates: last + subject: "{{ acme_roots[1].subject }}" + use_csr_content: true +- name: Store obtain results for cert 3 + set_fact: + cert_3_obtain_results: "{{ certificate_obtain_result }}" + cert_3_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 4 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 4 + certificate_name: cert-4 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.com,DNS:t1.example.com,DNS:test.t2.example.com,DNS:example.org,DNS:test.example.org" + subject_alt_name_critical: no + account_key: account-rsa + challenge: http-01 + modify_account: no + deactivate_authzs: yes + force: yes + remaining_days: 10 + terms_agreed: no + account_email: "" + acme_expected_root_number: 2 + select_chain: + - test_certificates: last + issuer: "{{ acme_roots[2].subject }}" + - test_certificates: last + issuer: "{{ acme_roots[1].subject }}" + use_csr_content: false +- name: Store obtain results for cert 4 + set_fact: + cert_4_obtain_results: "{{ certificate_obtain_result }}" + cert_4_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 5 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 5, Iteration 1/4 + certificate_name: cert-5 + key_type: ec521 + subject_alt_name: "DNS:t2.example.com" + subject_alt_name_critical: no + account_key: account-ec384 + challenge: http-01 + modify_account: no + deactivate_authzs: yes + force: yes + remaining_days: 10 + terms_agreed: no + account_email: "" + use_csr_content: true +- name: Store obtain results for cert 5a + set_fact: + cert_5a_obtain_results: "{{ certificate_obtain_result }}" + cert_5_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 5 (should not, since already there and valid for more than 10 days) + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 5, Iteration 2/4 + certificate_name: cert-5 + key_type: ec521 + subject_alt_name: "DNS:t2.example.com" + subject_alt_name_critical: no + account_key: account-ec384 + challenge: http-01 + modify_account: no + deactivate_authzs: yes + force: no + remaining_days: 10 + terms_agreed: no + account_email: "" + use_csr_content: false +- name: Store obtain results for cert 5b + set_fact: + cert_5_recreate_1: "{{ challenge_data is changed }}" +- name: Obtain cert 5 (should again by less days) + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 5, Iteration 3/4 + certificate_name: cert-5 + key_type: ec521 + subject_alt_name: "DNS:t2.example.com" + subject_alt_name_critical: no + account_key: account-ec384 + challenge: http-01 + modify_account: no + deactivate_authzs: yes + force: yes + remaining_days: 1000 + terms_agreed: no + account_email: "" + use_csr_content: true +- name: Store obtain results for cert 5c + set_fact: + cert_5_recreate_2: "{{ challenge_data is changed }}" + cert_5c_obtain_results: "{{ certificate_obtain_result }}" +- name: Obtain cert 5 (should again by force) + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 5, Iteration 4/4 + certificate_name: cert-5 + key_type: ec521 + subject_alt_name: "DNS:t2.example.com" + subject_alt_name_critical: no + account_key_content: "{{ lookup('file', output_dir ~ '/account-ec384.pem') }}" + challenge: http-01 + modify_account: no + deactivate_authzs: yes + force: yes + remaining_days: 10 + terms_agreed: no + account_email: "" + use_csr_content: false +- name: Store obtain results for cert 5d + set_fact: + cert_5_recreate_3: "{{ challenge_data is changed }}" + cert_5d_obtain_results: "{{ certificate_obtain_result }}" +- name: Obtain cert 6 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 6 + certificate_name: cert-6 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.org" + subject_alt_name_critical: no + account_key: account-ec256 + challenge: tls-alpn-01 + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" + acme_expected_root_number: 0 + select_chain: + # All intermediates have the same subject key identifier, so always + # the first chain will be found, and we need a second condition to + # make sure that the first condition actually works. (The second + # condition has been tested above.) + - test_certificates: first + subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}" + - test_certificates: last + issuer: "{{ acme_roots[1].subject }}" + use_csr_content: true +- name: Store obtain results for cert 6 + set_fact: + cert_6_obtain_results: "{{ certificate_obtain_result }}" + cert_6_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 7 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 7 + certificate_name: cert-7 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: + - "IP:127.0.0.1" + # - "IP:::1" + subject_alt_name_critical: no + account_key: account-ec256 + challenge: http-01 + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" + acme_expected_root_number: 2 + select_chain: + - test_certificates: last + authority_key_identifier: "{{ acme_roots[2].subject_key_identifier }}" + use_csr_content: false +- name: Store obtain results for cert 7 + set_fact: + cert_7_obtain_results: "{{ certificate_obtain_result }}" + cert_7_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}" +- name: Obtain cert 8 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 8 + certificate_name: cert-8 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: + - "IP:127.0.0.1" + # IPv4 only since our test validation server doesn't work + # with IPv6 (thanks to Python's socketserver). + subject_alt_name_critical: no + account_key: account-ec256 + challenge: tls-alpn-01 + challenge_alpn_tls: acme_challenge_cert_helper + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" + use_csr_content: true +- name: Store obtain results for cert 8 + set_fact: + cert_8_obtain_results: "{{ certificate_obtain_result }}" + cert_8_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}" +## DISSECT CERTIFICATES ####################################################################### +# Make sure certificates are valid. Root certificate for Pebble equals the chain certificate. +- name: Verifying cert 1 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-1-root.pem" -untrusted "{{ output_dir }}/cert-1-chain.pem" "{{ output_dir }}/cert-1.pem"' + ignore_errors: yes + register: cert_1_valid +- name: Verifying cert 2 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-2-root.pem" -untrusted "{{ output_dir }}/cert-2-chain.pem" "{{ output_dir }}/cert-2.pem"' + ignore_errors: yes + register: cert_2_valid +- name: Verifying cert 3 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-3-root.pem" -untrusted "{{ output_dir }}/cert-3-chain.pem" "{{ output_dir }}/cert-3.pem"' + ignore_errors: yes + register: cert_3_valid +- name: Verifying cert 4 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-4-root.pem" -untrusted "{{ output_dir }}/cert-4-chain.pem" "{{ output_dir }}/cert-4.pem"' + ignore_errors: yes + register: cert_4_valid +- name: Verifying cert 5 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-5-root.pem" -untrusted "{{ output_dir }}/cert-5-chain.pem" "{{ output_dir }}/cert-5.pem"' + ignore_errors: yes + register: cert_5_valid +- name: Verifying cert 6 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-6-root.pem" -untrusted "{{ output_dir }}/cert-6-chain.pem" "{{ output_dir }}/cert-6.pem"' + ignore_errors: yes + register: cert_6_valid +- name: Verifying cert 7 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-7-root.pem" -untrusted "{{ output_dir }}/cert-7-chain.pem" "{{ output_dir }}/cert-7.pem"' + ignore_errors: yes + register: cert_7_valid +- name: Verifying cert 8 + command: '{{ openssl_binary }} verify -CAfile "{{ output_dir }}/cert-8-root.pem" -untrusted "{{ output_dir }}/cert-8-chain.pem" "{{ output_dir }}/cert-8.pem"' + ignore_errors: yes + register: cert_8_valid +# Dump certificate info +- name: Dumping cert 1 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-1.pem" -noout -text' + register: cert_1_text +- name: Dumping cert 2 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-2.pem" -noout -text' + register: cert_2_text +- name: Dumping cert 3 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-3.pem" -noout -text' + register: cert_3_text +- name: Dumping cert 4 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-4.pem" -noout -text' + register: cert_4_text +- name: Dumping cert 5 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-5.pem" -noout -text' + register: cert_5_text +- name: Dumping cert 6 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-6.pem" -noout -text' + register: cert_6_text +- name: Dumping cert 7 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-7.pem" -noout -text' + register: cert_7_text +- name: Dumping cert 8 + command: '{{ openssl_binary }} x509 -in "{{ output_dir }}/cert-8.pem" -noout -text' + register: cert_8_text +# Dump certificate info +- name: Dumping cert 1 + x509_certificate_info: + path: "{{ output_dir }}/cert-1.pem" + register: cert_1_info +- name: Dumping cert 2 + x509_certificate_info: + path: "{{ output_dir }}/cert-2.pem" + register: cert_2_info +- name: Dumping cert 3 + x509_certificate_info: + path: "{{ output_dir }}/cert-3.pem" + register: cert_3_info +- name: Dumping cert 4 + x509_certificate_info: + path: "{{ output_dir }}/cert-4.pem" + register: cert_4_info +- name: Dumping cert 5 + x509_certificate_info: + path: "{{ output_dir }}/cert-5.pem" + register: cert_5_info +- name: Dumping cert 6 + x509_certificate_info: + path: "{{ output_dir }}/cert-6.pem" + register: cert_6_info +- name: Dumping cert 7 + x509_certificate_info: + path: "{{ output_dir }}/cert-7.pem" + register: cert_7_info +- name: Dumping cert 8 + x509_certificate_info: + path: "{{ output_dir }}/cert-8.pem" + register: cert_8_info +## GET ACCOUNT ORDERS ######################################################################### +- name: Don't retrieve orders + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec256.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + retrieve_orders: ignore + register: account_orders_not +- name: Retrieve orders as URL list (1/2) + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec256.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + retrieve_orders: url_list + register: account_orders_urls +- name: Retrieve orders as URL list (2/2) + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec384.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + retrieve_orders: url_list + register: account_orders_urls2 +- name: Retrieve orders as object list (1/2) + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec256.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + retrieve_orders: object_list + register: account_orders_full +- name: Retrieve orders as object list (2/2) + acme_account_info: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec384.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + retrieve_orders: object_list + register: account_orders_full2 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/main.yml new file mode 100644 index 00000000..2e732466 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/main.yml @@ -0,0 +1,107 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Obtain root and intermediate certificates + get_url: + url: "http://{{ acme_host }}:5000/{{ item.0 }}-certificate-for-ca/{{ item.1 }}" + dest: "{{ output_dir }}/acme-{{ item.0 }}-{{ item.1 }}.pem" + loop: "{{ query('nested', types, root_numbers) }}" + + - name: Analyze root certificates + x509_certificate_info: + path: "{{ output_dir }}/acme-root-{{ item }}.pem" + loop: "{{ root_numbers }}" + register: acme_roots + + - name: Analyze intermediate certificates + x509_certificate_info: + path: "{{ output_dir }}/acme-intermediate-{{ item }}.pem" + loop: "{{ root_numbers }}" + register: acme_intermediates + + - set_fact: + x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}" + y__: "{{ lookup('file', output_dir ~ '/acme-root-' ~ item.item ~ '.pem', rstrip=False) }}" + loop: "{{ acme_roots.results }}" + register: acme_roots_tmp + + - set_fact: + x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}" + y__: "{{ lookup('file', output_dir ~ '/acme-intermediate-' ~ item.item ~ '.pem', rstrip=False) }}" + loop: "{{ acme_intermediates.results }}" + register: acme_intermediates_tmp + + - set_fact: + acme_roots: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.x__') | list }}" + acme_root_certs: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.y__') | list }}" + acme_intermediates: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.x__') | list }}" + acme_intermediate_certs: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.y__') | list }}" + + vars: + types: + - root + - intermediate + root_numbers: + # The number 3 comes from here: https://github.com/ansible/acme-test-container/blob/master/run.sh#L12 + - 0 + - 1 + - 2 + - 3 + interesting_keys: + - authority_key_identifier + - subject_key_identifier + - issuer + - subject + #- serial_number + #- public_key_fingerprints + +- name: ACME root certificate info + debug: + var: acme_roots + +#- name: ACME root certificates as PEM +# debug: +# var: acme_root_certs + +- name: ACME intermediate certificate info + debug: + var: acme_intermediates + +#- name: ACME intermediate certificates as PEM +# debug: +# var: acme_intermediate_certs + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/obtain-cert.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/obtain-cert.yml new file mode 100644 index 00000000..ec53510b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tasks/obtain-cert.yml @@ -0,0 +1,144 @@ +--- +## PRIVATE KEY ################################################################################ +- name: ({{ certgen_title }}) Create cert private key (RSA) + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/{{ certificate_name }}.key {{ rsa_bits if key_type == 'rsa' else default_rsa_key_size }}" + when: "key_type == 'rsa'" +- name: ({{ certgen_title }}) Create cert private key (ECC 256) + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec256'" +- name: ({{ certgen_title }}) Create cert private key (ECC 384) + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec384'" +- name: ({{ certgen_title }}) Create cert private key (ECC 512) + command: "{{ openssl_binary }} ecparam -name secp521r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec521'" +## CSR ######################################################################################## +- name: ({{ certgen_title }}) Create cert CSR + openssl_csr: + path: "{{ output_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ output_dir }}/{{ certificate_name }}.key" + subject_alt_name: "{{ subject_alt_name }}" + subject_alt_name_critical: "{{ subject_alt_name_critical }}" + return_content: true + register: csr_result +## ACME STEP 1 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 1 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + register: challenge_data +- name: ({{ certgen_title }}) Print challenge data + debug: + var: challenge_data +- name: ({{ certgen_title }}) Create HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: PUT + body_format: raw + body: "{{ item.value['http-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Create DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: PUT + body_format: json + body: "{{ item.value }}" + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (acm_challenge_cert_helper) + acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: "{{ output_dir }}/{{ certificate_name }}.key" + with_dict: "{{ challenge_data.challenge_data }}" + register: tls_alpn_challenges + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Set TLS ALPN challenges (acm_challenge_cert_helper) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key" + method: PUT + body_format: raw + body: "{{ item.challenge_certificate }}\n{{ lookup('file', output_dir ~ '/' ~ certificate_name ~ '.key') }}" + headers: + content-type: "application/pem-certificate-chain" + with_items: "{{ tls_alpn_challenges.results }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64" + method: PUT + body_format: raw + body: "{{ item.value['tls-alpn-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is not defined or challenge_alpn_tls == 'der-value-b64')" +## ACME STEP 2 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 2 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_uri: "{{ challenge_data.account_uri }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + data: "{{ challenge_data }}" + retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}" + select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}" + register: certificate_obtain_result + when: challenge_data is changed +- name: ({{ certgen_title }}) Deleting HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Deleting DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Deleting TLS ALPN challenges + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01'" +- name: ({{ certgen_title }}) Get root certificate + get_url: + url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}" + dest: "{{ output_dir }}/{{ certificate_name }}-root.pem" +############################################################################################### diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tests/validate.yml new file mode 100644 index 00000000..cd7d28b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate/tests/validate.yml @@ -0,0 +1,144 @@ +--- +- name: Check that certificate 1 is valid + assert: + that: + - cert_1_valid is not failed +- name: Check that certificate 1 contains correct SANs + assert: + that: + - "'DNS:example.com' in cert_1_text.stdout" +- name: Check that certificate 1 retrieval got all chains + assert: + that: + - "'all_chains' in cert_1_obtain_results" + - "cert_1_obtain_results.all_chains | length > 1" + - "'cert' in cert_1_obtain_results.all_chains[cert_1_alternate | int]" + - "'chain' in cert_1_obtain_results.all_chains[cert_1_alternate | int]" + - "'full_chain' in cert_1_obtain_results.all_chains[cert_1_alternate | int]" + - "lookup('file', output_dir ~ '/cert-1.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].cert" + - "lookup('file', output_dir ~ '/cert-1-chain.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].chain" + - "lookup('file', output_dir ~ '/cert-1-fullchain.pem', rstrip=False) == cert_1_obtain_results.all_chains[cert_1_alternate | int].full_chain" + +- name: Check that certificate 2 is valid + assert: + that: + - cert_2_valid is not failed +- name: Check that certificate 2 contains correct SANs + assert: + that: + - "'DNS:*.example.com' in cert_2_text.stdout" + - "'DNS:example.com' in cert_2_text.stdout" +- name: Check that certificate 1 retrieval got all chains + assert: + that: + - "'all_chains' in cert_2_obtain_results" + - "cert_2_obtain_results.all_chains | length > 1" + - "'cert' in cert_2_obtain_results.all_chains[cert_2_alternate | int]" + - "'chain' in cert_2_obtain_results.all_chains[cert_2_alternate | int]" + - "'full_chain' in cert_2_obtain_results.all_chains[cert_2_alternate | int]" + - "lookup('file', output_dir ~ '/cert-2.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].cert" + - "lookup('file', output_dir ~ '/cert-2-chain.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].chain" + - "lookup('file', output_dir ~ '/cert-2-fullchain.pem', rstrip=False) == cert_2_obtain_results.all_chains[cert_2_alternate | int].full_chain" + +- name: Check that certificate 3 is valid + assert: + that: + - cert_3_valid is not failed +- name: Check that certificate 3 contains correct SANs + assert: + that: + - "'DNS:*.example.com' in cert_3_text.stdout" + - "'DNS:example.org' in cert_3_text.stdout" + - "'DNS:t1.example.com' in cert_3_text.stdout" +- name: Check that certificate 1 retrieval got all chains + assert: + that: + - "'all_chains' in cert_3_obtain_results" + - "cert_3_obtain_results.all_chains | length > 1" + - "'cert' in cert_3_obtain_results.all_chains[cert_3_alternate | int]" + - "'chain' in cert_3_obtain_results.all_chains[cert_3_alternate | int]" + - "'full_chain' in cert_3_obtain_results.all_chains[cert_3_alternate | int]" + - "lookup('file', output_dir ~ '/cert-3.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].cert" + - "lookup('file', output_dir ~ '/cert-3-chain.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].chain" + - "lookup('file', output_dir ~ '/cert-3-fullchain.pem', rstrip=False) == cert_3_obtain_results.all_chains[cert_3_alternate | int].full_chain" + +- name: Check that certificate 4 is valid + assert: + that: + - cert_4_valid is not failed +- name: Check that certificate 4 contains correct SANs + assert: + that: + - "'DNS:example.com' in cert_4_text.stdout" + - "'DNS:t1.example.com' in cert_4_text.stdout" + - "'DNS:test.t2.example.com' in cert_4_text.stdout" + - "'DNS:example.org' in cert_4_text.stdout" + - "'DNS:test.example.org' in cert_4_text.stdout" +- name: Check that certificate 4 retrieval did not get all chains + assert: + that: + - "'all_chains' not in cert_4_obtain_results" + +- name: Check that certificate 5 is valid + assert: + that: + - cert_5_valid is not failed +- name: Check that certificate 5 contains correct SANs + assert: + that: + - "'DNS:t2.example.com' in cert_5_text.stdout" +- name: Check that certificate 5 was not recreated on the first try + assert: + that: + - cert_5_recreate_1 == False +- name: Check that certificate 5 was recreated on the second try + assert: + that: + - cert_5_recreate_2 == True +- name: Check that certificate 5 was recreated on the third try + assert: + that: + - cert_5_recreate_3 == True + +- name: Check that certificate 6 is valid + assert: + that: + - cert_6_valid is not failed +- name: Check that certificate 6 contains correct SANs + assert: + that: + - "'DNS:example.org' in cert_6_text.stdout" + +- name: Validate that orders were not retrieved + assert: + that: + - "'account' in account_orders_not" + - "'orders' not in account_orders_not" + +- name: Validate that orders were retrieved as list of URLs (1/2) + assert: + that: + - "'account' in account_orders_urls" + - "'orders' in account_orders_urls" + - "account_orders_urls.orders[0] is string" + +- name: Validate that orders were retrieved as list of URLs (2/2) + assert: + that: + - "'account' in account_orders_urls2" + - "'orders' in account_orders_urls2" + - "account_orders_urls2.orders[0] is string" + +- name: Validate that orders were retrieved as list of objects (1/2) + assert: + that: + - "'account' in account_orders_full" + - "'orders' in account_orders_full" + - "account_orders_full.orders[0].status is string" + +- name: Validate that orders were retrieved as list of objects (2/2) + assert: + that: + - "'account' in account_orders_full2" + - "'orders' in account_orders_full2" + - "account_orders_full2.orders[0].status is string" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/impl.yml new file mode 100644 index 00000000..cafafa99 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/impl.yml @@ -0,0 +1,89 @@ +--- +## SET UP ACCOUNT KEYS ######################################################################## +- name: Create ECC256 account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/account-ec256.pem" +- name: Create ECC384 account key + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/account-ec384.pem" +- name: Create RSA account key + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/account-rsa.pem {{ default_rsa_key_size }}" +## CREATE ACCOUNTS AND OBTAIN CERTIFICATES #################################################### +- name: Obtain cert 1 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 1 for revocation + certificate_name: cert-1 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.com" + subject_alt_name_critical: no + account_key_content: "{{ lookup('file', output_dir ~ '/account-ec256.pem') }}" + challenge: http-01 + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" +- name: Obtain cert 2 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 2 for revocation + certificate_name: cert-2 + key_type: ec256 + subject_alt_name: "DNS:*.example.com" + subject_alt_name_critical: yes + account_key: account-ec384 + challenge: dns-01 + modify_account: yes + deactivate_authzs: yes + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" +- name: Obtain cert 3 + include_tasks: obtain-cert.yml + vars: + certgen_title: Certificate 3 for revocation + certificate_name: cert-3 + key_type: ec384 + subject_alt_name: "DNS:t1.example.com" + subject_alt_name_critical: no + account_key: account-rsa + challenge: dns-01 + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" +## REVOKE CERTIFICATES ######################################################################## +- name: Revoke certificate 1 via account key + acme_certificate_revoke: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_src: "{{ output_dir }}/account-ec256.pem" + certificate: "{{ output_dir }}/cert-1.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + ignore_errors: yes + register: cert_1_revoke +- name: Revoke certificate 2 via certificate private key + acme_certificate_revoke: + select_crypto_backend: "{{ select_crypto_backend }}" + private_key_src: "{{ output_dir }}/cert-2.key" + certificate: "{{ output_dir }}/cert-2.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + ignore_errors: yes + register: cert_2_revoke +- name: Revoke certificate 3 via account key (fullchain) + acme_certificate_revoke: + select_crypto_backend: "{{ select_crypto_backend }}" + account_key_content: "{{ lookup('file', output_dir ~ '/account-rsa.pem') }}" + certificate: "{{ output_dir }}/cert-3-fullchain.pem" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + ignore_errors: yes + register: cert_3_revoke diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/main.yml new file mode 100644 index 00000000..b7c7452a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/main.yml @@ -0,0 +1,36 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml new file mode 100644 index 00000000..ec53510b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tasks/obtain-cert.yml @@ -0,0 +1,144 @@ +--- +## PRIVATE KEY ################################################################################ +- name: ({{ certgen_title }}) Create cert private key (RSA) + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/{{ certificate_name }}.key {{ rsa_bits if key_type == 'rsa' else default_rsa_key_size }}" + when: "key_type == 'rsa'" +- name: ({{ certgen_title }}) Create cert private key (ECC 256) + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec256'" +- name: ({{ certgen_title }}) Create cert private key (ECC 384) + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec384'" +- name: ({{ certgen_title }}) Create cert private key (ECC 512) + command: "{{ openssl_binary }} ecparam -name secp521r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec521'" +## CSR ######################################################################################## +- name: ({{ certgen_title }}) Create cert CSR + openssl_csr: + path: "{{ output_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ output_dir }}/{{ certificate_name }}.key" + subject_alt_name: "{{ subject_alt_name }}" + subject_alt_name_critical: "{{ subject_alt_name_critical }}" + return_content: true + register: csr_result +## ACME STEP 1 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 1 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + register: challenge_data +- name: ({{ certgen_title }}) Print challenge data + debug: + var: challenge_data +- name: ({{ certgen_title }}) Create HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: PUT + body_format: raw + body: "{{ item.value['http-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Create DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: PUT + body_format: json + body: "{{ item.value }}" + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (acm_challenge_cert_helper) + acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: "{{ output_dir }}/{{ certificate_name }}.key" + with_dict: "{{ challenge_data.challenge_data }}" + register: tls_alpn_challenges + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Set TLS ALPN challenges (acm_challenge_cert_helper) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key" + method: PUT + body_format: raw + body: "{{ item.challenge_certificate }}\n{{ lookup('file', output_dir ~ '/' ~ certificate_name ~ '.key') }}" + headers: + content-type: "application/pem-certificate-chain" + with_items: "{{ tls_alpn_challenges.results }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64" + method: PUT + body_format: raw + body: "{{ item.value['tls-alpn-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is not defined or challenge_alpn_tls == 'der-value-b64')" +## ACME STEP 2 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 2 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_uri: "{{ challenge_data.account_uri }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + data: "{{ challenge_data }}" + retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}" + select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}" + register: certificate_obtain_result + when: challenge_data is changed +- name: ({{ certgen_title }}) Deleting HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Deleting DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Deleting TLS ALPN challenges + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01'" +- name: ({{ certgen_title }}) Get root certificate + get_url: + url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}" + dest: "{{ output_dir }}/{{ certificate_name }}-root.pem" +############################################################################################### diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tests/validate.yml new file mode 100644 index 00000000..7f8f9c54 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_revoke/tests/validate.yml @@ -0,0 +1,16 @@ +--- +- name: Check that certificate 1 was revoked + assert: + that: + - cert_1_revoke is changed + - cert_1_revoke is not failed +- name: Check that certificate 2 was revoked + assert: + that: + - cert_2_revoke is changed + - cert_2_revoke is not failed +- name: Check that certificate 3 was revoked + assert: + that: + - cert_3_revoke is changed + - cert_3_revoke is not failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/main.yml new file mode 100644 index 00000000..cd306a4a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/main.yml @@ -0,0 +1,30 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Create ECC256 account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/account-ec256.pem" + - name: Obtain cert 1 + include_tasks: obtain-cert.yml + vars: + select_crypto_backend: auto + certgen_title: Certificate 1 + certificate_name: cert-1 + key_type: rsa + rsa_bits: "{{ default_rsa_key_size }}" + subject_alt_name: "DNS:example.com" + subject_alt_name_critical: no + account_key: account-ec256 + challenge: tls-alpn-01 + challenge_alpn_tls: acme_challenge_cert_helper + modify_account: yes + deactivate_authzs: no + force: no + remaining_days: 10 + terms_agreed: yes + account_email: "example@example.org" + + when: openssl_version.stdout is version('1.0.0', '>=') or cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/obtain-cert.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/obtain-cert.yml new file mode 100644 index 00000000..ec53510b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_challenge_cert_helper/tasks/obtain-cert.yml @@ -0,0 +1,144 @@ +--- +## PRIVATE KEY ################################################################################ +- name: ({{ certgen_title }}) Create cert private key (RSA) + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/{{ certificate_name }}.key {{ rsa_bits if key_type == 'rsa' else default_rsa_key_size }}" + when: "key_type == 'rsa'" +- name: ({{ certgen_title }}) Create cert private key (ECC 256) + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec256'" +- name: ({{ certgen_title }}) Create cert private key (ECC 384) + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec384'" +- name: ({{ certgen_title }}) Create cert private key (ECC 512) + command: "{{ openssl_binary }} ecparam -name secp521r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec521'" +## CSR ######################################################################################## +- name: ({{ certgen_title }}) Create cert CSR + openssl_csr: + path: "{{ output_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ output_dir }}/{{ certificate_name }}.key" + subject_alt_name: "{{ subject_alt_name }}" + subject_alt_name_critical: "{{ subject_alt_name_critical }}" + return_content: true + register: csr_result +## ACME STEP 1 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 1 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + register: challenge_data +- name: ({{ certgen_title }}) Print challenge data + debug: + var: challenge_data +- name: ({{ certgen_title }}) Create HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: PUT + body_format: raw + body: "{{ item.value['http-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Create DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: PUT + body_format: json + body: "{{ item.value }}" + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (acm_challenge_cert_helper) + acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: "{{ output_dir }}/{{ certificate_name }}.key" + with_dict: "{{ challenge_data.challenge_data }}" + register: tls_alpn_challenges + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Set TLS ALPN challenges (acm_challenge_cert_helper) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key" + method: PUT + body_format: raw + body: "{{ item.challenge_certificate }}\n{{ lookup('file', output_dir ~ '/' ~ certificate_name ~ '.key') }}" + headers: + content-type: "application/pem-certificate-chain" + with_items: "{{ tls_alpn_challenges.results }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64" + method: PUT + body_format: raw + body: "{{ item.value['tls-alpn-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is not defined or challenge_alpn_tls == 'der-value-b64')" +## ACME STEP 2 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 2 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_uri: "{{ challenge_data.account_uri }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + data: "{{ challenge_data }}" + retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}" + select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}" + register: certificate_obtain_result + when: challenge_data is changed +- name: ({{ certgen_title }}) Deleting HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Deleting DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Deleting TLS ALPN challenges + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01'" +- name: ({{ certgen_title }}) Get root certificate + get_url: + url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}" + dest: "{{ output_dir }}/{{ certificate_name }}-root.pem" +############################################################################################### diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml new file mode 100644 index 00000000..3f5c561f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml @@ -0,0 +1,151 @@ +--- +- name: Generate account key + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem" + +- name: Parse account key (to ease debugging some test failures) + command: "{{ openssl_binary }} ec -in {{ output_dir }}/accountkey.pem -noout -text" + +- name: Get directory + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + method: directory-only + register: directory +- debug: var=directory + +- name: Create an account + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + url: "{{ directory.directory.newAccount}}" + method: post + content: '{"termsOfServiceAgreed":true}' + register: account_creation + # account_creation.headers.location contains the account URI + # if creation was successful +- debug: var=account_creation + +- name: Get account information + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ account_creation.headers.location }}" + method: get + register: account_get +- debug: var=account_get + +- name: Update account contacts + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ account_creation.headers.location }}" + method: post + content: '{{ account_info | to_json }}' + vars: + account_info: + # For valid values, see + # https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3 + contact: + - mailto:me@example.com + register: account_update +- debug: var=account_update + +- name: Create certificate order + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ directory.directory.newOrder }}" + method: post + content: '{{ create_order | to_json }}' + vars: + create_order: + # For valid values, see + # https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4 and + # https://www.rfc-editor.org/rfc/rfc8738.html + identifiers: + - type: dns + value: example.com + - type: dns + value: example.org + register: new_order +- debug: var=new_order + +- name: Get order information + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ new_order.headers.location }}" + method: get + register: order +- debug: var=order + +- name: Get authzs for order + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ item }}" + method: get + loop: "{{ order.output_json.authorizations }}" + register: authz +- debug: var=authz + +- name: Get HTTP-01 challenge for authz + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ (item.challenges | selectattr('type', 'equalto', 'http-01') | list)[0].url }}" + method: get + register: http01challenge + loop: "{{ authz.results | map(attribute='output_json') | list }}" +- debug: var=http01challenge + +- name: Activate HTTP-01 challenge manually + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ item.url }}" + method: post + content: '{}' + register: activation + loop: "{{ http01challenge.results | map(attribute='output_json') | list }}" +- debug: var=activation + +- name: Get HTTP-01 challenge results + acme_inspect: + acme_directory: https://{{ acme_host }}:14000/dir + acme_version: 2 + validate_certs: no + account_key_src: "{{ output_dir }}/accountkey.pem" + account_uri: "{{ account_creation.headers.location }}" + url: "{{ item.url }}" + method: get + register: validation_result + loop: "{{ http01challenge.results | map(attribute='output_json') | list }}" + until: "validation_result.output_json.status != 'pending'" + retries: 20 + delay: 1 +- debug: var=validation_result diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/main.yml new file mode 100644 index 00000000..b7c7452a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/main.yml @@ -0,0 +1,36 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: openssl + + - import_tasks: ../tests/validate.yml + + # Old 0.9.8 versions have insufficient CLI support for signing with EC keys + when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tests/validate.yml new file mode 100644 index 00000000..a45b512f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tests/validate.yml @@ -0,0 +1,131 @@ +--- +- name: Check directory output + assert: + that: + - directory is not changed + - "'directory' in directory" + - "'newAccount' in directory.directory" + - "'newOrder' in directory.directory" + - "'newNonce' in directory.directory" + - "'headers' not in directory" + - "'output_text' not in directory" + - "'output_json' not in directory" + +- name: Check account creation output + assert: + that: + - account_creation is changed + - "'directory' in account_creation" + - "'headers' in account_creation" + - "'output_text' in account_creation" + - "'output_json' in account_creation" + - account_creation.headers.status == 201 + - "'location' in account_creation.headers" + - account_creation.output_json.status == 'valid' + - not (account_creation.output_json.contact | default([])) + - account_creation.output_text | from_json == account_creation.output_json + +- name: Check account get output + assert: + that: + - account_get is not changed + - "'directory' in account_get" + - "'headers' in account_get" + - "'output_text' in account_get" + - "'output_json' in account_get" + - account_get.headers.status == 200 + - account_get.output_json == account_creation.output_json + +- name: Check account update output + assert: + that: + - account_update is changed + - "'directory' in account_update" + - "'headers' in account_update" + - "'output_text' in account_update" + - "'output_json' in account_update" + - account_update.output_json.status == 'valid' + - account_update.output_json.contact | length == 1 + - account_update.output_json.contact[0] == 'mailto:me@example.com' + +- name: Check certificate request output + assert: + that: + - new_order is changed + - "'directory' in new_order" + - "'headers' in new_order" + - "'output_text' in new_order" + - "'output_json' in new_order" + - new_order.output_json.authorizations | length == 2 + - new_order.output_json.identifiers | length == 2 + - new_order.output_json.status == 'pending' + - "'finalize' in new_order.output_json" + +- name: Check get order output + assert: + that: + - order is not changed + - "'directory' in order" + - "'headers' in order" + - "'output_text' in order" + - "'output_json' in order" + # The order of identifiers and authorizations is randomized! + # - new_order.output_json == order.output_json + +- name: Check get authz output + assert: + that: + - item is not changed + - "'directory' in item" + - "'headers' in item" + - "'output_text' in item" + - "'output_json' in item" + - item.output_json.challenges | length >= 3 + - item.output_json.identifier.type == 'dns' + - item.output_json.status == 'pending' + loop: "{{ authz.results }}" + +- name: Check get challenge output + assert: + that: + - item is not changed + - "'directory' in item" + - "'headers' in item" + - "'output_text' in item" + - "'output_json' in item" + - item.output_json.status == 'pending' + - item.output_json.type == 'http-01' + - item.output_json.url == item.invocation.module_args.url + - "'token' in item.output_json" + loop: "{{ http01challenge.results }}" + +- name: Check challenge activation output + assert: + that: + - item is changed + - "'directory' in item" + - "'headers' in item" + - "'output_text' in item" + - "'output_json' in item" + - item.output_json.status == 'pending' + - item.output_json.type == 'http-01' + - item.output_json.url == item.invocation.module_args.url + - "'token' in item.output_json" + loop: "{{ activation.results }}" + +- name: Check validation result + assert: + that: + - item is not changed + - "'directory' in item" + - "'headers' in item" + - "'output_text' in item" + - "'output_json' in item" + - item.output_json.status == 'invalid' + - item.output_json.type == 'http-01' + - item.output_json.url == item.invocation.module_args.url + - "'token' in item.output_json" + - "'validated' in item.output_json" + - "'error' in item.output_json" + - item.output_json.error.type == 'urn:ietf:params:acme:error:unauthorized' + loop: "{{ validation_result.results }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/aliases new file mode 100644 index 00000000..a6dafcf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/aliases @@ -0,0 +1 @@ +shippable/posix/group1 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-chain.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-chain.pem new file mode 100644 index 00000000..3e0f6c08 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-chain.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnzCCAyWgAwIBAgIQWyXOaQfEJlVm0zkMmalUrTAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwOTI1MDAw +MDAwWhcNMjkwOTI0MjM1OTU5WjCBkjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxODA2BgNVBAMTL0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlk +YXRpb24gU2VjdXJlIFNlcnZlciBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD +QgAEAjgZgTrJaYRwWQKOqIofMN+83gP8eR06JSxrQSEYgur5PkrkM8wSzypD/A7y +ZADA4SVQgiTNtkk4DyVHkUikraOCAWYwggFiMB8GA1UdIwQYMBaAFHVxpxlIGbyd +nepBR9+UxEh3mdN5MB0GA1UdDgQWBBRACWFn8LyDcU/eEggsb9TUK3Y9ljAOBgNV +HQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAECATBMBgNV +HR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9FQ0ND +ZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDByBggrBgEFBQcBAQRmMGQwOwYIKwYB +BQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET0VDQ0FkZFRydXN0 +Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC5jb21vZG9jYTQuY29tMAoG +CCqGSM49BAMDA2gAMGUCMQCsaEclgBNPE1bAojcJl1pQxOfttGHLKIoKETKm4nHf +EQGJbwd6IGZrGNC5LkP3Um8CMBKFfI4TZpIEuppFCZRKMGHRSdxv6+ctyYnPHmp8 +7IXOMCVZuoFwNLg0f+cB0eLLUg== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-fullchain.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-fullchain.pem new file mode 100644 index 00000000..e12a7ca8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-fullchain.pem @@ -0,0 +1,51 @@ +-----BEGIN CERTIFICATE----- +MIIFBTCCBKugAwIBAgIQL+c9oQXpvdcOD3BKAncbgDAKBggqhkjOPQQDAjCBkjEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxODA2BgNVBAMT +L0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAy +MB4XDTE4MDcxMTAwMDAwMFoXDTE5MDExNzIzNTk1OVowbDEhMB8GA1UECxMYRG9t +YWluIENvbnRyb2wgVmFsaWRhdGVkMSEwHwYDVQQLExhQb3NpdGl2ZVNTTCBNdWx0 +aS1Eb21haW4xJDAiBgNVBAMTG3NzbDgwMzAyNS5jbG91ZGZsYXJlc3NsLmNvbTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABMap9sMZnCzTXID1chTOmtOk8p6+SHbG +3fmyJJljI7sN9RddlLKar9VBS48WguVv1R6trvERIYj8TzKCVBzu9mmjggMGMIID +AjAfBgNVHSMEGDAWgBRACWFn8LyDcU/eEggsb9TUK3Y9ljAdBgNVHQ4EFgQUd/6a +t8j7v5DsL7xWacf8VyzOLJcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAw +HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCME8GA1UdIARIMEYwOgYLKwYB +BAGyMQECAgcwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNv +bS9DUFMwCAYGZ4EMAQIBMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwuY29t +b2RvY2E0LmNvbS9DT01PRE9FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVy +Q0EyLmNybDCBiAYIKwYBBQUHAQEEfDB6MFEGCCsGAQUFBzAChkVodHRwOi8vY3J0 +LmNvbW9kb2NhNC5jb20vQ09NT0RPRUNDRG9tYWluVmFsaWRhdGlvblNlY3VyZVNl +cnZlckNBMi5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLmNvbW9kb2NhNC5j +b20wSAYDVR0RBEEwP4Ibc3NsODAzMDI1LmNsb3VkZmxhcmVzc2wuY29tghAqLmhz +Y29zY2RuNDAubmV0gg5oc2Nvc2NkbjQwLm5ldDCCAQMGCisGAQQB1nkCBAIEgfQE +gfEA7wB2AO5Lvbd1zmC64UJpH6vhnmajD35fsHLYgwDEe4l6qP3LAAABZIbVA88A +AAQDAEcwRQIhANtN489Izy3iss/eF8rUw/gir8rqyA2t3lpxnco+J2NlAiBBku5M +iGD8whW5/31byPj0/ype1MmG0QYrq3qWvYiQ3QB1AHR+2oMxrTMQkSGcziVPQnDC +v/1eQiAIxjc1eeYQe8xWAAABZIbVBB4AAAQDAEYwRAIgSjcL7B4cbgm2XED69G7/ +iFPe2zkWhxnkgGISSwuXw1gCICzwPmfbjEfwDNXEuBs7JXkPRaT1pi7hZ9aR5wJJ +TKH9MAoGCCqGSM49BAMCA0gAMEUCIQDqxmFLcme3Ldd+jiMQf7fT5pSezZfMOL0S +cNmfGvNtPQIgec3sO/ylnnaztCy5KDjYsnh+rm01bxs+nz2DnOPF+xo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDnzCCAyWgAwIBAgIQWyXOaQfEJlVm0zkMmalUrTAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwOTI1MDAw +MDAwWhcNMjkwOTI0MjM1OTU5WjCBkjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxODA2BgNVBAMTL0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlk +YXRpb24gU2VjdXJlIFNlcnZlciBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD +QgAEAjgZgTrJaYRwWQKOqIofMN+83gP8eR06JSxrQSEYgur5PkrkM8wSzypD/A7y +ZADA4SVQgiTNtkk4DyVHkUikraOCAWYwggFiMB8GA1UdIwQYMBaAFHVxpxlIGbyd +nepBR9+UxEh3mdN5MB0GA1UdDgQWBBRACWFn8LyDcU/eEggsb9TUK3Y9ljAOBgNV +HQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAECATBMBgNV +HR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9FQ0ND +ZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDByBggrBgEFBQcBAQRmMGQwOwYIKwYB +BQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET0VDQ0FkZFRydXN0 +Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC5jb21vZG9jYTQuY29tMAoG +CCqGSM49BAMDA2gAMGUCMQCsaEclgBNPE1bAojcJl1pQxOfttGHLKIoKETKm4nHf +EQGJbwd6IGZrGNC5LkP3Um8CMBKFfI4TZpIEuppFCZRKMGHRSdxv6+ctyYnPHmp8 +7IXOMCVZuoFwNLg0f+cB0eLLUg== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-root.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-root.pem new file mode 100644 index 00000000..546c95e3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1-root.pem @@ -0,0 +1,16 @@ +-----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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1.pem new file mode 100644 index 00000000..d00d252d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert1.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFBTCCBKugAwIBAgIQL+c9oQXpvdcOD3BKAncbgDAKBggqhkjOPQQDAjCBkjEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxODA2BgNVBAMT +L0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAy +MB4XDTE4MDcxMTAwMDAwMFoXDTE5MDExNzIzNTk1OVowbDEhMB8GA1UECxMYRG9t +YWluIENvbnRyb2wgVmFsaWRhdGVkMSEwHwYDVQQLExhQb3NpdGl2ZVNTTCBNdWx0 +aS1Eb21haW4xJDAiBgNVBAMTG3NzbDgwMzAyNS5jbG91ZGZsYXJlc3NsLmNvbTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABMap9sMZnCzTXID1chTOmtOk8p6+SHbG +3fmyJJljI7sN9RddlLKar9VBS48WguVv1R6trvERIYj8TzKCVBzu9mmjggMGMIID +AjAfBgNVHSMEGDAWgBRACWFn8LyDcU/eEggsb9TUK3Y9ljAdBgNVHQ4EFgQUd/6a +t8j7v5DsL7xWacf8VyzOLJcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAw +HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCME8GA1UdIARIMEYwOgYLKwYB +BAGyMQECAgcwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNv +bS9DUFMwCAYGZ4EMAQIBMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwuY29t +b2RvY2E0LmNvbS9DT01PRE9FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVy +Q0EyLmNybDCBiAYIKwYBBQUHAQEEfDB6MFEGCCsGAQUFBzAChkVodHRwOi8vY3J0 +LmNvbW9kb2NhNC5jb20vQ09NT0RPRUNDRG9tYWluVmFsaWRhdGlvblNlY3VyZVNl +cnZlckNBMi5jcnQwJQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLmNvbW9kb2NhNC5j +b20wSAYDVR0RBEEwP4Ibc3NsODAzMDI1LmNsb3VkZmxhcmVzc2wuY29tghAqLmhz +Y29zY2RuNDAubmV0gg5oc2Nvc2NkbjQwLm5ldDCCAQMGCisGAQQB1nkCBAIEgfQE +gfEA7wB2AO5Lvbd1zmC64UJpH6vhnmajD35fsHLYgwDEe4l6qP3LAAABZIbVA88A +AAQDAEcwRQIhANtN489Izy3iss/eF8rUw/gir8rqyA2t3lpxnco+J2NlAiBBku5M +iGD8whW5/31byPj0/ype1MmG0QYrq3qWvYiQ3QB1AHR+2oMxrTMQkSGcziVPQnDC +v/1eQiAIxjc1eeYQe8xWAAABZIbVBB4AAAQDAEYwRAIgSjcL7B4cbgm2XED69G7/ +iFPe2zkWhxnkgGISSwuXw1gCICzwPmfbjEfwDNXEuBs7JXkPRaT1pi7hZ9aR5wJJ +TKH9MAoGCCqGSM49BAMCA0gAMEUCIQDqxmFLcme3Ldd+jiMQf7fT5pSezZfMOL0S +cNmfGvNtPQIgec3sO/ylnnaztCy5KDjYsnh+rm01bxs+nz2DnOPF+xo= +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altchain.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altchain.pem new file mode 100644 index 00000000..4e82cb56 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altchain.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 +WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX +NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf +89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl +Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc +Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz +uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB +AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU +BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB +FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo +SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js +LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF +BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG +AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD +VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB +ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx +A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM +UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 +DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 +eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu +OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw +p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY +2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 +ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR +PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b +rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altroot.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altroot.pem new file mode 100644 index 00000000..b85c8037 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-altroot.pem @@ -0,0 +1,31 @@ +-----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/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-chain.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-chain.pem new file mode 100644 index 00000000..0002462c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-chain.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-fullchain.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-fullchain.pem new file mode 100644 index 00000000..cf75a331 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-fullchain.pem @@ -0,0 +1,72 @@ +-----BEGIN CERTIFICATE----- +MIIH5jCCBs6gAwIBAgISA2gSCm/BtvCR2e2bIap5YbXaMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODA3MjcxNzMxMjdaFw0x +ODEwMjUxNzMxMjdaMB4xHDAaBgNVBAMTE3d3dy5sZXRzZW5jcnlwdC5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpL8ZjVL0MUkUAIbYO9+ZCni+c +ghGd9WhM2Ztaay6Wyh6lNoCdltdqTwUhE4O+d7UFModjM3G/KMyfuujr06c5iGKL +3saPmIzLaRPIEOUlB2rKgasKhe8mDRyRLzQSXXgnsaKcTBBuhIHvtP51ZMr05nJJ +sX/5FGjj96w+KJel6E/Ux1a1ZDOFkAYNSIrJJhA5jjIvUPr+Ri6Oc6UlhF9oueKI +uWBILxQpC778tBWdHoZeBCNTHA1VvtwC53OeuHvdZm1jB/e30Mgf5DtVizYpFXVD +mztkrd6z/3B6ZwPyfCE4KgzSf70/byOz971OJxNKTUVWedKHHDlrMxfsPclbAgMB +AAGjggTwMIIE7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFG1w4j/KDrYSFu7m9DPE +xRR0E5gzMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUF +BwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZy8wggHxBgNVHREEggHoMIIB5IIbY2VydC5pbnQteDEubGV0c2VuY3J5 +cHQub3JnghtjZXJ0LmludC14Mi5sZXRzZW5jcnlwdC5vcmeCG2NlcnQuaW50LXgz +LmxldHNlbmNyeXB0Lm9yZ4IbY2VydC5pbnQteDQubGV0c2VuY3J5cHQub3Jnghxj +ZXJ0LnJvb3QteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0YWdpbmcteDEubGV0 +c2VuY3J5cHQub3Jngh9jZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JngiBj +ZXJ0LnN0Zy1yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ISY3AubGV0c2VuY3J5cHQu +b3JnghpjcC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ITY3BzLmxldHNlbmNyeXB0 +Lm9yZ4IbY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3Jnghtjcmwucm9vdC14MS5s +ZXRzZW5jcnlwdC5vcmeCD2xldHNlbmNyeXB0Lm9yZ4IWb3JpZ2luLmxldHNlbmNy +eXB0Lm9yZ4IXb3JpZ2luMi5sZXRzZW5jcnlwdC5vcmeCFnN0YXR1cy5sZXRzZW5j +cnlwdC5vcmeCE3d3dy5sZXRzZW5jcnlwdC5vcmcwgf4GA1UdIASB9jCB8zAIBgZn +gQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3Bz +LmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmlj +YXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBh +bmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGlj +eSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzCC +AQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AMEWSuCnctLUOS3ICsEHcNTwxJvemRpI +QMH6B1Fk9jNgAAABZN0ChToAAAQDAEcwRQIgblal8oXnfoopr1+dWVhvBx+sqHT0 +eLYxJHBTaRp3j1QCIQDhFQqMk6DDXUgcU12K36zLVFwJTdAJI4RBisnX+g+W0AB2 +ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZN0Chz4AAAQDAEcw +RQIhAImOjvkritUNKJZB7dcUtjoyIbfNwdCspvRiEzXuvVQoAiAZryoyg3TcMun5 +Gb2dEn1cttMnPW9u670/JdRjvjU/wTANBgkqhkiG9w0BAQsFAAOCAQEAGepCmckP +Tn9Sz268FEwkdD+6wWaPfeYlh+9nacFh90nQ35EYQMOK8a+X7ixHGbRz19On3Wt4 +1fcbPa9SefocTjAintMwwreCxpRTmwGACYojd7vRWEmA6q7+/HO2BfZahWzclOjw +mSDBycDEm8R0ZK52vYjzVno8x0mrsmSO0403S/6syYB/guH6P17kIBw+Tgx6/i/c +I1C6MoFkuaAKUUcZmgGGBgE+L/7cWtWjbkVXyA3ZQQy9G7rcBT+N/RrDfBh4iZDq +jAN5UIIYL8upBhjiMYVuoJrH2nklzEwr5SWKcccJX5eWkGLUwlcY9LGAA8+17l2I +l1Ou20Dm9TxnNw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-root.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-root.pem new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2-root.pem @@ -0,0 +1,20 @@ +-----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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2.pem new file mode 100644 index 00000000..834eedc4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/cert2.pem @@ -0,0 +1,45 @@ +-----BEGIN CERTIFICATE----- +MIIH5jCCBs6gAwIBAgISA2gSCm/BtvCR2e2bIap5YbXaMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODA3MjcxNzMxMjdaFw0x +ODEwMjUxNzMxMjdaMB4xHDAaBgNVBAMTE3d3dy5sZXRzZW5jcnlwdC5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpL8ZjVL0MUkUAIbYO9+ZCni+c +ghGd9WhM2Ztaay6Wyh6lNoCdltdqTwUhE4O+d7UFModjM3G/KMyfuujr06c5iGKL +3saPmIzLaRPIEOUlB2rKgasKhe8mDRyRLzQSXXgnsaKcTBBuhIHvtP51ZMr05nJJ +sX/5FGjj96w+KJel6E/Ux1a1ZDOFkAYNSIrJJhA5jjIvUPr+Ri6Oc6UlhF9oueKI +uWBILxQpC778tBWdHoZeBCNTHA1VvtwC53OeuHvdZm1jB/e30Mgf5DtVizYpFXVD +mztkrd6z/3B6ZwPyfCE4KgzSf70/byOz971OJxNKTUVWedKHHDlrMxfsPclbAgMB +AAGjggTwMIIE7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFG1w4j/KDrYSFu7m9DPE +xRR0E5gzMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUF +BwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZy8wggHxBgNVHREEggHoMIIB5IIbY2VydC5pbnQteDEubGV0c2VuY3J5 +cHQub3JnghtjZXJ0LmludC14Mi5sZXRzZW5jcnlwdC5vcmeCG2NlcnQuaW50LXgz +LmxldHNlbmNyeXB0Lm9yZ4IbY2VydC5pbnQteDQubGV0c2VuY3J5cHQub3Jnghxj +ZXJ0LnJvb3QteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0YWdpbmcteDEubGV0 +c2VuY3J5cHQub3Jngh9jZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JngiBj +ZXJ0LnN0Zy1yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ISY3AubGV0c2VuY3J5cHQu +b3JnghpjcC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ITY3BzLmxldHNlbmNyeXB0 +Lm9yZ4IbY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3Jnghtjcmwucm9vdC14MS5s +ZXRzZW5jcnlwdC5vcmeCD2xldHNlbmNyeXB0Lm9yZ4IWb3JpZ2luLmxldHNlbmNy +eXB0Lm9yZ4IXb3JpZ2luMi5sZXRzZW5jcnlwdC5vcmeCFnN0YXR1cy5sZXRzZW5j +cnlwdC5vcmeCE3d3dy5sZXRzZW5jcnlwdC5vcmcwgf4GA1UdIASB9jCB8zAIBgZn +gQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3Bz +LmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmlj +YXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBh +bmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGlj +eSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzCC +AQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AMEWSuCnctLUOS3ICsEHcNTwxJvemRpI +QMH6B1Fk9jNgAAABZN0ChToAAAQDAEcwRQIgblal8oXnfoopr1+dWVhvBx+sqHT0 +eLYxJHBTaRp3j1QCIQDhFQqMk6DDXUgcU12K36zLVFwJTdAJI4RBisnX+g+W0AB2 +ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZN0Chz4AAAQDAEcw +RQIhAImOjvkritUNKJZB7dcUtjoyIbfNwdCspvRiEzXuvVQoAiAZryoyg3TcMun5 +Gb2dEn1cttMnPW9u670/JdRjvjU/wTANBgkqhkiG9w0BAQsFAAOCAQEAGepCmckP +Tn9Sz268FEwkdD+6wWaPfeYlh+9nacFh90nQ35EYQMOK8a+X7ixHGbRz19On3Wt4 +1fcbPa9SefocTjAintMwwreCxpRTmwGACYojd7vRWEmA6q7+/HO2BfZahWzclOjw +mSDBycDEm8R0ZK52vYjzVno8x0mrsmSO0403S/6syYB/guH6P17kIBw+Tgx6/i/c +I1C6MoFkuaAKUUcZmgGGBgE+L/7cWtWjbkVXyA3ZQQy9G7rcBT+N/RrDfBh4iZDq +jAN5UIIYL8upBhjiMYVuoJrH2nklzEwr5SWKcccJX5eWkGLUwlcY9LGAA8+17l2I +l1Ou20Dm9TxnNw== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots.pem new file mode 100644 index 00000000..ee6c058d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots.pem @@ -0,0 +1,3733 @@ +# ACCVRAIZ1 +-----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----- + +# AC RAIZ FNMT-RCM +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Actalis Authentication Root CA +-----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----- + +# AddTrust External Root +-----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----- + +# AffirmTrust Commercial +-----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----- + +# AffirmTrust Networking +-----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----- + +# AffirmTrust Premium +-----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----- + +# AffirmTrust Premium ECC +-----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----- + +# Amazon Root CA 1 +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Amazon Root CA 2 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Amazon Root CA 3 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Amazon Root CA 4 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Atos TrustedRoot 2011 +-----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----- + +# Autoridad de Certificacion Firmaprofesional CIF A62634068 +-----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----- + +# Baltimore CyberTrust Root +-----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----- + +# Buypass Class 2 Root CA +-----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----- + +# Buypass Class 3 Root CA +-----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----- + +# CA Disig Root R2 +-----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----- + +# Certigna +-----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----- + +# Certinomis - Root CA +-----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----- + +# Certplus Class 2 Primary CA +-----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----- + +# Certplus Root CA G1 +-----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----- + +# Certplus Root CA G2 +-----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----- + +# certSIGN ROOT CA +-----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----- + +# Certum Trusted Network CA 2 +-----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----- + +# Certum Trusted Network CA +-----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----- + +# CFCA EV ROOT +-----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----- + +# Chambers of Commerce Root - 2008 +-----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----- + +# Comodo AAA Services root +-----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----- + +# COMODO Certification Authority +-----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----- + +# COMODO ECC Certification Authority +-----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----- + +# COMODO RSA Certification Authority +-----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----- + +# Cybertrust Global Root +-----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----- + +# Deutsche Telekom Root CA 2 +-----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----- + +# DigiCert Assured ID Root CA +-----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----- + +# DigiCert Assured ID Root G2 +-----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----- + +# DigiCert Assured ID Root G3 +-----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----- + +# DigiCert Global Root CA +-----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----- + +# DigiCert Global Root G2 +-----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----- + +# DigiCert Global Root G3 +-----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----- + +# DigiCert High Assurance EV Root CA +-----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----- + +# DigiCert Trusted Root G4 +-----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----- + +# DST Root CA X3 +-----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----- + +# D-TRUST Root Class 3 CA 2 2009 +-----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----- + +# D-TRUST Root Class 3 CA 2 EV 2009 +-----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----- + +# EC-ACC +-----BEGIN CERTIFICATE----- +MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB +8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy +dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 +YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 +dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh +IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD +LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG +EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g +KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD +ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu +bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg +ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R +85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm +4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV +HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd +QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t +lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB +o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4 +opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo +dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW +ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN +AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y +/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k +SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy +Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS +Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl +nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= +-----END CERTIFICATE----- + +# EE Certification Centre Root CA +-----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----- + +# Entrust.net Premium 2048 Secure Server CA +-----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----- + +# Entrust Root Certification Authority +-----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----- + +# Entrust Root Certification Authority - EC1 +-----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----- + +# Entrust Root Certification Authority - G2 +-----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----- + +# ePKI Root Certification Authority +-----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----- + +# E-Tugra Certification Authority +-----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----- + +# GDCA TrustAUTH R5 ROOT +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# GeoTrust Global CA +-----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----- + +# GeoTrust Primary Certification Authority +-----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----- + +# GeoTrust Primary Certification Authority - G2 +-----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----- + +# GeoTrust Primary Certification Authority - G3 +-----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----- + +# GeoTrust Universal CA +-----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----- + +# GeoTrust Universal CA 2 +-----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----- + +# Global Chambersign Root - 2008 +-----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----- + +# GlobalSign ECC Root CA - R4 +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# GlobalSign ECC Root CA - R5 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# GlobalSign Root CA +-----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----- + +# GlobalSign Root CA - R2 +-----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----- + +# GlobalSign Root CA - R3 +-----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----- + +# Go Daddy Class 2 CA +-----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----- + +# Go Daddy Root Certificate Authority - G2 +-----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----- + +# Hellenic Academic and Research Institutions ECC RootCA 2015 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Hellenic Academic and Research Institutions RootCA 2011 +-----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----- + +# Hellenic Academic and Research Institutions RootCA 2015 +-----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----- + +# Hongkong Post Root CA 1 +-----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----- + +# IdenTrust Commercial Root CA 1 +-----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----- + +# IdenTrust Public Sector Root CA 1 +-----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----- + +# ISRG Root X1 +-----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----- + +# Izenpe.com +-----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----- + +# LuxTrust Global Root 2 +-----BEGIN CERTIFICATE----- +MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV +BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw +MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B +LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F +ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem +hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 +EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn +Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 +zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ +96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m +j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g +DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ +8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j +X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH +hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB +KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 +Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT ++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL +BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 +BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO +jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 +loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c +qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ +2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ +JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre +zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf +LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ +x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 +oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr +-----END CERTIFICATE----- + +# Microsec e-Szigno Root CA 2009 +-----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----- + +# NetLock Arany (Class Gold) FÅ‘tanúsÃtvány +-----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----- + +# Network Solutions Certificate Authority +-----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----- + +# OISTE WISeKey Global Root GA CA +-----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----- + +# OISTE WISeKey Global Root GB CA +-----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----- + +# OpenTrust Root CA G1 +-----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----- + +# OpenTrust Root CA G2 +-----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----- + +# OpenTrust Root CA G3 +-----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----- + +# QuoVadis Root CA 1 G3 +-----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----- + +# QuoVadis Root CA 2 +-----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----- + +# QuoVadis Root CA +-----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----- + +# QuoVadis Root CA 2 G3 +-----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----- + +# QuoVadis Root CA 3 +-----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----- + +# QuoVadis Root CA 3 G3 +-----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----- + +# Secure Global CA +-----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----- + +# SecureSign RootCA11 +-----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----- + +# SecureTrust CA +-----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----- + +# Security Communication Root CA +-----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----- + +# Security Communication RootCA2 +-----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----- + +# Sonera Class 2 Root CA +-----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----- + +# SSL.com EV Root Certification Authority ECC +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# SSL.com EV Root Certification Authority RSA R2 +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# SSL.com Root Certification Authority ECC +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# SSL.com Root Certification Authority RSA +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Staat der Nederlanden EV Root CA +-----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----- + +# Staat der Nederlanden Root CA - G2 +-----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----- + +# Staat der Nederlanden Root CA - G3 +-----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----- + +# Starfield Class 2 CA +-----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----- + +# Starfield Root Certificate Authority - G2 +-----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----- + +# Starfield Services Root Certificate Authority - G2 +-----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----- + +# SwissSign Gold CA - G2 +-----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----- + +# SwissSign Silver CA - G2 +-----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----- + +# SZAFIR ROOT CA2 +-----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----- + +# Taiwan GRCA +-----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----- + +# TeliaSonera Root CA v1 +-----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----- + +# thawte Primary Root CA +-----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----- + +# thawte Primary Root CA - G2 +-----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----- + +# thawte Primary Root CA - G3 +-----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----- + +# TrustCor ECA-1 +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----END CERTIFICATE----- + +# TrustCor RootCert CA-1 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +# TrustCor RootCert CA-2 +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ +-----END CERTIFICATE----- + +# Trustis FPS Root CA +-----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----- + +# T-TeleSec GlobalRoot Class 2 +-----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----- + +# T-TeleSec GlobalRoot Class 3 +-----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----- + +# TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# TWCA Global Root CA +-----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----- + +# TWCA Root Certification Authority +-----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----- + +# USERTrust ECC Certification Authority +-----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----- + +# USERTrust RSA Certification Authority +-----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----- + +# Verisign Class 3 Public Primary Certification Authority - G3 +-----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----- + +# VeriSign Class 3 Public Primary Certification Authority - G4 +-----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----- + +# VeriSign Class 3 Public Primary Certification Authority - G5 +-----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----- + +# VeriSign Universal Root Certification Authority +-----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----- + +# Visa eCommerce Root +-----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----- + +# XRamp Global CA Root +-----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----- + +# CAcert Class 3 Root +-----BEGIN CERTIFICATE----- +MIIHWTCCBUGgAwIBAgIDCkGKMA0GCSqGSIb3DQEBCwUAMHkxEDAOBgNVBAoTB1Jv +b3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAGA1UEAxMZ +Q0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYSc3VwcG9y +dEBjYWNlcnQub3JnMB4XDTExMDUyMzE3NDgwMloXDTIxMDUyMDE3NDgwMlowVDEU +MBIGA1UEChMLQ0FjZXJ0IEluYy4xHjAcBgNVBAsTFWh0dHA6Ly93d3cuQ0FjZXJ0 +Lm9yZzEcMBoGA1UEAxMTQ0FjZXJ0IENsYXNzIDMgUm9vdDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKtJNRFIfNImflOUz0Op3SjXQiqL84d4GVh8D57a +iX3h++tykA10oZZkq5+gJJlz2uJVdscXe/UErEa4w75/ZI0QbCTzYZzA8pD6Ueb1 +aQFjww9W4kpCz+JEjCUoqMV5CX1GuYrz6fM0KQhF5Byfy5QEHIGoFLOYZcRD7E6C +jQnRvapbjZLQ7N6QxX8KwuPr5jFaXnQ+lzNZ6MMDPWAzv/fRb0fEze5ig1JuLgia +pNkVGJGmhZJHsK5I6223IeyFGmhyNav/8BBdwPSUp2rVO5J+TJAFfpPBLIukjmJ0 +FXFuC3ED6q8VOJrU0gVyb4z5K+taciX5OUbjchs+BMNkJyIQKopPWKcDrb60LhPt +XapI19V91Cp7XPpGBFDkzA5CW4zt2/LP/JaT4NsRNlRiNDiPDGCbO5dWOK3z0luL +oFvqTpa4fNfVoIZwQNORKbeiPK31jLvPGpKK5DR7wNhsX+kKwsOnIJpa3yxdUly6 +R9Wb7yQocDggL9V/KcCyQQNokszgnMyXS0XvOhAKq3A6mJVwrTWx6oUrpByAITGp +rmB6gCZIALgBwJNjVSKRPFbnr9s6JfOPMVTqJouBWfmh0VMRxXudA/Z0EeBtsSw/ +LIaRmXGapneLNGDRFLQsrJ2vjBDTn8Rq+G8T/HNZ92ZCdB6K4/jc0m+YnMtHmJVA +BfvpAgMBAAGjggINMIICCTAdBgNVHQ4EFgQUdahxYEyIE/B42Yl3tW3Fid+8sXow +gaMGA1UdIwSBmzCBmIAUFrUyG9TH8+DmjvO90rA67rI5GNGhfaR7MHkxEDAOBgNV +BAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEiMCAG +A1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJARYS +c3VwcG9ydEBjYWNlcnQub3JnggEAMA8GA1UdEwEB/wQFMAMBAf8wXQYIKwYBBQUH +AQEEUTBPMCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5DQWNlcnQub3JnLzAoBggr +BgEFBQcwAoYcaHR0cDovL3d3dy5DQWNlcnQub3JnL2NhLmNydDBKBgNVHSAEQzBB +MD8GCCsGAQQBgZBKMDMwMQYIKwYBBQUHAgEWJWh0dHA6Ly93d3cuQ0FjZXJ0Lm9y +Zy9pbmRleC5waHA/aWQ9MTAwNAYJYIZIAYb4QgEIBCcWJWh0dHA6Ly93d3cuQ0Fj +ZXJ0Lm9yZy9pbmRleC5waHA/aWQ9MTAwUAYJYIZIAYb4QgENBEMWQVRvIGdldCB5 +b3VyIG93biBjZXJ0aWZpY2F0ZSBmb3IgRlJFRSwgZ28gdG8gaHR0cDovL3d3dy5D +QWNlcnQub3JnMA0GCSqGSIb3DQEBCwUAA4ICAQApKIWuRKm5r6R5E/CooyuXYPNc +7uMvwfbiZqARrjY3OnYVBFPqQvX56sAV2KaC2eRhrnILKVyQQ+hBsuF32wITRHhH +Va9Y/MyY9kW50SD42CEH/m2qc9SzxgfpCYXMO/K2viwcJdVxjDm1Luq+GIG6sJO4 +D+Pm1yaMMVpyA4RS5qb1MyJFCsgLDYq4Nm+QCaGrvdfVTi5xotSu+qdUK+s1jVq3 +VIgv7nSf7UgWyg1I0JTTrKSi9iTfkuO960NAkW4cGI5WtIIS86mTn9S8nK2cde5a +lxuV53QtHA+wLJef+6kzOXrnAzqSjiL2jA3k2X4Ndhj3AfnvlpaiVXPAPHG0HRpW +Q7fDCo1y/OIQCQtBzoyUoPkD/XFzS4pXM+WOdH4VAQDmzEoc53+VGS3FpQyLu7Xt +hbNc09+4ufLKxw0BFKxwWMWMjTPUnWajGlCVI/xI4AZDEtnNp4Y5LzZyo4AQ5OHz +0ctbGsDkgJp8E3MGT9ujayQKurMcvEp4u+XjdTilSKeiHq921F73OIZWWonO1sOn +ebJSoMbxhbQljPI/lrMQ2Y1sVzufb4Y6GIIiNsiwkTjbKqGTqoQ/9SdlrnPVyNXT +d+pLncdBu8fA46A/5H2kjXPmEkvfoXNzczqA6NXLji/L6hOn1kGLrPo8idck9U60 +4GGSt/M3mMS+lqO3ig== +-----END CERTIFICATE----- + +# CA Cert Signing Authority +-----BEGIN CERTIFICATE----- +MIIHPTCCBSWgAwIBAgIBADANBgkqhkiG9w0BAQQFADB5MRAwDgYDVQQKEwdSb290 +IENBMR4wHAYDVQQLExVodHRwOi8vd3d3LmNhY2VydC5vcmcxIjAgBgNVBAMTGUNB +IENlcnQgU2lnbmluZyBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEWEnN1cHBvcnRA +Y2FjZXJ0Lm9yZzAeFw0wMzAzMzAxMjI5NDlaFw0zMzAzMjkxMjI5NDlaMHkxEDAO +BgNVBAoTB1Jvb3QgQ0ExHjAcBgNVBAsTFWh0dHA6Ly93d3cuY2FjZXJ0Lm9yZzEi +MCAGA1UEAxMZQ0EgQ2VydCBTaWduaW5nIEF1dGhvcml0eTEhMB8GCSqGSIb3DQEJ +ARYSc3VwcG9ydEBjYWNlcnQub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAziLA4kZ97DYoB1CW8qAzQIxL8TtmPzHlawI229Z89vGIj053NgVBlfkJ +8BLPRoZzYLdufujAWGSuzbCtRRcMY/pnCujW0r8+55jE8Ez64AO7NV1sId6eINm6 +zWYyN3L69wj1x81YyY7nDl7qPv4coRQKFWyGhFtkZip6qUtTefWIonvuLwphK42y +fk1WpRPs6tqSnqxEQR5YYGUFZvjARL3LlPdCfgv3ZWiYUQXw8wWRBB0bF4LsyFe7 +w2t6iPGwcswlWyCR7BYCEo8y6RcYSNDHBS4CMEK4JZwFaz+qOqfrU0j36NK2B5jc +G8Y0f3/JHIJ6BVgrCFvzOKKrF11myZjXnhCLotLddJr3cQxyYN/Nb5gznZY0dj4k +epKwDpUeb+agRThHqtdB7Uq3EvbXG4OKDy7YCbZZ16oE/9KTfWgu3YtLq1i6L43q +laegw1SJpfvbi1EinbLDvhG+LJGGi5Z4rSDTii8aP8bQUWWHIbEZAWV/RRyH9XzQ +QUxPKZgh/TMfdQwEUfoZd9vUFBzugcMd9Zi3aQaRIt0AUMyBMawSB3s42mhb5ivU +fslfrejrckzzAeVLIL+aplfKkQABi6F1ITe1Yw1nPkZPcCBnzsXWWdsC4PDSy826 +YreQQejdIOQpvGQpQsgi3Hia/0PsmBsJUUtaWsJx8cTLc6nloQsCAwEAAaOCAc4w +ggHKMB0GA1UdDgQWBBQWtTIb1Mfz4OaO873SsDrusjkY0TCBowYDVR0jBIGbMIGY +gBQWtTIb1Mfz4OaO873SsDrusjkY0aF9pHsweTEQMA4GA1UEChMHUm9vdCBDQTEe +MBwGA1UECxMVaHR0cDovL3d3dy5jYWNlcnQub3JnMSIwIAYDVQQDExlDQSBDZXJ0 +IFNpZ25pbmcgQXV0aG9yaXR5MSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGNhY2Vy +dC5vcmeCAQAwDwYDVR0TAQH/BAUwAwEB/zAyBgNVHR8EKzApMCegJaAjhiFodHRw +czovL3d3dy5jYWNlcnQub3JnL3Jldm9rZS5jcmwwMAYJYIZIAYb4QgEEBCMWIWh0 +dHBzOi8vd3d3LmNhY2VydC5vcmcvcmV2b2tlLmNybDA0BglghkgBhvhCAQgEJxYl +aHR0cDovL3d3dy5jYWNlcnQub3JnL2luZGV4LnBocD9pZD0xMDBWBglghkgBhvhC +AQ0ESRZHVG8gZ2V0IHlvdXIgb3duIGNlcnRpZmljYXRlIGZvciBGUkVFIGhlYWQg +b3ZlciB0byBodHRwOi8vd3d3LmNhY2VydC5vcmcwDQYJKoZIhvcNAQEEBQADggIB +ACjH7pyCArpcgBLKNQodgW+JapnM8mgPf6fhjViVPr3yBsOQWqy1YPaZQwGjiHCc +nWKdpIevZ1gNMDY75q1I08t0AoZxPuIrA2jxNGJARjtT6ij0rPtmlVOKTV39O9lg +18p5aTuxZZKmxoGCXJzN600BiqXfEVWqFcofN8CCmHBh22p8lqOOLlQ+TyGpkO/c +gr/c6EWtTZBzCDyUZbAEmXZ/4rzCahWqlwQ3JNgelE5tDlG+1sSPypZt90Pf6DBl +Jzt7u0NDY8RD97LsaMzhGY4i+5jhe1o+ATc7iwiwovOVThrLm82asduycPAtStvY +sONvRUgzEv/+PDIqVPfE94rwiCPCR/5kenHA0R6mY7AHfqQv0wGP3J8rtsYIqQ+T +SCX8Ev2fQtzzxD72V7DX3WnRBnc0CkvSyqD/HMaMyRa+xMwyN2hzXwj7UfdJUzYF +CpUCTPJ5GhD22Dp1nPMd8aINcGeGG7MW9S/lpOt5hvk9C8JzC6WZrG/8Z7jlLwum +GCSNe9FINSkYQKyTYOGWhlC0elnYjyELn8+CkcY7v2vcB5G5l1YjqrZslMZIBjzk +zk6q5PYvCdxTby78dOs6Y5nCpqyJvKeyRKANihDjbPIky/qbn3BHLt4Ui9SyIAmW +omTxJBzcoTWcFbLUvFUufQb1nA5V9FrWk9p2rSVzTMVD +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_Certification_Authority.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_Certification_Authority.pem new file mode 100644 index 00000000..6146dcb5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_Certification_Authority.pem @@ -0,0 +1,25 @@ +-----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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_ECC_Certification_Authority.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_ECC_Certification_Authority.pem new file mode 100644 index 00000000..546c95e3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_ECC_Certification_Authority.pem @@ -0,0 +1,16 @@ +-----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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_RSA_Certification_Authority.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_RSA_Certification_Authority.pem new file mode 100644 index 00000000..6508d1e8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/COMODO_RSA_Certification_Authority.pem @@ -0,0 +1,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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/DST_Root_CA_X3.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/DST_Root_CA_X3.pem new file mode 100644 index 00000000..b2e43c93 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/DST_Root_CA_X3.pem @@ -0,0 +1,20 @@ +-----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----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/ISRG_Root_X1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/ISRG_Root_X1.pem new file mode 100644 index 00000000..b85c8037 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/files/roots/ISRG_Root_X1.pem @@ -0,0 +1,31 @@ +-----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/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/meta/main.yml new file mode 100644 index 00000000..1810d4be --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_remote_tmp_dir diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/tasks/main.yml new file mode 100644 index 00000000..a718349c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/certificate_complete_chain/tasks/main.yml @@ -0,0 +1,84 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: register cryptography version + command: '{{ ansible_python.executable }} -c ''import cryptography; print(cryptography.__version__)''' + register: cryptography_version +- block: + - name: Make sure testhost directory exists + file: + path: '{{ remote_tmp_dir }}/files/' + state: directory + when: ansible_version.string is version('2.10', '<') + - name: Copy test files to testhost + copy: + src: '{{ role_path }}/files/' + dest: '{{ remote_tmp_dir }}/files/' + remote_src: yes + - name: Find root for cert 1 + certificate_complete_chain: + input_chain: '{{ lookup(''file'', ''cert1-fullchain.pem'', rstrip=False) }}' + root_certificates: + - '{{ remote_tmp_dir }}/files/roots/' + register: cert1_root + - name: Verify root for cert 1 + assert: + that: + - cert1_root.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False)) + - cert1_root.root == lookup('file', 'cert1-root.pem', rstrip=False) + - name: Find rootchain for cert 1 + certificate_complete_chain: + input_chain: '{{ lookup(''file'', ''cert1.pem'', rstrip=False) }}' + intermediate_certificates: + - '{{ remote_tmp_dir }}/files/cert1-chain.pem' + root_certificates: + - '{{ remote_tmp_dir }}/files/roots.pem' + register: cert1_rootchain + - name: Verify rootchain for cert 1 + assert: + that: + - cert1_rootchain.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False)) + - cert1_rootchain.chain[:-1] | join('') == lookup('file', 'cert1-chain.pem', rstrip=False) + - cert1_rootchain.root == lookup('file', 'cert1-root.pem', rstrip=False) + - name: Find root for cert 2 + certificate_complete_chain: + input_chain: '{{ lookup(''file'', ''cert2-fullchain.pem'', rstrip=False) }}' + root_certificates: + - '{{ remote_tmp_dir }}/files/roots/' + register: cert2_root + - name: Verify root for cert 2 + assert: + that: + - cert2_root.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False)) + - cert2_root.root == lookup('file', 'cert2-root.pem', rstrip=False) + - name: Find rootchain for cert 2 + certificate_complete_chain: + input_chain: '{{ lookup(''file'', ''cert2.pem'', rstrip=False) }}' + intermediate_certificates: + - '{{ remote_tmp_dir }}/files/cert2-chain.pem' + root_certificates: + - '{{ remote_tmp_dir }}/files/roots.pem' + register: cert2_rootchain + - name: Verify rootchain for cert 2 + assert: + that: + - cert2_rootchain.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False)) + - cert2_rootchain.chain[:-1] | join('') == lookup('file', 'cert2-chain.pem', rstrip=False) + - cert2_rootchain.root == lookup('file', 'cert2-root.pem', rstrip=False) + - name: Find alternate rootchain for cert 2 + certificate_complete_chain: + input_chain: '{{ lookup(''file'', ''cert2.pem'', rstrip=True) }}' + intermediate_certificates: + - '{{ remote_tmp_dir }}/files/cert2-altchain.pem' + root_certificates: + - '{{ remote_tmp_dir }}/files/roots.pem' + register: cert2_rootchain_alt + - name: Verify rootchain for cert 2 + assert: + that: + - cert2_rootchain_alt.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-altchain.pem', rstrip=False) ~ lookup('file', 'cert2-altroot.pem', rstrip=False)) + - cert2_rootchain_alt.chain[:-1] | join('') == lookup('file', 'cert2-altchain.pem', rstrip=False) + - cert2_rootchain_alt.root == lookup('file', 'cert2-altroot.pem', rstrip=False) + when: cryptography_version.stdout is version('1.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/aliases new file mode 100644 index 00000000..f320bbb3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/aliases @@ -0,0 +1,15 @@ +# Not enabled due to lack of access to test environments. May be enabled using custom integration_config.yml +# Example integation_config.yml +# --- +# entrust_api_user: +# entrust_api_key: +# entrust_api_client_cert_path: /var/integration-testing/publicCert.pem +# entrust_api_client_cert_key_path: /var/integration-testing/privateKey.pem +# entrust_api_ip_address: 127.0.0.1 +# entrust_cloud_ip_address: 127.0.0.1 +# # Used for certificate path validation of QA environments - we chose not to support disabling path validation ever. +# cacerts_bundle_path_local: /var/integration-testing/cacerts + +### WARNING: This test will update HOSTS file and CERTIFICATE STORE of target host, in order to be able to validate +# to a QA environment. ### +unsupported diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/defaults/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/defaults/main.yml new file mode 100644 index 00000000..86bc36c3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for test_ecs_certificate diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/meta/main.yml new file mode 100644 index 00000000..a35b3306 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_openssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/tasks/main.yml new file mode 100644 index 00000000..0b8fc662 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/tasks/main.yml @@ -0,0 +1,220 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +## Verify that integration_config was specified +- block: + - assert: + that: + - entrust_api_user is defined + - entrust_api_key is defined + - entrust_api_ip_address is defined + - entrust_cloud_ip_address is defined + - entrust_api_client_cert_path is defined or entrust_api_client_cert_contents is defined + - entrust_api_client_cert_key_path is defined or entrust_api_client_cert_key_contents + - cacerts_bundle_path_local is defined + +## SET UP TEST ENVIRONMENT ######################################################################## +- name: copy the files needed for verifying test server certificate to the host + copy: + src: '{{ cacerts_bundle_path_local }}/' + dest: '{{ cacerts_bundle_path }}' + +- name: Update the CA certificates for our QA certs (collection may need updating if new QA environments used) + command: c_rehash {{ cacerts_bundle_path }} + +- name: Update hosts file + lineinfile: + path: /etc/hosts + state: present + regexp: 'api.entrust.net$' + line: '{{ entrust_api_ip_address }} api.entrust.net' + +- name: Update hosts file + lineinfile: + path: /etc/hosts + state: present + regexp: 'cloud.entrust.net$' + line: '{{ entrust_cloud_ip_address }} cloud.entrust.net' + +- name: Clear out the temporary directory for storing the API connection information + file: + path: '{{ tmpdir_path }}' + state: absent + +- name: Create a directory for storing the API connection Information + file: + path: '{{ tmpdir_path }}' + state: directory + +- name: Copy the files needed for the connection to entrust API to the host + copy: + src: '{{ entrust_api_client_cert_path }}' + dest: '{{ entrust_api_cert }}' + +- name: Copy the files needed for the connection to entrust API to the host + copy: + src: '{{ entrust_api_client_cert_key_path }}' + dest: '{{ entrust_api_cert_key }}' + +## SETUP CSR TO REQUEST +- name: Generate a 2048 bit RSA private key + openssl_privatekey: + path: '{{ privatekey_path }}' + passphrase: '{{ privatekey_passphrase }}' + cipher: auto + type: RSA + size: 2048 + +- name: Generate a certificate signing request using the generated key + openssl_csr: + path: '{{ csr_path }}' + privatekey_path: '{{ privatekey_path }}' + privatekey_passphrase: '{{ privatekey_passphrase }}' + common_name: '{{ common_name }}' + organization_name: '{{ organization_name | default(omit) }}' + organizational_unit_name: '{{ organizational_unit_name | default(omit) }}' + country_name: '{{ country_name | default(omit) }}' + state_or_province_name: '{{ state_or_province_name | default(omit) }}' + digest: sha256 + +- block: + - name: Have ECS generate a signed certificate + ecs_certificate: + backup: True + path: '{{ example1_cert_path }}' + full_chain_path: '{{ example1_chain_path }}' + csr: '{{ csr_path }}' + cert_type: '{{ example1_cert_type }}' + requester_name: '{{ entrust_requester_name }}' + requester_email: '{{ entrust_requester_email }}' + requester_phone: '{{ entrust_requester_phone }}' + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: example1_result + + - assert: + that: + - example1_result is not failed + - example1_result.changed + - example1_result.tracking_id > 0 + - example1_result.serial_number is string + + # Internal CA refuses to issue certificates with the same DN in a short time frame + - name: Sleep for 5 seconds so we don't run into duplicate-request errors + pause: + seconds: 5 + + - name: Attempt to have ECS generate a signed certificate, but existing one is valid + ecs_certificate: + backup: True + path: '{{ example1_cert_path }}' + full_chain_path: '{{ example1_chain_path }}' + csr: '{{ csr_path }}' + cert_type: '{{ example1_cert_type }}' + requester_name: '{{ entrust_requester_name }}' + requester_email: '{{ entrust_requester_email }}' + requester_phone: '{{ entrust_requester_phone }}' + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: example2_result + + - assert: + that: + - example2_result is not failed + - not example2_result.changed + - example2_result.backup_file is undefined + - example2_result.backup_full_chain_file is undefined + - example2_result.serial_number == example1_result.serial_number + - example2_result.tracking_id == example1_result.tracking_id + + # Internal CA refuses to issue certificates with the same DN in a short time frame + - name: Sleep for 5 seconds so we don't run into duplicate-request errors + pause: + seconds: 5 + + - name: Force a reissue with no CSR, verify that contents changed + ecs_certificate: + backup: True + force: True + path: '{{ example1_cert_path }}' + full_chain_path: '{{ example1_chain_path }}' + cert_type: '{{ example1_cert_type }}' + request_type: reissue + requester_name: '{{ entrust_requester_name }}' + requester_email: '{{ entrust_requester_email }}' + requester_phone: '{{ entrust_requester_phone }}' + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: example3_result + + - assert: + that: + - example3_result is not failed + - example3_result.changed + - example3_result.backup_file is string + - example3_result.backup_full_chain_file is string + - example3_result.tracking_id > 0 + - example3_result.tracking_id != example1_result.tracking_id + - example3_result.serial_number != example1_result.serial_number + + # Internal CA refuses to issue certificates with the same DN in a short time frame + - name: Sleep for 5 seconds so we don't run into duplicate-request errors + pause: + seconds: 5 + + - name: Test a request with all of the various optional possible fields populated + ecs_certificate: + path: '{{ example4_cert_path }}' + full_chain_path: '{{ example4_full_chain_path }}' + csr: '{{ csr_path }}' + subject_alt_name: '{{ example4_subject_alt_name }}' + eku: '{{ example4_eku }}' + ct_log: True + cert_type: '{{ example4_cert_type }}' + org: '{{ example4_org }}' + ou: '{{ example4_ou }}' + tracking_info: '{{ example4_tracking_info }}' + additional_emails: '{{ example4_additional_emails }}' + custom_fields: '{{ example4_custom_fields }}' + cert_expiry: '{{ example4_cert_expiry }}' + requester_name: '{{ entrust_requester_name }}' + requester_email: '{{ entrust_requester_email }}' + requester_phone: '{{ entrust_requester_phone }}' + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: example4_result + + - assert: + that: + - example4_result is not failed + - example4_result.changed + - example4_result.backup_file is undefined + - example4_result.backup_full_chain_file is undefined + - example4_result.tracking_id > 0 + - example4_result.serial_number is string + + # For bug 61738, verify that the full chain is valid + - name: Verify that the full chain path can be successfully imported + command: '{{ openssl_binary }} verify "{{ example4_full_chain_path }}"' + register: openssl_result + + - assert: + that: + - "' OK' in openssl_result.stdout_lines[0]" + + always: + - name: clean-up temporary folder + file: + path: '{{ tmpdir_path }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/vars/main.yml new file mode 100644 index 00000000..8e617618 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_certificate/vars/main.yml @@ -0,0 +1,52 @@ +--- +# vars file for test_ecs_certificate + +# Path on various hosts that cacerts need to be put as a prerequisite to API server cert validation. +# May need to be customized for some environments based on SSL implementations +# that ansible "urls" module utility is using as a backing. +cacerts_bundle_path: /etc/pki/tls/certs + +common_name: '{{ ansible_date_time.epoch }}.ansint.testcertificates.com' +organization_name: CMS API, Inc. +organizational_unit_name: RSA +country_name: US +state_or_province_name: MA +privatekey_passphrase: Passphrase452! +tmpdir_path: /tmp/ecs_cert_test/{{ ansible_date_time.epoch }} +privatekey_path: '{{ tmpdir_path }}/testcertificates.key' +entrust_api_cert: '{{ tmpdir_path }}/authcert.cer' +entrust_api_cert_key: '{{ tmpdir_path }}/authkey.cer' +csr_path: '{{ tmpdir_path }}/request.csr' + +entrust_requester_name: C Trufan +entrust_requester_email: CTIntegrationTests@entrustdatacard.com +entrust_requester_phone: 1-555-555-5555 # e.g. 15555555555 + +# TEST 1 +example1_cert_path: '{{ tmpdir_path }}/issuedcert_1.pem' +example1_chain_path: '{{ tmpdir_path }}/issuedcert_1_chain.pem' +example1_cert_type: EV_SSL + +example4_cert_path: '{{ tmpdir_path }}/issuedcert_2.pem' +example4_subject_alt_name: + - ansible.testcertificates.com + - www.testcertificates.com +example4_eku: SERVER_AND_CLIENT_AUTH +example4_cert_type: UC_SSL +# Test a secondary org and special characters +example4_org: Cañon City, Inc. +example4_ou: + - StringrsaString +example4_tracking_info: Submitted via Ansible Integration +example4_additional_emails: + - itsupport@testcertificates.com + - jsmith@ansible.com +example4_custom_fields: + text1: Admin + text2: Invoice 25 + number1: 342 + date3: '2018-01-01' + email2: sales@ansible.testcertificates.com + dropdown2: Dropdown 2 Value 1 +example4_cert_expiry: 2020-08-15 +example4_full_chain_path: '{{ tmpdir_path }}/issuedcert_2_chain.pem' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/aliases new file mode 100644 index 00000000..f320bbb3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/aliases @@ -0,0 +1,15 @@ +# Not enabled due to lack of access to test environments. May be enabled using custom integration_config.yml +# Example integation_config.yml +# --- +# entrust_api_user: +# entrust_api_key: +# entrust_api_client_cert_path: /var/integration-testing/publicCert.pem +# entrust_api_client_cert_key_path: /var/integration-testing/privateKey.pem +# entrust_api_ip_address: 127.0.0.1 +# entrust_cloud_ip_address: 127.0.0.1 +# # Used for certificate path validation of QA environments - we chose not to support disabling path validation ever. +# cacerts_bundle_path_local: /var/integration-testing/cacerts + +### WARNING: This test will update HOSTS file and CERTIFICATE STORE of target host, in order to be able to validate +# to a QA environment. ### +unsupported diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/defaults/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/defaults/main.yml new file mode 100644 index 00000000..69034c4b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for test_ecs_domain diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/meta/main.yml new file mode 100644 index 00000000..07faa217 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - prepare_tests diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/tasks/main.yml new file mode 100644 index 00000000..b91b6f43 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/tasks/main.yml @@ -0,0 +1,275 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +## Verify that integration_config was specified +- block: + - assert: + that: + - entrust_api_user is defined + - entrust_api_key is defined + - entrust_api_ip_address is defined + - entrust_cloud_ip_address is defined + - entrust_api_client_cert_path is defined or entrust_api_client_cert_contents is defined + - entrust_api_client_cert_key_path is defined or entrust_api_client_cert_key_contents + - cacerts_bundle_path_local is defined + +## SET UP TEST ENVIRONMENT ######################################################################## +- name: copy the files needed for verifying test server certificate to the host + copy: + src: '{{ cacerts_bundle_path_local }}/' + dest: '{{ cacerts_bundle_path }}' + +- name: Update the CA certificates for our QA certs (collection may need updating if new QA environments used) + command: c_rehash {{ cacerts_bundle_path }} + +- name: Update hosts file + lineinfile: + path: /etc/hosts + state: present + regexp: 'api.entrust.net$' + line: '{{ entrust_api_ip_address }} api.entrust.net' + +- name: Update hosts file + lineinfile: + path: /etc/hosts + state: present + regexp: 'cloud.entrust.net$' + line: '{{ entrust_cloud_ip_address }} cloud.entrust.net' + +- name: Clear out the temporary directory for storing the API connection information + file: + path: '{{ tmpdir_path }}' + state: absent + +- name: Create a directory for storing the API connection Information + file: + path: '{{ tmpdir_path }}' + state: directory + +- name: Copy the files needed for the connection to entrust API to the host + copy: + src: '{{ entrust_api_client_cert_path }}' + dest: '{{ entrust_api_cert }}' + +- name: Copy the files needed for the connection to entrust API to the host + copy: + src: '{{ entrust_api_client_cert_key_path }}' + dest: '{{ entrust_api_cert_key }}' + +- block: + - name: Have ECS request a domain validation via dns + ecs_domain: + domain_name: dns.{{ common_name }} + verification_method: dns + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: dns_result + + - assert: + that: + - dns_result is not failed + - dns_result.changed + - dns_result.domain_status == 'INITIAL_VERIFICATION' + - dns_result.verification_method == 'dns' + - dns_result.dns_location is string + - dns_result.dns_contents is string + - dns_result.dns_resource_type is string + - dns_result.file_location is undefined + - dns_result.file_contents is undefined + - dns_result.emails is undefined + + - name: Have ECS request a domain validation via web_server + ecs_domain: + domain_name: FILE.{{ common_name }} + verification_method: web_server + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: file_result + + - assert: + that: + - file_result is not failed + - file_result.changed + - file_result.domain_status == 'INITIAL_VERIFICATION' + - file_result.verification_method == 'web_server' + - file_result.dns_location is undefined + - file_result.dns_contents is undefined + - file_result.dns_resource_type is undefined + - file_result.file_location is string + - file_result.file_contents is string + - file_result.emails is undefined + + - name: Have ECS request a domain validation via email + ecs_domain: + domain_name: email.{{ common_name }} + verification_method: email + verification_email: admin@testcertificates.com + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: email_result + + - assert: + that: + - email_result is not failed + - email_result.changed + - email_result.domain_status == 'INITIAL_VERIFICATION' + - email_result.verification_method == 'email' + - email_result.dns_location is undefined + - email_result.dns_contents is undefined + - email_result.dns_resource_type is undefined + - email_result.file_location is undefined + - email_result.file_contents is undefined + - email_result.emails[0] == 'admin@testcertificates.com' + + - name: Have ECS request a domain validation via email with no address provided + ecs_domain: + domain_name: email2.{{ common_name }} + verification_method: email + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: email_result2 + + - assert: + that: + - email_result2 is not failed + - email_result2.changed + - email_result2.domain_status == 'INITIAL_VERIFICATION' + - email_result2.verification_method == 'email' + - email_result2.dns_location is undefined + - email_result2.dns_contents is undefined + - email_result2.dns_resource_type is undefined + - email_result2.file_location is undefined + - email_result2.file_contents is undefined + - email_result2.emails is defined + + - name: Have ECS request a domain validation via manual + ecs_domain: + domain_name: manual.{{ common_name }} + verification_method: manual + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: manual_result + + - assert: + that: + - manual_result is not failed + - manual_result.changed + - manual_result.domain_status == 'INITIAL_VERIFICATION' + - manual_result.verification_method == 'manual' + - manual_result.dns_location is undefined + - manual_result.dns_contents is undefined + - manual_result.dns_resource_type is undefined + - manual_result.file_location is undefined + - manual_result.file_contents is undefined + - manual_result.emails is undefined + + - name: Have ECS request a domain validation via dns that remains unchanged + ecs_domain: + domain_name: dns.{{ common_name }} + verification_method: dns + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: dns_result2 + + - assert: + that: + - dns_result2 is not failed + - not dns_result2.changed + - dns_result2.domain_status == 'INITIAL_VERIFICATION' + - dns_result2.verification_method == 'dns' + - dns_result2.dns_location is string + - dns_result2.dns_contents is string + - dns_result2.dns_resource_type is string + - dns_result2.file_location is undefined + - dns_result2.file_contents is undefined + - dns_result2.emails is undefined + + - name: Have ECS request a domain validation via FILE for dns, to change verification method + ecs_domain: + domain_name: dns.{{ common_name }} + verification_method: web_server + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: dns_result_now_file + + - assert: + that: + - dns_result_now_file is not failed + - dns_result_now_file.changed + - dns_result_now_file.domain_status == 'INITIAL_VERIFICATION' + - dns_result_now_file.verification_method == 'web_server' + - dns_result_now_file.dns_location is undefined + - dns_result_now_file.dns_contents is undefined + - dns_result_now_file.dns_resource_type is undefined + - dns_result_now_file.file_location is string + - dns_result_now_file.file_contents is string + - dns_result_now_file.emails is undefined + + - name: Request revalidation of an approved domain + ecs_domain: + domain_name: '{{ existing_domain_common_name }}' + verification_method: manual + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: manual_existing_domain + + - assert: + that: + - manual_existing_domain is not failed + - not manual_existing_domain.changed + - manual_existing_domain.domain_status == 'RE_VERIFICATION' + - manual_existing_domain.dns_location is undefined + - manual_existing_domain.dns_contents is undefined + - manual_existing_domain.dns_resource_type is undefined + - manual_existing_domain.file_location is undefined + - manual_existing_domain.file_contents is undefined + - manual_existing_domain.emails is undefined + + - name: Request revalidation of an approved domain + ecs_domain: + domain_name: '{{ existing_domain_common_name }}' + verification_method: web_server + entrust_api_user: '{{ entrust_api_user }}' + entrust_api_key: '{{ entrust_api_key }}' + entrust_api_client_cert_path: '{{ entrust_api_cert }}' + entrust_api_client_cert_key_path: '{{ entrust_api_cert_key }}' + register: file_existing_domain_revalidate + + - assert: + that: + - file_existing_domain_revalidate is not failed + - file_existing_domain_revalidate.changed + - file_existing_domain_revalidate.domain_status == 'RE_VERIFICATION' + - file_existing_domain_revalidate.verification_method == 'web_server' + - file_existing_domain_revalidate.dns_location is undefined + - file_existing_domain_revalidate.dns_contents is undefined + - file_existing_domain_revalidate.dns_resource_type is undefined + - file_existing_domain_revalidate.file_location is string + - file_existing_domain_revalidate.file_contents is string + - file_existing_domain_revalidate.emails is undefined + + + always: + - name: clean-up temporary folder + file: + path: '{{ tmpdir_path }}' + state: absent diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/vars/main.yml new file mode 100644 index 00000000..9cf9fdb7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/ecs_domain/vars/main.yml @@ -0,0 +1,15 @@ +--- +# vars file for test_ecs_certificate + +# Path on various hosts that cacerts need to be put as a prerequisite to API server cert validation. +# May need to be customized for some environments based on SSL implementations +# that ansible "urls" module utility is using as a backing. +cacerts_bundle_path: /etc/pki/tls/certs + +common_name: '{{ ansible_date_time.epoch }}.testcertificates.com' +existing_domain_common_name: 'testcertificates.com' + +tmpdir_path: /tmp/ecs_cert_test/{{ ansible_date_time.epoch }} + +entrust_api_cert: '{{ tmpdir_path }}/authcert.cer' +entrust_api_cert_key: '{{ tmpdir_path }}/authkey.cer' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/aliases new file mode 100644 index 00000000..db2a5672 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/aliases @@ -0,0 +1,3 @@ +shippable/posix/group1 +destructive +needs/httptester diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/bogus_ca.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/bogus_ca.pem new file mode 100644 index 00000000..16119c9e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/bogus_ca.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+DCCAeACCQCWuDvGDH3otTANBgkqhkiG9w0BAQsFADA+MQswCQYDVQQGEwJV +UzEOMAwGA1UECAwFQm9ndXMxEDAOBgNVBAcMB0JhbG9uZXkxDTALBgNVBAoMBEFD +TUUwHhcNMTgwNzEyMTgxNDA0WhcNMjMwNzExMTgxNDA0WjA+MQswCQYDVQQGEwJV +UzEOMAwGA1UECAwFQm9ndXMxEDAOBgNVBAcMB0JhbG9uZXkxDTALBgNVBAoMBEFD +TUUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLTGCpn8b+/2qdpkvK +iwXU8PMOXBOmRa+GmzxsxMr1QZcY0m6pY3uuIvqErMFf4qp4BMxQF+VpDLVJUJX/ +1oKCM7J3hEfgmKRD4RmKhBlnWVv5YGZmvlXRJBl1AsDTONZy8iKJB5NYnB3ZyrJq +H2GAgyJ55aYckoU55vwjRzKp49dZmzX5YS04Kzzzw/SmOuW8kMypZV5TJH+NXqKc +pw3u3cJ4yJ9DHSU5pnhC5BeKl8XDMO42jRWt5/7C7JDiCbZ9lu5jQiv/4DhsRsHF +A8/Lgl47sNDaBMbha786I9laPHLlVycpYaP6pwtizhN9ZRTdDOHmWi/vjiamERLL +FjjLAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA+1uj3tHaCai+A1H/kOgTN5e0eW +/wmaxu8gNK5eiHrecNJNAlFxVTrCwhvv4nUW7NXVcW/1WUqSO0QMiPJhCsSLVAMF +8MuYH73B+ctRqAGdeOAWF+ftCywZTEj5h5F0XiWB+TmkPlTVNShMiPFelDJpLy7u +9MfiPEJjo4sZotQl8/pZ6R9cY6GpEXWnttcuhLJCEuiB8fWO7epiWYCt/Ak+CVmZ +OzfI/euV6Upaen22lNu8V3ZwWEFtmU5CioKJ3S8DK5Mw/LJIJw1ZY9E+fTtn8x0k +xlI4e7urD2FYhTdv2fFUG8Z5arb/3bICgsUYQZ+G1c3wjWtJg9zcy8hpnZQ= +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/process_certs.py b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/process_certs.py new file mode 100644 index 00000000..8a21af71 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/files/process_certs.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from sys import argv +from subprocess import Popen, PIPE, STDOUT + +p = Popen(["openssl", "s_client", "-host", argv[1], "-port", "443", "-prexit", "-showcerts"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) +stdout = p.communicate(input=b'\n')[0] +data = stdout.decode() + +certs = [] +cert = "" +capturing = False +for line in data.split('\n'): + if line == '-----BEGIN CERTIFICATE-----': + capturing = True + + if capturing: + cert = "{0}{1}\n".format(cert, line) + + if line == '-----END CERTIFICATE-----': + capturing = False + certs.append(cert) + cert = "" + +with open(argv[2], 'w') as f: + for cert in set(certs): + f.write(cert) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/meta/main.yml new file mode 100644 index 00000000..fe62e922 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - setup_openssl + - setup_pyopenssl + - prepare_http_tests diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tasks/main.yml new file mode 100644 index 00000000..fae5ab7e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tasks/main.yml @@ -0,0 +1,47 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + + - name: Get servers certificate with backend auto-detection + get_certificate: + host: "{{ httpbin_host }}" + port: 443 + + when: | + pyopenssl_version.stdout is version('0.15', '>=') or + (cryptography_version.stdout is version('1.6', '>=') and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)) + +- block: + + - include_tasks: ../tests/validate.yml + vars: + select_crypto_backend: pyopenssl + + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + + - include_tasks: ../tests/validate.yml + vars: + select_crypto_backend: cryptography + + # The module doesn't work with CentOS 6. Since the pyOpenSSL installed there is too old, + # we never noticed before. This becomes a problem with the new cryptography backend, + # since there is a new enough cryptography version... + when: | + cryptography_version.stdout is version('1.6', '>=') and + (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tests/validate.yml new file mode 100644 index 00000000..d77f0119 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/get_certificate/tests/validate.yml @@ -0,0 +1,140 @@ +--- +- name: Get servers certificate for SNI test part 1 + get_certificate: + host: "{{ httpbin_host }}" + port: 443 + server_name: "{{ sni_host }}" + register: result + +- debug: var=result + +- assert: + that: + # This module should never change anything + - result is not changed + - result is not failed + # We got the correct ST from the cert + - "'{{ sni_host }}' == result.subject.CN" + +- name: Get servers certificate for SNI test part 2 + get_certificate: + host: "{{ sni_host }}" + port: 443 + server_name: "{{ httpbin_host }}" + register: result + +- debug: var=result + +- assert: + that: + # This module should never change anything + - result is not changed + - result is not failed + # We got the correct ST from the cert + - "'{{ httpbin_host }}' == result.subject.CN" + +- name: Get servers certificate + get_certificate: + host: "{{ httpbin_host }}" + port: 443 + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + +- debug: var=result + +- assert: + that: + # This module should never change anything + - result is not changed + - result is not failed + # We got the correct ST from the cert + - "'North Carolina' == result.subject.ST" + +- name: Connect to http port (will fail because there is no SSL cert to get) + get_certificate: + host: "{{ httpbin_host }}" + port: 80 + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result is failed + # We got the expected error message + - "'The handshake operation timed out' in result.msg or 'unknown protocol' in result.msg or 'wrong version number' in result.msg" + +- name: Test timeout option + get_certificate: + host: "{{ httpbin_host }}" + port: 1234 + timeout: 1 + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result is failed + # We got the expected error message + - "'Failed to get cert from port with error: timed out' == result.msg or 'Connection refused' in result.msg" + +- name: Test failure if ca_cert is not a valid file + get_certificate: + host: "{{ httpbin_host }}" + port: 443 + ca_cert: dn.e + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result is failed + # We got the correct response from the module + - "'ca_cert file does not exist' == result.msg" + +- name: Download CA Cert as pem from server + get_url: + url: "http://ansible.http.tests/cacert.pem" + dest: "{{ output_dir }}/temp.pem" + +- name: Get servers certificate comparing it to its own ca_cert file + get_certificate: + ca_cert: '{{ output_dir }}/temp.pem' + host: "{{ httpbin_host }}" + port: 443 + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + +- assert: + that: + - result is not changed + - result is not failed + +- name: Get a temp directory + tempfile: + state: directory + register: my_temp_dir + +- name: Deploy the bogus_ca.pem file + copy: + src: "bogus_ca.pem" + dest: "{{ my_temp_dir.path }}/bogus_ca.pem" + +- name: Get servers certificate comparing it to an invalid ca_cert file + get_certificate: + ca_cert: '{{ my_temp_dir.path }}/bogus_ca.pem' + host: "{{ httpbin_host }}" + port: 443 + select_crypto_backend: "{{ select_crypto_backend }}" + register: result + ignore_errors: true + +- assert: + that: + - result is not changed + - result.failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/aliases new file mode 100644 index 00000000..f0df7981 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/aliases @@ -0,0 +1,7 @@ +shippable/posix/group1 +skip/osx +skip/macos +skip/freebsd +skip/docker +needs/root +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile1 b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile1 new file mode 100644 index 00000000..5e40c087 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile1 @@ -0,0 +1 @@ +asdf
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile2 b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile2 new file mode 100644 index 00000000..5e4f2565 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/files/keyfile2 @@ -0,0 +1 @@ +test1234
\ No newline at end of file diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/main.yml new file mode 100644 index 00000000..81862c8c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/main.yml @@ -0,0 +1,41 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Make sure cryptsetup is installed + package: + name: cryptsetup + state: present + become: yes +- name: Create cryptfile + command: dd if=/dev/zero of={{ output_dir.replace('~', ansible_env.HOME) }}/cryptfile bs=1M count=32 +- name: Create lookback device + command: losetup -f {{ output_dir.replace('~', ansible_env.HOME) }}/cryptfile + become: yes +- name: Determine loop device name + command: losetup -j {{ output_dir.replace('~', ansible_env.HOME) }}/cryptfile --output name + become: yes + register: cryptfile_device_output +- set_fact: + cryptfile_device: "{{ cryptfile_device_output.stdout_lines[1] }}" + cryptfile_passphrase1: "uNiJ9vKG2mUOEWDiQVuBHJlfMHE" + cryptfile_passphrase2: "HW4Ak2HtE2vvne0qjJMPTtmbV4M" + cryptfile_passphrase3: "qQJqsjabO9pItV792k90VvX84MM" +- block: + - include_tasks: run-test.yml + with_fileglob: + - "tests/*.yml" + always: + - name: Make sure LUKS device is gone + luks_device: + device: "{{ cryptfile_device }}" + state: absent + become: yes + ignore_errors: yes + - command: losetup -d "{{ cryptfile_device }}" + become: yes + - file: + dest: "{{ output_dir }}/cryptfile" + state: absent diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/run-test.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/run-test.yml new file mode 100644 index 00000000..a2ec73b2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/run-test.yml @@ -0,0 +1,8 @@ +--- +- name: Make sure LUKS device is gone + luks_device: + device: "{{ cryptfile_device }}" + state: absent + become: yes +- name: "Loading tasks from {{ item }}" + include_tasks: "{{ item }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/create-destroy.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/create-destroy.yml new file mode 100644 index 00000000..9e4e1f3f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/create-destroy.yml @@ -0,0 +1,195 @@ +--- +- name: Create (check) + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + check_mode: yes + become: yes + register: create_check +- name: Create + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + become: yes + register: create +- name: Create (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + become: yes + register: create_idem +- name: Create (idempotent, check) + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + check_mode: yes + become: yes + register: create_idem_check +- assert: + that: + - create_check is changed + - create is changed + - create_idem is not changed + - create_idem_check is not changed + +- name: Open (check) + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + check_mode: yes + become: yes + register: open_check +- name: Open + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + register: open +- name: Open (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + register: open_idem +- name: Open (idempotent, check) + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + check_mode: yes + become: yes + register: open_idem_check +- assert: + that: + - open_check is changed + - open is changed + - open_idem is not changed + - open_idem_check is not changed + +- name: Closed (via name, check) + luks_device: + name: "{{ open.name }}" + state: closed + check_mode: yes + become: yes + register: close_check +- name: Closed (via name) + luks_device: + name: "{{ open.name }}" + state: closed + become: yes + register: close +- name: Closed (via name, idempotent) + luks_device: + name: "{{ open.name }}" + state: closed + become: yes + register: close_idem +- name: Closed (via name, idempotent, check) + luks_device: + name: "{{ open.name }}" + state: closed + check_mode: yes + become: yes + register: close_idem_check +- assert: + that: + - close_check is changed + - close is changed + - close_idem is not changed + - close_idem_check is not changed + +- name: Re-open + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + +- name: Closed (via device, check) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + check_mode: yes + become: yes + register: close_check +- name: Closed (via device) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + register: close +- name: Closed (via device, idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + register: close_idem +- name: Closed (via device, idempotent, check) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + check_mode: yes + become: yes + register: close_idem_check +- assert: + that: + - close_check is changed + - close is changed + - close_idem is not changed + - close_idem_check is not changed + +- name: Re-opened + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + +- name: Absent (check) + luks_device: + device: "{{ cryptfile_device }}" + state: absent + check_mode: yes + become: yes + register: absent_check +- name: Absent + luks_device: + device: "{{ cryptfile_device }}" + state: absent + become: yes + register: absent +- name: Absent (idempotence) + luks_device: + device: "{{ cryptfile_device }}" + state: absent + become: yes + register: absent_idem +- name: Absent (idempotence, check) + luks_device: + device: "{{ cryptfile_device }}" + state: absent + check_mode: yes + become: yes + register: absent_idem_check +- assert: + that: + - absent_check is changed + - absent is changed + - absent_idem is not changed + - absent_idem_check is not changed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/device-check.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/device-check.yml new file mode 100644 index 00000000..56797f91 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/device-check.yml @@ -0,0 +1,56 @@ +--- +- name: Create with invalid device name (check) + luks_device: + device: /dev/asdfasdfasdf + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + check_mode: yes + ignore_errors: yes + become: yes + register: create_check +- name: Create with invalid device name + luks_device: + device: /dev/asdfasdfasdf + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + ignore_errors: yes + become: yes + register: create +- assert: + that: + - create_check is failed + - create is failed + - "'o such file or directory' in create_check.msg" + - "'o such file or directory' in create.msg" + +- name: Create with something which is not a device (check) + luks_device: + device: /tmp/ + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + check_mode: yes + ignore_errors: yes + become: yes + register: create_check +- name: Create with something which is not a device + luks_device: + device: /tmp/ + state: present + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + ignore_errors: yes + become: yes + register: create +- assert: + that: + - create_check is failed + - create is failed + - "'is not a device' in create_check.msg" + - "'is not a device' in create.msg" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/key-management.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/key-management.yml new file mode 100644 index 00000000..cdf1d594 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/key-management.yml @@ -0,0 +1,202 @@ +--- +- name: Create with keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + become: yes + +# Access: keyfile1 + +- name: Try to open with keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Try to open with keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Give access to keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + new_keyfile: "{{ role_path }}/files/keyfile2" + pbkdf: + iteration_time: 0.1 + become: yes + register: result_1 + +- name: Give access to keyfile2 (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + new_keyfile: "{{ role_path }}/files/keyfile2" + become: yes + register: result_2 + +- assert: + that: + - result_1 is changed + - result_2 is not changed + +# Access: keyfile1 and keyfile2 + +- name: Try to open with keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Dump LUKS header + command: "cryptsetup luksDump {{ cryptfile_device }}" + become: yes + +- name: Remove access from keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + remove_keyfile: "{{ role_path }}/files/keyfile1" + become: yes + register: result_1 + +- name: Remove access from keyfile1 (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + remove_keyfile: "{{ role_path }}/files/keyfile1" + become: yes + register: result_2 + +- assert: + that: + - result_1 is changed + - result_2 is not changed + +# Access: keyfile2 + +- name: Try to open with keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Try to open with keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Dump LUKS header + command: "cryptsetup luksDump {{ cryptfile_device }}" + become: yes + +- name: Remove access from keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile2" + remove_keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: remove_last_key +- assert: + that: + - remove_last_key is failed + - "'force_remove_last_key' in remove_last_key.msg" + +# Access: keyfile2 + +- name: Try to open with keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Remove access from keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile2" + remove_keyfile: "{{ role_path }}/files/keyfile2" + force_remove_last_key: yes + become: yes + +# Access: none + +- name: Try to open with keyfile2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile2" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/options.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/options.yml new file mode 100644 index 00000000..62ac3e9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/options.yml @@ -0,0 +1,49 @@ +--- +- name: Create with keysize + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + keysize: 256 + pbkdf: + iteration_count: 1000 + become: yes + register: create_with_keysize +- name: Create with keysize (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + keysize: 256 + pbkdf: + iteration_count: 1000 + become: yes + register: create_idem_with_keysize +- name: Create with different keysize (idempotent since we do not update keysize) + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + keysize: 512 + pbkdf: + iteration_count: 1000 + become: yes + register: create_idem_with_diff_keysize +- name: Create with ambiguous arguments + luks_device: + device: "{{ cryptfile_device }}" + state: present + keyfile: "{{ role_path }}/files/keyfile1" + passphrase: "{{ cryptfile_passphrase1 }}" + pbkdf: + iteration_count: 1000 + ignore_errors: yes + become: yes + register: create_with_ambiguous + +- assert: + that: + - create_with_keysize is changed + - create_idem_with_keysize is not changed + - create_idem_with_diff_keysize is not changed + - create_with_ambiguous is failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/passphrase.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/passphrase.yml new file mode 100644 index 00000000..d91bb8c6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/luks_device/tasks/tests/passphrase.yml @@ -0,0 +1,240 @@ +--- +- name: Create with passphrase1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + pbkdf: + iteration_time: 0.1 + algorithm: argon2i + memory: 1000 + parallel: 1 + become: yes + ignore_errors: yes + register: create_passphrase_1 + +- name: Make sure that the previous task only fails because the LUKS version used cannot handle the PBKDF parameters + assert: + that: + - create_passphrase_1 is not failed or 'Failed to set pbkdf parameters' in create_passphrase_1.msg + +- name: Create with passphrase1 (without argon2i) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + pbkdf: + iteration_time: 0.1 + become: yes + when: create_passphrase_1 is failed and 'Failed to set pbkdf parameters' in create_passphrase_1.msg + +- name: Open with passphrase1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase1 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Give access with ambiguous new_ arguments + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + new_passphrase: "{{ cryptfile_passphrase2 }}" + new_keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + become: yes + ignore_errors: yes + register: new_try +- assert: + that: + - new_try is failed + +- name: Try to open with passphrase2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase2 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Give access to passphrase2 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + new_passphrase: "{{ cryptfile_passphrase2 }}" + pbkdf: + iteration_time: 0.1 + become: yes + register: result_1 + +- name: Give access to passphrase2 (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + new_passphrase: "{{ cryptfile_passphrase2 }}" + become: yes + register: result_2 + +- assert: + that: + - result_1 is changed + - result_2 is not changed + +- name: Open with passphrase2 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase2 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Try to open with keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Give access to keyfile1 from passphrase1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + passphrase: "{{ cryptfile_passphrase1 }}" + new_keyfile: "{{ role_path }}/files/keyfile1" + pbkdf: + iteration_time: 0.1 + become: yes + +- name: Remove access with ambiguous remove_ arguments + luks_device: + device: "{{ cryptfile_device }}" + state: closed + remove_keyfile: "{{ role_path }}/files/keyfile1" + remove_passphrase: "{{ cryptfile_passphrase1 }}" + become: yes + ignore_errors: yes + register: remove_try +- assert: + that: + - remove_try is failed + +- name: Open with keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + keyfile: "{{ role_path }}/files/keyfile1" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes + +- name: Remove access for passphrase1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + remove_passphrase: "{{ cryptfile_passphrase1 }}" + become: yes + register: result_1 + +- name: Remove access for passphrase1 (idempotent) + luks_device: + device: "{{ cryptfile_device }}" + state: closed + remove_passphrase: "{{ cryptfile_passphrase1 }}" + become: yes + register: result_2 + +- assert: + that: + - result_1 is changed + - result_2 is not changed + +- name: Try to open with passphrase1 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase1 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Try to open with passphrase3 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase3 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is failed + +- name: Give access to passphrase3 from keyfile1 + luks_device: + device: "{{ cryptfile_device }}" + state: closed + keyfile: "{{ role_path }}/files/keyfile1" + new_passphrase: "{{ cryptfile_passphrase3 }}" + pbkdf: + iteration_time: 0.1 + become: yes + +- name: Open with passphrase3 + luks_device: + device: "{{ cryptfile_device }}" + state: opened + passphrase: "{{ cryptfile_passphrase3 }}" + become: yes + ignore_errors: yes + register: open_try +- assert: + that: + - open_try is not failed +- name: Close + luks_device: + device: "{{ cryptfile_device }}" + state: closed + become: yes diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/meta/main.yml new file mode 100644 index 00000000..476cfb39 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_ssh_keygen + - setup_ssh_agent diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/tasks/main.yml new file mode 100644 index 00000000..15782613 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_cert/tasks/main.yml @@ -0,0 +1,467 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: openssh_cert integration tests + when: not (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") + block: + - name: Generate keypair + openssh_keypair: + path: '{{ output_dir }}/id_key' + type: rsa + size: 2048 + - name: Generate always valid cert (check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + check_mode: yes + - name: Generate always valid cert + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + - name: Generate always valid cert (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + - name: Generate always valid cert (idempotent, check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + check_mode: yes + - name: Generate restricted validity cert with valid_at (check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: +0s + valid_to: +32w + valid_at: +2w + check_mode: yes + - name: Generate restricted validity cert with valid_at + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: +0s + valid_to: +32w + valid_at: +2w + - name: Generate restricted validity cert with valid_at (idempotent) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: +0s + valid_to: +32w + valid_at: +2w + - name: Generate restricted validity cert with valid_at (idempotent, check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: +0s + valid_to: +32w + valid_at: +2w + check_mode: yes + - name: Generate always valid cert only for example.com and examplehost (check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + principals: + - example.com + - examplehost + check_mode: yes + - name: Generate always valid cert only for example.com and examplehost + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + principals: + - example.com + - examplehost + - name: Generate always valid cert only for example.com and examplehost (idempotent) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + principals: + - example.com + - examplehost + - name: Generate always valid cert only for example.com and examplehost (idempotent, check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + principals: + - example.com + - examplehost + check_mode: yes + - name: Generate always valid cert only for example.com and examplehost (idempotent, switch) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: always + valid_to: forever + principals: + - examplehost + - example.com + - name: Generate OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019 (check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: "2001-01-21" + valid_to: "2019-01-21" + check_mode: yes + - name: Generate OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019 + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: "2001-01-21" + valid_to: "2019-01-21" + - name: Generate OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019 (idempotent) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: "2001-01-21" + valid_to: "2019-01-21" + - name: Generate OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019 (idempotent, check mode) + openssh_cert: + type: host + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + valid_from: "2001-01-21" + valid_to: "2019-01-21" + check_mode: yes + - name: Generate an OpenSSH user Certificate with clear and force-command option (check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + options: + - "clear" + - "force-command=/tmp/bla/foo" + valid_from: "2001-01-21" + valid_to: "2019-01-21" + check_mode: yes + - name: Generate an OpenSSH user Certificate with clear and force-command option + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + options: + - "clear" + - "force-command=/tmp/bla/foo" + valid_from: "2001-01-21" + valid_to: "2019-01-21" + - name: Generate an OpenSSH user Certificate with clear and force-command option (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + options: + - "clear" + - "force-command=/tmp/bla/foo" + valid_from: "2001-01-21" + valid_to: "2019-01-21" + - name: Generate an OpenSSH user Certificate with clear and force-command option (idempotent, check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + options: + - "clear" + - "force-command=/tmp/bla/foo" + valid_from: "2001-01-21" + valid_to: "2019-01-21" + check_mode: yes + - name: Generate an OpenSSH user Certificate with clear and force-command option (idempotent, switch) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert' + options: + - "force-command=/tmp/bla/foo" + - "clear" + valid_from: "2001-01-21" + valid_to: "2019-01-21" + - name: Generate cert without serial + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_no_serial' + valid_from: always + valid_to: forever + register: rc_no_serial_number + - name: check default serial + assert: + that: + - "'Serial: 0' in rc_no_serial_number.info" + msg: OpenSSH user certificate contains the default serial number. + - name: Generate cert without serial (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_no_serial' + valid_from: always + valid_to: forever + register: rc_no_serial_number_idempotent + - name: check idempotent + assert: + that: + - rc_no_serial_number_idempotent is not changed + msg: OpenSSH certificate generation without serial number is idempotent. + - name: Generate cert with serial 42 + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_42' + valid_from: always + valid_to: forever + serial_number: 42 + register: rc_serial_number + - name: check serial 42 + assert: + that: + - "'Serial: 42' in rc_serial_number.info" + msg: OpenSSH user certificate contains the serial number from the params. + - name: Generate cert with serial 42 (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_42' + valid_from: always + valid_to: forever + serial_number: 42 + register: rc_serial_number_idempotent + - name: check idempotent + assert: + that: + - rc_serial_number_idempotent is not changed + msg: OpenSSH certificate generation with serial number is idempotent. + - name: Generate cert with changed serial number + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_42' + valid_from: always + valid_to: forever + serial_number: 1337 + register: rc_serial_number_changed + - name: check changed + assert: + that: + - rc_serial_number_changed is changed + msg: OpenSSH certificate regenerated upon serial number change. + - name: Generate cert with removed serial number + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_42' + valid_from: always + valid_to: forever + serial_number: 0 + register: rc_serial_number_removed + - name: check changed + assert: + that: + - rc_serial_number_removed is changed + msg: OpenSSH certificate regenerated upon serial number removal. + - name: Generate a new cert with serial number + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_ignore' + valid_from: always + valid_to: forever + serial_number: 42 + - name: Generate cert again, omitting the parameter serial_number (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_serial_ignore' + valid_from: always + valid_to: forever + register: rc_serial_number_ignored + - name: check idempotent + assert: + that: + - rc_serial_number_ignored is not changed + msg: OpenSSH certificate generation with omitted serial number is idempotent. + - name: Remove certificate (check mode) + openssh_cert: + state: absent + path: '{{ output_dir }}/id_cert' + #type: user + #signing_key: '{{ output_dir }}/id_key' + #public_key: '{{ output_dir }}/id_key.pub' + #valid_from: "2001-01-21" + #valid_to: "2019-01-21" + check_mode: yes + - name: Remove certificate + openssh_cert: + state: absent + path: '{{ output_dir }}/id_cert' + #type: user + #signing_key: '{{ output_dir }}/id_key' + #public_key: '{{ output_dir }}/id_key.pub' + #valid_from: "2001-01-21" + #valid_to: "2019-01-21" + - name: Remove certificate (idempotent) + openssh_cert: + state: absent + path: '{{ output_dir }}/id_cert' + #type: user + #signing_key: '{{ output_dir }}/id_key' + #public_key: '{{ output_dir }}/id_key.pub' + #valid_from: "2001-01-21" + #valid_to: "2019-01-21" + - name: Remove certificate (idempotent, check mode) + openssh_cert: + state: absent + path: '{{ output_dir }}/id_cert' + #type: user + #signing_key: '{{ output_dir }}/id_key' + #public_key: '{{ output_dir }}/id_key.pub' + #valid_from: "2001-01-21" + #valid_to: "2019-01-21" + check_mode: yes + - name: Remove keypair + openssh_keypair: + path: '{{ output_dir }}/id_key' + state: absent + +- name: openssh_cert integration tests that require ssh-agent + when: openssh_version is version("7.6",">=") + environment: + SSH_AUTH_SOCK: "{{ openssh_agent_sock }}" + block: + - name: Generate keypair for agent tests + openssh_keypair: + path: '{{ output_dir }}/id_key' + type: rsa + size: 2048 + - name: Generate always valid cert using agent without key in agent (should fail) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_with_agent' + use_agent: yes + valid_from: always + valid_to: forever + register: rc_no_key_in_agent + ignore_errors: yes + - name: Make sure cert creation with agent fails if key not in agent + assert: + that: + - rc_no_key_in_agent is failed + - "'agent contains no identities' in rc_no_key_in_agent.msg or 'not found in agent' in rc_no_key_in_agent.msg" + - name: Add key to agent + command: 'ssh-add {{ output_dir }}/id_key' + - name: Generate always valid cert with agent (check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_with_agent' + use_agent: yes + valid_from: always + valid_to: forever + check_mode: yes + - name: Generate always valid cert with agent + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_with_agent' + use_agent: yes + valid_from: always + valid_to: forever + - name: Generate always valid cert with agent (idempotent) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_with_agent' + use_agent: yes + valid_from: always + valid_to: forever + register: rc_cert_with_agent_idempotent + - name: Check agent idempotency + assert: + that: + - rc_cert_with_agent_idempotent is not changed + msg: OpenSSH certificate generation without serial number is idempotent. + - name: Generate always valid cert with agent (idempotent, check mode) + openssh_cert: + type: user + signing_key: '{{ output_dir }}/id_key' + public_key: '{{ output_dir }}/id_key.pub' + path: '{{ output_dir }}/id_cert_with_agent' + use_agent: yes + valid_from: always + valid_to: forever + check_mode: yes + - name: Remove keypair for agent tests + openssh_keypair: + path: '{{ output_dir }}/id_key' + state: absent + - name: Remove certificate + openssh_cert: + state: absent + path: '{{ output_dir }}/id_cert_with_agent' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/meta/main.yml new file mode 100644 index 00000000..dc973f4e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_ssh_keygen diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tasks/main.yml new file mode 100644 index 00000000..993c8ad4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tasks/main.yml @@ -0,0 +1,407 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Generate privatekey1 - standard (check mode) + openssh_keypair: + path: '{{ output_dir }}/privatekey1' + size: 2048 + register: privatekey1_result_check + check_mode: true + +- name: Generate privatekey1 - standard + openssh_keypair: + path: '{{ output_dir }}/privatekey1' + size: 2048 + register: privatekey1_result + +- name: Generate privatekey1 - standard (check mode idempotent) + openssh_keypair: + path: '{{ output_dir }}/privatekey1' + size: 2048 + register: privatekey1_idem_result_check + check_mode: true + +- name: Generate privatekey1 - standard (idempotent) + openssh_keypair: + path: '{{ output_dir }}/privatekey1' + size: 2048 + register: privatekey1_idem_result + +- name: Generate privatekey2 - default size + openssh_keypair: + path: '{{ output_dir }}/privatekey2' + +- name: Generate privatekey3 - type dsa + openssh_keypair: + path: '{{ output_dir }}/privatekey3' + type: dsa + +- name: Generate privatekey4 - standard + openssh_keypair: + path: '{{ output_dir }}/privatekey4' + size: 2048 + +- name: Delete privatekey4 - standard + openssh_keypair: + state: absent + path: '{{ output_dir }}/privatekey4' + +- name: Generate privatekey5 - standard + openssh_keypair: + path: '{{ output_dir }}/privatekey5' + size: 2048 + register: publickey_gen + +- name: Generate privatekey6 + openssh_keypair: + path: '{{ output_dir }}/privatekey6' + type: rsa + size: 2048 + +- name: Regenerate privatekey6 via force + openssh_keypair: + path: '{{ output_dir }}/privatekey6' + type: rsa + size: 2048 + force: yes + register: output_regenerated_via_force + +- name: Create broken key + copy: + dest: '{{ item }}' + content: '' + mode: '0700' + loop: + - '{{ output_dir }}/privatekeybroken' + - '{{ output_dir }}/privatekeybroken.pub' + +- name: Regenerate broken key - should fail + openssh_keypair: + path: '{{ output_dir }}/privatekeybroken' + type: rsa + size: 2048 + register: output_broken + ignore_errors: yes + +- name: Regenerate broken key with force + openssh_keypair: + path: '{{ output_dir }}/privatekeybroken' + type: rsa + force: yes + size: 2048 + register: output_broken_force + +- name: Generate read-only private key + openssh_keypair: + path: '{{ output_dir }}/privatekeyreadonly' + type: rsa + mode: '0200' + size: 2048 + +- name: Regenerate read-only private key via force + openssh_keypair: + path: '{{ output_dir }}/privatekeyreadonly' + type: rsa + force: yes + size: 2048 + register: output_read_only + +- name: Generate privatekey7 - standard with comment + openssh_keypair: + path: '{{ output_dir }}/privatekey7' + comment: 'test@privatekey7' + size: 2048 + register: privatekey7_result + +- name: Modify privatekey7 comment + openssh_keypair: + path: '{{ output_dir }}/privatekey7' + comment: 'test_modified@privatekey7' + size: 2048 + register: privatekey7_modified_result + +- name: Generate password protected key + command: 'ssh-keygen -f {{ output_dir }}/privatekey8 -N password' + +- name: Try to modify the password protected key - should fail + openssh_keypair: + path: '{{ output_dir }}/privatekey8' + size: 2048 + register: privatekey8_result + ignore_errors: yes + +- name: Try to modify the password protected key with force=yes + openssh_keypair: + path: '{{ output_dir }}/privatekey8' + force: yes + size: 2048 + register: privatekey8_result_force + +- import_tasks: ../tests/validate.yml + + +# Test regenerate option + +- name: Regenerate - setup simple keys + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: rsa + size: 1024 + loop: "{{ regenerate_values }}" +- name: Regenerate - setup password protected keys + command: 'ssh-keygen -f {{ output_dir }}/regenerate-b-{{ item }} -N password' + loop: "{{ regenerate_values }}" +- name: Regenerate - setup broken keys + copy: + dest: '{{ output_dir }}/regenerate-c-{{ item.0 }}{{ item.1 }}' + content: 'broken key' + mode: '0700' + with_nested: + - "{{ regenerate_values }}" + - [ '', '.pub' ] + +- name: Regenerate - modify broken keys (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-c-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - modify broken keys + openssh_keypair: + path: '{{ output_dir }}/regenerate-c-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - modify password protected keys (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-b-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - modify password protected keys + openssh_keypair: + path: '{{ output_dir }}/regenerate-b-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - not modify regular keys (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + register: result +- assert: + that: + - result.results[0] is not changed + - result.results[1] is not changed + - result.results[2] is not changed + - result.results[3] is not changed + - result.results[4] is changed + +- name: Regenerate - not modify regular keys + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: rsa + size: 1024 + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + register: result +- assert: + that: + - result.results[0] is not changed + - result.results[1] is not changed + - result.results[2] is not changed + - result.results[3] is not changed + - result.results[4] is changed + +- name: Regenerate - adjust key size (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: rsa + size: 1048 + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - adjust key size + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: rsa + size: 1048 + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - redistribute keys + copy: + src: '{{ output_dir }}/regenerate-a-always{{ item.1 }}' + dest: '{{ output_dir }}/regenerate-a-{{ item.0 }}{{ item.1 }}' + remote_src: true + with_nested: + - "{{ regenerate_values }}" + - [ '', '.pub' ] + when: "item.0 != 'always'" + +- name: Regenerate - adjust key type (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: dsa + size: 1024 + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - adjust key type + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: dsa + size: 1024 + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: Regenerate - redistribute keys + copy: + src: '{{ output_dir }}/regenerate-a-always{{ item.1 }}' + dest: '{{ output_dir }}/regenerate-a-{{ item.0 }}{{ item.1 }}' + remote_src: true + with_nested: + - "{{ regenerate_values }}" + - [ '', '.pub' ] + when: "item.0 != 'always'" + +- name: Regenerate - adjust comment (check mode) + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: dsa + size: 1024 + comment: test comment + regenerate: '{{ item }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result is changed + +- name: Regenerate - adjust comment + openssh_keypair: + path: '{{ output_dir }}/regenerate-a-{{ item }}' + type: dsa + size: 1024 + comment: test comment + regenerate: '{{ item }}' + loop: "{{ regenerate_values }}" + register: result +- assert: + that: + - result is changed + # for all values but 'always', the key should have not been regenerated. + # verify this by comparing fingerprints: + - result.results[0].fingerprint == result.results[1].fingerprint + - result.results[0].fingerprint == result.results[2].fingerprint + - result.results[0].fingerprint == result.results[3].fingerprint + - result.results[0].fingerprint != result.results[4].fingerprint diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tests/validate.yml new file mode 100644 index 00000000..e9d76a7a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/tests/validate.yml @@ -0,0 +1,140 @@ +--- +- name: Log privatekey1 return values + debug: + var: privatekey1_result + +- name: Validate general behavior + assert: + that: + - privatekey1_result_check is changed + - privatekey1_result is changed + - privatekey1_idem_result_check is not changed + - privatekey1_idem_result is not changed + +- name: Validate privatekey1 return fingerprint + assert: + that: + - privatekey1_result["fingerprint"] is string + - privatekey1_result["fingerprint"].startswith("SHA256:") + # only distro old enough that it still gives md5 with no prefix + when: ansible_distribution != 'CentOS' and ansible_distribution_major_version != '6' + +- name: Validate privatekey1 return public_key + assert: + that: + - privatekey1_result["public_key"] is string + - privatekey1_result["public_key"].startswith("ssh-rsa ") + +- name: Validate privatekey1 return size value + assert: + that: + - privatekey1_result["size"]|type_debug == 'int' + - privatekey1_result["size"] == 2048 + +- name: Validate privatekey1 return key type + assert: + that: + - privatekey1_result["type"] is string + - privatekey1_result["type"] == "rsa" + +- name: Validate privatekey1 (test - RSA key with size 2048 bits) + shell: "ssh-keygen -lf {{ output_dir }}/privatekey1 | grep -o -E '^[0-9]+'" + register: privatekey1 + +- name: Validate privatekey1 (assert - RSA key with size 2048 bits) + assert: + that: + - privatekey1.stdout == '2048' + +- name: Validate privatekey1 idempotence + assert: + that: + - privatekey1_idem_result is not changed + + +- name: Validate privatekey2 (test - RSA key with default size 4096 bits) + shell: "ssh-keygen -lf {{ output_dir }}/privatekey2 | grep -o -E '^[0-9]+'" + register: privatekey2 + +- name: Validate privatekey2 (assert - RSA key with size 4096 bits) + assert: + that: + - privatekey2.stdout == '4096' + + +- name: Validate privatekey3 (test - DSA key with size 1024 bits) + shell: "ssh-keygen -lf {{ output_dir }}/privatekey3 | grep -o -E '^[0-9]+'" + register: privatekey3 + +- name: Validate privatekey3 (assert - DSA key with size 4096 bits) + assert: + that: + - privatekey3.stdout == '1024' + + +- name: Validate privatekey4 (test - Ensure key has been removed) + stat: + path: '{{ output_dir }}/privatekey4' + register: privatekey4 + +- name: Validate privatekey4 (assert - Ensure key has been removed) + assert: + that: + - privatekey4.stat.exists == False + + +- name: Validate privatekey5 (assert - Public key module output equal to the public key on host) + assert: + that: + - "publickey_gen.public_key == lookup('file', output_dir ~ '/privatekey5.pub').strip('\n')" + +- name: Verify that privatekey6 will be regenerated via force + assert: + that: + - output_regenerated_via_force is changed + + +- name: Verify that broken key will cause failure + assert: + that: + - output_broken is failed + - "'Unable to read the key. The key is protected with a passphrase or broken.' in output_broken.msg" + + +- name: Verify that broken key will be regenerated if force=yes is specified + assert: + that: + - output_broken_force is changed + + +- name: Verify that read-only key will be regenerated + assert: + that: + - output_read_only is changed + + +- name: Validate privatekey7 (assert - Public key remains the same after comment change) + assert: + that: + - privatekey7_result.public_key == privatekey7_modified_result.public_key + +- name: Validate privatekey7 comment on creation + assert: + that: + - privatekey7_result.comment == 'test@privatekey7' + +- name: Validate privatekey7 comment update + assert: + that: + - privatekey7_modified_result.comment == 'test_modified@privatekey7' + +- name: Check that password protected key made module fail + assert: + that: + - privatekey8_result is failed + - "'Unable to read the key. The key is protected with a passphrase or broken.' in privatekey8_result.msg" + +- name: Check that password protected key was regenerated with force=yes + assert: + that: + - privatekey8_result_force is changed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/vars/main.yml new file mode 100644 index 00000000..81eb611f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssh_keypair/vars/main.yml @@ -0,0 +1,7 @@ +--- +regenerate_values: + - never + - fail + - partial_idempotence + - full_idempotence + - always diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/impl.yml new file mode 100644 index 00000000..20227ce9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/impl.yml @@ -0,0 +1,997 @@ +--- +- name: "({{ select_crypto_backend }}) Generate privatekey" + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate CSR (check mode)" + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + check_mode: yes + register: generate_csr_check + +- name: "({{ select_crypto_backend }}) Generate CSR" + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: generate_csr + +- name: "({{ select_crypto_backend }}) Generate CSR (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: generate_csr_idempotent + +- name: "({{ select_crypto_backend }}) Generate CSR (idempotent, check mode)" + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + check_mode: yes + register: generate_csr_idempotent_check + +- name: "({{ select_crypto_backend }}) Generate CSR without SAN (check mode)" + openssl_csr: + path: '{{ output_dir }}/csr-nosan.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_csr_nosan_check + +- name: "({{ select_crypto_backend }}) Generate CSR without SAN" + openssl_csr: + path: '{{ output_dir }}/csr-nosan.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_nosan + +- name: "({{ select_crypto_backend }}) Generate CSR without SAN (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr-nosan.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_nosan_check_idempotent + +- name: "({{ select_crypto_backend }}) Generate CSR without SAN (idempotent, check mode)" + openssl_csr: + path: '{{ output_dir }}/csr-nosan.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_csr_nosan_check_idempotent_check + +# keyUsage longname and shortname should be able to be used +# interchangeably. Hence the long name is specified here +# but the short name is used to test idempotency for ipsecuser +# and vice-versa for biometricInfo +- name: "({{ select_crypto_backend }}) Generate CSR with KU and XKU" + openssl_csr: + path: '{{ output_dir }}/csr_ku_xku.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + CN: www.ansible.com + keyUsage: + - digitalSignature + - keyAgreement + extendedKeyUsage: + - qcStatements + - DVCS + - IPSec User + - biometricInfo + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with KU and XKU (test idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_ku_xku.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: 'www.ansible.com' + keyUsage: + - Key Agreement + - digitalSignature + extendedKeyUsage: + - ipsecUser + - qcStatements + - DVCS + - Biometric Info + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_ku_xku + +- name: "({{ select_crypto_backend }}) Generate CSR with KU and XKU (test XKU change)" + openssl_csr: + path: '{{ output_dir }}/csr_ku_xku.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: 'www.ansible.com' + keyUsage: + - digitalSignature + - keyAgreement + extendedKeyUsage: + - ipsecUser + - qcStatements + - Biometric Info + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_ku_xku_change + +- name: "({{ select_crypto_backend }}) Generate CSR with KU and XKU (test KU change)" + openssl_csr: + path: '{{ output_dir }}/csr_ku_xku.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: 'www.ansible.com' + keyUsage: + - digitalSignature + extendedKeyUsage: + - ipsecUser + - qcStatements + - Biometric Info + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_ku_xku_change_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with old API" + openssl_csr: + path: '{{ output_dir }}/csr_oldapi.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with invalid SAN (1/2)" + openssl_csr: + path: '{{ output_dir }}/csrinvsan.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: invalid-san.example.com + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_invalid_san + ignore_errors: yes + +- name: "({{ select_crypto_backend }}) Generate CSR with invalid SAN (2/2)" + openssl_csr: + path: '{{ output_dir }}/csrinvsan2.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: "DNS:system:kube-controller-manager" + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_invalid_san_2 + ignore_errors: yes + +- name: "({{ select_crypto_backend }}) Generate CSR with OCSP Must Staple" + openssl_csr: + path: '{{ output_dir }}/csr_ocsp.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: "DNS:www.ansible.com" + ocsp_must_staple: true + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with OCSP Must Staple (test idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_ocsp.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject_alt_name: "DNS:www.ansible.com" + ocsp_must_staple: true + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_ocsp_idempotency + +- name: "({{ select_crypto_backend }}) Generate ECC privatekey" + openssl_privatekey: + path: '{{ output_dir }}/privatekey2.pem' + type: ECC + curve: secp384r1 + +- name: "({{ select_crypto_backend }}) Generate CSR with ECC privatekey" + openssl_csr: + path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with text common name" + openssl_csr: + path: '{{ output_dir }}/csr3.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + subject: + commonName: This is for Ansible + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with country name" + openssl_csr: + path: '{{ output_dir }}/csr4.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + country_name: de + select_crypto_backend: '{{ select_crypto_backend }}' + register: country_idempotent_1 + +- name: "({{ select_crypto_backend }}) Generate CSR with country name (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr4.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + country_name: de + select_crypto_backend: '{{ select_crypto_backend }}' + register: country_idempotent_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with country name (idempotent 2)" + openssl_csr: + path: '{{ output_dir }}/csr4.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + subject: + C: de + select_crypto_backend: '{{ select_crypto_backend }}' + register: country_idempotent_3 + +- name: "({{ select_crypto_backend }}) Generate CSR with country name (bad country name)" + openssl_csr: + path: '{{ output_dir }}/csr4.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + subject: + C: dex + select_crypto_backend: '{{ select_crypto_backend }}' + register: country_fail_4 + ignore_errors: yes + +- name: "({{ select_crypto_backend }}) Generate privatekey with password" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate CSR with privatekey passphrase" + openssl_csr: + path: '{{ output_dir }}/csr_pw.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: hunter2 + select_crypto_backend: '{{ select_crypto_backend }}' + register: passphrase_1 + +- name: "({{ select_crypto_backend }}) Generate CSR (failed passphrase 1)" + openssl_csr: + path: '{{ output_dir }}/csr_pw1.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + privatekey_passphrase: hunter2 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_1 + +- name: "({{ select_crypto_backend }}) Generate CSR (failed passphrase 2)" + openssl_csr: + path: '{{ output_dir }}/csr_pw2.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: wrong_password + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_2 + +- name: "({{ select_crypto_backend }}) Generate CSR (failed passphrase 3)" + openssl_csr: + path: '{{ output_dir }}/csr_pw3.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_3 + +- name: "({{ select_crypto_backend }}) Create broken CSR" + copy: + dest: "{{ output_dir }}/csrbroken.csr" + content: "broken" +- name: "({{ select_crypto_backend }}) Regenerate broken CSR" + openssl_csr: + path: '{{ output_dir }}/csrbroken.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + subject: + commonName: This is for Ansible + useCommonNameForSAN: no + select_crypto_backend: '{{ select_crypto_backend }}' + register: output_broken + +- name: "({{ select_crypto_backend }}) Generate CSR" + openssl_csr: + path: '{{ output_dir }}/csr_backup.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_backup_1 +- name: "({{ select_crypto_backend }}) Generate CSR (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr_backup.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_backup_2 +- name: "({{ select_crypto_backend }}) Generate CSR (change)" + openssl_csr: + path: '{{ output_dir }}/csr_backup.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: ansible.com + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_backup_3 +- name: "({{ select_crypto_backend }}) Generate CSR (remove)" + openssl_csr: + path: '{{ output_dir }}/csr_backup.csr' + state: absent + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: csr_backup_4 +- name: "({{ select_crypto_backend }}) Generate CSR (remove, idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr_backup.csr' + state: absent + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: csr_backup_5 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + subject_key_identifier: "00:11:22:33" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_1 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + subject_key_identifier: "00:11:22:33" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (change)" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + subject_key_identifier: "44:55:66:77:88" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_3 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (auto-create)" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + create_subject_key_identifier: yes + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_4 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (auto-create idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + create_subject_key_identifier: yes + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_5 + +- name: "({{ select_crypto_backend }}) Generate CSR with subject key identifier (remove)" + openssl_csr: + path: '{{ output_dir }}/csr_ski.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: subject_key_identifier_6 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier" + openssl_csr: + path: '{{ output_dir }}/csr_aki.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_key_identifier: "00:11:22:33" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_key_identifier_1 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_aki.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_key_identifier: "00:11:22:33" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_key_identifier_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (change)" + openssl_csr: + path: '{{ output_dir }}/csr_aki.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_key_identifier: "44:55:66:77:88" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_key_identifier_3 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority key identifier (remove)" + openssl_csr: + path: '{{ output_dir }}/csr_aki.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_key_identifier_4 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number" + openssl_csr: + path: '{{ output_dir }}/csr_acisn.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + authority_cert_serial_number: 12345 + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_cert_issuer_sn_1 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (idempotency)" + openssl_csr: + path: '{{ output_dir }}/csr_acisn.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + authority_cert_serial_number: 12345 + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_cert_issuer_sn_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (change issuer)" + openssl_csr: + path: '{{ output_dir }}/csr_acisn.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_cert_issuer: + - "IP:1.2.3.4" + - "DNS:ca.example.org" + authority_cert_serial_number: 12345 + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_cert_issuer_sn_3 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (change serial number)" + openssl_csr: + path: '{{ output_dir }}/csr_acisn.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + authority_cert_issuer: + - "IP:1.2.3.4" + - "DNS:ca.example.org" + authority_cert_serial_number: 54321 + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: authority_cert_issuer_sn_4 + +- name: "({{ select_crypto_backend }}) Generate CSR with authority cert issuer / serial number (remove)" + openssl_csr: + path: '{{ output_dir }}/csr_acisn.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + when: select_crypto_backend != 'pyopenssl' + register: authority_cert_issuer_sn_5 + +- name: "({{ select_crypto_backend }}) Generate CSR with everything" + openssl_csr: + path: '{{ output_dir }}/csr_everything.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + C: de + L: Somewhere + ST: Zurich + streetAddress: Welcome Street + O: Ansible + organizationalUnitName: Crypto Department + serialNumber: "1234" + SN: Last Name + GN: First Name + title: Chief + pseudonym: test + UID: asdf + emailAddress: test@example.com + postalAddress: 1234 Somewhere + postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}' + subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}' + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' + name_constraints_excluded: + - "DNS:.example.com" + - "DNS:.org" + name_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' + authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}' + select_crypto_backend: '{{ select_crypto_backend }}' + vars: + value_for_extended_key_usage_pyopenssl: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + value_for_extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + - 1.2.3.4.5.6 + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + value_for_san_pyopenssl: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + value_for_san: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + - "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71" + - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" + - "dirName:O = Example Net, CN = example.net" + - "dirName:/O=Example Com/CN=example.com" + value_for_name_constraints_permitted: + - "DNS:www.example.com" + - "IP:1.2.3.0/24" + - "IP:::1:0:0/112" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + register: everything_1 + +- name: "({{ select_crypto_backend }}) Generate CSR with everything (idempotent, check mode)" + openssl_csr: + path: '{{ output_dir }}/csr_everything.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + C: de + L: Somewhere + ST: Zurich + streetAddress: Welcome Street + O: Ansible + organizationalUnitName: Crypto Department + serialNumber: "1234" + SN: Last Name + GN: First Name + title: Chief + pseudonym: test + UID: asdf + emailAddress: test@example.com + postalAddress: 1234 Somewhere + postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}' + subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}' + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' + name_constraints_excluded: + - "DNS:.org" + - "DNS:.example.com" + name_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' + authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}' + select_crypto_backend: '{{ select_crypto_backend }}' + vars: + value_for_extended_key_usage_pyopenssl: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + value_for_extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + - 1.2.3.4.5.6 + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + value_for_san_pyopenssl: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + value_for_san: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + - "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71" + - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" + - "dirName:O=Example Net,CN=example.net" + - "dirName:/O = Example Com/CN = example.com" + value_for_name_constraints_permitted: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + - "IP:0::0:1:0:0/112" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + check_mode: yes + register: everything_2 + +- name: "({{ select_crypto_backend }}) Generate CSR with everything (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr_everything.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + C: de + L: Somewhere + ST: Zurich + streetAddress: Welcome Street + O: Ansible + organizationalUnitName: Crypto Department + serialNumber: "1234" + SN: Last Name + GN: First Name + title: Chief + pseudonym: test + UID: asdf + emailAddress: test@example.com + postalAddress: 1234 Somewhere + postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}' + subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}' + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}' + name_constraints_excluded: + - "DNS:.org" + - "DNS:.example.com" + name_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}' + authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_issuer: '{{ value_for_authority_cert_issuer if select_crypto_backend != "pyopenssl" else omit }}' + authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}' + select_crypto_backend: '{{ select_crypto_backend }}' + vars: + value_for_extended_key_usage_pyopenssl: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + value_for_extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + - 1.2.3.4.5.6 + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + value_for_san_pyopenssl: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + value_for_san: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + - "RID:1.2.3.4" + - "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71" + - "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost" + - "dirName:O =Example Net, CN= example.net" + - "dirName:/O =Example Com/CN= example.com" + value_for_name_constraints_permitted: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + - "IP:0::0:1:0:0/112" + value_for_name_constraints_permitted_pyopenssl: + - "DNS:www.example.com" + - "IP:1.2.3.0/255.255.255.0" + register: everything_3 + +- name: "({{ select_crypto_backend }}) Get info from CSR with everything" + community.crypto.openssl_csr_info: + path: '{{ output_dir }}/csr_everything.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: everything_info + +- name: "({{ select_crypto_backend }}) Ed25519 and Ed448 tests (for cryptography >= 2.6)" + block: + - name: "({{ select_crypto_backend }}) Generate privatekeys" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_{{ item }}.pem' + type: '{{ item }}' + loop: + - Ed25519 + - Ed448 + register: generate_csr_ed25519_ed448_privatekey + ignore_errors: yes + + - name: "({{ select_crypto_backend }}) Generate CSR if private key generation succeeded" + when: generate_csr_ed25519_ed448_privatekey is not failed + block: + + - name: "({{ select_crypto_backend }}) Generate CSR" + openssl_csr: + path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: generate_csr_ed25519_ed448 + ignore_errors: yes + + - name: "({{ select_crypto_backend }}) Generate CSR (idempotent)" + openssl_csr: + path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: generate_csr_ed25519_ed448_idempotent + ignore_errors: yes + + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') + +- name: "({{ select_crypto_backend }}) CRL distribution endpoints (for cryptography >= 1.6)" + block: + - name: "({{ select_crypto_backend }}) Create CSR with CRL distribution endpoints" + openssl_csr: + path: '{{ output_dir }}/csr_crl_d_e.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + crl_distribution_points: + - full_name: + - "URI:https://ca.example.com/revocations.crl" + crl_issuer: + - "URI:https://ca.example.com/" + reasons: + - key_compromise + - ca_compromise + - cessation_of_operation + - relative_name: + - CN=ca.example.com + reasons: + - certificate_hold + - {} + select_crypto_backend: '{{ select_crypto_backend }}' + register: crl_distribution_endpoints_1 + + - name: "({{ select_crypto_backend }}) Create CSR with CRL distribution endpoints (idempotence)" + openssl_csr: + path: '{{ output_dir }}/csr_crl_d_e.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + crl_distribution_points: + - full_name: + - "URI:https://ca.example.com/revocations.crl" + crl_issuer: + - "URI:https://ca.example.com/" + reasons: + - key_compromise + - ca_compromise + - cessation_of_operation + - relative_name: + - CN=ca.example.com + reasons: + - certificate_hold + - {} + select_crypto_backend: '{{ select_crypto_backend }}' + register: crl_distribution_endpoints_2 + + - name: "({{ select_crypto_backend }}) Create CSR with CRL distribution endpoints (change)" + openssl_csr: + path: '{{ output_dir }}/csr_crl_d_e.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + crl_distribution_points: + - full_name: + - "URI:https://ca.example.com/revocations.crl" + crl_issuer: + - "URI:https://ca.example.com/" + reasons: + - key_compromise + - ca_compromise + - cessation_of_operation + - relative_name: + - CN=ca.example.com + reasons: + - certificate_hold + select_crypto_backend: '{{ select_crypto_backend }}' + register: crl_distribution_endpoints_3 + + - name: "({{ select_crypto_backend }}) Create CSR with CRL distribution endpoints (no endpoints)" + openssl_csr: + path: '{{ output_dir }}/csr_crl_d_e.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + register: crl_distribution_endpoints_4 + + - name: "({{ select_crypto_backend }}) Create CSR with CRL distribution endpoints" + openssl_csr: + path: '{{ output_dir }}/csr_crl_d_e.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + crl_distribution_points: + - full_name: + - "URI:https://ca.example.com/revocations.crl" + select_crypto_backend: '{{ select_crypto_backend }}' + register: crl_distribution_endpoints_5 + + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.6', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/main.yml new file mode 100644 index 00000000..575bc79d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tasks/main.yml @@ -0,0 +1,50 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Prepare private key for backend autodetection test + openssl_privatekey: + path: '{{ output_dir }}/privatekey_backend_selection.pem' + size: '{{ default_rsa_key_size }}' +- name: Run module with backend autodetection + openssl_csr: + path: '{{ output_dir }}/csr_backend_selection.csr' + privatekey_path: '{{ output_dir }}/privatekey_backend_selection.pem' + subject: + commonName: www.ansible.com + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: pyopenssl + + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('1.3', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tests/validate.yml new file mode 100644 index 00000000..8958a7ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr/tests/validate.yml @@ -0,0 +1,346 @@ +--- +- name: "({{ select_crypto_backend }}) Validate CSR (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey_modulus + +- name: "({{ select_crypto_backend }}) Validate CSR (test - Common Name)" + shell: "{{ openssl_binary }} req -noout -subject -in {{ output_dir }}/csr.csr -nameopt oneline,-space_eq" + register: csr_cn + +- name: "({{ select_crypto_backend }}) Validate CSR (test - csr modulus)" + shell: '{{ openssl_binary }} req -noout -modulus -in {{ output_dir }}/csr.csr' + register: csr_modulus + +- name: "({{ select_crypto_backend }}) Validate CSR (assert)" + assert: + that: + - csr_cn.stdout.split('=')[-1] == 'www.ansible.com' + - csr_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate CSR (check mode, idempotency)" + assert: + that: + - generate_csr_check is changed + - generate_csr is changed + - generate_csr_idempotent is not changed + - generate_csr_idempotent_check is not changed + +- name: "({{ select_crypto_backend }}) Validate CSR (data retrieval)" + assert: + that: + - generate_csr_check.csr is none + - generate_csr.csr == lookup('file', output_dir ~ '/csr.csr', rstrip=False) + - generate_csr.csr == generate_csr_idempotent.csr + - generate_csr.csr == generate_csr_idempotent_check.csr + +- name: "({{ select_crypto_backend }}) Validate CSR without SAN (check mode, idempotency)" + assert: + that: + - generate_csr_nosan_check is changed + - generate_csr_nosan is changed + - generate_csr_nosan_check_idempotent is not changed + - generate_csr_nosan_check_idempotent_check is not changed + +- name: "({{ select_crypto_backend }}) Validate CSR_KU_XKU (assert idempotency, change)" + assert: + that: + - csr_ku_xku is not changed + - csr_ku_xku_change is changed + - csr_ku_xku_change_2 is changed + +- name: "({{ select_crypto_backend }}) Validate old_API CSR (test - Common Name)" + shell: "{{ openssl_binary }} req -noout -subject -in {{ output_dir }}/csr_oldapi.csr -nameopt oneline,-space_eq" + register: csr_oldapi_cn + +- name: "({{ select_crypto_backend }}) Validate old_API CSR (test - csr modulus)" + shell: '{{ openssl_binary }} req -noout -modulus -in {{ output_dir }}/csr_oldapi.csr' + register: csr_oldapi_modulus + +- name: "({{ select_crypto_backend }}) Validate old_API CSR (assert)" + assert: + that: + - csr_oldapi_cn.stdout.split('=')[-1] == 'www.ansible.com' + - csr_oldapi_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate invalid SAN (1/2)" + assert: + that: + - generate_csr_invalid_san is failed + - "'Subject Alternative Name' in generate_csr_invalid_san.msg" + +- name: "({{ select_crypto_backend }}) Validate invalid SAN (2/2)" + # Note that pyOpenSSL simply accepts this name, and modern cryptography versions do so as well. + # The error has been observed with cryptography 1.7.2 and 1.9, but not with 2.3 and newer. + assert: + that: + - generate_csr_invalid_san_2 is failed + - "'The label system:kube-controller-manager is not a valid A-label' in generate_csr_invalid_san_2.msg" + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.0', '<') + +- name: "({{ select_crypto_backend }}) Validate OCSP Must Staple CSR (test - everything)" + shell: "{{ openssl_binary }} req -noout -in {{ output_dir }}/csr_ocsp.csr -text" + register: csr_ocsp + +- name: "({{ select_crypto_backend }}) Validate OCSP Must Staple CSR (assert)" + assert: + that: + - "(csr_ocsp.stdout is search('\\s+TLS Feature:\\s*\\n\\s+status_request\\s+')) or + (csr_ocsp.stdout is search('\\s+1.3.6.1.5.5.7.1.24:\\s*\\n\\s+0\\.\\.\\.\\.\\s+'))" + +- name: "({{ select_crypto_backend }}) Validate OCSP Must Staple CSR (assert idempotency)" + assert: + that: + - csr_ocsp_idempotency is not changed + +- name: "({{ select_crypto_backend }}) Validate ECC CSR (test - privatekey's public key)" + shell: '{{ openssl_binary }} ec -pubout -in {{ output_dir }}/privatekey2.pem' + register: privatekey_ecc_key + +- name: "({{ select_crypto_backend }}) Validate ECC CSR (test - Common Name)" + shell: "{{ openssl_binary }} req -noout -subject -in {{ output_dir }}/csr2.csr -nameopt oneline,-space_eq" + register: csr_ecc_cn + +- name: "({{ select_crypto_backend }}) Validate ECC CSR (test - CSR pubkey)" + shell: '{{ openssl_binary }} req -noout -pubkey -in {{ output_dir }}/csr2.csr' + register: csr_ecc_pubkey + +- name: "({{ select_crypto_backend }}) Validate ECC CSR (assert)" + assert: + that: + - csr_ecc_cn.stdout.split('=')[-1] == 'www.ansible.com' + - csr_ecc_pubkey.stdout == privatekey_ecc_key.stdout + +- name: "({{ select_crypto_backend }}) Validate CSR (text common name - Common Name)" + shell: "{{ openssl_binary }} req -noout -subject -in {{ output_dir }}/csr3.csr -nameopt oneline,-space_eq" + register: csr3_cn + +- name: "({{ select_crypto_backend }}) Validate CSR (assert)" + assert: + that: + - csr3_cn.stdout.split('=')[-1] == 'This is for Ansible' + +- name: "({{ select_crypto_backend }}) Validate country name idempotency and validation" + assert: + that: + - country_idempotent_1 is changed + - country_idempotent_2 is not changed + - country_idempotent_3 is not changed + - country_fail_4 is failed + +- name: + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" + +- name: "({{ select_crypto_backend }}) Verify that broken CSR will be regenerated" + assert: + that: + - output_broken is changed + +- name: "({{ select_crypto_backend }}) Verify that subject key identifier handling works" + assert: + that: + - subject_key_identifier_1 is changed + - subject_key_identifier_2 is not changed + - subject_key_identifier_3 is changed + - subject_key_identifier_4 is changed + - subject_key_identifier_5 is not changed + - subject_key_identifier_6 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: "({{ select_crypto_backend }}) Verify that authority key identifier handling works" + assert: + that: + - authority_key_identifier_1 is changed + - authority_key_identifier_2 is not changed + - authority_key_identifier_3 is changed + - authority_key_identifier_4 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: "({{ select_crypto_backend }}) Verify that authority cert issuer / serial number handling works" + assert: + that: + - authority_cert_issuer_sn_1 is changed + - authority_cert_issuer_sn_2 is not changed + - authority_cert_issuer_sn_3 is changed + - authority_cert_issuer_sn_4 is changed + - authority_cert_issuer_sn_5 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: "({{ select_crypto_backend }}) Check backup" + assert: + that: + - csr_backup_1 is changed + - csr_backup_1.backup_file is undefined + - csr_backup_2 is not changed + - csr_backup_2.backup_file is undefined + - csr_backup_3 is changed + - csr_backup_3.backup_file is string + - csr_backup_4 is changed + - csr_backup_4.backup_file is string + - csr_backup_5 is not changed + - csr_backup_5.backup_file is undefined + - csr_backup_4.csr is none + +- name: "({{ select_crypto_backend }}) Check CSR with everything" + assert: + that: + - everything_1 is changed + - everything_2 is not changed + - everything_3 is not changed + - everything_info.basic_constraints == [ + "CA:TRUE", + "pathlen:23", + ] + - everything_info.basic_constraints_critical == true + - everything_info.extended_key_usage_critical == false + - everything_info.key_usage == [ + "CRL Sign", + "Certificate Sign", + "Data Encipherment", + "Decipher Only", + "Digital Signature", + "Encipher Only", + "Key Agreement", + "Key Encipherment", + "Non Repudiation" + ] + - everything_info.key_usage_critical == true + - everything_info.ocsp_must_staple == true + - everything_info.ocsp_must_staple_critical == false + - everything_info.signature_valid == true + - everything_info.subject.commonName == "www.example.com" + - everything_info.subject.countryName == "de" + - everything_info.subject.emailAddress == "test@example.com" + - everything_info.subject.givenName == "First Name" + - everything_info.subject.localityName == "Somewhere" + - everything_info.subject.organizationName == "Ansible" + - everything_info.subject.organizationalUnitName == "Crypto Department" + - everything_info.subject.postalAddress == "1234 Somewhere" + - everything_info.subject.postalCode == "1234" + - everything_info.subject.pseudonym == "test" + - everything_info.subject.serialNumber == "1234" + - everything_info.subject.stateOrProvinceName == "Zurich" + - everything_info.subject.streetAddress == "Welcome Street" + - everything_info.subject.surname == "Last Name" + - everything_info.subject.title == "Chief" + - everything_info.subject.userId == "asdf" + - everything_info.subject | length == 16 + - everything_info.subject_alt_name_critical == false + - everything_info.name_constraints_excluded == [ + "DNS:.example.com", + "DNS:.org", + ] + - everything_info.name_constraints_critical == true + +- name: "({{ select_crypto_backend }}) Check CSR with everything (pyOpenSSL specific)" + assert: + that: + - everything_info.subject_alt_name == [ + "DNS:www.ansible.com", + "IP:1.2.3.4", + "IP:::1", + "email:test@example.org", + "URI:https://example.org/test/index.html", + "RID:1.2.3.4", + ] + - everything_info.extended_key_usage == [ + "Any Extended Key Usage", + "Biometric Info", + "Code Signing", + "E-mail Protection", + "IPSec User", + "OCSP Signing", + "TLS Web Client Authentication", + "TLS Web Server Authentication", + "TLS Web Server Authentication", + "Time Stamping", + "dvcs", + "qcStatements", + ] + - everything_info.name_constraints_permitted == [ + "DNS:www.example.com", + "IP:1.2.3.0/24", + ] + when: select_crypto_backend == 'pyopenssl' + +- name: "({{ select_crypto_backend }}) Check CSR with everything (non-pyOpenSSL specific)" + assert: + that: + - everything_info.authority_cert_issuer == [ + "DNS:ca.example.org", + "IP:1.2.3.4" + ] + - everything_info.authority_cert_serial_number == 12345 + - everything_info.authority_key_identifier == "44:55:66:77" + - everything_info.subject_alt_name == [ + "DNS:www.ansible.com", + "IP:1.2.3.4", + "IP:::1", + "email:test@example.org", + "URI:https://example.org/test/index.html", + "RID:1.2.3.4", + "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71", + "otherName:1.3.6.1.4.1.311.20.2.3;0c:0d:62:6f:62:40:6c:6f:63:61:6c:68:6f:73:74", + "dirName:/O=Example Net/CN=example.net", + "dirName:/O=Example Com/CN=example.com" + ] + - everything_info.subject_key_identifier == "00:11:22:33" + - everything_info.extended_key_usage == [ + "1.2.3.4.5.6", + "Any Extended Key Usage", + "Biometric Info", + "Code Signing", + "E-mail Protection", + "IPSec User", + "OCSP Signing", + "TLS Web Client Authentication", + "TLS Web Server Authentication", + "TLS Web Server Authentication", + "Time Stamping", + "dvcs", + "qcStatements", + ] + - everything_info.name_constraints_permitted == [ + "DNS:www.example.com", + "IP:1.2.3.0/24", + "IP:::1:0:0/112", + ] + when: select_crypto_backend != 'pyopenssl' + +- name: "({{ select_crypto_backend }}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)" + assert: + that: + - generate_csr_ed25519_ed448.results[0] is failed + - generate_csr_ed25519_ed448.results[1] is failed + - generate_csr_ed25519_ed448.results[0].msg == 'Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.' + - generate_csr_ed25519_ed448.results[1].msg == 'Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.' + - generate_csr_ed25519_ed448_idempotent.results[0] is failed + - generate_csr_ed25519_ed448_idempotent.results[1] is failed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') and cryptography_version.stdout is version('2.8', '<') and generate_csr_ed25519_ed448_privatekey is not failed + +- name: "({{ select_crypto_backend }}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.8)" + assert: + that: + - generate_csr_ed25519_ed448 is succeeded + - generate_csr_ed25519_ed448.results[0] is changed + - generate_csr_ed25519_ed448.results[1] is changed + - generate_csr_ed25519_ed448_idempotent is succeeded + - generate_csr_ed25519_ed448_idempotent.results[0] is not changed + - generate_csr_ed25519_ed448_idempotent.results[1] is not changed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.8', '>=') and generate_csr_ed25519_ed448_privatekey is not failed + +- name: "({{ select_crypto_backend }}) Verify CRL distribution endpoints (for cryptography >= 1.6)" + assert: + that: + - crl_distribution_endpoints_1 is changed + - crl_distribution_endpoints_2 is not changed + - crl_distribution_endpoints_3 is changed + - crl_distribution_endpoints_4 is changed + - crl_distribution_endpoints_5 is changed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.6', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/impl.yml new file mode 100644 index 00000000..bc9037eb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/impl.yml @@ -0,0 +1,94 @@ +--- +- debug: + msg: "Executing tests with backend {{ select_crypto_backend }}" + +- name: "({{ select_crypto_backend }}) Get CSR info" + openssl_csr_info: + path: '{{ output_dir }}/csr_1.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: "({{ select_crypto_backend }}) Check whether subject behaves as expected" + assert: + that: + - result.subject.organizationalUnitName == 'ACME Department' + - "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered" + - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" + +- name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier" + assert: + that: + - result.subject_key_identifier == "00:11:22:33" + - result.authority_key_identifier == "44:55:66:77" + - result.authority_cert_issuer == expected_authority_cert_issuer + - result.authority_cert_serial_number == 12345 + vars: + expected_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: "({{ select_crypto_backend }}) Update result list" + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: "({{ select_crypto_backend }}) Get CSR info directly" + openssl_csr_info: + content: '{{ lookup("file", output_dir ~ "/csr_1.csr") }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result_direct + +- name: "({{ select_crypto_backend }}) Compare output of direct and loaded info" + assert: + that: + - result == result_direct + +- name: "({{ select_crypto_backend }}) Get CSR info" + openssl_csr_info: + path: '{{ output_dir }}/csr_2.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: "({{ select_crypto_backend }}) Update result list" + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: "({{ select_crypto_backend }}) Get CSR info" + openssl_csr_info: + path: '{{ output_dir }}/csr_3.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: "({{ select_crypto_backend }}) Check AuthorityKeyIdentifier" + assert: + that: + - result.authority_key_identifier is none + - result.authority_cert_issuer == expected_authority_cert_issuer + - result.authority_cert_serial_number == 12345 + vars: + expected_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: "({{ select_crypto_backend }}) Update result list" + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: "({{ select_crypto_backend }}) Get CSR info" + openssl_csr_info: + path: '{{ output_dir }}/csr_4.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: "({{ select_crypto_backend }}) Check AuthorityKeyIdentifier" + assert: + that: + - result.authority_key_identifier == "44:55:66:77" + - result.authority_cert_issuer is none + - result.authority_cert_serial_number is none + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: "({{ select_crypto_backend }}) Update result list" + set_fact: + info_results: "{{ info_results + [result] }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/main.yml new file mode 100644 index 00000000..e55ffa44 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/tasks/main.yml @@ -0,0 +1,168 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size }}' + +- name: Generate privatekey with password + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size }}' + +- name: Generate CSR 1 + openssl_csr: + path: '{{ output_dir }}/csr_1.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + C: de + L: Somewhere + ST: Zurich + streetAddress: Welcome Street + O: Ansible + organizationalUnitName: + - Crypto Department + - ACME Department + serialNumber: "1234" + SN: Last Name + GN: First Name + title: Chief + pseudonym: test + UID: asdf + emailAddress: test@example.com + postalAddress: 1234 Somewhere + postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + subject_alt_name: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: '{{ "00:11:22:33" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}' + vars: + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + +- name: Generate CSR 2 + openssl_csr: + path: '{{ output_dir }}/csr_2.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: hunter2 + useCommonNameForSAN: no + basic_constraints: + - "CA:TRUE" + +- name: Generate CSR 3 + openssl_csr: + path: '{{ output_dir }}/csr_3.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + useCommonNameForSAN: no + subject_alt_name: + - "DNS:*.ansible.com" + - "DNS:*.example.org" + - "IP:DEAD:BEEF::1" + basic_constraints: + - "CA:FALSE" + authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}' + vars: + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + +- name: Generate CSR 4 + openssl_csr: + path: '{{ output_dir }}/csr_4.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + useCommonNameForSAN: no + authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + +- name: Prepare result list + set_fact: + info_results: [] + +- name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Prepare result list + set_fact: + pyopenssl_info_results: "{{ info_results }}" + info_results: [] + +- name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('1.3', '>=') + +- name: Prepare result list + set_fact: + cryptography_info_results: "{{ info_results }}" + +- block: + - name: Dump pyOpenSSL results + debug: + var: pyopenssl_info_results + - name: Dump cryptography results + debug: + var: cryptography_info_results + - name: Compare results + assert: + that: + - ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict) + == (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)' + quiet: yes + loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}" + when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.3', '>=') + vars: + keys_to_ignore: + - deprecations + - subject_key_identifier + - authority_key_identifier + - authority_cert_issuer + - authority_cert_serial_number diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/test_plugins/jinja_compatibility.py b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/test_plugins/jinja_compatibility.py new file mode 100644 index 00000000..fc2b5f0f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_info/test_plugins/jinja_compatibility.py @@ -0,0 +1,15 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def compatibility_in_test(a, b): + return a in b + + +class TestModule: + ''' Ansible math jinja2 tests ''' + + def tests(self): + return { + 'in': compatibility_in_test, + } diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/impl.yml new file mode 100644 index 00000000..844ed77e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/impl.yml @@ -0,0 +1,92 @@ +--- +- name: "({{ select_crypto_backend }}) Generate privatekey" + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate CSR (check mode)" + openssl_csr_pipe: + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_csr_check + +- name: "({{ select_crypto_backend }}) Generate CSR" + openssl_csr_pipe: + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr + +- name: "({{ select_crypto_backend }}) Generate CSR (idempotent)" + openssl_csr_pipe: + content: "{{ generate_csr.csr }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_idempotent + +- name: "({{ select_crypto_backend }}) Generate CSR (idempotent, check mode)" + openssl_csr_pipe: + content: "{{ generate_csr.csr }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_csr_idempotent_check + +- name: "({{ select_crypto_backend }}) Generate CSR (changed)" + openssl_csr_pipe: + content: "{{ generate_csr.csr }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_csr_changed + +- name: "({{ select_crypto_backend }}) Generate CSR (changed, check mode)" + openssl_csr_pipe: + content: "{{ generate_csr.csr }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_csr_changed_check + +- name: "({{ select_crypto_backend }}) Validate CSR (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey_modulus + +- name: "({{ select_crypto_backend }}) Validate CSR (test - Common Name)" + shell: "{{ openssl_binary }} req -noout -subject -in /dev/stdin -nameopt oneline,-space_eq" + args: + stdin: "{{ generate_csr.csr }}" + register: csr_cn + +- name: "({{ select_crypto_backend }}) Validate CSR (test - csr modulus)" + shell: '{{ openssl_binary }} req -noout -modulus -in /dev/stdin' + args: + stdin: "{{ generate_csr.csr }}" + register: csr_modulus + +- name: "({{ select_crypto_backend }}) Validate CSR (assert)" + assert: + that: + - csr_cn.stdout.split('=')[-1] == 'www.ansible.com' + - csr_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate CSR (check mode, idempotency)" + assert: + that: + - generate_csr_check is changed + - generate_csr is changed + - generate_csr_idempotent is not changed + - generate_csr_idempotent_check is not changed + - generate_csr_changed is changed + - generate_csr_changed_check is changed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/main.yml new file mode 100644 index 00000000..8f3d9c59 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_csr_pipe/tasks/main.yml @@ -0,0 +1,41 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Prepare private key for backend autodetection test + openssl_privatekey: + path: '{{ output_dir }}/privatekey_backend_selection.pem' + size: '{{ default_rsa_key_size }}' +- name: Run module with backend autodetection + openssl_csr_pipe: + privatekey_path: '{{ output_dir }}/privatekey_backend_selection.pem' + subject: + commonName: www.ansible.com + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('1.3', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/meta/main.yml new file mode 100644 index 00000000..800aff64 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_openssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/impl.yml new file mode 100644 index 00000000..d367436d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/impl.yml @@ -0,0 +1,101 @@ +--- +# The tests for this module generate unsafe parameters for testing purposes; +# otherwise tests would be too slow. Use sizes of at least 2048 in production! +- name: "[{{ select_crypto_backend }}] Generate parameter" + openssl_dhparam: + size: 768 + path: '{{ output_dir }}/dh768.pem' + select_crypto_backend: "{{ select_crypto_backend }}" + return_content: yes + register: dhparam + +- name: "[{{ select_crypto_backend }}] Don't regenerate parameters with no change" + openssl_dhparam: + size: 768 + path: '{{ output_dir }}/dh768.pem' + select_crypto_backend: "{{ select_crypto_backend }}" + return_content: yes + register: dhparam_changed + +- name: "[{{ select_crypto_backend }}] Generate parameters with size option" + openssl_dhparam: + path: '{{ output_dir }}/dh512.pem' + size: 512 + select_crypto_backend: "{{ select_crypto_backend }}" + +- name: "[{{ select_crypto_backend }}] Don't regenerate parameters with size option and no change" + openssl_dhparam: + path: '{{ output_dir }}/dh512.pem' + size: 512 + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_changed_512 + +- copy: + src: '{{ output_dir }}/dh768.pem' + remote_src: yes + dest: '{{ output_dir }}/dh512.pem' + +- name: "[{{ select_crypto_backend }}] Re-generate if size is different" + openssl_dhparam: + path: '{{ output_dir }}/dh512.pem' + size: 512 + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_changed_to_512 + +- name: "[{{ select_crypto_backend }}] Force re-generate parameters with size option" + openssl_dhparam: + path: '{{ output_dir }}/dh512.pem' + size: 512 + force: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_changed_force + +- name: "[{{ select_crypto_backend }}] Create broken params" + copy: + dest: "{{ output_dir }}/dhbroken.pem" + content: "broken" +- name: "[{{ select_crypto_backend }}] Regenerate broken params" + openssl_dhparam: + path: '{{ output_dir }}/dhbroken.pem' + size: 512 + force: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: output_broken + +- name: "[{{ select_crypto_backend }}] Generate params" + openssl_dhparam: + path: '{{ output_dir }}/dh_backup.pem' + size: 512 + backup: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_backup_1 +- name: "[{{ select_crypto_backend }}] Generate params (idempotent)" + openssl_dhparam: + path: '{{ output_dir }}/dh_backup.pem' + size: 512 + backup: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_backup_2 +- name: "[{{ select_crypto_backend }}] Generate params (change)" + openssl_dhparam: + path: '{{ output_dir }}/dh_backup.pem' + size: 512 + force: yes + backup: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_backup_3 +- name: "[{{ select_crypto_backend }}] Generate params (remove)" + openssl_dhparam: + path: '{{ output_dir }}/dh_backup.pem' + state: absent + backup: yes + select_crypto_backend: "{{ select_crypto_backend }}" + return_content: yes + register: dhparam_backup_4 +- name: "[{{ select_crypto_backend }}] Generate params (remove, idempotent)" + openssl_dhparam: + path: '{{ output_dir }}/dh_backup.pem' + state: absent + backup: yes + select_crypto_backend: "{{ select_crypto_backend }}" + register: dhparam_backup_5 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/main.yml new file mode 100644 index 00000000..b0339dfa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tasks/main.yml @@ -0,0 +1,43 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# The tests for this module generate unsafe parameters for testing purposes; +# otherwise tests would be too slow. Use sizes of at least 2048 in production! + +- name: Run module with backend autodetection + openssl_dhparam: + path: '{{ output_dir }}/dh_backend_selection.pem' + size: 512 + +- block: + - name: Running tests with OpenSSL backend + include_tasks: impl.yml + + - include_tasks: ../tests/validate.yml + + vars: + select_crypto_backend: openssl + # when: openssl_version.stdout is version('1.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + + - include_tasks: ../tests/validate.yml + + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('2.0', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tests/validate.yml new file mode 100644 index 00000000..a9ed03ef --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_dhparam/tests/validate.yml @@ -0,0 +1,58 @@ +--- +- name: "[{{ select_crypto_backend }}] Validate generated params" + shell: '{{ openssl_binary }} dhparam -in {{ output_dir }}/{{ item }}.pem -noout -check' + with_items: + - dh768 + - dh512 + +- name: "[{{ select_crypto_backend }}] Get bit size of 768" + shell: '{{ openssl_binary }} dhparam -noout -in {{ output_dir }}/dh768.pem -text | head -n1 | sed -ne "s@.*(\\([[:digit:]]\{1,\}\\) bit).*@\\1@p"' + register: bit_size_dhparam + +- name: "[{{ select_crypto_backend }}] Check bit size of default" + assert: + that: + - bit_size_dhparam.stdout == "768" + +- name: "[{{ select_crypto_backend }}] Get bit size of 512" + shell: '{{ openssl_binary }} dhparam -noout -in {{ output_dir }}/dh512.pem -text | head -n1 | sed -ne "s@.*(\\([[:digit:]]\{1,\}\\) bit).*@\\1@p"' + register: bit_size_dhparam_512 + +- name: "[{{ select_crypto_backend }}] Check bit size of default" + assert: + that: + - bit_size_dhparam_512.stdout == "512" + +- name: "[{{ select_crypto_backend }}] Check if changed works correctly" + assert: + that: + - dhparam_changed is not changed + - dhparam_changed_512 is not changed + - dhparam_changed_to_512 is changed + - dhparam_changed_force is changed + +- name: "[{{ select_crypto_backend }}] Make sure correct values are returned" + assert: + that: + - dhparam.dhparams == lookup('file', output_dir ~ '/dh768.pem', rstrip=False) + - dhparam.dhparams == dhparam_changed.dhparams + +- name: "[{{ select_crypto_backend }}] Verify that broken params will be regenerated" + assert: + that: + - output_broken is changed + +- name: "[{{ select_crypto_backend }}] Check backup" + assert: + that: + - dhparam_backup_1 is changed + - dhparam_backup_1.backup_file is undefined + - dhparam_backup_2 is not changed + - dhparam_backup_2.backup_file is undefined + - dhparam_backup_3 is changed + - dhparam_backup_3.backup_file is string + - dhparam_backup_4 is changed + - dhparam_backup_4.backup_file is string + - dhparam_backup_5 is not changed + - dhparam_backup_5.backup_file is undefined + - dhparam_backup_4.dhparams is none diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/impl.yml new file mode 100644 index 00000000..9b36027f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/impl.yml @@ -0,0 +1,287 @@ +- block: + - name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/ansible_pkey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + - name: Generate privatekey2 + openssl_privatekey: + path: '{{ output_dir }}/ansible_pkey2.pem' + size: '{{ default_rsa_key_size_certifiates }}' + - name: Generate privatekey3 + openssl_privatekey: + path: '{{ output_dir }}/ansible_pkey3.pem' + size: '{{ default_rsa_key_size_certifiates }}' + - name: Generate CSR + openssl_csr: + path: '{{ output_dir }}/ansible.csr' + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + commonName: www.ansible.com + - name: Generate CSR 2 + openssl_csr: + path: '{{ output_dir }}/ansible2.csr' + privatekey_path: '{{ output_dir }}/ansible_pkey2.pem' + commonName: www2.ansible.com + - name: Generate CSR 3 + openssl_csr: + path: '{{ output_dir }}/ansible3.csr' + privatekey_path: '{{ output_dir }}/ansible_pkey3.pem' + commonName: www3.ansible.com + - name: Generate certificate + x509_certificate: + path: '{{ output_dir }}/{{ item.name }}.crt' + privatekey_path: '{{ output_dir }}/{{ item.pkey }}' + csr_path: '{{ output_dir }}/{{ item.name }}.csr' + provider: selfsigned + loop: + - name: ansible + pkey: ansible_pkey.pem + - name: ansible2 + pkey: ansible_pkey2.pem + - name: ansible3 + pkey: ansible_pkey3.pem + - name: Generate concatenated PEM file + copy: + dest: '{{ output_dir }}/ansible23.crt' + content: | + {{ lookup("file", output_dir ~ "/ansible2.crt") }} + {{ lookup("file", output_dir ~ "/ansible3.crt") }} + - name: Generate PKCS#12 file + openssl_pkcs12: + path: '{{ output_dir }}/ansible.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + return_content: true + register: p12_standard + - name: Generate PKCS#12 file again, idempotency + openssl_pkcs12: + path: '{{ output_dir }}/ansible.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + return_content: true + register: p12_standard_idempotency + - name: Read ansible.p12 + slurp: + src: '{{ output_dir }}/ansible.p12' + register: ansible_p12_content + - name: Validate PKCS#12 + assert: + that: + - p12_standard.pkcs12 == ansible_p12_content.content + - p12_standard_idempotency.pkcs12 == p12_standard.pkcs12 + - name: Generate PKCS#12 file (force) + openssl_pkcs12: + path: '{{ output_dir }}/ansible.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + force: true + register: p12_force + - name: Generate PKCS#12 file (force + change mode) + openssl_pkcs12: + path: '{{ output_dir }}/ansible.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + force: true + mode: '0644' + register: p12_force_and_mode + - name: Dump PKCS#12 + openssl_pkcs12: + src: '{{ output_dir }}/ansible.p12' + path: '{{ output_dir }}/ansible_parse.pem' + action: parse + state: present + register: p12_dumped + - name: Dump PKCS#12 file again, idempotency + openssl_pkcs12: + src: '{{ output_dir }}/ansible.p12' + path: '{{ output_dir }}/ansible_parse.pem' + action: parse + state: present + register: p12_dumped_idempotency + - name: Dump PKCS#12, check mode + openssl_pkcs12: + src: '{{ output_dir }}/ansible.p12' + path: '{{ output_dir }}/ansible_parse.pem' + action: parse + state: present + check_mode: true + register: p12_dumped_check_mode + - name: Generate PKCS#12 file with multiple certs + openssl_pkcs12: + path: '{{ output_dir }}/ansible_multi_certs.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + other_certificates: + - '{{ output_dir }}/ansible2.crt' + - '{{ output_dir }}/ansible3.crt' + state: present + register: p12_multiple_certs + - name: Generate PKCS#12 file with multiple certs, again (idempotency) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_multi_certs.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + other_certificates: + - '{{ output_dir }}/ansible2.crt' + - '{{ output_dir }}/ansible3.crt' + state: present + register: p12_multiple_certs_idempotency + - name: Dump PKCS#12 with multiple certs + openssl_pkcs12: + src: '{{ output_dir }}/ansible_multi_certs.p12' + path: '{{ output_dir }}/ansible_parse_multi_certs.pem' + action: parse + state: present + - name: Generate privatekey with password + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + size: '{{ default_rsa_key_size }}' + select_crypto_backend: cryptography + - name: Generate PKCS#12 file (password fail 1) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_pw1.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + privatekey_passphrase: hunter2 + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + ignore_errors: true + register: passphrase_error_1 + - name: Generate PKCS#12 file (password fail 2) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_pw2.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: wrong_password + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + ignore_errors: true + register: passphrase_error_2 + - name: Generate PKCS#12 file (password fail 3) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_pw3.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + ignore_errors: true + register: passphrase_error_3 + - name: Generate PKCS#12 file, no privatekey + openssl_pkcs12: + path: '{{ output_dir }}/ansible_no_pkey.p12' + friendly_name: abracadabra + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + register: p12_no_pkey + - name: Create broken PKCS#12 + copy: + dest: '{{ output_dir }}/broken.p12' + content: broken + - name: Regenerate broken PKCS#12 + openssl_pkcs12: + path: '{{ output_dir }}/broken.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + force: true + mode: '0644' + register: output_broken + - name: Generate PKCS#12 file + openssl_pkcs12: + path: '{{ output_dir }}/ansible_backup.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + backup: true + register: p12_backup_1 + - name: Generate PKCS#12 file (idempotent) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_backup.p12' + friendly_name: abracadabra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + backup: true + register: p12_backup_2 + - name: Generate PKCS#12 file (change) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_backup.p12' + friendly_name: abra + privatekey_path: '{{ output_dir }}/ansible_pkey.pem' + certificate_path: '{{ output_dir }}/ansible.crt' + state: present + force: true + backup: true + register: p12_backup_3 + - name: Generate PKCS#12 file (remove) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_backup.p12' + state: absent + backup: true + return_content: true + register: p12_backup_4 + - name: Generate PKCS#12 file (remove, idempotent) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_backup.p12' + state: absent + backup: true + register: p12_backup_5 + - name: Generate 'empty' PKCS#12 file + openssl_pkcs12: + path: '{{ output_dir }}/ansible_empty.p12' + friendly_name: abracadabra + other_certificates: + - '{{ output_dir }}/ansible2.crt' + - '{{ output_dir }}/ansible3.crt' + state: present + register: p12_empty + - name: Generate 'empty' PKCS#12 file (idempotent) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_empty.p12' + friendly_name: abracadabra + other_certificates: + - '{{ output_dir }}/ansible3.crt' + - '{{ output_dir }}/ansible2.crt' + state: present + register: p12_empty_idem + - name: Generate 'empty' PKCS#12 file (idempotent, concatenated other certificates) + openssl_pkcs12: + path: '{{ output_dir }}/ansible_empty.p12' + friendly_name: abracadabra + other_certificates: + - '{{ output_dir }}/ansible23.crt' + other_certificates_parse_all: true + state: present + register: p12_empty_concat_idem + - name: Generate 'empty' PKCS#12 file (parse) + openssl_pkcs12: + src: '{{ output_dir }}/ansible_empty.p12' + path: '{{ output_dir }}/ansible_empty.pem' + action: parse + - import_tasks: ../tests/validate.yml + always: + - name: Delete PKCS#12 file + openssl_pkcs12: + state: absent + path: '{{ output_dir }}/{{ item }}.p12' + loop: + - ansible + - ansible_no_pkey + - ansible_multi_certs + - ansible_pw1 + - ansible_pw2 + - ansible_pw3 + - ansible_empty diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/main.yml new file mode 100644 index 00000000..8d57ff45 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tasks/main.yml @@ -0,0 +1,9 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Run tests + include_tasks: impl.yml + when: pyopenssl_version.stdout is version('17.1.0', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tests/validate.yml new file mode 100644 index 00000000..86e4115d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_pkcs12/tests/validate.yml @@ -0,0 +1,68 @@ +--- +- name: 'Validate PKCS#12' + command: "{{ openssl_binary }} pkcs12 -info -in {{ output_dir }}/ansible.p12 -nodes -passin pass:''" + register: p12 + +- name: 'Validate PKCS#12 with no private key' + command: "{{ openssl_binary }} pkcs12 -info -in {{ output_dir }}/ansible_no_pkey.p12 -nodes -passin pass:''" + register: p12_validate_no_pkey + +- name: 'Validate PKCS#12 with multiple certs' + shell: "{{ openssl_binary }} pkcs12 -info -in {{ output_dir }}/ansible_multi_certs.p12 -nodes -passin pass:'' | grep subject" + register: p12_validate_multi_certs + +- name: 'Validate PKCS#12 (assert)' + assert: + that: + - p12.stdout_lines[2].split(':')[-1].strip() == 'abracadabra' + - p12_standard.mode == '0400' + - p12_no_pkey.changed + - p12_validate_no_pkey.stdout_lines[-1] == '-----END CERTIFICATE-----' + - p12_force.changed + - p12_force_and_mode.mode == '0644' and p12_force_and_mode.changed + - p12_dumped.changed + - not p12_standard_idempotency.changed + - not p12_multiple_certs_idempotency.changed + - not p12_dumped_idempotency.changed + - not p12_dumped_check_mode.changed + - "'www.' in p12_validate_multi_certs.stdout" + - "'www2.' in p12_validate_multi_certs.stdout" + - "'www3.' in p12_validate_multi_certs.stdout" + +- name: Check passphrase on private key + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" + +- name: "Verify that broken PKCS#12 will be regenerated" + assert: + that: + - output_broken is changed + +- name: Check backup + assert: + that: + - p12_backup_1 is changed + - p12_backup_1.backup_file is undefined + - p12_backup_2 is not changed + - p12_backup_2.backup_file is undefined + - p12_backup_3 is changed + - p12_backup_3.backup_file is string + - p12_backup_4 is changed + - p12_backup_4.backup_file is string + - p12_backup_5 is not changed + - p12_backup_5.backup_file is undefined + - p12_backup_4.pkcs12 is none + +- name: Check 'empty' file + assert: + that: + - p12_empty is changed + - p12_empty_idem is not changed + - p12_empty_concat_idem is not changed + - "lookup('file', output_dir ~ '/ansible_empty.pem') == lookup('file', output_dir ~ '/ansible3.crt') ~ '\n' ~ lookup('file', output_dir ~ '/ansible2.crt')" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/impl.yml new file mode 100644 index 00000000..6fb8def8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/impl.yml @@ -0,0 +1,851 @@ +--- +- name: "({{ select_crypto_backend }}) Generate privatekey1 - standard" + openssl_privatekey: + path: '{{ output_dir }}/privatekey1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: privatekey1 + +- name: "({{ select_crypto_backend }}) Generate privatekey1 - standard (idempotence)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: privatekey1_idempotence + +- name: "({{ select_crypto_backend }}) Generate privatekey2 - size 2048" + openssl_privatekey: + path: '{{ output_dir }}/privatekey2.pem' + size: 2048 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate privatekey3 - type DSA" + openssl_privatekey: + path: '{{ output_dir }}/privatekey3.pem' + type: DSA + size: 3072 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate privatekey4 - standard" + openssl_privatekey: + path: '{{ output_dir }}/privatekey4.pem' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Delete privatekey4 - standard" + openssl_privatekey: + state: absent + path: '{{ output_dir }}/privatekey4.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: privatekey4_delete + +- name: "({{ select_crypto_backend }}) Delete privatekey4 - standard (idempotence)" + openssl_privatekey: + state: absent + path: '{{ output_dir }}/privatekey4.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey4_delete_idempotence + +- name: "({{ select_crypto_backend }}) Generate privatekey5 - standard - with passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekey5.pem' + passphrase: ansible + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate privatekey5 - standard - idempotence" + openssl_privatekey: + path: '{{ output_dir }}/privatekey5.pem' + passphrase: ansible + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey5_idempotence + +- name: "({{ select_crypto_backend }}) Generate privatekey6 - standard - with non-ASCII passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekey6.pem' + passphrase: à nsïblé + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + +- set_fact: + ecc_types: [] + when: select_crypto_backend == 'pyopenssl' +- set_fact: + ecc_types: + - curve: secp384r1 + openssl_name: secp384r1 + min_cryptography_version: "0.5" + - curve: secp521r1 + openssl_name: secp521r1 + min_cryptography_version: "0.5" + - curve: secp224r1 + openssl_name: secp224r1 + min_cryptography_version: "0.5" + - curve: secp192r1 + openssl_name: prime192v1 + min_cryptography_version: "0.5" + - curve: secp256r1 + openssl_name: secp256r1 + min_cryptography_version: "0.5" + - curve: secp256k1 + openssl_name: secp256k1 + min_cryptography_version: "0.9" + - curve: brainpoolP256r1 + openssl_name: brainpoolP256r1 + min_cryptography_version: "2.2" + - curve: brainpoolP384r1 + openssl_name: brainpoolP384r1 + min_cryptography_version: "2.2" + - curve: brainpoolP512r1 + openssl_name: brainpoolP512r1 + min_cryptography_version: "2.2" + - curve: sect571k1 + openssl_name: sect571k1 + min_cryptography_version: "0.5" + - curve: sect409k1 + openssl_name: sect409k1 + min_cryptography_version: "0.5" + - curve: sect283k1 + openssl_name: sect283k1 + min_cryptography_version: "0.5" + - curve: sect233k1 + openssl_name: sect233k1 + min_cryptography_version: "0.5" + - curve: sect163k1 + openssl_name: sect163k1 + min_cryptography_version: "0.5" + - curve: sect571r1 + openssl_name: sect571r1 + min_cryptography_version: "0.5" + - curve: sect409r1 + openssl_name: sect409r1 + min_cryptography_version: "0.5" + - curve: sect283r1 + openssl_name: sect283r1 + min_cryptography_version: "0.5" + - curve: sect233r1 + openssl_name: sect233r1 + min_cryptography_version: "0.5" + - curve: sect163r2 + openssl_name: sect163r2 + min_cryptography_version: "0.5" + when: select_crypto_backend == 'cryptography' + +- name: "({{ select_crypto_backend }}) Test ECC key generation" + openssl_privatekey: + path: '{{ output_dir }}/privatekey-{{ item.curve }}.pem' + type: ECC + curve: "{{ item.curve }}" + select_crypto_backend: '{{ select_crypto_backend }}' + when: | + cryptography_version.stdout is version(item.min_cryptography_version, '>=') and + item.openssl_name in openssl_ecc_list + loop: "{{ ecc_types }}" + loop_control: + label: "{{ item.curve }}" + register: privatekey_ecc_generate + +- name: "({{ select_crypto_backend }}) Test ECC key generation (idempotency)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey-{{ item.curve }}.pem' + type: ECC + curve: "{{ item.curve }}" + select_crypto_backend: '{{ select_crypto_backend }}' + when: | + cryptography_version.stdout is version(item.min_cryptography_version, '>=') and + item.openssl_name in openssl_ecc_list + loop: "{{ ecc_types }}" + loop_control: + label: "{{ item.curve }}" + register: privatekey_ecc_idempotency + +- block: + - name: "({{ select_crypto_backend }}) Test other type generation" + openssl_privatekey: + path: '{{ output_dir }}/privatekey-{{ item.type }}.pem' + type: "{{ item.type }}" + select_crypto_backend: '{{ select_crypto_backend }}' + when: cryptography_version.stdout is version(item.min_version, '>=') + loop: "{{ types }}" + loop_control: + label: "{{ item.type }}" + ignore_errors: yes + register: privatekey_t1_generate + + - name: "({{ select_crypto_backend }}) Test other type generation (idempotency)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey-{{ item.type }}.pem' + type: "{{ item.type }}" + select_crypto_backend: '{{ select_crypto_backend }}' + when: cryptography_version.stdout is version(item.min_version, '>=') + loop: "{{ types }}" + loop_control: + label: "{{ item.type }}" + ignore_errors: yes + register: privatekey_t1_idempotency + + when: select_crypto_backend == 'cryptography' + vars: + types: + - type: X25519 + min_version: '2.5' + - type: Ed25519 + min_version: '2.6' + - type: Ed448 + min_version: '2.6' + - type: X448 + min_version: '2.6' + +- name: "({{ select_crypto_backend }}) Generate privatekey with passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + register: passphrase_1 + +- name: "({{ select_crypto_backend }}) Generate privatekey with passphrase (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + register: passphrase_2 + +- name: "({{ select_crypto_backend }}) Regenerate privatekey without passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + register: passphrase_3 + +- name: "({{ select_crypto_backend }}) Regenerate privatekey without passphrase (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + register: passphrase_4 + +- name: "({{ select_crypto_backend }}) Regenerate privatekey with passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + register: passphrase_5 + +- name: "({{ select_crypto_backend }}) Create broken key" + copy: + dest: "{{ output_dir }}/broken" + content: "broken" +- name: "({{ select_crypto_backend }}) Regenerate broken key" + openssl_privatekey: + path: '{{ output_dir }}/broken.pem' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: output_broken + +- name: "({{ select_crypto_backend }}) Remove module" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + state: absent + register: remove_1 + +- name: "({{ select_crypto_backend }}) Remove module (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + backup: yes + state: absent + register: remove_2 + +- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_mode.pem' + mode: '0400' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_mode_1 +- name: "({{ select_crypto_backend }}) Stat for privatekey_mode" + stat: + path: '{{ output_dir }}/privatekey_mode.pem' + register: privatekey_mode_1_stat + +- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, idempotency)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_mode.pem' + mode: '0400' + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_mode_2 + +- name: Make sure that mtime actually changes. + # The "privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime" test should be + # changed to compare content instead of mtime. On macOS 10.15, mtime resolution is one second, + # and the machine (VM) is fast enough that both modifications can happen in the same second. + pause: + seconds: 1 + +- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, force)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_mode.pem' + mode: '0400' + force: yes + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_mode_3 +- name: "({{ select_crypto_backend }}) Stat for privatekey_mode" + stat: + path: '{{ output_dir }}/privatekey_mode.pem' + register: privatekey_mode_3_stat + +- block: + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: auto + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_1 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: auto + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_2 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS1 format" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: pkcs1 + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_3 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS8 format" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: pkcs8 + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_4 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS8 format (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: pkcs8 + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_5 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format (ignore)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: auto_ignore + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_6 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format (no ignore)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: auto + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_7 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - raw format (fail)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: raw + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: privatekey_fmt_1_step_8 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS8 format (convert)" + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_9_before + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS8 format (convert)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + format: pkcs8 + format_mismatch: convert + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_9 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - PKCS8 format (convert)" + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_fmt_1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey_fmt_1_step_9_after + + when: 'select_crypto_backend == "cryptography"' + +- block: + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - PKCS8 format" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: pkcs8 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: privatekey_fmt_2_step_1 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - PKCS8 format (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: pkcs8 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: privatekey_fmt_2_step_2 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - raw format" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: raw + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + ignore_errors: yes + register: privatekey_fmt_2_step_3 + + - name: "({{ select_crypto_backend }}) Read privatekey_fmt_2.pem" + slurp: + src: "{{ output_dir }}/privatekey_fmt_2.pem" + ignore_errors: yes + register: content + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - verify that returned content is base64 encoded" + assert: + that: + - privatekey_fmt_2_step_3.privatekey == content.content + when: privatekey_fmt_2_step_1 is not failed + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - raw format (idempotent)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: raw + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + ignore_errors: yes + register: privatekey_fmt_2_step_4 + + - name: "({{ select_crypto_backend }}) Read privatekey_fmt_2.pem" + slurp: + src: "{{ output_dir }}/privatekey_fmt_2.pem" + ignore_errors: yes + register: content + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - verify that returned content is base64 encoded" + assert: + that: + - privatekey_fmt_2_step_4.privatekey == content.content + when: privatekey_fmt_2_step_1 is not failed + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - auto format (ignore)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: auto_ignore + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + ignore_errors: yes + register: privatekey_fmt_2_step_5 + + - name: "({{ select_crypto_backend }}) Read privatekey_fmt_2.pem" + slurp: + src: "{{ output_dir }}/privatekey_fmt_2.pem" + ignore_errors: yes + register: content + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - verify that returned content is base64 encoded" + assert: + that: + - privatekey_fmt_2_step_5.privatekey == content.content + when: privatekey_fmt_2_step_1 is not failed + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - auto format (no ignore)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey_fmt_2.pem' + type: X448 + format: auto + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + ignore_errors: yes + register: privatekey_fmt_2_step_6 + + - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_2 - verify that returned content is not base64 encoded" + assert: + that: + - privatekey_fmt_2_step_6.privatekey == lookup('file', output_dir ~ '/privatekey_fmt_2.pem', rstrip=False) + when: privatekey_fmt_2_step_1 is not failed + + when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=")' + + + +# Test regenerate option + +- name: "({{ select_crypto_backend }}) Regenerate - setup simple keys" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" +- name: "({{ select_crypto_backend }}) Regenerate - setup password protected keys" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-b-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + passphrase: hunter2 + cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}" + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" +- name: "({{ select_crypto_backend }}) Regenerate - setup broken keys" + copy: + dest: '{{ output_dir }}/regenerate-c-{{ item }}.pem' + content: 'broken key' + mode: '0700' + loop: "{{ regenerate_values }}" + +- name: "({{ select_crypto_backend }}) Regenerate - modify broken keys (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-c-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg or 'Cannot load raw key' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg or 'Cannot load raw key' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg or 'Cannot load raw key' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - modify broken keys" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-c-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg or 'Cannot load raw key' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg or 'Cannot load raw key' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg or 'Cannot load raw key' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - modify password protected keys (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-b-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - modify password protected keys" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-b-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg" + - result.results[1] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg" + - result.results[2] is failed + - "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg" + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - not modify regular keys (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + register: result +- assert: + that: + - result.results[0] is not changed + - result.results[1] is not changed + - result.results[2] is not changed + - result.results[3] is not changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - not modify regular keys" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + register: result +- assert: + that: + - result.results[0] is not changed + - result.results[1] is not changed + - result.results[2] is not changed + - result.results[3] is not changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - adjust key size (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size + 20 }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - adjust key size" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: RSA + size: '{{ default_rsa_key_size + 20 }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - redistribute keys" + copy: + src: '{{ output_dir }}/regenerate-a-always.pem' + dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + remote_src: true + loop: "{{ regenerate_values }}" + when: "item != 'always'" + +- name: "({{ select_crypto_backend }}) Regenerate - adjust key type (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- name: "({{ select_crypto_backend }}) Regenerate - adjust key type" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result +- assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + +- block: + - name: "({{ select_crypto_backend }}) Regenerate - redistribute keys" + copy: + src: '{{ output_dir }}/regenerate-a-always.pem' + dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + remote_src: true + loop: "{{ regenerate_values }}" + when: "item != 'always'" + + - name: "({{ select_crypto_backend }}) Regenerate - format mismatch (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + format: pkcs8 + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result + - assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong format. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + + - name: "({{ select_crypto_backend }}) Regenerate - format mismatch" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + format: pkcs8 + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + ignore_errors: yes + register: result + - assert: + that: + - result.results[0] is success and result.results[0] is not changed + - result.results[1] is failed + - "'Key has wrong format. Will not proceed.' in result.results[1].msg" + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + + - name: "({{ select_crypto_backend }}) Regenerate - redistribute keys" + copy: + src: '{{ output_dir }}/regenerate-a-always.pem' + dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + remote_src: true + loop: "{{ regenerate_values }}" + when: "item != 'always'" + + - name: "({{ select_crypto_backend }}) Regenerate - convert format (check mode)" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + format: pkcs1 + format_mismatch: convert + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + loop: "{{ regenerate_values }}" + register: result + - assert: + that: + - result.results[0] is changed + - result.results[1] is changed + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + + - name: "({{ select_crypto_backend }}) Regenerate - convert format" + openssl_privatekey: + path: '{{ output_dir }}/regenerate-a-{{ item }}.pem' + type: DSA + size: '{{ default_rsa_key_size }}' + format: pkcs1 + format_mismatch: convert + regenerate: '{{ item }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: "{{ regenerate_values }}" + register: result + - assert: + that: + - result.results[0] is changed + - result.results[1] is changed + - result.results[2] is changed + - result.results[3] is changed + - result.results[4] is changed + # for all values but 'always', the key should have not been regenerated. + # verify this by comparing fingerprints: + - result.results[0].fingerprint == result.results[1].fingerprint + - result.results[0].fingerprint == result.results[2].fingerprint + - result.results[0].fingerprint == result.results[3].fingerprint + - result.results[0].fingerprint != result.results[4].fingerprint + when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=")' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/main.yml new file mode 100644 index 00000000..057a7b9a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tasks/main.yml @@ -0,0 +1,114 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Find out which elliptic curves are supported by installed OpenSSL + command: "{{ openssl_binary }} ecparam -list_curves" + register: openssl_ecc + +- name: Compile list of elliptic curves supported by OpenSSL + set_fact: + openssl_ecc_list: | + {{ + openssl_ecc.stdout_lines + | map('regex_search', '^ *([a-zA-Z0-9_-]+) *: .*$') + | select() + | map('regex_replace', '^ *([a-zA-Z0-9_-]+) *: .*$', '\1') + | list + }} + when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' + # CentOS comes with a very old jinja2 which does not include the map() filter... +- name: Compile list of elliptic curves supported by OpenSSL (CentOS 6) + set_fact: + openssl_ecc_list: + - secp384r1 + - secp521r1 + - prime256v1 + when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6' + +- name: List of elliptic curves supported by OpenSSL + debug: var=openssl_ecc_list + +- name: Run module with backend autodetection + openssl_privatekey: + path: '{{ output_dir }}/privatekey_backend_selection.pem' + size: '{{ default_rsa_key_size }}' + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: pyopenssl + + # FIXME: minimal pyOpenSSL version?! + when: pyopenssl_version.stdout is version('0.6', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('0.5', '>=') + +- name: Check that fingerprints do not depend on the backend + block: + - name: "Fingerprint comparison: pyOpenSSL" + openssl_privatekey: + path: '{{ output_dir }}/fingerprint-{{ item }}.pem' + type: "{{ item }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: pyopenssl + loop: + - RSA + - DSA + register: fingerprint_pyopenssl + + - name: "Fingerprint comparison: cryptography" + openssl_privatekey: + path: '{{ output_dir }}/fingerprint-{{ item }}.pem' + type: "{{ item }}" + size: '{{ default_rsa_key_size }}' + select_crypto_backend: cryptography + loop: + - RSA + - DSA + register: fingerprint_cryptography + + - name: Verify that keys were not regenerated + assert: + that: + - fingerprint_cryptography is not changed + + - name: Verify that fingerprints match + assert: + that: item.0.fingerprint[item.2] == item.1.fingerprint[item.2] + when: item.0 is not skipped and item.1 is not skipped + loop: | + {{ query('nested', + fingerprint_pyopenssl.results | zip(fingerprint_cryptography.results), + fingerprint_pyopenssl.results[0].fingerprint.keys() + ) if fingerprint_pyopenssl.results[0].fingerprint else [] }} + loop_control: + label: "{{ [item.0.item, item.2] }}" + when: pyopenssl_version.stdout is version('0.6', '>=') and cryptography_version.stdout is version('0.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tests/validate.yml new file mode 100644 index 00000000..5672d927 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/tests/validate.yml @@ -0,0 +1,215 @@ +--- +- set_fact: + system_potentially_has_no_algorithm_support: "{{ ansible_os_family == 'FreeBSD' }}" + +- name: "({{ select_crypto_backend }}) Validate privatekey1 idempotency and content returned" + assert: + that: + - privatekey1_idempotence is not changed + - privatekey1.privatekey == lookup('file', output_dir ~ '/privatekey1.pem', rstrip=False) + - privatekey1.privatekey == privatekey1_idempotence.privatekey + + +- name: "({{ select_crypto_backend }}) Validate privatekey1 (test - RSA key with size 4096 bits)" + shell: "{{ openssl_binary }} rsa -noout -text -in {{ output_dir }}/privatekey1.pem | grep Private | sed 's/\\(RSA *\\)*Private-Key: (\\(.*\\) bit.*)/\\2/'" + register: privatekey1 + +- name: "({{ select_crypto_backend }}) Validate privatekey1 (assert - RSA key with size 4096 bits)" + assert: + that: + - privatekey1.stdout == '4096' + + +- name: "({{ select_crypto_backend }}) Validate privatekey2 (test - RSA key with size 2048 bits)" + shell: "{{ openssl_binary }} rsa -noout -text -in {{ output_dir }}/privatekey2.pem | grep Private | sed 's/\\(RSA *\\)*Private-Key: (\\(.*\\) bit.*)/\\2/'" + register: privatekey2 + +- name: "({{ select_crypto_backend }}) Validate privatekey2 (assert - RSA key with size 2048 bits)" + assert: + that: + - privatekey2.stdout == '2048' + + +- name: "({{ select_crypto_backend }}) Validate privatekey3 (test - DSA key with size 3072 bits)" + shell: "{{ openssl_binary }} dsa -noout -text -in {{ output_dir }}/privatekey3.pem | grep Private | sed 's/\\(RSA *\\)*Private-Key: (\\(.*\\) bit.*)/\\2/'" + register: privatekey3 + +- name: Validate privatekey3 (assert - DSA key with size 3072 bits) + assert: + that: + - privatekey3.stdout == '3072' + + +- name: "({{ select_crypto_backend }}) Validate privatekey4 (test - Ensure key has been removed)" + stat: + path: '{{ output_dir }}/privatekey4.pem' + register: privatekey4 + +- name: "({{ select_crypto_backend }}) Validate privatekey4 (assert - Ensure key has been removed)" + assert: + that: + - privatekey4.stat.exists == False + +- name: "({{ select_crypto_backend }}) Validate privatekey4 removal behavior" + assert: + that: + - privatekey4_delete is changed + - privatekey4_delete.privatekey is none + - privatekey4_delete_idempotence is not changed + + +- name: "({{ select_crypto_backend }}) Validate privatekey5 (test - Passphrase protected key + idempotence)" + shell: "{{ openssl_binary }} rsa -noout -text -in {{ output_dir }}/privatekey5.pem -passin pass:ansible | grep Private | sed 's/\\(RSA *\\)*Private-Key: (\\(.*\\) bit.*)/\\2/'" + register: privatekey5 + # Current version of OS/X that runs in the CI (10.11) does not have an up to date version of the OpenSSL library + # leading to this test to fail when run in the CI. However, this test has been run for 10.12 and has returned succesfully. + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate privatekey5 (assert - Passphrase protected key + idempotence)" + assert: + that: + - privatekey5.stdout == '{{ default_rsa_key_size }}' + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate privatekey5 idempotence (assert - Passphrase protected key + idempotence)" + assert: + that: + - privatekey5_idempotence is not changed + + +- name: "({{ select_crypto_backend }}) Validate privatekey6 (test - Passphrase protected key with non ascii character)" + shell: "{{ openssl_binary }} rsa -noout -text -in {{ output_dir }}/privatekey6.pem -passin pass:à nsïblé | grep Private | sed 's/\\(RSA *\\)*Private-Key: (\\(.*\\) bit.*)/\\2/'" + register: privatekey6 + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate privatekey6 (assert - Passphrase protected key with non ascii character)" + assert: + that: + - privatekey6.stdout == '{{ default_rsa_key_size }}' + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate ECC generation (dump with OpenSSL)" + shell: "{{ openssl_binary }} ec -in {{ output_dir }}/privatekey-{{ item.item.curve }}.pem -noout -text | grep 'ASN1 OID: ' | sed 's/ASN1 OID: \\([^ ]*\\)/\\1/'" + loop: "{{ privatekey_ecc_generate.results }}" + register: privatekey_ecc_dump + when: openssl_version.stdout is version('0.9.8zh', '>=') and 'skip_reason' not in item + loop_control: + label: "{{ item.item.curve }}" + +- name: "({{ select_crypto_backend }}) Validate ECC generation" + assert: + that: + - item is changed + loop: "{{ privatekey_ecc_generate.results }}" + when: "'skip_reason' not in item" + loop_control: + label: "{{ item.item.curve }}" + +- name: "({{ select_crypto_backend }}) Validate ECC generation (curve type)" + assert: + that: + - "'skip_reason' in item or item.item.item.openssl_name == item.stdout" + loop: "{{ privatekey_ecc_dump.results }}" + when: "'skip_reason' not in item" + loop_control: + label: "{{ item.item.item }} - {{ item.stdout if 'stdout' in item else '<unsupported>' }}" + +- name: "({{ select_crypto_backend }}) Validate ECC generation idempotency" + assert: + that: + - item is not changed + loop: "{{ privatekey_ecc_idempotency.results }}" + when: "'skip_reason' not in item" + loop_control: + label: "{{ item.item.curve }}" + +- name: "({{ select_crypto_backend }}) Validate other type generation (just check changed)" + assert: + that: + - (item is succeeded and item is changed) or + (item is failed and 'Cryptography backend does not support the algorithm required for ' in item.msg and system_potentially_has_no_algorithm_support) + loop: "{{ privatekey_t1_generate.results }}" + when: "'skip_reason' not in item" + loop_control: + label: "{{ item.item.type }}" + +- name: "({{ select_crypto_backend }}) Validate other type generation idempotency" + assert: + that: + - (item is succeeded and item is not changed) or + (item is failed and 'Cryptography backend does not support the algorithm required for ' in item.msg and system_potentially_has_no_algorithm_support) + loop: "{{ privatekey_t1_idempotency.results }}" + when: "'skip_reason' not in item" + loop_control: + label: "{{ item.item.type }}" + +- name: "({{ select_crypto_backend }}) Validate passphrase changing" + assert: + that: + - passphrase_1 is changed + - passphrase_2 is not changed + - passphrase_3 is changed + - passphrase_4 is not changed + - passphrase_5 is changed + - passphrase_1.backup_file is undefined + - passphrase_2.backup_file is undefined + - passphrase_3.backup_file is string + - passphrase_4.backup_file is undefined + - passphrase_5.backup_file is string + +- name: "({{ select_crypto_backend }}) Verify that broken key will be regenerated" + assert: + that: + - output_broken is changed + +- name: "({{ select_crypto_backend }}) Validate remove" + assert: + that: + - remove_1 is changed + - remove_2 is not changed + - remove_1.backup_file is string + - remove_2.backup_file is undefined + +- name: "({{ select_crypto_backend }}) Validate mode" + assert: + that: + - privatekey_mode_1 is changed + - privatekey_mode_1_stat.stat.mode == '0400' + - privatekey_mode_2 is not changed + - privatekey_mode_3 is changed + - privatekey_mode_3_stat.stat.mode == '0400' + - privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime + +- name: "({{ select_crypto_backend }}) Validate format 1" + assert: + that: + - privatekey_fmt_1_step_1 is changed + - privatekey_fmt_1_step_2 is not changed + - privatekey_fmt_1_step_3 is not changed + - privatekey_fmt_1_step_4 is changed + - privatekey_fmt_1_step_5 is not changed + - privatekey_fmt_1_step_6 is not changed + - privatekey_fmt_1_step_7 is changed + - privatekey_fmt_1_step_8 is failed + - privatekey_fmt_1_step_9 is changed + - privatekey_fmt_1_step_9_before.public_key == privatekey_fmt_1_step_9_after.public_key + when: 'select_crypto_backend == "cryptography"' + +- name: "({{ select_crypto_backend }}) Validate format 2 (failed)" + assert: + that: + - system_potentially_has_no_algorithm_support + - privatekey_fmt_2_step_1 is failed + - "'Cryptography backend does not support the algorithm required for ' in privatekey_fmt_2_step_1.msg" + when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=") and privatekey_fmt_2_step_1 is failed' + +- name: "({{ select_crypto_backend }}) Validate format 2" + assert: + that: + - privatekey_fmt_2_step_1 is succeeded and privatekey_fmt_2_step_1 is changed + - privatekey_fmt_2_step_2 is succeeded and privatekey_fmt_2_step_2 is not changed + - privatekey_fmt_2_step_3 is succeeded and privatekey_fmt_2_step_3 is changed + - privatekey_fmt_2_step_4 is succeeded and privatekey_fmt_2_step_4 is not changed + - privatekey_fmt_2_step_5 is succeeded and privatekey_fmt_2_step_5 is not changed + - privatekey_fmt_2_step_6 is succeeded and privatekey_fmt_2_step_6 is changed + when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=") and privatekey_fmt_2_step_1 is not failed' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/vars/main.yml new file mode 100644 index 00000000..81eb611f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey/vars/main.yml @@ -0,0 +1,7 @@ +--- +regenerate_values: + - never + - fail + - partial_idempotence + - full_idempotence + - always diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/impl.yml new file mode 100644 index 00000000..2ecbb48a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/impl.yml @@ -0,0 +1,178 @@ +--- +- debug: + msg: "Executing tests with backend {{ select_crypto_backend }}" + +- name: ({{select_crypto_backend}}) Get key 1 info + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check that RSA key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type == 'RSA'" + - "'public_data' in result" + - "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" + - "result.public_data.exponent > 5" + - "'private_data' not in result" + +- name: Update result list + set_fact: + info_results: "{{ info_results | combine({'key1': result}) }}" + +- name: ({{select_crypto_backend}}) Get key 1 info directly + openssl_privatekey_info: + content: '{{ lookup("file", output_dir ~ "/privatekey_1.pem") }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result_direct + +- name: ({{select_crypto_backend}}) Compare output of direct and loaded info + assert: + that: + - result == result_direct + +- name: ({{select_crypto_backend}}) Get key 2 info + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_2.pem' + return_private_key_data: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check that RSA key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type == 'RSA'" + - "'public_data' in result" + - "result.public_data.size == default_rsa_key_size" + - "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" + - "result.public_data.exponent > 5" + - "'private_data' in result" + - "result.public_data.modulus == result.private_data.p * result.private_data.q" + - "result.private_data.exponent > 5" + +- name: Update result list + set_fact: + info_results: "{{ info_results | combine({'key2': result}) }}" + +- name: ({{select_crypto_backend}}) Get key 3 info (without passphrase) + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_3.pem' + return_private_key_data: yes + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: result + +- name: Check that loading passphrase protected key without passphrase failed + assert: + that: + - result is failed + # Check that return values are there + - result.can_load_key is defined + - result.can_parse_key is defined + # Check that return values are correct + - result.can_load_key + - not result.can_parse_key + # Check that additional data isn't there + - "'pulic_key' not in result" + - "'pulic_key_fingerprints' not in result" + - "'type' not in result" + - "'public_data' not in result" + - "'private_data' not in result" + +- name: ({{select_crypto_backend}}) Get key 3 info (with passphrase) + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_3.pem' + passphrase: hunter2 + return_private_key_data: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check that RSA key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type == 'RSA'" + - "'public_data' in result" + - "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" + - "result.public_data.exponent > 5" + - "'private_data' in result" + - "result.public_data.modulus == result.private_data.p * result.private_data.q" + - "result.private_data.exponent > 5" + +- name: Update result list + set_fact: + info_results: "{{ info_results | combine({'key3': result}) }}" + +- name: ({{select_crypto_backend}}) Get key 4 info + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_4.pem' + return_private_key_data: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- block: + - name: Check that ECC key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type == 'ECC'" + - "'public_data' in result" + - "result.public_data.curve is string" + - "result.public_data.x != 0" + - "result.public_data.y != 0" + - "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)" + - "'private_data' in result" + - "result.private_data.multiplier > 1024" + + - name: Update result list + set_fact: + info_results: "{{ info_results | combine({'key4': result}) }}" + when: select_crypto_backend != 'pyopenssl' or (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>')) + +- name: Check that ECC key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type.startswith('unknown ')" + - "'public_data' in result" + - "'private_data' in result" + when: select_crypto_backend == 'pyopenssl' and not (pyopenssl_version.stdout is version('16.1.0', '>=') and cryptography_version.stdout is version('0.0', '>')) + +- name: ({{select_crypto_backend}}) Get key 5 info + openssl_privatekey_info: + path: '{{ output_dir }}/privatekey_5.pem' + return_private_key_data: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check that DSA key info is ok + assert: + that: + - "'public_key' in result" + - "'public_key_fingerprints' in result" + - "'type' in result" + - "result.type == 'DSA'" + - "'public_data' in result" + - "result.public_data.p > 2" + - "result.public_data.q > 2" + - "result.public_data.g >= 2" + - "result.public_data.y > 2" + - "'private_data' in result" + - "result.private_data.x > 2" + +- name: Update result list + set_fact: + info_results: "{{ info_results | combine({'key5': result}) }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/main.yml new file mode 100644 index 00000000..c477d194 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_info/tasks/main.yml @@ -0,0 +1,77 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Generate privatekey 1 + openssl_privatekey: + path: '{{ output_dir }}/privatekey_1.pem' + +- name: Generate privatekey 2 (less bits) + openssl_privatekey: + path: '{{ output_dir }}/privatekey_2.pem' + type: RSA + size: '{{ default_rsa_key_size }}' + +- name: Generate privatekey 3 (with password) + openssl_privatekey: + path: '{{ output_dir }}/privatekey_3.pem' + passphrase: hunter2 + cipher: auto + size: '{{ default_rsa_key_size }}' + select_crypto_backend: cryptography + +- name: Generate privatekey 4 (ECC) + openssl_privatekey: + path: '{{ output_dir }}/privatekey_4.pem' + type: ECC + curve: "{{ (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') | ternary('secp521r1', 'secp256k1') }}" + # ^ cryptography on CentOS6 doesn't support secp256k1, so we use secp521r1 instead + select_crypto_backend: cryptography + +- name: Generate privatekey 5 (DSA) + openssl_privatekey: + path: '{{ output_dir }}/privatekey_5.pem' + type: DSA + size: 1024 + +- name: Prepare result list + set_fact: + info_results: {} + +- name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Prepare result list + set_fact: + pyopenssl_info_results: "{{ info_results }}" + info_results: {} + +- name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('1.2.3', '>=') + +- name: Prepare result list + set_fact: + cryptography_info_results: "{{ info_results }}" + +- block: + - name: Dump pyOpenSSL results + debug: + var: pyopenssl_info_results + - name: Dump cryptography results + debug: + var: cryptography_info_results + - name: Compare results + assert: + that: + - ' (pyopenssl_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict) + == (cryptography_info_results[item] | dict2items | rejectattr("key", "equalto", "deprecations") | list | items2dict)' + loop: "{{ pyopenssl_info_results.keys() | intersect(cryptography_info_results.keys()) | list }}" + when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.2.3', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/impl.yml new file mode 100644 index 00000000..bee4fce6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/impl.yml @@ -0,0 +1,103 @@ +--- +- name: ({{select_crypto_backend}}) Create key + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: ({{select_crypto_backend}}) Get key info + openssl_privatekey_info: + content: "{{ result.privatekey }}" + register: result_info + +- assert: + that: + - result is changed + - result.privatekey.startswith('----') + - result_info.type == 'RSA' + - result_info.public_data.size == 4096 + - result_info.public_data.exponent >= 5 + +- assert: + that: + - result_info.public_key_fingerprints.sha256 | length > 10 + - result.fingerprint.sha256 == result_info.public_key_fingerprints.sha256 + when: result.fingerprint is not none + +- name: ({{select_crypto_backend}}) Update key (check mode) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ result.privatekey }}" + size: '{{ default_rsa_key_size }}' + register: update_check + check_mode: true + +- name: ({{select_crypto_backend}}) Update key (check mode, with return_current_key=true) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ result.privatekey }}" + size: '{{ default_rsa_key_size }}' + return_current_key: true + register: update_check_return + check_mode: true + +- name: ({{select_crypto_backend}}) Update key + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ result.privatekey }}" + size: '{{ default_rsa_key_size }}' + register: update + +- name: ({{select_crypto_backend}}) Update key (idempotent, check mode) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ update.privatekey }}" + size: '{{ default_rsa_key_size }}' + register: update_idempotent_check + check_mode: true + +- name: ({{select_crypto_backend}}) Update key (idempotent) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ update.privatekey }}" + size: '{{ default_rsa_key_size }}' + register: update_idempotent + +- name: ({{select_crypto_backend}}) Update key (idempotent, check mode, with return_current_key=true) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ update.privatekey }}" + size: '{{ default_rsa_key_size }}' + return_current_key: true + register: update_idempotent_return_check + check_mode: true + +- name: ({{select_crypto_backend}}) Update key (idempotent, with return_current_key=true) + openssl_privatekey_pipe: + select_crypto_backend: '{{ select_crypto_backend }}' + content: "{{ update.privatekey }}" + size: '{{ default_rsa_key_size }}' + return_current_key: true + register: update_idempotent_return + +- name: ({{select_crypto_backend}}) Get key info + openssl_privatekey_info: + content: "{{ update.privatekey }}" + register: update_info + +- assert: + that: + - update_check is changed + - update_check.privatekey == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER' + - update_check_return is changed + - update_check_return.privatekey == result.privatekey + - update is changed + - update.privatekey != result.privatekey + - update_info.public_data.size == default_rsa_key_size + - update_idempotent_check is not changed + - update_idempotent_check.privatekey is undefined + - update_idempotent is not changed + - update_idempotent.privatekey is undefined + - update_idempotent_return_check is not changed + - update_idempotent_return_check.privatekey == update.privatekey + - update_idempotent_return is not changed + - update_idempotent_return.privatekey == update.privatekey diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/main.yml new file mode 100644 index 00000000..c8205aeb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_privatekey_pipe/tasks/main.yml @@ -0,0 +1,36 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Run module with backend autodetection + openssl_privatekey_pipe: + size: '{{ default_rsa_key_size }}' + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + # FIXME: minimal pyOpenSSL version?! + when: pyopenssl_version.stdout is version('0.6', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('0.5', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/impl.yml new file mode 100644 index 00000000..cfe930fd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/impl.yml @@ -0,0 +1,190 @@ +--- +- name: "({{ select_crypto_backend }}) Generate privatekey" + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format" + openssl_publickey: + path: '{{ output_dir }}/publickey.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: publickey + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (idempotence)" + openssl_publickey: + path: '{{ output_dir }}/publickey.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: publickey_idempotence + +- name: "({{ select_crypto_backend }}) Generate publickey - OpenSSH format" + openssl_publickey: + path: '{{ output_dir }}/publickey-ssh.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + format: OpenSSH + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + +- name: "({{ select_crypto_backend }}) Generate publickey - OpenSSH format - test idempotence (issue 33256)" + openssl_publickey: + path: '{{ output_dir }}/publickey-ssh.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + format: OpenSSH + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + register: publickey_ssh_idempotence + +- name: "({{ select_crypto_backend }}) Generate publickey2 - standard" + openssl_publickey: + path: '{{ output_dir }}/publickey2.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Delete publickey2 - standard" + openssl_publickey: + state: absent + path: '{{ output_dir }}/publickey2.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: publickey2_absent + +- name: "({{ select_crypto_backend }}) Delete publickey2 - standard (idempotence)" + openssl_publickey: + state: absent + path: '{{ output_dir }}/publickey2.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: publickey2_absent_idempotence + +- name: "({{ select_crypto_backend }}) Generate privatekey3 - with passphrase" + openssl_privatekey: + path: '{{ output_dir }}/privatekey3.pem' + passphrase: ansible + cipher: aes256 + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate publickey3 - with passphrase protected privatekey" + openssl_publickey: + path: '{{ output_dir }}/publickey3.pub' + privatekey_path: '{{ output_dir }}/privatekey3.pem' + privatekey_passphrase: ansible + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate publickey3 - with passphrase protected privatekey - idempotence" + openssl_publickey: + path: '{{ output_dir }}/publickey3.pub' + privatekey_path: '{{ output_dir }}/privatekey3.pem' + privatekey_passphrase: ansible + select_crypto_backend: '{{ select_crypto_backend }}' + register: publickey3_idempotence + +- name: "({{ select_crypto_backend }}) Generate empty file that will hold a public key (issue 33072)" + file: + path: '{{ output_dir }}/publickey4.pub' + state: touch + +- name: "({{ select_crypto_backend }}) Generate publickey in empty existing file (issue 33072)" + openssl_publickey: + path: '{{ output_dir }}/publickey4.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "({{ select_crypto_backend }}) Generate privatekey 5 (ECC)" + openssl_privatekey: + path: '{{ output_dir }}/privatekey5.pem' + type: ECC + curve: secp256r1 + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate publickey 5 - PEM format" + openssl_publickey: + path: '{{ output_dir }}/publickey5.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey5_1 +- name: "({{ select_crypto_backend }}) Generate publickey 5 - PEM format (idempotent)" + openssl_publickey: + path: '{{ output_dir }}/publickey5.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey5_2 +- name: "({{ select_crypto_backend }}) Generate publickey 5 - PEM format (different private key)" + openssl_publickey: + path: '{{ output_dir }}/publickey5.pub' + privatekey_path: '{{ output_dir }}/privatekey5.pem' + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: privatekey5_3 + +- name: "({{ select_crypto_backend }}) Generate privatekey with password" + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size }}' + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (failed passphrase 1)" + openssl_publickey: + path: '{{ output_dir }}/publickey_pw1.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + privatekey_passphrase: hunter2 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_1 + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (failed passphrase 2)" + openssl_publickey: + path: '{{ output_dir }}/publickey_pw2.pub' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: wrong_password + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_2 + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (failed passphrase 3)" + openssl_publickey: + path: '{{ output_dir }}/publickey_pw3.pub' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_3 + +- name: "({{ select_crypto_backend }}) Create broken key" + copy: + dest: "{{ output_dir }}/publickeybroken.pub" + content: "broken" +- name: "({{ select_crypto_backend }}) Regenerate broken key" + openssl_publickey: + path: '{{ output_dir }}/publickeybroken.pub' + privatekey_path: '{{ output_dir }}/privatekey5.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: output_broken + +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (for removal)" + openssl_publickey: + path: '{{ output_dir }}/publickey_removal.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (removal)" + openssl_publickey: + state: absent + path: '{{ output_dir }}/publickey_removal.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: remove_1 +- name: "({{ select_crypto_backend }}) Generate publickey - PEM format (removal, idempotent)" + openssl_publickey: + state: absent + path: '{{ output_dir }}/publickey_removal.pub' + privatekey_path: '{{ output_dir }}/privatekey.pem' + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: remove_2 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/main.yml new file mode 100644 index 00000000..eb423054 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tasks/main.yml @@ -0,0 +1,54 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Generate privatekey1 - standard + openssl_privatekey: + path: '{{ output_dir }}/privatekey_autodetect.pem' + size: '{{ default_rsa_key_size }}' + + - name: Run module with backend autodetection + openssl_publickey: + path: '{{ output_dir }}/privatekey_autodetect_public.pem' + privatekey_path: '{{ output_dir }}/privatekey_autodetect.pem' + + when: | + pyopenssl_version.stdout is version('16.0.0', '>=') or + cryptography_version.stdout is version('1.2.3', '>=') + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: pyopenssl + + when: pyopenssl_version.stdout is version('16.0.0', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('1.2.3', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tests/validate.yml new file mode 100644 index 00000000..34e67b54 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_publickey/tests/validate.yml @@ -0,0 +1,146 @@ +--- +- name: "({{ select_crypto_backend }}) Validate publickey 1 idempotence and result behavior" + assert: + that: + - publickey is changed + - publickey_idempotence is not changed + - publickey.publickey == lookup('file', output_dir ~ '/publickey.pub', rstrip=False) + - publickey.publickey == publickey_idempotence.publickey + +- name: "({{ select_crypto_backend }}) Validate public key (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey_modulus + +- name: "({{ select_crypto_backend }}) Validate public key (test - publickey modulus)" + shell: '{{ openssl_binary }} rsa -pubin -noout -modulus < {{ output_dir }}/publickey.pub' + register: publickey_modulus + +- name: "({{ select_crypto_backend }}) Validate public key (assert)" + assert: + that: + - publickey_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate public key - OpenSSH format (test - privatekey's publickey)" + shell: 'ssh-keygen -y -f {{ output_dir }}/privatekey.pem' + register: privatekey_publickey + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + +- name: "({{ select_crypto_backend }}) Validate public key - OpenSSH format (test - publickey)" + slurp: + src: '{{ output_dir }}/publickey-ssh.pub' + register: publickey + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + +- name: "({{ select_crypto_backend }}) Validate public key - OpenSSH format (assert)" + assert: + that: + - privatekey_publickey.stdout == '{{ publickey.content|b64decode }}' + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + +- name: "({{ select_crypto_backend }}) Validate public key - OpenSSH format - test idempotence (issue 33256)" + assert: + that: + - publickey_ssh_idempotence is not changed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('1.4.0', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey2 (test - Ensure key has been removed)" + stat: + path: '{{ output_dir }}/publickey2.pub' + register: publickey2 + +- name: "({{ select_crypto_backend }}) Validate publickey2 (assert - Ensure key has been removed)" + assert: + that: + - publickey2.stat.exists == False + +- name: "({{ select_crypto_backend }}) Validate publickey2 removal behavior" + assert: + that: + - publickey2_absent is changed + - publickey2_absent_idempotence is not changed + - publickey2_absent.publickey is none + + +- name: "({{ select_crypto_backend }}) Validate publickey3 (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey3.pem -passin pass:ansible' + register: privatekey3_modulus + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey3 (test - publickey modulus)" + shell: '{{ openssl_binary }} rsa -pubin -noout -modulus < {{ output_dir }}/publickey3.pub' + register: publickey3_modulus + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey3 (assert)" + assert: + that: + - publickey3_modulus.stdout == privatekey3_modulus.stdout + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey3 idempotence (assert)" + assert: + that: + - publickey3_idempotence is not changed + +- name: "({{ select_crypto_backend }}) Validate publickey4 (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey4_modulus + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey4 (test - publickey modulus)" + shell: '{{ openssl_binary }} rsa -pubin -noout -modulus < {{ output_dir }}/publickey4.pub' + register: publickey4_modulus + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate publickey4 (assert)" + assert: + that: + - publickey4_modulus.stdout == privatekey4_modulus.stdout + when: openssl_version.stdout is version('0.9.8zh', '>=') + +- name: "({{ select_crypto_backend }}) Validate idempotency and backup" + assert: + that: + - privatekey5_1 is changed + - privatekey5_1.backup_file is undefined + - privatekey5_2 is not changed + - privatekey5_2.backup_file is undefined + - privatekey5_3 is changed + - privatekey5_3.backup_file is string + +- name: "({{ select_crypto_backend }}) Validate public key 5 (test - privatekey's pubkey)" + command: '{{ openssl_binary }} ec -in {{ output_dir }}/privatekey5.pem -pubout' + register: privatekey5_pubkey + +- name: "({{ select_crypto_backend }}) Validate public key 5 (test - publickey pubkey)" + # Fancy way of writing "cat {{ output_dir }}/publickey5.pub" + command: '{{ openssl_binary }} ec -pubin -in {{ output_dir }}/publickey5.pub -pubout' + register: publickey5_pubkey + +- name: "({{ select_crypto_backend }}) Validate public key 5 (assert)" + assert: + that: + - publickey5_pubkey.stdout == privatekey5_pubkey.stdout + +- name: + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" + +- name: "({{ select_crypto_backend }}) Verify that broken key will be regenerated" + assert: + that: + - output_broken is changed + +- name: "({{ select_crypto_backend }}) Validate remove" + assert: + that: + - remove_1 is changed + - remove_2 is not changed + - remove_1.backup_file is string + - remove_2.backup_file is undefined diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/aliases new file mode 100644 index 00000000..5a35a4b5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/aliases @@ -0,0 +1,3 @@ +shippable/posix/group1 +openssl_signature_info +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/loop.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/loop.yml new file mode 100644 index 00000000..c33a6091 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/loop.yml @@ -0,0 +1,28 @@ +--- +# This file is intended to be included in a loop statement +- name: Sign statement with {{ item.type }} key - {{ item.passwd }} using {{ item.backend }} + openssl_signature: + privatekey_path: '{{ output_dir }}/{{item.backend}}_privatekey_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_passphrase: '{{ item.privatekey_passphrase | default(omit) }}' + path: '{{ output_dir }}/statement.txt' + select_crypto_backend: '{{ item.backend }}' + register: sign_result + +- debug: + var: sign_result + +- name: Verify {{ item.type }} signature - {{ item.passwd }} using {{ item.backend }} + openssl_signature_info: + certificate_path: '{{ output_dir }}/{{item.backend}}_certificate_{{ item.type }}_{{ item.passwd }}.pem' + path: '{{ output_dir }}/statement.txt' + signature: '{{ sign_result.signature }}' + select_crypto_backend: '{{ item.backend }}' + register: verify_result + +- name: Make sure the signature is valid + assert: + that: + - verify_result.valid + +- debug: + var: verify_result diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/main.yml new file mode 100644 index 00000000..b0b97c89 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/openssl_signature/tasks/main.yml @@ -0,0 +1,113 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Test matrix: +# * pyopenssl or cryptography +# * DSA or ECC or ... +# * password protected private key or not + +- name: Set up test combinations + set_fact: + all_tests: [] + backends: [] + key_types: [] + key_password: + - passwd: nopasswd + - passwd: passwd + privatekey_passphrase: hunter2 + privatekey_cipher: auto + +- name: Add cryptography backend + set_fact: + backends: "{{ backends + [ { 'backend': 'cryptography' } ] }}" + when: cryptography_version.stdout is version('1.4', '>=') + +- name: Add pyopenssl backend + set_fact: + backends: "{{ backends + [ { 'backend': 'pyopenssl' } ] }}" + when: pyopenssl_version.stdout is version('0.11', '>=') + +- name: Add RSA tests + set_fact: + key_types: "{{ key_types + [ { 'type': 'RSA', 'size': default_rsa_key_size } ] }}" + when: cryptography_version.stdout is version('1.4', '>=') + +- name: Add DSA + ECDSA tests + set_fact: + key_types: "{{ key_types + [ { 'type': 'DSA', 'size': 2048 }, { 'type': 'ECC', 'curve': 'secp256r1' } ] }}" + when: + - cryptography_version.stdout is version('1.5', '>=') + # FreeBSD 11 fails on secp256r1 keys + - not ansible_os_family == 'FreeBSD' + +- name: Add Ed25519 + Ed448 tests + set_fact: + key_types: "{{ key_types + [ { 'type': 'Ed25519' }, { 'type': 'Ed448' } ] }}" + when: + # The module under tests works with >= 2.6, but we also need to be able to create a certificate which requires 2.8 + - cryptography_version.stdout is version('2.8', '>=') + # FreeBSD doesn't have support for Ed448/25519 + - not ansible_os_family == 'FreeBSD' + +- name: Create all test combinations + set_fact: + # Explanation: see https://serverfault.com/a/1004124 + all_tests: >- + [ + {% for b in backends %} + {% for kt in key_types %} + {% for kp in key_password %} + {# Exclude Ed25519 and Ed448 tests on pyopenssl #} + {% if not (b.backend == 'pyopenssl' and (kt.type == 'Ed25519' or kt.type == 'Ed448')) %} + {{ b | combine (kt) | combine(kp) }}, + {% endif %} + {% endfor %} + {% endfor %} + {% endfor %} + ] + +- name: Generate private keys + openssl_privatekey: + path: '{{ output_dir }}/{{item.backend}}_privatekey_{{ item.type }}_{{ item.passwd }}.pem' + type: '{{ item.type }}' + curve: '{{ item.curve | default(omit) }}' + size: '{{ item.size | default(omit) }}' + passphrase: '{{ item.privatekey_passphrase | default(omit) }}' + cipher: '{{ item.privatekey_cipher | default(omit) }}' + select_crypto_backend: cryptography + loop: '{{ all_tests }}' + +- name: Generate public keys + openssl_publickey: + path: '{{ output_dir }}/{{item.backend}}_publickey_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_path: '{{ output_dir }}/{{item.backend}}_privatekey_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_passphrase: '{{ item.privatekey_passphrase | default(omit) }}' + loop: '{{ all_tests }}' + +- name: Generate CSRs + openssl_csr: + path: '{{ output_dir }}/{{item.backend}}_{{ item.type }}_{{ item.passwd }}.csr' + privatekey_path: '{{ output_dir }}/{{item.backend}}_privatekey_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_passphrase: '{{ item.privatekey_passphrase | default(omit) }}' + loop: '{{ all_tests }}' + +- name: Generate selfsigned certificates + x509_certificate: + provider: selfsigned + path: '{{ output_dir }}/{{item.backend}}_certificate_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_path: '{{ output_dir }}/{{item.backend}}_privatekey_{{ item.type }}_{{ item.passwd }}.pem' + privatekey_passphrase: '{{ item.privatekey_passphrase | default(omit) }}' + csr_path: '{{ output_dir }}/{{item.backend}}_{{ item.type }}_{{ item.passwd }}.csr' + loop: '{{ all_tests }}' + +- name: Create statement to be signed + copy: + content: "Erst wenn der Subwoofer die Katze inhaliert, fickt der Bass richtig übel. -- W.A. Mozart" + dest: '{{ output_dir }}/statement.txt' + +- name: Loop over all variants + include_tasks: loop.yml + loop: '{{ all_tests }}' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/defaults/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/defaults/main.yml new file mode 100644 index 00000000..a1e5b8d1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/defaults/main.yml @@ -0,0 +1,4 @@ +badssl_host: wrong.host.badssl.com +httpbin_host: httpbin.org +sni_host: ci-files.testing.ansible.com +badssl_host_substring: wrong.host.badssl.com diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/meta/main.yml new file mode 100644 index 00000000..1810d4be --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_remote_tmp_dir diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/default.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/default.yml new file mode 100644 index 00000000..bff90350 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/default.yml @@ -0,0 +1,64 @@ +- name: RedHat - Enable the dynamic CA configuration feature + command: update-ca-trust force-enable + when: ansible_os_family == 'RedHat' + +- name: RedHat - Retrieve test cacert + get_url: + url: "http://ansible.http.tests/cacert.pem" + dest: "/etc/pki/ca-trust/source/anchors/ansible.pem" + when: ansible_os_family == 'RedHat' + +- name: Get client cert/key + get_url: + url: "http://ansible.http.tests/{{ item }}" + dest: "{{ remote_tmp_dir }}/{{ item }}" + with_items: + - client.pem + - client.key + +- name: Suse - Retrieve test cacert + get_url: + url: "http://ansible.http.tests/cacert.pem" + dest: "/etc/pki/trust/anchors/ansible.pem" + when: ansible_os_family == 'Suse' + +- name: Debian - Retrieve test cacert + get_url: + url: "http://ansible.http.tests/cacert.pem" + dest: "/usr/local/share/ca-certificates/ansible.crt" + when: ansible_os_family == 'Debian' + +- name: Redhat - Update ca trust + command: update-ca-trust extract + when: ansible_os_family == 'RedHat' + +- name: Debian/Suse - Update ca certificates + command: update-ca-certificates + when: ansible_os_family == 'Debian' or ansible_os_family == 'Suse' + +- name: FreeBSD - Retrieve test cacert + get_url: + url: "http://ansible.http.tests/cacert.pem" + dest: "/tmp/ansible.pem" + when: ansible_os_family == 'FreeBSD' + +- name: FreeBSD - Add cacert to root certificate store + blockinfile: + path: "/etc/ssl/cert.pem" + block: "{{ lookup('file', '/tmp/ansible.pem') }}" + when: ansible_os_family == 'FreeBSD' + +- name: MacOS - Retrieve test cacert + when: ansible_os_family == 'Darwin' + block: + - uri: + url: "http://ansible.http.tests/cacert.pem" + return_content: true + register: cacert_pem + + - raw: '{{ ansible_python_interpreter }} -c "import ssl; print(ssl.get_default_verify_paths().cafile)"' + register: macos_cafile + + - blockinfile: + path: "{{ macos_cafile.stdout_lines|first }}" + block: "{{ cacert_pem.content }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/main.yml new file mode 100644 index 00000000..49e1fa20 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/tasks/main.yml @@ -0,0 +1,27 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# The docker --link functionality gives us an ENV var we can key off of to see if we have access to +# the httptester container +- set_fact: + has_httptester: "{{ lookup('env', 'HTTPTESTER') != '' }}" + +- name: make sure we have the ansible_os_family and ansible_distribution_version facts + setup: + gather_subset: distribution + when: ansible_facts == {} + +# If we are running with access to a httptester container, grab it's cacert and install it +- block: + # Override hostname defaults with httptester linked names + - include_vars: httptester.yml + + - include_tasks: "{{ lookup('first_found', files)}}" + vars: + files: + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" + when: + - has_httptester|bool diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/vars/httptester.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/vars/httptester.yml new file mode 100644 index 00000000..0e23ae93 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_http_tests/vars/httptester.yml @@ -0,0 +1,5 @@ +# these are fake hostnames provided by docker link for the httptester container +badssl_host: fail.ansible.http.tests +httpbin_host: ansible.http.tests +sni_host: sni1.ansible.http.tests +badssl_host_substring: HTTP Client Testing Service diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_tests/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_tests/tasks/main.yml new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/prepare_tests/tasks/main.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/meta/main.yml new file mode 100644 index 00000000..96d5b2b8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: [] +# - setup_openssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/main.yml new file mode 100644 index 00000000..b18e5872 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/main.yml @@ -0,0 +1,22 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# BEGIN HACK: remove whenever we know how to properly detect 'default' docker container !!!!!!!!!!!!!!!!!!!!! +- name: Default value for OpenSSL binary path + set_fact: + openssl_binary: openssl + +- name: Register openssl version + shell: "{{ openssl_binary }} version | cut -d' ' -f2" + register: openssl_version + +- name: Register cryptography version + command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'" + register: cryptography_version +# END HACK !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +- debug: + msg: "ACME test container IP is {{ acme_host }}; OpenSSL version is {{ openssl_version.stdout }}; cryptography version is {{ cryptography_version.stdout }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/obtain-cert.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/obtain-cert.yml new file mode 100644 index 00000000..ec53510b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/tasks/obtain-cert.yml @@ -0,0 +1,144 @@ +--- +## PRIVATE KEY ################################################################################ +- name: ({{ certgen_title }}) Create cert private key (RSA) + command: "{{ openssl_binary }} genrsa -out {{ output_dir }}/{{ certificate_name }}.key {{ rsa_bits if key_type == 'rsa' else default_rsa_key_size }}" + when: "key_type == 'rsa'" +- name: ({{ certgen_title }}) Create cert private key (ECC 256) + command: "{{ openssl_binary }} ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec256'" +- name: ({{ certgen_title }}) Create cert private key (ECC 384) + command: "{{ openssl_binary }} ecparam -name secp384r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec384'" +- name: ({{ certgen_title }}) Create cert private key (ECC 512) + command: "{{ openssl_binary }} ecparam -name secp521r1 -genkey -out {{ output_dir }}/{{ certificate_name }}.key" + when: "key_type == 'ec521'" +## CSR ######################################################################################## +- name: ({{ certgen_title }}) Create cert CSR + openssl_csr: + path: "{{ output_dir }}/{{ certificate_name }}.csr" + privatekey_path: "{{ output_dir }}/{{ certificate_name }}.key" + subject_alt_name: "{{ subject_alt_name }}" + subject_alt_name_critical: "{{ subject_alt_name_critical }}" + return_content: true + register: csr_result +## ACME STEP 1 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 1 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + register: challenge_data +- name: ({{ certgen_title }}) Print challenge data + debug: + var: challenge_data +- name: ({{ certgen_title }}) Create HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: PUT + body_format: raw + body: "{{ item.value['http-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Create DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: PUT + body_format: json + body: "{{ item.value }}" + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (acm_challenge_cert_helper) + acme_challenge_cert_helper: + challenge: tls-alpn-01 + challenge_data: "{{ item.value['tls-alpn-01'] }}" + private_key_src: "{{ output_dir }}/{{ certificate_name }}.key" + with_dict: "{{ challenge_data.challenge_data }}" + register: tls_alpn_challenges + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Set TLS ALPN challenges (acm_challenge_cert_helper) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key" + method: PUT + body_format: raw + body: "{{ item.challenge_certificate }}\n{{ lookup('file', output_dir ~ '/' ~ certificate_name ~ '.key') }}" + headers: + content-type: "application/pem-certificate-chain" + with_items: "{{ tls_alpn_challenges.results }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is defined and challenge_alpn_tls == 'acme_challenge_cert_helper')" +- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64) + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64" + method: PUT + body_format: raw + body: "{{ item.value['tls-alpn-01'].resource_value }}" + headers: + content-type: "application/octet-stream" + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls is not defined or challenge_alpn_tls == 'der-value-b64')" +## ACME STEP 2 ################################################################################ +- name: ({{ certgen_title }}) Obtain cert, step 2 + acme_certificate: + select_crypto_backend: "{{ select_crypto_backend }}" + acme_version: 2 + acme_directory: https://{{ acme_host }}:14000/dir + validate_certs: no + account_key: "{{ (output_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}" + account_key_content: "{{ account_key_content | default(omit) }}" + account_uri: "{{ challenge_data.account_uri }}" + modify_account: "{{ modify_account }}" + csr: "{{ omit if use_csr_content | default(false) else output_dir ~ '/' ~ certificate_name ~ '.csr' }}" + csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}" + dest: "{{ output_dir }}/{{ certificate_name }}.pem" + fullchain_dest: "{{ output_dir }}/{{ certificate_name }}-fullchain.pem" + chain_dest: "{{ output_dir }}/{{ certificate_name }}-chain.pem" + challenge: "{{ challenge }}" + deactivate_authzs: "{{ deactivate_authzs }}" + force: "{{ force }}" + remaining_days: "{{ remaining_days }}" + terms_agreed: "{{ terms_agreed }}" + account_email: "{{ account_email }}" + data: "{{ challenge_data }}" + retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}" + select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}" + register: certificate_obtain_result + when: challenge_data is changed +- name: ({{ certgen_title }}) Deleting HTTP challenges + uri: + url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'http-01'" +- name: ({{ certgen_title }}) Deleting DNS challenges + uri: + url: "http://{{ acme_host }}:5000/dns/{{ item.key }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data_dns }}" + when: "challenge_data is changed and challenge == 'dns-01'" +- name: ({{ certgen_title }}) Deleting TLS ALPN challenges + uri: + url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}" + method: DELETE + with_dict: "{{ challenge_data.challenge_data }}" + when: "challenge_data is changed and challenge == 'tls-alpn-01'" +- name: ({{ certgen_title }}) Get root certificate + get_url: + url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}" + dest: "{{ output_dir }}/{{ certificate_name }}-root.pem" +############################################################################################### diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/vars/main.yml new file mode 100644 index 00000000..f2b772af --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_acme/vars/main.yml @@ -0,0 +1,9 @@ +--- +default_rsa_key_size: 1024 +default_rsa_key_size_certifiates: >- + {{ + 2048 if + (ansible_os_family == "RedHat" and ansible_facts.distribution_major_version | int >= 8) or + (ansible_distribution == "Ubuntu" and ansible_facts.distribution_major_version | int >= 20) + else 1024 + }} diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/meta/main.yml new file mode 100644 index 00000000..2be15776 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_remote_constraints + - setup_pkg_mgr diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/tasks/main.yml new file mode 100644 index 00000000..907f44b7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/tasks/main.yml @@ -0,0 +1,102 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Register system environment + command: "{{ ansible_python.executable }} -c 'import os; print(dict(os.environ))'" + register: sys_environment + +- debug: var=sys_environment + +- name: Default value for OpenSSL binary path + set_fact: + openssl_binary: openssl + +- name: Include OS-specific variables + include_vars: '{{ ansible_os_family }}.yml' + when: not ansible_os_family == "Darwin" + +- name: Install OpenSSL + become: true + package: + name: '{{ openssl_package_name }}' + when: not ansible_os_family == 'Darwin' + +- name: Register openssl version (full) + shell: "{{ openssl_binary }} version" + register: openssl_version_full + +- name: Show openssl version (full) + debug: + var: openssl_version_full.stdout_lines + +- when: ansible_os_family == "Darwin" and "LibreSSL" in openssl_version_full.stdout + # In case LibreSSL is installed on macOS, we need to install a more modern OpenSSL + block: + - name: MACOS | Find brew binary + command: which brew + register: brew_which + + - name: MACOS | Get owner of brew binary + stat: + path: "{{ brew_which.stdout }}" + register: brew_stat + + - name: MACOS | Install openssl + homebrew: + name: openssl + state: present + become: yes + become_user: "{{ brew_stat.stat.pw_name }}" + + - name: MACOS | Locale openssl binary + command: brew --prefix openssl + register: brew_openssl_prefix + + - name: MACOS | Point to OpenSSL binary + set_fact: + openssl_binary: "{{ brew_openssl_prefix.stdout }}/bin/openssl" + + - name: MACOS | Register openssl version (full) + shell: "{{ openssl_binary }} version" + register: openssl_version_full_again + # We must use a different variable to prevent the 'when' condition of the surrounding block to fail + + - name: MACOS | Show openssl version (full) + debug: + var: openssl_version_full_again.stdout_lines + +- name: Register openssl version + shell: "{{ openssl_binary }} version | cut -d' ' -f2" + register: openssl_version + +- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6'] + block: + - name: Install cryptography (Python 3) + become: true + package: + name: '{{ cryptography_package_name_python3 }}' + when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '>=') + + - name: Install cryptography (Python 2) + become: true + package: + name: '{{ cryptography_package_name }}' + when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '<') + + - name: Install cryptography (Darwin) + become: true + pip: + name: cryptography + extra_args: "-c {{ remote_constraints }}" + when: ansible_os_family == 'Darwin' + +- name: Register cryptography version + command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'" + register: cryptography_version + +- name: Print default key sizes + debug: + msg: "Default RSA key size: {{ default_rsa_key_size }} (for certificates: {{ default_rsa_key_size_certifiates }})" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Debian.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Debian.yml new file mode 100644 index 00000000..810d314c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Debian.yml @@ -0,0 +1,4 @@ +--- +openssl_package_name: openssl +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/FreeBSD.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/FreeBSD.yml new file mode 100644 index 00000000..4040bda4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/FreeBSD.yml @@ -0,0 +1,4 @@ +--- +openssl_package_name: openssl +cryptography_package_name: py27-cryptography +cryptography_package_name_python3: "py{{ ansible_python.version.major }}{{ ansible_python.version.minor }}-cryptography" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/RedHat.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/RedHat.yml new file mode 100644 index 00000000..810d314c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/RedHat.yml @@ -0,0 +1,4 @@ +--- +openssl_package_name: openssl +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Suse.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Suse.yml new file mode 100644 index 00000000..810d314c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/Suse.yml @@ -0,0 +1,4 @@ +--- +openssl_package_name: openssl +cryptography_package_name: python-cryptography +cryptography_package_name_python3: python3-cryptography diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/main.yml new file mode 100644 index 00000000..f2b772af --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_openssl/vars/main.yml @@ -0,0 +1,9 @@ +--- +default_rsa_key_size: 1024 +default_rsa_key_size_certifiates: >- + {{ + 2048 if + (ansible_os_family == "RedHat" and ansible_facts.distribution_major_version | int >= 8) or + (ansible_distribution == "Ubuntu" and ansible_facts.distribution_major_version | int >= 20) + else 1024 + }} diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pkg_mgr/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pkg_mgr/tasks/main.yml new file mode 100644 index 00000000..eeecb835 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pkg_mgr/tasks/main.yml @@ -0,0 +1,17 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + pkg_mgr: community.general.pkgng + ansible_pkg_mgr: community.general.pkgng + cacheable: yes + when: ansible_os_family == 'FreeBSD' and ansible_version.string is version('2.10', '>=') + +- set_fact: + pkg_mgr: community.general.zypper + ansible_pkg_mgr: community.general.zypper + cacheable: yes + when: ansible_os_family == 'Suse' and ansible_version.string is version('2.10', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/meta/main.yml new file mode 100644 index 00000000..2be15776 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_remote_constraints + - setup_pkg_mgr diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/tasks/main.yml new file mode 100644 index 00000000..2057f884 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/tasks/main.yml @@ -0,0 +1,45 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Include OS-specific variables + include_vars: '{{ ansible_os_family }}.yml' + when: not ansible_os_family == "Darwin" + +- name: Install pyOpenSSL (Python 3) + become: true + package: + name: '{{ pyopenssl_package_name_python3 }}' + when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '>=') + +- name: Install pyOpenSSL (Python 2) + become: true + package: + name: '{{ pyopenssl_package_name }}' + when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '<') + +- name: Install pyOpenSSL (Darwin) + become: true + pip: + name: pyOpenSSL + extra_args: "-c {{ remote_constraints }}" + when: ansible_os_family == 'Darwin' + +- name: Register pyOpenSSL version + command: "{{ ansible_python.executable }} -c 'import OpenSSL; print(OpenSSL.__version__)'" + register: pyopenssl_version + +- name: Register pyOpenSSL debug details + command: "{{ ansible_python.executable }} -m OpenSSL.debug" + register: pyopenssl_debug_version + ignore_errors: yes + +# Depending on which pyOpenSSL version has been installed, it could be that cryptography has +# been upgraded to a newer version. Make sure to register cryptography_version another time here +# to avoid strange testing behavior due to wrong values of cryptography_version. +- name: Register cryptography version + command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'" + register: cryptography_version + ignore_errors: yes # in case cryptography was not installed, and setup_openssl hasn't been run before, ignore errors diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Debian.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Debian.yml new file mode 100644 index 00000000..45c183e9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Debian.yml @@ -0,0 +1,3 @@ +--- +pyopenssl_package_name: python-openssl +pyopenssl_package_name_python3: python3-openssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/FreeBSD.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/FreeBSD.yml new file mode 100644 index 00000000..7deca97a --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/FreeBSD.yml @@ -0,0 +1,3 @@ +--- +pyopenssl_package_name: py27-openssl +pyopenssl_package_name_python3: "py{{ ansible_python.version.major }}{{ ansible_python.version.minor }}-openssl" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/RedHat.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/RedHat.yml new file mode 100644 index 00000000..ffe6cd9b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/RedHat.yml @@ -0,0 +1,3 @@ +--- +pyopenssl_package_name: pyOpenSSL +pyopenssl_package_name_python3: python3-pyOpenSSL diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Suse.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Suse.yml new file mode 100644 index 00000000..a0f085c7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_pyopenssl/vars/Suse.yml @@ -0,0 +1,3 @@ +--- +pyopenssl_package_name: python-pyOpenSSL +pyopenssl_package_name_python3: python3-pyOpenSSL diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/aliases new file mode 100644 index 00000000..1ad133ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/aliases @@ -0,0 +1 @@ +needs/file/tests/utils/constraints.txt diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/meta/main.yml new file mode 100644 index 00000000..1810d4be --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_remote_tmp_dir diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/tasks/main.yml new file mode 100644 index 00000000..d4f8148c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_constraints/tasks/main.yml @@ -0,0 +1,13 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: record constraints.txt path on remote host + set_fact: + remote_constraints: "{{ remote_tmp_dir }}/constraints.txt" + +- name: copy constraints.txt to remote host + copy: + src: "{{ role_path }}/../../../utils/constraints.txt" + dest: "{{ remote_constraints }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml new file mode 100644 index 00000000..e8099486 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/handlers/main.yml @@ -0,0 +1,2 @@ +- name: delete temporary directory + include_tasks: default-cleanup.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml new file mode 100644 index 00000000..39872d74 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default-cleanup.yml @@ -0,0 +1,5 @@ +- name: delete temporary directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + no_log: yes diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml new file mode 100644 index 00000000..1e0f51b8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/default.yml @@ -0,0 +1,11 @@ +- name: create temporary directory + tempfile: + state: directory + suffix: .test + register: remote_tmp_dir + notify: + - delete temporary directory + +- name: record temporary directory + set_fact: + remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml new file mode 100644 index 00000000..93d786f0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_remote_tmp_dir/tasks/main.yml @@ -0,0 +1,15 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: make sure we have the ansible_os_family and ansible_distribution_version facts + setup: + gather_subset: distribution + when: ansible_facts == {} + +- include_tasks: "{{ lookup('first_found', files)}}" + vars: + files: + - "{{ ansible_os_family | lower }}.yml" + - "default.yml" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/meta/main.yml new file mode 100644 index 00000000..dc973f4e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_ssh_keygen diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/tasks/main.yml new file mode 100644 index 00000000..fe552825 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_agent/tasks/main.yml @@ -0,0 +1,43 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Start an ssh agent to use for tests + shell: eval $(ssh-agent)>/dev/null&&echo "${SSH_AGENT_PID};${SSH_AUTH_SOCK}" + register: openssh_agent_env_vars + +- name: Register ssh agent facts + set_fact: + openssh_agent_pid: "{{ openssh_agent_env_vars.stdout.split(';')[0] }}" + openssh_agent_sock: "{{ openssh_agent_env_vars.stdout.split(';')[1] }}" + +- name: stat agent socket + stat: + path: "{{ openssh_agent_sock }}" + register: openssh_agent_socket_stat + +- name: Assert agent socket file is a socket + assert: + that: + - openssh_agent_socket_stat.stat.issock is defined + - openssh_agent_socket_stat.stat.issock + fail_msg: "{{ openssh_agent_sock }} is not a socket" + +- name: Verify agent responds + command: ssh-add -l + register: rc_openssh_agent_ssh_add_check + environment: + SSH_AUTH_SOCK: "{{ openssh_agent_sock }}" + when: openssh_agent_socket_stat.stat.issock + failed_when: rc_openssh_agent_ssh_add_check.rc == 2 + +- name: Get ssh version + shell: ssh -Vq 2>&1|sed 's/^.*OpenSSH_\([0-9]\{1,\}\.[0-9]\{1,\}\).*$/\1/' + register: + rc_openssh_version_output + +- name: Set ssh version facts + set_fact: + openssh_version: "{{ rc_openssh_version_output.stdout.strip() }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/meta/main.yml new file mode 100644 index 00000000..5438ced5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_pkg_mgr diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/tasks/main.yml new file mode 100644 index 00000000..1c8a7143 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/tasks/main.yml @@ -0,0 +1,14 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Include OS-specific variables + include_vars: '{{ ansible_os_family }}.yml' + when: not ansible_os_family == "Darwin" and not ansible_os_family == "FreeBSD" + +- name: Install ssh-keygen + package: + name: '{{ openssh_client_package_name }}' + when: not ansible_os_family == "Darwin" and not ansible_os_family == "FreeBSD" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Debian.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Debian.yml new file mode 100644 index 00000000..d7ff0c73 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Debian.yml @@ -0,0 +1 @@ +openssh_client_package_name: openssh-client diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/RedHat.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/RedHat.yml new file mode 100644 index 00000000..bc656edf --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/RedHat.yml @@ -0,0 +1 @@ +openssh_client_package_name: openssh-clients diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Suse.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Suse.yml new file mode 100644 index 00000000..4091fa7b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/setup_ssh_keygen/vars/Suse.yml @@ -0,0 +1 @@ +openssh_client_package_name: openssh diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/aliases new file mode 100644 index 00000000..d7936330 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/aliases @@ -0,0 +1,2 @@ +shippable/cloud/group1 +cloud/acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/meta/main.yml new file mode 100644 index 00000000..81d1e7e7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_acme diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/impl.yml new file mode 100644 index 00000000..7de7da68 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/impl.yml @@ -0,0 +1,69 @@ +--- +- name: Generate account key + openssl_privatekey: + path: '{{ output_dir }}/account.key' + size: '{{ default_rsa_key_size }}' + +- name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size }}' + +- name: Generate CSRs + openssl_csr: + privatekey_path: '{{ output_dir }}/privatekey.pem' + path: '{{ output_dir }}/{{ item.name }}.csr' + subject_alt_name: '{{ item.sans }}' + loop: + - name: cert-1 + sans: + - DNS:example.com + - name: cert-2 + sans: + - DNS:example.com + - DNS:example.org + +- name: Retrieve certificate 1 + x509_certificate: + provider: acme + path: '{{ output_dir }}/cert-1.pem' + csr_path: '{{ output_dir }}/cert-1.csr' + acme_accountkey_path: '{{ output_dir }}/account.key' + acme_challenge_path: '{{ output_dir }}/challenges/' + acme_directory: https://{{ acme_host }}:14000/dir + environment: + PATH: '{{ lookup("env", "PATH") }}:{{ output_dir }}' + +- name: Get certificate information + x509_certificate_info: + path: '{{ output_dir }}/cert-1.pem' + register: result + +- name: Validate certificate information + assert: + that: + - result.subject_alt_name | length == 1 + - "'DNS:example.com' in result.subject_alt_name" + +- name: Retrieve certificate 2 + x509_certificate: + provider: acme + path: '{{ output_dir }}/cert-2.pem' + csr_path: '{{ output_dir }}/cert-2.csr' + acme_accountkey_path: '{{ output_dir }}/account.key' + acme_challenge_path: '{{ output_dir }}/challenges/' + acme_directory: https://{{ acme_host }}:14000/dir + environment: + PATH: '{{ lookup("env", "PATH") }}:{{ output_dir }}' + +- name: Get certificate information + x509_certificate_info: + path: '{{ output_dir }}/cert-2.pem' + register: result + +- name: Validate certificate information + assert: + that: + - result.subject_alt_name | length == 2 + - "'DNS:example.com' in result.subject_alt_name" + - "'DNS:example.org' in result.subject_alt_name" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/main.yml new file mode 100644 index 00000000..d0888de9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate-acme/tasks/main.yml @@ -0,0 +1,121 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- block: + - name: Obtain root and intermediate certificates + get_url: + url: "http://{{ acme_host }}:5000/{{ item.0 }}-certificate-for-ca/{{ item.1 }}" + dest: "{{ output_dir }}/acme-{{ item.0 }}-{{ item.1 }}.pem" + loop: "{{ query('nested', types, root_numbers) }}" + + - name: Analyze root certificates + x509_certificate_info: + path: "{{ output_dir }}/acme-root-{{ item }}.pem" + loop: "{{ root_numbers }}" + register: acme_roots + + - name: Analyze intermediate certificates + x509_certificate_info: + path: "{{ output_dir }}/acme-intermediate-{{ item }}.pem" + loop: "{{ root_numbers }}" + register: acme_intermediates + + - set_fact: + x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}" + y__: "{{ lookup('file', output_dir ~ '/acme-root-' ~ item.item ~ '.pem', rstrip=False) }}" + loop: "{{ acme_roots.results }}" + register: acme_roots_tmp + + - set_fact: + x__: "{{ item | dict2items | selectattr('key', 'in', interesting_keys) | list | items2dict }}" + y__: "{{ lookup('file', output_dir ~ '/acme-intermediate-' ~ item.item ~ '.pem', rstrip=False) }}" + loop: "{{ acme_intermediates.results }}" + register: acme_intermediates_tmp + + - set_fact: + acme_roots: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.x__') | list }}" + acme_root_certs: "{{ acme_roots_tmp.results | map(attribute='ansible_facts.y__') | list }}" + acme_intermediates: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.x__') | list }}" + acme_intermediate_certs: "{{ acme_intermediates_tmp.results | map(attribute='ansible_facts.y__') | list }}" + + vars: + types: + - root + - intermediate + root_numbers: + - 0 + interesting_keys: + - authority_key_identifier + - subject_key_identifier + - issuer + - subject + +- name: Get hold of acme-tiny executable + get_url: + url: https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py + dest: "{{ output_dir }}/acme-tiny" + +- name: Make sure acme-tiny is executable + file: + path: "{{ output_dir }}/acme-tiny" + mode: "0755" + +- name: "Monkey-patch acme-tiny: Disable certificate validation" + blockinfile: + path: "{{ output_dir }}/acme-tiny" + marker: "# {mark} ANSIBLE MANAGED BLOCK: DISABLE CERTIFICATE VALIDATION FOR HTTPS REQUESTS" + insertafter: '^#!.*' + block: | + import ssl + try: + ssl._create_default_https_context = ssl._create_unverified_context + except Exception: + # Python before 2.7.9 has no verification at all. So nothing to disable. + pass + # For later: + try: + from urllib.request import Request # Python 3 + except ImportError: + from urllib2 import Request # Python 2 + +- name: "Monkey-patch acme-tiny: Disable check that challenge file is reachable via HTTP" + replace: + path: "{{ output_dir }}/acme-tiny" + regexp: 'parser\.add_argument\("--disable-check", default=False,' + replace: 'parser.add_argument("--disable-check", default=True,' + +- name: "Monkey-patch acme-tiny: Instead of writing challenge files to disk, post them to challenge server" + replace: + path: "{{ output_dir }}/acme-tiny" + regexp: 'with open\(wellknown_path, "w"\) as [^:]+:\n\s+[^. ]+\.write\(([^)]+)\)' + replace: 'r = Request(url="http://{{ acme_host }}:5000/http/" + domain + "/" + token, data=\1.encode("utf8"), headers={"content-type": "application/octet-stream"}) ; r.get_method = lambda: "PUT" ; urlopen(r).close()' + +- name: "Monkey-patch acme-tiny: Remove file cleanup" + replace: + path: "{{ output_dir }}/acme-tiny" + regexp: 'os\.remove\(wellknown_path\)' + replace: 'pass' + +- name: "Monkey-patch acme-tiny: Avoid crash on already valid challenges (https://github.com/diafygi/acme-tiny/pull/254)" + blockinfile: + path: "{{ output_dir }}/acme-tiny" + marker: "# {mark} ANSIBLE MANAGED BLOCK: AVOID CRASH ON ALREADY VALID CHALLENGES" + insertbefore: '# find the http-01 challenge and write the challenge file' + block: |2 + # skip if already valid + if authorization['status'] == "valid": + log.info("{0} already verified. Skipping.".format(domain)) + continue + +- name: Create challenges directory + file: + path: '{{ output_dir }}/challenges' + state: directory + +- name: Running tests + include_tasks: impl.yml + # Make x509_certificate module happy + when: cryptography_version.stdout is version('1.6', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/assertonly.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/assertonly.yml new file mode 100644 index 00000000..b6f355a7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/assertonly.yml @@ -0,0 +1,154 @@ +--- +- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey with password + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Assertonly, {{select_crypto_backend}}) - Generate CSR (no extensions) + openssl_csr: + path: '{{ output_dir }}/csr_noext.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + useCommonNameForSAN: no + +- name: (Assertonly, {{select_crypto_backend}}) - Generate CSR (with SANs) + openssl_csr: + path: '{{ output_dir }}/csr_sans.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + subject_alt_name: + - "DNS:ansible.com" + - "IP:127.0.0.1" + - "IP:::1" + useCommonNameForSAN: no + +- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (no extensions) + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + csr_path: '{{ output_dir }}/csr_noext.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (with SANs) + x509_certificate: + path: '{{ output_dir }}/cert_sans.pem' + csr_path: '{{ output_dir }}/csr_sans.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (should fail) + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + provider: assertonly + subject_alt_name: + - "DNS:example.com" + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: extension_missing_san + +- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there + x509_certificate: + path: '{{ output_dir }}/cert_sans.pem' + provider: assertonly + subject_alt_name: + - "DNS:ansible.com" + - "IP:127.0.0.1" + - "IP:::1" + select_crypto_backend: '{{ select_crypto_backend }}' + register: extension_san + +- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (strict) + x509_certificate: + path: '{{ output_dir }}/cert_sans.pem' + provider: assertonly + subject_alt_name: + - "DNS:ansible.com" + - "IP:127.0.0.1" + - "IP:::1" + subject_alt_name_strict: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: extension_san_strict + +- name: (Assertonly, {{select_crypto_backend}}) - Assert that key_usage is there (should fail) + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + provider: assertonly + key_usage: + - digitalSignature + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: extension_missing_ku + +- name: (Assertonly, {{select_crypto_backend}}) - Assert that extended_key_usage is there (should fail) + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + provider: assertonly + extended_key_usage: + - biometricInfo + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: extension_missing_eku + +- assert: + that: + - extension_missing_san is failed + - "'Found no subjectAltName extension' in extension_missing_san.msg" + - extension_san is succeeded + - extension_san_strict is succeeded + - extension_missing_ku is failed + - "'Found no keyUsage extension' in extension_missing_ku.msg" + - extension_missing_eku is failed + - "'Found no extendedKeyUsage extension' in extension_missing_eku.msg" + +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 1 + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + privatekey_passphrase: hunter2 + provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_1 + +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 2 + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: wrong_password + provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_2 + +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 3 + x509_certificate: + path: '{{ output_dir }}/cert_noext.pem' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_3 + +- name: (Assertonly, {{select_crypto_backend}}) - + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/expired.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/expired.yml new file mode 100644 index 00000000..76e21c83 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/expired.yml @@ -0,0 +1,49 @@ +--- +- name: (Expired, {{select_crypto_backend}}) Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/has_expired_privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Expired, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/has_expired_csr.csr' + privatekey_path: '{{ output_dir }}/has_expired_privatekey.pem' + subject: + commonName: www.example.com + +- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/has_expired_cert.pem' + csr_path: '{{ output_dir }}/has_expired_csr.csr' + privatekey_path: '{{ output_dir }}/has_expired_privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_not_after: "-1s" + selfsigned_not_before: "-100s" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend == 'pyopenssl' # cryptography won't allow creating expired certificates + +- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate + command: "{{ openssl_binary }} x509 -req -days -1 -in {{ output_dir }}/has_expired_csr.csr -signkey {{ output_dir }}/has_expired_privatekey.pem -out {{ output_dir }}/has_expired_cert.pem" + when: select_crypto_backend == 'cryptography' # So we create it with 'command' + +- name: "(Expired) Check task fails because cert is expired (has_expired: false)" + x509_certificate: + provider: assertonly + path: "{{ output_dir }}/has_expired_cert.pem" + has_expired: false + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: true + register: expired_cert_check + +- name: (Expired, {{select_crypto_backend}}) Ensure previous task failed + assert: + that: expired_cert_check is failed + +- name: "(Expired) Check expired cert check is ignored (has_expired: true)" + x509_certificate: + provider: assertonly + path: "{{ output_dir }}/has_expired_cert.pem" + has_expired: true + select_crypto_backend: '{{ select_crypto_backend }}' + register: expired_cert_skip diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/impl.yml new file mode 100644 index 00000000..f215591f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/impl.yml @@ -0,0 +1,8 @@ +--- +- debug: + msg: "Executing tests with backend {{ select_crypto_backend }}" +- import_tasks: assertonly.yml +- import_tasks: expired.yml +- import_tasks: selfsigned.yml +- import_tasks: ownca.yml +- import_tasks: removal.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/main.yml new file mode 100644 index 00000000..36b5c280 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/main.yml @@ -0,0 +1,27 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('1.6', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml new file mode 100644 index 00000000..3feea15f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml @@ -0,0 +1,580 @@ +--- +- name: (OwnCA, {{select_crypto_backend}}) Generate CA privatekey + openssl_privatekey: + path: '{{ output_dir }}/ca_privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate CA privatekey with passphrase + openssl_privatekey: + path: '{{ output_dir }}/ca_privatekey_pw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR + openssl_csr: + path: '{{ output_dir }}/ca_csr.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + subject: + commonName: Example CA + useCommonNameForSAN: no + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: yes + +- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR (privatekey passphrase) + openssl_csr: + path: '{{ output_dir }}/ca_csr_pw.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey_pw.pem' + privatekey_passphrase: hunter2 + subject: + commonName: Example CA + useCommonNameForSAN: no + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: yes + +- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate + x509_certificate: + path: '{{ output_dir }}/ca_cert.pem' + csr_path: '{{ output_dir }}/ca_csr.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase) + x509_certificate: + path: '{{ output_dir }}/ca_cert_pw.pem' + csr_path: '{{ output_dir }}/ca_csr_pw.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey_pw.pem' + privatekey_passphrase: hunter2 + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: ownca_certificate + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent) + x509_certificate: + path: '{{ output_dir }}/ownca_cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: ownca_certificate_idempotence + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (check mode) + x509_certificate: + path: '{{ output_dir }}/ownca_cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + +- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + subject: + commonName: www.example.com + issuer: + commonName: Example CA + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca v2 certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert_v2.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_version: 2 + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_v2_certificate + ignore_errors: true + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate2 + x509_certificate: + path: '{{ output_dir }}/ownca_cert2.pem' + csr_path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2 + x509_certificate: + path: '{{ output_dir }}/ownca_cert2.pem' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + subject: + commonName: www.example.com + C: US + ST: California + L: Los Angeles + O: ACME Inc. + OU: + - Roadrunner pest control + - Pyrotechnics + keyUsage: + - digitalSignature + extendedKeyUsage: + - ipsecUser + - biometricInfo + issuer: + commonName: Example CA + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter + x509_certificate: + provider: ownca + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + path: "{{ output_dir }}/ownca_cert3.pem" + csr_path: "{{ output_dir }}/csr.csr" + privatekey_path: "{{ output_dir }}/privatekey3.pem" + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter + x509_certificate: + provider: ownca + ownca_not_before: +1s + ownca_not_after: +52w + path: "{{ output_dir }}/ownca_cert4.pem" + csr_path: "{{ output_dir }}/csr.csr" + privatekey_path: "{{ output_dir }}/privatekey3.pem" + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca ECC certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ecc.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_certificate_ecc + +- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ecc_2.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert_pw.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey_pw.pem' + ownca_privatekey_passphrase: hunter2 + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_certificate_passphrase + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 1) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_pw1.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + ownca_privatekey_passphrase: hunter2 + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_1 + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 2) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_pw2.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekeypw.pem' + ownca_privatekey_passphrase: wrong_password + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_2 + +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 3) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_pw3.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekeypw.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_3 + +- name: (OwnCA, {{select_crypto_backend}}) Create broken certificate + copy: + dest: "{{ output_dir }}/ownca_broken.pem" + content: "broken" +- name: (OwnCA, {{select_crypto_backend}}) Regenerate broken cert + x509_certificate: + path: '{{ output_dir }}/ownca_broken.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + register: ownca_broken + +- name: (OwnCA, {{select_crypto_backend}}) Backup test + x509_certificate: + path: '{{ output_dir }}/ownca_cert_backup.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_backup_1 +- name: (OwnCA, {{select_crypto_backend}}) Backup test (idempotent) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_backup.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_backup_2 +- name: (OwnCA, {{select_crypto_backend}}) Backup test (change) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_backup.pem' + csr_path: '{{ output_dir }}/csr.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_backup_3 +- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_backup.pem' + state: absent + provider: ownca + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_backup_4 +- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove, idempotent) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_backup.pem' + state: absent + provider: ownca + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_backup_5 + +- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_subject_key_identifier_1 + +- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (idempotency) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_subject_key_identifier_2 + +- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_subject_key_identifier: never_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_subject_key_identifier_3 + +- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove idempotency) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_subject_key_identifier: never_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_subject_key_identifier_4 + +- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (re-enable) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_subject_key_identifier_5 + +- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier + x509_certificate: + path: '{{ output_dir }}/ownca_cert_aki.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_authority_key_identifier: yes + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_authority_key_identifier_1 + +- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (idempotency) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_aki.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_authority_key_identifier: yes + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_authority_key_identifier_2 + +- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_aki.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_authority_key_identifier: no + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_authority_key_identifier_3 + +- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove idempotency) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_aki.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_authority_key_identifier: no + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_authority_key_identifier_4 + +- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (re-add) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_aki.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: ownca + ownca_digest: sha256 + ownca_create_authority_key_identifier: yes + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: ownca_authority_key_identifier_5 + +- name: (OwnCA, {{select_crypto_backend}}) Ed25519 and Ed448 tests (for cryptography >= 2.6) + block: + - name: (OwnCA, {{select_crypto_backend}}) Generate privatekeys + openssl_privatekey: + path: '{{ output_dir }}/privatekey_{{ item }}.pem' + type: '{{ item }}' + loop: + - Ed25519 + - Ed448 + register: ownca_certificate_ed25519_ed448_privatekey + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate CSR etc. if private key generation succeeded + when: ownca_certificate_ed25519_ed448_privatekey is not failed + block: + + - name: (OwnCA, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/csr_{{ item }}.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: ownca_certificate_ed25519_ed448 + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/csr_{{ item }}.csr' + ownca_path: '{{ output_dir }}/ca_cert.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: ownca_certificate_ed25519_ed448_idempotence + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate CA privatekey + openssl_privatekey: + path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem' + type: '{{ item }}' + cipher: auto + passphrase: Test123 + ignore_errors: yes + loop: + - Ed25519 + - Ed448 + + - name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR + openssl_csr: + path: '{{ output_dir }}/ca_csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem' + privatekey_passphrase: Test123 + subject: + commonName: Example CA + useCommonNameForSAN: no + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: yes + key_usage: + - cRLSign + - keyCertSign + loop: + - Ed25519 + - Ed448 + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate + x509_certificate: + path: '{{ output_dir }}/ca_cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/ca_csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem' + privatekey_passphrase: Test123 + provider: selfsigned + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate + x509_certificate: + path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem' + csr_path: '{{ output_dir }}/csr.csr' + ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem' + ownca_privatekey_passphrase: Test123 + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: ownca_certificate_ed25519_ed448_2 + ignore_errors: yes + + - name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent) + x509_certificate: + path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem' + csr_path: '{{ output_dir }}/csr.csr' + ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem' + ownca_privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem' + ownca_privatekey_passphrase: Test123 + provider: ownca + ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: ownca_certificate_ed25519_ed448_2_idempotence + ignore_errors: yes + + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') + +- import_tasks: ../tests/validate_ownca.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/removal.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/removal.yml new file mode 100644 index 00000000..581021c4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/removal.yml @@ -0,0 +1,53 @@ +--- +- name: (Removal, {{select_crypto_backend}}) Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/removal_privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Removal, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/removal_csr.csr' + privatekey_path: '{{ output_dir }}/removal_privatekey.pem' + +- name: (Removal, {{select_crypto_backend}}) Generate selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/removal_cert.pem' + csr_path: '{{ output_dir }}/removal_csr.csr' + privatekey_path: '{{ output_dir }}/removal_privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: "(Removal, {{select_crypto_backend}}) Check that file is not gone" + stat: + path: "{{ output_dir }}/removal_cert.pem" + register: removal_1_prestat + +- name: "(Removal, {{select_crypto_backend}}) Remove certificate" + x509_certificate: + path: "{{ output_dir }}/removal_cert.pem" + state: absent + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: removal_1 + +- name: "(Removal, {{select_crypto_backend}}) Check that file is gone" + stat: + path: "{{ output_dir }}/removal_cert.pem" + register: removal_1_poststat + +- name: "(Removal, {{select_crypto_backend}}) Remove certificate (idempotent)" + x509_certificate: + path: "{{ output_dir }}/removal_cert.pem" + state: absent + select_crypto_backend: '{{ select_crypto_backend }}' + register: removal_2 + +- name: (Removal, {{select_crypto_backend}}) Ensure removal worked + assert: + that: + - removal_1_prestat.stat.exists + - removal_1 is changed + - not removal_1_poststat.stat.exists + - removal_2 is not changed + - removal_1.certificate is none diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml new file mode 100644 index 00000000..03c197aa --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml @@ -0,0 +1,465 @@ +--- +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey with password + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR + x509_certificate: + path: '{{ output_dir }}/cert_no_csr.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: selfsigned_certificate_no_csr + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR - idempotency + x509_certificate: + path: '{{ output_dir }}/cert_no_csr.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: selfsigned_certificate_no_csr_idempotence + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate without CSR (check mode) + x509_certificate: + path: '{{ output_dir }}/cert_no_csr.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: selfsigned_certificate_no_csr_idempotence_check + +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr_minimal_change.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.org + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: selfsigned_certificate + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency + x509_certificate: + path: '{{ output_dir }}/cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + return_content: yes + register: selfsigned_certificate_idempotence + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode) + x509_certificate: + path: '{{ output_dir }}/cert.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode, other CSR) + x509_certificate: + path: '{{ output_dir }}/cert.pem' + csr_path: '{{ output_dir }}/csr_minimal_change.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: selfsigned_certificate_csr_minimal_change + +- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/cert.pem' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + subject: + commonName: www.example.com + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned v2 certificate + x509_certificate: + path: '{{ output_dir }}/cert_v2.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_version: 2 + select_crypto_backend: "{{ select_crypto_backend }}" + register: selfsigned_v2_cert + ignore_errors: true + +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey2 + openssl_privatekey: + path: '{{ output_dir }}/privatekey2.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR2 + openssl_csr: + subject: + CN: www.example.com + C: US + ST: California + L: Los Angeles + O: ACME Inc. + OU: + - Roadrunner pest control + - Pyrotechnics + path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + keyUsage: + - digitalSignature + extendedKeyUsage: + - ipsecUser + - biometricInfo + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate2 + x509_certificate: + path: '{{ output_dir }}/cert2.pem' + csr_path: '{{ output_dir }}/csr2.csr' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2 + x509_certificate: + path: '{{ output_dir }}/cert2.pem' + privatekey_path: '{{ output_dir }}/privatekey2.pem' + provider: assertonly + has_expired: False + version: 3 + signature_algorithms: + - sha256WithRSAEncryption + - sha256WithECDSAEncryption + subject: + commonName: www.example.com + C: US + ST: California + L: Los Angeles + O: ACME Inc. + OU: + - Roadrunner pest control + - Pyrotechnics + keyUsage: + - digitalSignature + extendedKeyUsage: + - ipsecUser + - biometricInfo + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Create private key 3 + openssl_privatekey: + path: "{{ output_dir }}/privatekey3.pem" + size: '{{ default_rsa_key_size_certifiates }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Create CSR 3 + openssl_csr: + subject: + CN: www.example.com + privatekey_path: "{{ output_dir }}/privatekey3.pem" + path: "{{ output_dir }}/csr3.pem" + +- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter + x509_certificate: + provider: selfsigned + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + path: "{{ output_dir }}/cert3.pem" + csr_path: "{{ output_dir }}/csr3.pem" + privatekey_path: "{{ output_dir }}/privatekey3.pem" + select_crypto_backend: '{{ select_crypto_backend }}' + +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey_ecc.pem' + type: ECC + curve: "{{ (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') | ternary('secp521r1', 'secp256k1') }}" + # ^ cryptography on CentOS6 doesn't support secp256k1, so we use secp521r1 instead + +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + subject: + commonName: www.example.com + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/cert_ecc.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_certificate_ecc + +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR (privatekey passphrase) + openssl_csr: + path: '{{ output_dir }}/csr_pass.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: hunter2 + subject: + commonName: www.example.com + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase) + x509_certificate: + path: '{{ output_dir }}/cert_pass.pem' + csr_path: '{{ output_dir }}/csr_pass.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: hunter2 + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_certificate_passphrase + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 1) + x509_certificate: + path: '{{ output_dir }}/cert_pw1.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + privatekey_passphrase: hunter2 + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_1 + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 2) + x509_certificate: + path: '{{ output_dir }}/cert_pw2.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: wrong_password + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_2 + +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 3) + x509_certificate: + path: '{{ output_dir }}/cert_pw3.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + ignore_errors: yes + register: passphrase_error_3 + +- name: (Selfsigned, {{select_crypto_backend}}) Create broken certificate + copy: + dest: "{{ output_dir }}/cert_broken.pem" + content: "broken" +- name: (Selfsigned, {{select_crypto_backend}}) Regenerate broken cert + x509_certificate: + path: '{{ output_dir }}/cert_broken.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + register: selfsigned_broken + +- name: (Selfsigned, {{select_crypto_backend}}) Backup test + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_backup.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_backup_1 +- name: (Selfsigned, {{select_crypto_backend}}) Backup test (idempotent) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_backup.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_backup_2 +- name: (Selfsigned, {{select_crypto_backend}}) Backup test (change) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_backup.pem' + csr_path: '{{ output_dir }}/csr.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_backup_3 +- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_backup.pem' + state: absent + provider: selfsigned + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_backup_4 +- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove, idempotent) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_backup.pem' + state: absent + provider: selfsigned + backup: yes + select_crypto_backend: '{{ select_crypto_backend }}' + register: selfsigned_backup_5 + +- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: selfsigned_subject_key_identifier_1 + +- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (idempotency) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: selfsigned_subject_key_identifier_2 + +- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_create_subject_key_identifier: never_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: selfsigned_subject_key_identifier_3 + +- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove idempotency) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_create_subject_key_identifier: never_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: selfsigned_subject_key_identifier_4 + +- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (re-enable) + x509_certificate: + path: '{{ output_dir }}/selfsigned_cert_ski.pem' + csr_path: '{{ output_dir }}/csr_ecc.csr' + privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_create_subject_key_identifier: always_create + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend != 'pyopenssl' + register: selfsigned_subject_key_identifier_5 + +- name: (Selfsigned, {{select_crypto_backend}}) Ed25519 and Ed448 tests (for cryptography >= 2.6) + block: + - name: (Selfsigned, {{select_crypto_backend}}) Generate privatekeys + openssl_privatekey: + path: '{{ output_dir }}/privatekey_{{ item }}.pem' + type: '{{ item }}' + loop: + - Ed25519 + - Ed448 + register: selfsigned_certificate_ed25519_ed448_privatekey + ignore_errors: yes + + - name: (Selfsigned, {{select_crypto_backend}}) Generate CSR etc. if private key generation succeeded + when: selfsigned_certificate_ed25519_ed448_privatekey is not failed + block: + + - name: (Selfsigned, {{select_crypto_backend}}) Generate CSR + openssl_csr: + path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + subject: + commonName: www.ansible.com + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + ignore_errors: yes + + - name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate + x509_certificate: + path: '{{ output_dir }}/cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: selfsigned_certificate_ed25519_ed448 + ignore_errors: yes + + - name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency + x509_certificate: + path: '{{ output_dir }}/cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem' + provider: selfsigned + selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - Ed25519 + - Ed448 + register: selfsigned_certificate_ed25519_ed448_idempotence + ignore_errors: yes + + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') + +- import_tasks: ../tests/validate_selfsigned.yml diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml new file mode 100644 index 00000000..e8d23f46 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml @@ -0,0 +1,178 @@ +--- +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - verify CA) + shell: '{{ openssl_binary }} verify -CAfile {{ output_dir }}/ca_cert.pem {{ output_dir }}/ownca_cert.pem | sed "s/.*: \(.*\)/\1/g"' + register: ownca_verify_ca + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca certificate modulus) + shell: '{{ openssl_binary }} x509 -noout -modulus -in {{ output_dir }}/ownca_cert.pem' + register: ownca_cert_modulus + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca issuer value) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/ownca_cert.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g"' + register: ownca_cert_issuer + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca certficate version == default == 3) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/ownca_cert.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: ownca_cert_version + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (assert) + assert: + that: + - ownca_verify_ca.stdout == 'OK' + - ownca_cert_modulus.stdout == privatekey_modulus.stdout + - ownca_cert_version.stdout == '3' + # openssl 1.1.x adds a space between the output + - ownca_cert_issuer.stdout in ['CN=Example CA', 'CN = Example CA'] + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate idempotence + assert: + that: + - ownca_certificate.serial_number == ownca_certificate_idempotence.serial_number + - ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore + - ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca data return + assert: + that: + - ownca_certificate.certificate == lookup('file', output_dir ~ '/ownca_cert.pem', rstrip=False) + - ownca_certificate.certificate == ownca_certificate_idempotence.certificate + +- block: + - name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate v2 (test - ownca certificate version == 2) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/ownca_cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: ownca_cert_v2_version + + - name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate version 2 (assert) + assert: + that: + - ownca_cert_v2_version.stdout == '2' + when: "select_crypto_backend != 'cryptography'" + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate v2 (test - ownca certificate version == 2) + assert: + that: + - ownca_v2_certificate is failed + - "'The cryptography backend does not support v2 certificates' in ownca_v2_certificate.msg" + when: "select_crypto_backend == 'cryptography'" + + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate2 (test - ownca certificate modulus) + shell: '{{ openssl_binary }} x509 -noout -modulus -in {{ output_dir }}/ownca_cert2.pem' + register: ownca_cert2_modulus + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate2 (assert) + assert: + that: + - ownca_cert2_modulus.stdout == privatekey2_modulus.stdout + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate owncal certificate3 (test - notBefore) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir }}/ownca_cert3.pem -text | grep "Not Before" | sed "s/.*: \(.*\) .*/\1/g"' + register: ownca_cert3_notBefore + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (test - notAfter) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir }}/ownca_cert3.pem -text | grep "Not After" | sed "s/.*: \(.*\) .*/\1/g"' + register: ownca_cert3_notAfter + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (assert - notBefore) + assert: + that: + - ownca_cert3_notBefore.stdout == 'Oct 23 13:37:42 2018' + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (assert - notAfter) + assert: + that: + - ownca_cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca certificate pubkey) + shell: '{{ openssl_binary }} x509 -noout -pubkey -in {{ output_dir }}/ownca_cert_ecc.pem' + register: ownca_cert_ecc_pubkey + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca issuer value) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/ownca_cert_ecc.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g"' + register: ownca_cert_ecc_issuer + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (assert) + assert: + that: + - ownca_cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout + # openssl 1.1.x adds a space between the output + - ownca_cert_ecc_issuer.stdout in ['CN=Example CA', 'CN = Example CA'] + +- name: (OwnCA validation, {{select_crypto_backend}}) + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" + +- name: (OwnCA validation, {{select_crypto_backend}})Verify that broken certificate will be regenerated + assert: + that: + - ownca_broken is changed + +- name: (OwnCA validation, {{select_crypto_backend}}) Check backup + assert: + that: + - ownca_backup_1 is changed + - ownca_backup_1.backup_file is undefined + - ownca_backup_2 is not changed + - ownca_backup_2.backup_file is undefined + - ownca_backup_3 is changed + - ownca_backup_3.backup_file is string + - ownca_backup_4 is changed + - ownca_backup_4.backup_file is string + - ownca_backup_5 is not changed + - ownca_backup_5.backup_file is undefined + +- name: (OwnCA validation, {{select_crypto_backend}}) Check create subject key identifier + assert: + that: + - ownca_subject_key_identifier_1 is changed + - ownca_subject_key_identifier_2 is not changed + - ownca_subject_key_identifier_3 is changed + - ownca_subject_key_identifier_4 is not changed + - ownca_subject_key_identifier_5 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: (OwnCA validation, {{select_crypto_backend}}) Check create authority key identifier + assert: + that: + - ownca_authority_key_identifier_1 is changed + - ownca_authority_key_identifier_2 is not changed + - ownca_authority_key_identifier_3 is changed + - ownca_authority_key_identifier_4 is not changed + - ownca_authority_key_identifier_5 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: (OwnCA validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8) + assert: + that: + - ownca_certificate_ed25519_ed448.results[0] is failed + - ownca_certificate_ed25519_ed448.results[1] is failed + - ownca_certificate_ed25519_ed448_idempotence.results[0] is failed + - ownca_certificate_ed25519_ed448_idempotence.results[1] is failed + - ownca_certificate_ed25519_ed448_2.results[0] is failed + - ownca_certificate_ed25519_ed448_2.results[1] is failed + - ownca_certificate_ed25519_ed448_2_idempotence.results[0] is failed + - ownca_certificate_ed25519_ed448_2_idempotence.results[1] is failed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') and cryptography_version.stdout is version('2.8', '<') and ownca_certificate_ed25519_ed448_privatekey is not failed + +- name: (OwnCA validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.8) + assert: + that: + - ownca_certificate_ed25519_ed448 is succeeded + - ownca_certificate_ed25519_ed448.results[0] is changed + - ownca_certificate_ed25519_ed448.results[1] is changed + - ownca_certificate_ed25519_ed448_idempotence is succeeded + - ownca_certificate_ed25519_ed448_idempotence.results[0] is not changed + - ownca_certificate_ed25519_ed448_idempotence.results[1] is not changed + - ownca_certificate_ed25519_ed448_2 is succeeded + - ownca_certificate_ed25519_ed448_2.results[0] is changed + - ownca_certificate_ed25519_ed448_2.results[1] is changed + - ownca_certificate_ed25519_ed448_2_idempotence is succeeded + - ownca_certificate_ed25519_ed448_2_idempotence.results[0] is not changed + - ownca_certificate_ed25519_ed448_2_idempotence.results[1] is not changed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.8', '>=') and ownca_certificate_ed25519_ed448_privatekey is not failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml new file mode 100644 index 00000000..03da84e6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml @@ -0,0 +1,198 @@ +--- +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - privatekey modulus) + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey_modulus + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate behavior for no CSR + assert: + that: + - selfsigned_certificate_no_csr is changed + - selfsigned_certificate_no_csr_idempotence is not changed + - selfsigned_certificate_no_csr_idempotence_check is not changed + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (test - certificate modulus) + shell: '{{ openssl_binary }} x509 -noout -modulus -in {{ output_dir }}/cert_no_csr.pem' + register: cert_modulus + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (test - certficate version == default == 3) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/cert_no_csr.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert_version + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR (assert) + assert: + that: + - cert_modulus.stdout == privatekey_modulus.stdout + - cert_version.stdout == '3' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate with no CSR idempotence + assert: + that: + - selfsigned_certificate_no_csr.serial_number == selfsigned_certificate_no_csr_idempotence.serial_number + - selfsigned_certificate_no_csr.notBefore == selfsigned_certificate_no_csr_idempotence.notBefore + - selfsigned_certificate_no_csr.notAfter == selfsigned_certificate_no_csr_idempotence.notAfter + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate data retrieval with no CSR + assert: + that: + - selfsigned_certificate_no_csr.certificate == lookup('file', output_dir ~ '/cert_no_csr.pem', rstrip=False) + - selfsigned_certificate_no_csr.certificate == selfsigned_certificate_no_csr_idempotence.certificate + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certificate modulus) + shell: '{{ openssl_binary }} x509 -noout -modulus -in {{ output_dir }}/cert.pem' + register: cert_modulus + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - issuer value) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/cert.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g; s/ //g;"' + register: cert_issuer + + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certficate version == default == 3) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/cert.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert_version + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (assert) + assert: + that: + - cert_modulus.stdout == privatekey_modulus.stdout + - cert_version.stdout == '3' + - cert_issuer.stdout == 'CN=www.example.com' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate idempotence + assert: + that: + - selfsigned_certificate.serial_number == selfsigned_certificate_idempotence.serial_number + - selfsigned_certificate.notBefore == selfsigned_certificate_idempotence.notBefore + - selfsigned_certificate.notAfter == selfsigned_certificate_idempotence.notAfter + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate data retrieval + assert: + that: + - selfsigned_certificate.certificate == lookup('file', output_dir ~ '/cert.pem', rstrip=False) + - selfsigned_certificate.certificate == selfsigned_certificate_idempotence.certificate + +- name: Make sure that changes in CSR are detected even if private key is specified + assert: + that: + - selfsigned_certificate_csr_minimal_change is changed + +- block: + - name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate v2 (test - certificate version == 2) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir}}/cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert_v2_version + + - name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate version 2 (assert) + assert: + that: + - cert_v2_version.stdout == '2' + when: select_crypto_backend != 'cryptography' + +- block: + - name: (Selfsigned validateion, {{ select_crypto_backend }} Validate certificate v2 is failed + assert: + that: + - selfsigned_v2_cert is failed + - "'The cryptography backend does not support v2 certificates' in selfsigned_v2_cert.msg" + when: select_crypto_backend == 'cryptography' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (test - privatekey modulus) + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey2.pem' + register: privatekey2_modulus + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (test - certificate modulus) + shell: '{{ openssl_binary }} x509 -noout -modulus -in {{ output_dir }}/cert2.pem' + register: cert2_modulus + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (assert) + assert: + that: + - cert2_modulus.stdout == privatekey2_modulus.stdout + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (test - notBefore) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir }}/cert3.pem -text | grep "Not Before" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert3_notBefore + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (test - notAfter) + shell: '{{ openssl_binary }} x509 -noout -in {{ output_dir }}/cert3.pem -text | grep "Not After" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert3_notAfter + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (assert - notBefore) + assert: + that: + - cert3_notBefore.stdout == 'Oct 23 13:37:42 2018' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (assert - notAfter) + assert: + that: + - cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - privatekey's pubkey) + shell: '{{ openssl_binary }} ec -pubout -in {{ output_dir }}/privatekey_ecc.pem' + register: privatekey_ecc_pubkey + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - certificate pubkey) + shell: '{{ openssl_binary }} x509 -noout -pubkey -in {{ output_dir }}/cert_ecc.pem' + register: cert_ecc_pubkey + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (assert) + assert: + that: + - cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout + +- name: (Selfsigned validation, {{select_crypto_backend}}) + assert: + that: + - passphrase_error_1 is failed + - "'assphrase' in passphrase_error_1.msg or 'assword' in passphrase_error_1.msg" + - passphrase_error_2 is failed + - "'assphrase' in passphrase_error_2.msg or 'assword' in passphrase_error_2.msg or 'serializ' in passphrase_error_2.msg" + - passphrase_error_3 is failed + - "'assphrase' in passphrase_error_3.msg or 'assword' in passphrase_error_3.msg or 'serializ' in passphrase_error_3.msg" + +- name: (Selfsigned validation, {{select_crypto_backend}}) Verify that broken certificate will be regenerated + assert: + that: + - selfsigned_broken is changed + +- name: (Selfsigned validation, {{select_crypto_backend}}) Check backup + assert: + that: + - selfsigned_backup_1 is changed + - selfsigned_backup_1.backup_file is undefined + - selfsigned_backup_2 is not changed + - selfsigned_backup_2.backup_file is undefined + - selfsigned_backup_3 is changed + - selfsigned_backup_3.backup_file is string + - selfsigned_backup_4 is changed + - selfsigned_backup_4.backup_file is string + - selfsigned_backup_5 is not changed + - selfsigned_backup_5.backup_file is undefined + +- name: (Selfsigned validation, {{select_crypto_backend}}) Check create subject key identifier + assert: + that: + - selfsigned_subject_key_identifier_1 is changed + - selfsigned_subject_key_identifier_2 is not changed + - selfsigned_subject_key_identifier_3 is changed + - selfsigned_subject_key_identifier_4 is not changed + - selfsigned_subject_key_identifier_5 is changed + when: select_crypto_backend != 'pyopenssl' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8) + assert: + that: + - selfsigned_certificate_ed25519_ed448.results[0] is failed + - selfsigned_certificate_ed25519_ed448.results[1] is failed + - selfsigned_certificate_ed25519_ed448_idempotence.results[0] is failed + - selfsigned_certificate_ed25519_ed448_idempotence.results[1] is failed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') and cryptography_version.stdout is version('2.8', '<') and selfsigned_certificate_ed25519_ed448_privatekey is not failed + +- name: (Selfsigned validation, {{select_crypto_backend}}) Verify Ed25519 and Ed448 tests (for cryptography >= 2.8) + assert: + that: + - selfsigned_certificate_ed25519_ed448 is succeeded + - selfsigned_certificate_ed25519_ed448.results[0] is changed + - selfsigned_certificate_ed25519_ed448.results[1] is changed + - selfsigned_certificate_ed25519_ed448_idempotence is succeeded + - selfsigned_certificate_ed25519_ed448_idempotence.results[0] is not changed + - selfsigned_certificate_ed25519_ed448_idempotence.results[1] is not changed + when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.8', '>=') and selfsigned_certificate_ed25519_ed448_privatekey is not failed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/files/cert1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/files/cert1.pem new file mode 100644 index 00000000..834eedc4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/files/cert1.pem @@ -0,0 +1,45 @@ +-----BEGIN CERTIFICATE----- +MIIH5jCCBs6gAwIBAgISA2gSCm/BtvCR2e2bIap5YbXaMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODA3MjcxNzMxMjdaFw0x +ODEwMjUxNzMxMjdaMB4xHDAaBgNVBAMTE3d3dy5sZXRzZW5jcnlwdC5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpL8ZjVL0MUkUAIbYO9+ZCni+c +ghGd9WhM2Ztaay6Wyh6lNoCdltdqTwUhE4O+d7UFModjM3G/KMyfuujr06c5iGKL +3saPmIzLaRPIEOUlB2rKgasKhe8mDRyRLzQSXXgnsaKcTBBuhIHvtP51ZMr05nJJ +sX/5FGjj96w+KJel6E/Ux1a1ZDOFkAYNSIrJJhA5jjIvUPr+Ri6Oc6UlhF9oueKI +uWBILxQpC778tBWdHoZeBCNTHA1VvtwC53OeuHvdZm1jB/e30Mgf5DtVizYpFXVD +mztkrd6z/3B6ZwPyfCE4KgzSf70/byOz971OJxNKTUVWedKHHDlrMxfsPclbAgMB +AAGjggTwMIIE7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFG1w4j/KDrYSFu7m9DPE +xRR0E5gzMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUF +BwEBBGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNy +eXB0Lm9yZy8wggHxBgNVHREEggHoMIIB5IIbY2VydC5pbnQteDEubGV0c2VuY3J5 +cHQub3JnghtjZXJ0LmludC14Mi5sZXRzZW5jcnlwdC5vcmeCG2NlcnQuaW50LXgz +LmxldHNlbmNyeXB0Lm9yZ4IbY2VydC5pbnQteDQubGV0c2VuY3J5cHQub3Jnghxj +ZXJ0LnJvb3QteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0YWdpbmcteDEubGV0 +c2VuY3J5cHQub3Jngh9jZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JngiBj +ZXJ0LnN0Zy1yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ISY3AubGV0c2VuY3J5cHQu +b3JnghpjcC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ITY3BzLmxldHNlbmNyeXB0 +Lm9yZ4IbY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3Jnghtjcmwucm9vdC14MS5s +ZXRzZW5jcnlwdC5vcmeCD2xldHNlbmNyeXB0Lm9yZ4IWb3JpZ2luLmxldHNlbmNy +eXB0Lm9yZ4IXb3JpZ2luMi5sZXRzZW5jcnlwdC5vcmeCFnN0YXR1cy5sZXRzZW5j +cnlwdC5vcmeCE3d3dy5sZXRzZW5jcnlwdC5vcmcwgf4GA1UdIASB9jCB8zAIBgZn +gQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3Bz +LmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmlj +YXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBh +bmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGlj +eSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzCC +AQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AMEWSuCnctLUOS3ICsEHcNTwxJvemRpI +QMH6B1Fk9jNgAAABZN0ChToAAAQDAEcwRQIgblal8oXnfoopr1+dWVhvBx+sqHT0 +eLYxJHBTaRp3j1QCIQDhFQqMk6DDXUgcU12K36zLVFwJTdAJI4RBisnX+g+W0AB2 +ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZN0Chz4AAAQDAEcw +RQIhAImOjvkritUNKJZB7dcUtjoyIbfNwdCspvRiEzXuvVQoAiAZryoyg3TcMun5 +Gb2dEn1cttMnPW9u670/JdRjvjU/wTANBgkqhkiG9w0BAQsFAAOCAQEAGepCmckP +Tn9Sz268FEwkdD+6wWaPfeYlh+9nacFh90nQ35EYQMOK8a+X7ixHGbRz19On3Wt4 +1fcbPa9SefocTjAintMwwreCxpRTmwGACYojd7vRWEmA6q7+/HO2BfZahWzclOjw +mSDBycDEm8R0ZK52vYjzVno8x0mrsmSO0403S/6syYB/guH6P17kIBw+Tgx6/i/c +I1C6MoFkuaAKUUcZmgGGBgE+L/7cWtWjbkVXyA3ZQQy9G7rcBT+N/RrDfBh4iZDq +jAN5UIIYL8upBhjiMYVuoJrH2nklzEwr5SWKcccJX5eWkGLUwlcY9LGAA8+17l2I +l1Ou20Dm9TxnNw== +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/impl.yml new file mode 100644 index 00000000..91838bd4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/impl.yml @@ -0,0 +1,125 @@ +--- +- debug: + msg: "Executing tests with backend {{ select_crypto_backend }}" + +- name: ({{select_crypto_backend}}) Get certificate info + x509_certificate_info: + path: '{{ output_dir }}/cert_1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check whether issuer and subject behave as expected + assert: + that: + - result.issuer.organizationalUnitName == 'ACME Department' + - "['organizationalUnitName', 'Crypto Department'] in result.issuer_ordered" + - "['organizationalUnitName', 'ACME Department'] in result.issuer_ordered" + - result.subject.organizationalUnitName == 'ACME Department' + - "['organizationalUnitName', 'Crypto Department'] in result.subject_ordered" + - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" + +- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier + assert: + that: + - result.subject_key_identifier == "00:11:22:33" + - result.authority_key_identifier == "44:55:66:77" + - result.authority_cert_issuer == expected_authority_cert_issuer + - result.authority_cert_serial_number == 12345 + vars: + expected_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: Update result list + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: ({{select_crypto_backend}}) Get certificate info directly + x509_certificate_info: + content: '{{ lookup("file", output_dir ~ "/cert_1.pem") }}' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result_direct + +- name: ({{select_crypto_backend}}) Compare output of direct and loaded info + assert: + that: + - result == result_direct + +- name: ({{select_crypto_backend}}) Get certificate info + x509_certificate_info: + path: '{{ output_dir }}/cert_2.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + valid_at: + today: "+0d" + past: "20190101235901Z" + twentydays: "+20d" + register: result +- assert: + that: + - result.valid_at.today + - not result.valid_at.past + - not result.valid_at.twentydays + +- name: Update result list + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: ({{select_crypto_backend}}) Get certificate info + x509_certificate_info: + path: '{{ output_dir }}/cert_3.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check AuthorityKeyIdentifier + assert: + that: + - result.authority_key_identifier is none + - result.authority_cert_issuer == expected_authority_cert_issuer + - result.authority_cert_serial_number == 12345 + vars: + expected_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: Update result list + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: ({{select_crypto_backend}}) Get certificate info + x509_certificate_info: + path: '{{ output_dir }}/cert_4.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result + +- name: Check AuthorityKeyIdentifier + assert: + that: + - result.authority_key_identifier == "44:55:66:77" + - result.authority_cert_issuer is none + - result.authority_cert_serial_number is none + when: select_crypto_backend != 'pyopenssl' and cryptography_version.stdout is version('1.3', '>=') + +- name: Update result list + set_fact: + info_results: "{{ info_results + [result] }}" + +- name: ({{select_crypto_backend}}) Get certificate info for packaged cert 1 + x509_certificate_info: + path: '{{ role_path }}/files/cert1.pem' + select_crypto_backend: '{{ select_crypto_backend }}' + register: result +- assert: + that: + - "'ocsp_uri' in result" + - "result.ocsp_uri == 'http://ocsp.int-x3.letsencrypt.org'" +- name: Check fingerprints + assert: + that: + - (result.fingerprints.sha256 == '57:7c:f1:f5:dd:cc:6e:e9:f3:17:28:73:17:e4:25:c7:69:74:3e:f7:9a:df:58:20:7a:5a:e4:aa:de:bf:24:5b' if result.fingerprints.sha256 is defined else true) + - (result.fingerprints.sha1 == 'b7:79:64:f4:2b:e0:ae:45:74:d4:f3:08:f6:53:cb:39:26:fa:52:6b' if result.fingerprints.sha1 is defined else true) + +- name: Update result list + set_fact: + info_results: "{{ info_results + [result] }}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/main.yml new file mode 100644 index 00000000..44da12e2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/tasks/main.yml @@ -0,0 +1,183 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Generate privatekey + openssl_privatekey: + path: '{{ output_dir }}/privatekey.pem' + size: '{{ default_rsa_key_size_certifiates }}' + +- name: Generate privatekey with password + openssl_privatekey: + path: '{{ output_dir }}/privatekeypw.pem' + passphrase: hunter2 + cipher: auto + select_crypto_backend: cryptography + size: '{{ default_rsa_key_size_certifiates }}' + +- name: Generate CSR 1 + openssl_csr: + path: '{{ output_dir }}/csr_1.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + subject: + commonName: www.example.com + C: de + L: Somewhere + ST: Zurich + streetAddress: Welcome Street + O: Ansible + organizationalUnitName: + - Crypto Department + - ACME Department + serialNumber: "1234" + SN: Last Name + GN: First Name + title: Chief + pseudonym: test + UID: asdf + emailAddress: test@example.com + postalAddress: 1234 Somewhere + postalCode: "1234" + useCommonNameForSAN: no + key_usage: + - digitalSignature + - keyAgreement + - Non Repudiation + - Key Encipherment + - dataEncipherment + - Certificate Sign + - cRLSign + - Encipher Only + - decipherOnly + key_usage_critical: yes + extended_key_usage: + - serverAuth # the same as "TLS Web Server Authentication" + - TLS Web Server Authentication + - TLS Web Client Authentication + - Code Signing + - E-mail Protection + - timeStamping + - OCSPSigning + - Any Extended Key Usage + - qcStatements + - DVCS + - IPSec User + - biometricInfo + subject_alt_name: + - "DNS:www.ansible.com" + - "IP:1.2.3.4" + - "IP:::1" + - "email:test@example.org" + - "URI:https://example.org/test/index.html" + basic_constraints: + - "CA:TRUE" + - "pathlen:23" + basic_constraints_critical: yes + ocsp_must_staple: yes + subject_key_identifier: '{{ "00:11:22:33" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}' + vars: + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + +- name: Generate CSR 2 + openssl_csr: + path: '{{ output_dir }}/csr_2.csr' + privatekey_path: '{{ output_dir }}/privatekeypw.pem' + privatekey_passphrase: hunter2 + useCommonNameForSAN: no + basic_constraints: + - "CA:TRUE" + +- name: Generate CSR 3 + openssl_csr: + path: '{{ output_dir }}/csr_3.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + useCommonNameForSAN: no + subject_alt_name: + - "DNS:*.ansible.com" + - "DNS:*.example.org" + - "IP:DEAD:BEEF::1" + basic_constraints: + - "CA:FALSE" + authority_cert_issuer: '{{ value_for_authority_cert_issuer if cryptography_version.stdout is version("1.3", ">=") else omit }}' + authority_cert_serial_number: '{{ 12345 if cryptography_version.stdout is version("1.3", ">=") else omit }}' + vars: + value_for_authority_cert_issuer: + - "DNS:ca.example.org" + - "IP:1.2.3.4" + +- name: Generate CSR 4 + openssl_csr: + path: '{{ output_dir }}/csr_4.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + useCommonNameForSAN: no + authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}' + +- name: Generate selfsigned certificates + x509_certificate: + path: '{{ output_dir }}/cert_{{ item }}.pem' + csr_path: '{{ output_dir }}/csr_{{ item }}.csr' + privatekey_path: '{{ output_dir }}/privatekey.pem' + provider: selfsigned + selfsigned_digest: sha256 + selfsigned_not_after: "+10d" + selfsigned_not_before: "-3d" + loop: + - 1 + - 2 + - 3 + - 4 + +- name: Prepare result list + set_fact: + info_results: [] + +- name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Prepare result list + set_fact: + pyopenssl_info_results: "{{ info_results }}" + info_results: [] + +- name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('1.6', '>=') + +- name: Prepare result list + set_fact: + cryptography_info_results: "{{ info_results }}" + +- block: + - name: Dump pyOpenSSL results + debug: + var: pyopenssl_info_results + - name: Dump cryptography results + debug: + var: cryptography_info_results + - name: Compare results + assert: + that: + - ' (item.0 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict) + == (item.1 | dict2items | rejectattr("key", "in", keys_to_ignore) | list | items2dict)' + quiet: yes + loop: "{{ pyopenssl_info_results | zip(cryptography_info_results) | list }}" + when: pyopenssl_version.stdout is version('0.15', '>=') and cryptography_version.stdout is version('1.6', '>=') + vars: + keys_to_ignore: + - deprecations + - subject_key_identifier + - authority_key_identifier + - authority_cert_issuer + - authority_cert_serial_number diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/test_plugins/jinja_compatibility.py b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/test_plugins/jinja_compatibility.py new file mode 100644 index 00000000..fc2b5f0f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_info/test_plugins/jinja_compatibility.py @@ -0,0 +1,15 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def compatibility_in_test(a, b): + return a in b + + +class TestModule: + ''' Ansible math jinja2 tests ''' + + def tests(self): + return { + 'in': compatibility_in_test, + } diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/aliases new file mode 100644 index 00000000..6eae8bd8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/aliases @@ -0,0 +1,2 @@ +shippable/posix/group1 +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/meta/main.yml new file mode 100644 index 00000000..d1a318db --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - setup_openssl + - setup_pyopenssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/impl.yml new file mode 100644 index 00000000..854ae5d6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/impl.yml @@ -0,0 +1,237 @@ +--- +- name: "({{ select_crypto_backend }}) Generate privatekey" + openssl_privatekey: + path: '{{ output_dir }}/{{ item }}.pem' + size: '{{ default_rsa_key_size_certifiates }}' + loop: + - privatekey + - privatekey2 + +- name: "({{ select_crypto_backend }}) Generate CSRs" + openssl_csr: + privatekey_path: '{{ output_dir }}/{{ item.key }}.pem' + path: '{{ output_dir }}/{{ item.name }}.csr' + subject: + commonName: '{{ item.cn }}' + select_crypto_backend: '{{ select_crypto_backend }}' + loop: + - name: cert + key: privatekey + cn: www.ansible.com + - name: cert-2 + key: privatekey + cn: ansible.com + - name: cert-3 + key: privatekey2 + cn: example.com + - name: cert-4 + key: privatekey2 + cn: example.org + +## Self Signed + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate (check mode)" + x509_certificate_pipe: + provider: selfsigned + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_certificate_check + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate" + x509_certificate_pipe: + provider: selfsigned + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_certificate + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate (idempotent)" + x509_certificate_pipe: + provider: selfsigned + content: "{{ generate_certificate.certificate }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_certificate_idempotent + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate (idempotent, check mode)" + x509_certificate_pipe: + provider: selfsigned + content: "{{ generate_certificate.certificate }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_certificate_idempotent_check + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate (changed)" + x509_certificate_pipe: + provider: selfsigned + content: "{{ generate_certificate.certificate }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-2.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: generate_certificate_changed + +- name: "({{ select_crypto_backend }}) Generate self-signed certificate (changed, check mode)" + x509_certificate_pipe: + provider: selfsigned + content: "{{ generate_certificate.certificate }}" + privatekey_path: '{{ output_dir }}/privatekey.pem' + selfsigned_not_before: 20181023133742Z + selfsigned_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-2.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: generate_certificate_changed_check + +- name: "({{ select_crypto_backend }}) Validate certificate (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' + register: privatekey_modulus + +- name: "({{ select_crypto_backend }}) Validate certificate (test - Common Name)" + shell: "{{ openssl_binary }} x509 -noout -subject -in /dev/stdin -nameopt oneline,-space_eq" + args: + stdin: "{{ generate_certificate.certificate }}" + register: certificate_cn + +- name: "({{ select_crypto_backend }}) Validate certificate (test - certificate modulus)" + shell: '{{ openssl_binary }} x509 -noout -modulus -in /dev/stdin' + args: + stdin: "{{ generate_certificate.certificate }}" + register: certificate_modulus + +- name: "({{ select_crypto_backend }}) Validate certificate (assert)" + assert: + that: + - certificate_cn.stdout.split('=')[-1] == 'www.ansible.com' + - certificate_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate certificate (check mode, idempotency)" + assert: + that: + - generate_certificate_check is changed + - generate_certificate is changed + - generate_certificate_idempotent is not changed + - generate_certificate_idempotent_check is not changed + - generate_certificate_changed is changed + - generate_certificate_changed_check is changed + +## Own CA + +- name: "({{ select_crypto_backend }}) Generate own CA certificate (check mode)" + x509_certificate_pipe: + provider: ownca + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-3.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: ownca_generate_certificate_check + +- name: "({{ select_crypto_backend }}) Generate own CA certificate" + x509_certificate_pipe: + provider: ownca + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-3.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_generate_certificate + +- name: "({{ select_crypto_backend }}) Generate own CA certificate (idempotent)" + x509_certificate_pipe: + provider: ownca + content: "{{ ownca_generate_certificate.certificate }}" + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-3.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_generate_certificate_idempotent + +- name: "({{ select_crypto_backend }}) Generate own CA certificate (idempotent, check mode)" + x509_certificate_pipe: + provider: ownca + content: "{{ ownca_generate_certificate.certificate }}" + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-3.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: ownca_generate_certificate_idempotent_check + +- name: "({{ select_crypto_backend }}) Generate own CA certificate (changed)" + x509_certificate_pipe: + provider: ownca + content: "{{ ownca_generate_certificate.certificate }}" + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-4.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_generate_certificate_changed + +- name: "({{ select_crypto_backend }}) Generate own CA certificate (changed, check mode)" + x509_certificate_pipe: + provider: ownca + content: "{{ ownca_generate_certificate.certificate }}" + ownca_content: '{{ generate_certificate.certificate }}' + ownca_privatekey_path: '{{ output_dir }}/privatekey.pem' + ownca_not_before: 20181023133742Z + ownca_not_after: 20191023133742Z + csr_path: '{{ output_dir }}/cert-4.csr' + select_crypto_backend: '{{ select_crypto_backend }}' + check_mode: yes + register: ownca_generate_certificate_changed_check + +- name: "({{ select_crypto_backend }}) Validate certificate (test - privatekey modulus)" + shell: '{{ openssl_binary }} rsa -noout -modulus -in {{ output_dir }}/privatekey2.pem' + register: privatekey_modulus + +- name: "({{ select_crypto_backend }}) Validate certificate (test - Common Name)" + shell: "{{ openssl_binary }} x509 -noout -subject -in /dev/stdin -nameopt oneline,-space_eq" + args: + stdin: "{{ ownca_generate_certificate.certificate }}" + register: certificate_cn + +- name: "({{ select_crypto_backend }}) Validate certificate (test - certificate modulus)" + shell: '{{ openssl_binary }} x509 -noout -modulus -in /dev/stdin' + args: + stdin: "{{ ownca_generate_certificate.certificate }}" + register: certificate_modulus + +- name: "({{ select_crypto_backend }}) Validate certificate (assert)" + assert: + that: + - certificate_cn.stdout.split('=')[-1] == 'example.com' + - certificate_modulus.stdout == privatekey_modulus.stdout + +- name: "({{ select_crypto_backend }}) Validate certificate (check mode, idempotency)" + assert: + that: + - ownca_generate_certificate_check is changed + - ownca_generate_certificate is changed + - ownca_generate_certificate_idempotent is not changed + - ownca_generate_certificate_idempotent_check is not changed + - ownca_generate_certificate_changed is changed + - ownca_generate_certificate_changed_check is changed diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/main.yml new file mode 100644 index 00000000..6cb76213 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_certificate_pipe/tasks/main.yml @@ -0,0 +1,40 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- name: Prepare private key for backend autodetection test + openssl_privatekey: + path: '{{ output_dir }}/privatekey_backend_selection.pem' + size: '{{ default_rsa_key_size_certifiates }}' +- name: Run module with backend autodetection + x509_certificate_pipe: + provider: selfsigned + privatekey_path: '{{ output_dir }}/privatekey_backend_selection.pem' + +- block: + - name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl + + when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('1.6', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/aliases b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/aliases new file mode 100644 index 00000000..96537bac --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/aliases @@ -0,0 +1,3 @@ +shippable/posix/group1 +x509_crl_info +destructive diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/meta/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/meta/main.yml new file mode 100644 index 00000000..800aff64 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_openssl diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/impl.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/impl.yml new file mode 100644 index 00000000..68ed2fc7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/impl.yml @@ -0,0 +1,388 @@ +--- +- name: Create CRL 1 (check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + check_mode: yes + register: crl_1_check +- name: Create CRL 1 + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + register: crl_1 +- name: Retrieve CRL 1 infos + x509_crl_info: + path: '{{ output_dir }}/ca-crl1.crl' + register: crl_1_info_1 +- name: Retrieve CRL 1 infos via file content + x509_crl_info: + content: '{{ lookup("file", output_dir ~ "/ca-crl1.crl") }}' + register: crl_1_info_2 +- name: Retrieve CRL 1 infos via file content (Base64) + x509_crl_info: + content: '{{ lookup("file", output_dir ~ "/ca-crl1.crl") | b64encode }}' + register: crl_1_info_3 +- name: Create CRL 1 (idempotent, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + check_mode: yes + register: crl_1_idem_check +- name: Create CRL 1 (idempotent) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + register: crl_1_idem +- name: Create CRL 1 (idempotent with content, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}" + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}" + revocation_date: 20191013000000Z + - content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}" + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + check_mode: yes + register: crl_1_idem_content_check +- name: Create CRL 1 (idempotent with content) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}" + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}" + revocation_date: 20191013000000Z + - content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}" + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + register: crl_1_idem_content +- name: Create CRL 1 (format, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + format: der + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + check_mode: yes + register: crl_1_format_check +- name: Create CRL 1 (format) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + format: der + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + register: crl_1_format +- name: Create CRL 1 (format, idempotent, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + format: der + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + check_mode: yes + register: crl_1_format_idem_check +- name: Create CRL 1 (format, idempotent) + x509_crl: + path: '{{ output_dir }}/ca-crl1.crl' + privatekey_path: '{{ output_dir }}/ca.key' + format: der + issuer: + CN: Ansible + last_update: 20191013000000Z + next_update: 20191113000000Z + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + revocation_date: 20191013000000Z + - path: '{{ output_dir }}/cert-2.pem' + revocation_date: 20191013000000Z + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + revocation_date: 20191001000000Z + return_content: yes + register: crl_1_format_idem +- name: Retrieve CRL 1 infos via file + x509_crl_info: + path: '{{ output_dir }}/ca-crl1.crl' + register: crl_1_info_4 +- name: Read ca-crl1.crl + slurp: + src: "{{ output_dir }}/ca-crl1.crl" + register: content +- name: Retrieve CRL 1 infos via file content (Base64) + x509_crl_info: + content: '{{ content.content }}' + register: crl_1_info_5 + +- name: Create CRL 2 (check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + check_mode: yes + register: crl_2_check +- name: Create CRL 2 + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + register: crl_2 +- name: Create CRL 2 (idempotent, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + ignore_timestamps: yes + check_mode: yes + register: crl_2_idem_check +- name: Create CRL 2 (idempotent) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-1.pem' + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + - serial_number: 1234 + ignore_timestamps: yes + register: crl_2_idem +- name: Create CRL 2 (idempotent update, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - serial_number: 1235 + ignore_timestamps: yes + mode: update + check_mode: yes + register: crl_2_idem_update_change_check +- name: Create CRL 2 (idempotent update) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - serial_number: 1235 + ignore_timestamps: yes + mode: update + register: crl_2_idem_update_change +- name: Create CRL 2 (idempotent update, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: yes + mode: update + check_mode: yes + register: crl_2_idem_update_check +- name: Create CRL 2 (idempotent update) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: yes + mode: update + register: crl_2_idem_update +- name: Create CRL 2 (changed timestamps, check mode) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: no + mode: update + check_mode: yes + register: crl_2_change_check +- name: Create CRL 2 (changed timestamps) + x509_crl: + path: '{{ output_dir }}/ca-crl2.crl' + privatekey_path: '{{ output_dir }}/ca.key' + issuer: + CN: Ansible + last_update: +0d + next_update: +0d + revoked_certificates: + - path: '{{ output_dir }}/cert-2.pem' + reason: key_compromise + reason_critical: yes + invalidity_date: 20191012000000Z + ignore_timestamps: no + mode: update + return_content: yes + register: crl_2_change diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/main.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/main.yml new file mode 100644 index 00000000..c2467213 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tasks/main.yml @@ -0,0 +1,88 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +- set_fact: + certificates: + - name: ca + subject: + commonName: Ansible + is_ca: yes + - name: ca-2 + subject: + commonName: Ansible Other CA + is_ca: yes + - name: cert-1 + subject_alt_name: + - DNS:ansible.com + - name: cert-2 + subject_alt_name: + - DNS:example.com + - name: cert-3 + subject_alt_name: + - DNS:example.org + - IP:1.2.3.4 + - name: cert-4 + subject_alt_name: + - DNS:test.ansible.com + - DNS:b64.ansible.com + +- name: Generate private keys + openssl_privatekey: + path: '{{ output_dir }}/{{ item.name }}.key' + type: ECC + curve: secp256r1 + loop: "{{ certificates }}" + +- name: Generate CSRs + openssl_csr: + path: '{{ output_dir }}/{{ item.name }}.csr' + privatekey_path: '{{ output_dir }}/{{ item.name }}.key' + subject: "{{ item.subject | default(omit) }}" + subject_alt_name: "{{ item.subject_alt_name | default(omit) }}" + basic_constraints: "{{ 'CA:TRUE' if item.is_ca | default(false) else omit }}" + use_common_name_for_san: no + loop: "{{ certificates }}" + +- name: Generate CA certificates + x509_certificate: + path: '{{ output_dir }}/{{ item.name }}.pem' + csr_path: '{{ output_dir }}/{{ item.name }}.csr' + privatekey_path: '{{ output_dir }}/{{ item.name }}.key' + provider: selfsigned + loop: "{{ certificates }}" + when: item.is_ca | default(false) + +- name: Generate other certificates + x509_certificate: + path: '{{ output_dir }}/{{ item.name }}.pem' + csr_path: '{{ output_dir }}/{{ item.name }}.csr' + provider: ownca + ownca_path: '{{ output_dir }}/ca.pem' + ownca_privatekey_path: '{{ output_dir }}/ca.key' + loop: "{{ certificates }}" + when: not (item.is_ca | default(false)) + +- name: Get certificate infos + x509_certificate_info: + path: '{{ output_dir }}/{{ item }}.pem' + loop: + - cert-1 + - cert-2 + - cert-3 + - cert-4 + register: certificate_infos + +- block: + - name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + + - import_tasks: ../tests/validate.yml + vars: + select_crypto_backend: cryptography + + when: cryptography_version.stdout is version('1.2', '>=') diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tests/validate.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tests/validate.yml new file mode 100644 index 00000000..c31fa942 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/integration/targets/x509_crl/tests/validate.yml @@ -0,0 +1,82 @@ +--- +- name: Validate CRL 1 + assert: + that: + - crl_1_check is changed + - crl_1 is changed + - crl_1_idem_check is not changed + - crl_1_idem is not changed + - crl_1_idem_content_check is not changed + - crl_1_idem_content is not changed + +- name: Validate CRL 1 info + assert: + that: + - crl_1_info_1.format == 'pem' + - crl_1_info_1.digest == 'ecdsa-with-SHA256' + - crl_1_info_1.issuer | length == 1 + - crl_1_info_1.issuer.commonName == 'Ansible' + - crl_1_info_1.issuer_ordered | length == 1 + - crl_1_info_1.last_update == '20191013000000Z' + - crl_1_info_1.next_update == '20191113000000Z' + - crl_1_info_1.revoked_certificates | length == 3 + - crl_1_info_1.revoked_certificates[0].invalidity_date is none + - crl_1_info_1.revoked_certificates[0].invalidity_date_critical == false + - crl_1_info_1.revoked_certificates[0].issuer is none + - crl_1_info_1.revoked_certificates[0].issuer_critical == false + - crl_1_info_1.revoked_certificates[0].reason is none + - crl_1_info_1.revoked_certificates[0].reason_critical == false + - crl_1_info_1.revoked_certificates[0].revocation_date == '20191013000000Z' + - crl_1_info_1.revoked_certificates[0].serial_number == certificate_infos.results[0].serial_number + - crl_1_info_1.revoked_certificates[1].invalidity_date == '20191012000000Z' + - crl_1_info_1.revoked_certificates[1].invalidity_date_critical == false + - crl_1_info_1.revoked_certificates[1].issuer is none + - crl_1_info_1.revoked_certificates[1].issuer_critical == false + - crl_1_info_1.revoked_certificates[1].reason == 'key_compromise' + - crl_1_info_1.revoked_certificates[1].reason_critical == true + - crl_1_info_1.revoked_certificates[1].revocation_date == '20191013000000Z' + - crl_1_info_1.revoked_certificates[1].serial_number == certificate_infos.results[1].serial_number + - crl_1_info_1.revoked_certificates[2].invalidity_date is none + - crl_1_info_1.revoked_certificates[2].invalidity_date_critical == false + - crl_1_info_1.revoked_certificates[2].issuer is none + - crl_1_info_1.revoked_certificates[2].issuer_critical == false + - crl_1_info_1.revoked_certificates[2].reason is none + - crl_1_info_1.revoked_certificates[2].reason_critical == false + - crl_1_info_1.revoked_certificates[2].revocation_date == '20191001000000Z' + - crl_1_info_1.revoked_certificates[2].serial_number == 1234 + - crl_1_info_1 == crl_1_info_2 + - crl_1_info_1 == crl_1_info_3 + +- name: Validate CRL 1 + assert: + that: + - crl_1_format_check is changed + - crl_1_format is changed + - crl_1_format_idem_check is not changed + - crl_1_format_idem is not changed + - crl_1_info_4.format == 'der' + - crl_1_info_5.format == 'der' + +- name: Read ca-crl1.crl + slurp: + src: "{{ output_dir }}/ca-crl1.crl" + register: content +- name: Validate CRL 1 Base64 content + assert: + that: + - crl_1_format_idem.crl | b64decode == content.content | b64decode + +- name: Validate CRL 2 + assert: + that: + - crl_2_check is changed + - crl_2 is changed + - crl_2_idem_check is not changed + - crl_2_idem is not changed + - crl_2_idem_update_change_check is changed + - crl_2_idem_update_change is changed + - crl_2_idem_update_check is not changed + - crl_2_idem_update is not changed + - crl_2_change_check is changed + - crl_2_change is changed + - crl_2_change.crl == lookup('file', output_dir ~ '/ca-crl2.crl', rstrip=False) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/requirements.yml b/collections-debian-merged/ansible_collections/community/crypto/tests/requirements.yml new file mode 100644 index 00000000..5a2c9c80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/requirements.yml @@ -0,0 +1,3 @@ +integration_tests_dependencies: +- community.general +unit_tests_dependencies: [] diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.json b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.json new file mode 100644 index 00000000..c789a7fd --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.json @@ -0,0 +1,7 @@ +{ + "include_symlinks": true, + "prefixes": [ + "plugins/" + ], + "output": "path-message" +} diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.py b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.py new file mode 100755 index 00000000..49806f2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/no-unwanted-files.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +"""Prevent unwanted files from being added to the source tree.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys + + +def main(): + """Main entry point.""" + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + allowed_extensions = ( + '.cs', + '.ps1', + '.psm1', + '.py', + ) + + skip_paths = set([ + ]) + + skip_directories = ( + ) + + for path in paths: + if path in skip_paths: + continue + + if any(path.startswith(skip_directory) for skip_directory in skip_directories): + continue + + ext = os.path.splitext(path)[1] + + if ext not in allowed_extensions: + print('%s: extension must be one of: %s' % (path, ', '.join(allowed_extensions))) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.json b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.json new file mode 100644 index 00000000..83d27fb0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.json @@ -0,0 +1,15 @@ +{ + "disabled": true, + "all_targets": true, + "ignore_self": true, + "extensions": [ + ".py" + ], + "prefixes": [ + "plugins/module_utils/compat/" + ], + "output": "path-message", + "requirements": [ + "requests" + ] +} diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.py b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.py new file mode 100755 index 00000000..09f3c296 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/extra/update-bundled.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +This test checks whether the libraries we're bundling are out of date and need to be synced with +a newer upstream release. +""" + + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import re +import sys +from distutils.version import LooseVersion + +import packaging.specifiers + +import requests + + +BUNDLED_RE = re.compile(b'\\b_BUNDLED_METADATA\\b') + + +def get_bundled_libs(paths): + """ + Return the set of known bundled libraries + + :arg paths: The paths which the test has been instructed to check + :returns: The list of all files which we know to contain bundled libraries. If a bundled + library consists of multiple files, this should be the file which has metadata included. + """ + bundled_libs = set() + bundled_libs.add('plugins/module_utils/compat/ipaddress.py') + + return bundled_libs + + +def get_files_with_bundled_metadata(paths): + """ + Search for any files which have bundled metadata inside of them + + :arg paths: Iterable of filenames to search for metadata inside of + :returns: A set of pathnames which contained metadata + """ + + with_metadata = set() + for path in paths: + with open(path, 'rb') as f: + body = f.read() + + if BUNDLED_RE.search(body): + with_metadata.add(path) + + return with_metadata + + +def get_bundled_metadata(filename): + """ + Retrieve the metadata about a bundled library from a python file + + :arg filename: The filename to look inside for the metadata + :raises ValueError: If we're unable to extract metadata from the file + :returns: The metadata from the python file + """ + with open(filename, 'r') as module: + for line in module: + if line.strip().startswith('_BUNDLED_METADATA'): + data = line[line.index('{'):].strip() + break + else: + raise ValueError('Unable to check bundled library for update. Please add' + ' _BUNDLED_METADATA dictionary to the library file with' + ' information on pypi name and bundled version.') + metadata = json.loads(data) + return metadata + + +def get_latest_applicable_version(pypi_data, constraints=None): + """Get the latest pypi version of the package that we allow + + :arg pypi_data: Pypi information about the data as returned by + ``https://pypi.org/pypi/{pkg_name}/json`` + :kwarg constraints: version constraints on what we're allowed to use as specified by + the bundled metadata + :returns: The most recent version on pypi that are allowed by ``constraints`` + """ + latest_version = "0" + if constraints: + version_specification = packaging.specifiers.SpecifierSet(constraints) + for version in pypi_data['releases']: + if version in version_specification: + if LooseVersion(version) > LooseVersion(latest_version): + latest_version = version + else: + latest_version = pypi_data['info']['version'] + + return latest_version + + +def main(): + """Entrypoint to the script""" + + paths = sys.argv[1:] or sys.stdin.read().splitlines() + + bundled_libs = get_bundled_libs(paths) + files_with_bundled_metadata = get_files_with_bundled_metadata(paths) + + for filename in files_with_bundled_metadata.difference(bundled_libs): + print('{0}: ERROR: File contains _BUNDLED_METADATA but needs to be added to' + ' test/sanity/code-smell/update-bundled.py'.format(filename)) + + for filename in bundled_libs: + try: + metadata = get_bundled_metadata(filename) + except ValueError as e: + print('{0}: ERROR: {1}'.format(filename, e)) + continue + except (IOError, OSError) as e: + if e.errno == 2: + print('{0}: ERROR: {1}. Perhaps the bundled library has been removed' + ' or moved and the bundled library test needs to be modified as' + ' well?'.format(filename, e)) + + pypi_r = requests.get('https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name'])) + pypi_data = pypi_r.json() + + constraints = metadata.get('version_constraints', None) + latest_version = get_latest_applicable_version(pypi_data, constraints) + + if LooseVersion(metadata['version']) < LooseVersion(latest_version): + print('{0}: UPDATE {1} from {2} to {3} {4}'.format( + filename, + metadata['pypi_name'], + metadata['version'], + latest_version, + 'https://pypi.org/pypi/{0}/json'.format(metadata['pypi_name']))) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.10.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.10.txt new file mode 100644 index 00000000..43335011 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.10.txt @@ -0,0 +1,8 @@ +plugins/module_utils/compat/ipaddress.py future-import-boilerplate +plugins/module_utils/compat/ipaddress.py metaclass-boilerplate +plugins/module_utils/compat/ipaddress.py no-assert +plugins/module_utils/compat/ipaddress.py no-unicode-literals +plugins/module_utils/crypto/__init__.py empty-init +plugins/modules/acme_account_info.py validate-modules:return-syntax-error +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.11.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.11.txt new file mode 100644 index 00000000..43335011 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.11.txt @@ -0,0 +1,8 @@ +plugins/module_utils/compat/ipaddress.py future-import-boilerplate +plugins/module_utils/compat/ipaddress.py metaclass-boilerplate +plugins/module_utils/compat/ipaddress.py no-assert +plugins/module_utils/compat/ipaddress.py no-unicode-literals +plugins/module_utils/crypto/__init__.py empty-init +plugins/modules/acme_account_info.py validate-modules:return-syntax-error +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.9.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.9.txt new file mode 100644 index 00000000..f12ed5f7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/sanity/ignore-2.9.txt @@ -0,0 +1,7 @@ +plugins/module_utils/compat/ipaddress.py future-import-boilerplate +plugins/module_utils/compat/ipaddress.py metaclass-boilerplate +plugins/module_utils/compat/ipaddress.py no-assert +plugins/module_utils/compat/ipaddress.py no-unicode-literals +plugins/module_utils/crypto/__init__.py empty-init +tests/utils/shippable/check_matrix.py replace-urlopen +tests/utils/shippable/timing.py shebang diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/builtins.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/builtins.py new file mode 100644 index 00000000..f60ee678 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/mock.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/mock.py new file mode 100644 index 00000000..0972cd2e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/unittest.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/unittest.py new file mode 100644 index 00000000..98f08ad6 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/compat/unittest.py @@ -0,0 +1,38 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/loader.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/loader.py new file mode 100644 index 00000000..0ee47fbb --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/loader.py @@ -0,0 +1,116 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.parsing.dataloader import DataLoader +from ansible.module_utils._text import to_bytes, to_text + + +class DictDataLoader(DataLoader): + + def __init__(self, file_mapping=None): + file_mapping = {} if file_mapping is None else file_mapping + assert type(file_mapping) == dict + + super(DictDataLoader, self).__init__() + + self._file_mapping = file_mapping + self._build_known_directories() + self._vault_secrets = None + + def load_from_file(self, path, cache=True, unsafe=False): + path = to_text(path) + if path in self._file_mapping: + return self.load(self._file_mapping[path], path) + return None + + # TODO: the real _get_file_contents returns a bytestring, so we actually convert the + # unicode/text it's created with to utf-8 + def _get_file_contents(self, path): + path = to_text(path) + if path in self._file_mapping: + return (to_bytes(self._file_mapping[path]), False) + else: + raise AnsibleParserError("file not found: %s" % path) + + def path_exists(self, path): + path = to_text(path) + return path in self._file_mapping or path in self._known_directories + + def is_file(self, path): + path = to_text(path) + return path in self._file_mapping + + def is_directory(self, path): + path = to_text(path) + return path in self._known_directories + + def list_directory(self, path): + ret = [] + path = to_text(path) + for x in (list(self._file_mapping.keys()) + self._known_directories): + if x.startswith(path): + if os.path.dirname(x) == path: + ret.append(os.path.basename(x)) + return ret + + def is_executable(self, path): + # FIXME: figure out a way to make paths return true for this + return False + + def _add_known_directory(self, directory): + if directory not in self._known_directories: + self._known_directories.append(directory) + + def _build_known_directories(self): + self._known_directories = [] + for path in self._file_mapping: + dirname = os.path.dirname(path) + while dirname not in ('/', ''): + self._add_known_directory(dirname) + dirname = os.path.dirname(dirname) + + def push(self, path, content): + rebuild_dirs = False + if path not in self._file_mapping: + rebuild_dirs = True + + self._file_mapping[path] = content + + if rebuild_dirs: + self._build_known_directories() + + def pop(self, path): + if path in self._file_mapping: + del self._file_mapping[path] + self._build_known_directories() + + def clear(self): + self._file_mapping = dict() + self._known_directories = [] + + def get_basedir(self): + return os.getcwd() + + def set_vault_secrets(self, vault_secrets): + self._vault_secrets = vault_secrets diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/path.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/path.py new file mode 100644 index 00000000..a6c87429 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/path.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.crypto.tests.unit.compat.mock import MagicMock +from ansible.utils.path import unfrackpath + + +mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/procenv.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/procenv.py new file mode 100644 index 00000000..866f7bf8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/procenv.py @@ -0,0 +1,90 @@ +# (c) 2016, Matt Davis <mdavis@ansible.com> +# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import json + +from contextlib import contextmanager +from io import BytesIO, StringIO +from ansible_collections.community.crypto.tests.unit.compat import unittest +from ansible.module_utils.six import PY3 +from ansible.module_utils._text import to_bytes + + +@contextmanager +def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): + """ + context manager that temporarily masks the test runner's values for stdin and argv + """ + real_stdin = sys.stdin + real_argv = sys.argv + + if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + else: + fake_stream = BytesIO(to_bytes(stdin_data)) + + try: + sys.stdin = fake_stream + sys.argv = argv_data + + yield + finally: + sys.stdin = real_stdin + sys.argv = real_argv + + +@contextmanager +def swap_stdout(): + """ + context manager that temporarily replaces stdout for tests that need to verify output + """ + old_stdout = sys.stdout + + if PY3: + fake_stream = StringIO() + else: + fake_stream = BytesIO() + + try: + sys.stdout = fake_stream + + yield fake_stream + finally: + sys.stdout = old_stdout + + +class ModuleTestCase(unittest.TestCase): + def setUp(self, module_args=None): + if module_args is None: + module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} + + args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) + + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap = swap_stdin_and_argv(stdin_data=args) + self.stdin_swap.__enter__() + + def tearDown(self): + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap.__exit__(None, None, None) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/vault_helper.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/vault_helper.py new file mode 100644 index 00000000..dcce9c78 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/vault_helper.py @@ -0,0 +1,39 @@ +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_bytes + +from ansible.parsing.vault import VaultSecret + + +class TextVaultSecret(VaultSecret): + '''A secret piece of text. ie, a password. Tracks text encoding. + + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.''' + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or 'utf-8' + self._bytes = _bytes + self.errors = errors or 'strict' + + @property + def bytes(self): + '''The text encoded with encoding, unless we specifically set _bytes.''' + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/yaml_helper.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/yaml_helper.py new file mode 100644 index 00000000..bf0aa8d8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/mock/yaml_helper.py @@ -0,0 +1,124 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import io +import yaml + +from ansible.module_utils.six import PY3 +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.parsing.yaml.dumper import AnsibleDumper + + +class YamlTestUtils(object): + """Mixin class to combine with a unittest.TestCase subclass.""" + def _loader(self, stream): + """Vault related tests will want to override this. + + Vault cases should setup a AnsibleLoader that has the vault password.""" + return AnsibleLoader(stream) + + def _dump_stream(self, obj, stream, dumper=None): + """Dump to a py2-unicode or py3-string stream.""" + if PY3: + return yaml.dump(obj, stream, Dumper=dumper) + else: + return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + + def _dump_string(self, obj, dumper=None): + """Dump to a py2-unicode or py3-string""" + if PY3: + return yaml.dump(obj, Dumper=dumper) + else: + return yaml.dump(obj, Dumper=dumper, encoding=None) + + def _dump_load_cycle(self, obj): + # Each pass though a dump or load revs the 'generation' + # obj to yaml string + string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) + + # wrap a stream/file like StringIO around that yaml + stream_from_object_dump = io.StringIO(string_from_object_dump) + loader = self._loader(stream_from_object_dump) + # load the yaml stream to create a new instance of the object (gen 2) + obj_2 = loader.get_data() + + # dump the gen 2 objects directory to strings + string_from_object_dump_2 = self._dump_string(obj_2, + dumper=AnsibleDumper) + + # The gen 1 and gen 2 yaml strings + self.assertEqual(string_from_object_dump, string_from_object_dump_2) + # the gen 1 (orig) and gen 2 py object + self.assertEqual(obj, obj_2) + + # again! gen 3... load strings into py objects + stream_3 = io.StringIO(string_from_object_dump_2) + loader_3 = self._loader(stream_3) + obj_3 = loader_3.get_data() + + string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) + + self.assertEqual(obj, obj_3) + # should be transitive, but... + self.assertEqual(obj_2, obj_3) + self.assertEqual(string_from_object_dump, string_from_object_dump_3) + + def _old_dump_load_cycle(self, obj): + '''Dump the passed in object to yaml, load it back up, dump again, compare.''' + stream = io.StringIO() + + yaml_string = self._dump_string(obj, dumper=AnsibleDumper) + self._dump_stream(obj, stream, dumper=AnsibleDumper) + + yaml_string_from_stream = stream.getvalue() + + # reset stream + stream.seek(0) + + loader = self._loader(stream) + # loader = AnsibleLoader(stream, vault_password=self.vault_password) + obj_from_stream = loader.get_data() + + stream_from_string = io.StringIO(yaml_string) + loader2 = self._loader(stream_from_string) + # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) + obj_from_string = loader2.get_data() + + stream_obj_from_stream = io.StringIO() + stream_obj_from_string = io.StringIO() + + if PY3: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) + else: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None) + + yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() + yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() + + stream_obj_from_stream.seek(0) + stream_obj_from_string.seek(0) + + if PY3: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) + else: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None) + + assert yaml_string == yaml_string_obj_from_stream + assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream == + yaml_string_stream_obj_from_string) + assert obj == obj_from_stream + assert obj == obj_from_string + assert obj == yaml_string_obj_from_stream + assert obj == yaml_string_obj_from_string + assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + return {'obj': obj, + 'yaml_string': yaml_string, + 'yaml_string_from_stream': yaml_string_from_stream, + 'obj_from_stream': obj_from_stream, + 'obj_from_string': obj_from_string, + 'yaml_string_obj_from_string': yaml_string_obj_from_string} diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.pem new file mode 100644 index 00000000..bb4aca51 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATugAwIBAgIBATAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwthbnNpYmxl +LmNvbTAeFw0xODExMjUxNTI4MjNaFw0xODExMjYxNTI4MjRaMBYxFDASBgNVBAMT +C2Fuc2libGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAJz0yAAXAwEm +OhTRkjXxwgedbWO6gobYM3lWszrS68G8QSzhXR6AmQ3IzZDimnTTXO7XhVylDT8S +LzE44/Epm6N6MHgwIwYDVR0RBBwwGoILZXhhbXBsZS5jb22CC2V4YW1wbGUub3Jn +MAwGA1UdEwEB/wQCMAAwDwYDVR0PAQH/BAUDAweAADATBgNVHSUEDDAKBggrBgEF +BQcDATAdBgNVHQ4EFgQUmNL9PMzNaUX74owwLFRiGDS3B3MwCgYIKoZIzj0EAwID +SQAwRgIhALz7Ur96ky0OfM5D9MwFmCg2jccqm/UglGI9+4KeOEIyAiEAwFX4tdll +QSrd1HY/jMsHwdK5wH3JkK/9+fGwyRP11VI= +-----END CERTIFICATE----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem new file mode 100644 index 00000000..8fc37c40 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem @@ -0,0 +1,9 @@ +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs +4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE +MCMGA1UdEQQcMBqCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZzAMBgNVHRMBAf8E +AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl +FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr +URnCJfTLr2T3 +-----END NEW CERTIFICATE REQUEST----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.txt new file mode 100644 index 00000000..37c5cbda --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_1.txt @@ -0,0 +1,28 @@ +Certificate Request: + Data: + Version: 1 (0x0) + Subject: CN = ansible.com + Subject Public Key Info: + Public Key Algorithm: id-ecPublicKey + Public-Key: (256 bit) + pub: + 04:00:9c:f4:c8:00:17:03:01:26:3a:14:d1:92:35: + f1:c2:07:9d:6d:63:ba:82:86:d8:33:79:56:b3:3a: + d2:eb:c1:bc:41:2c:e1:5d:1e:80:99:0d:c8:cd:90: + e2:9a:74:d3:5c:ee:d7:85:5c:a5:0d:3f:12:2f:31: + 38:e3:f1:29:9b + ASN1 OID: prime256v1 + NIST CURVE: P-256 + Attributes: + Requested Extensions: + X509v3 Subject Alternative Name: + DNS:example.com, DNS:example.org + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Key Usage: critical + Digital Signature + Signature Algorithm: ecdsa-with-SHA256 + 30:44:02:20:70:3c:a8:46:6c:05:54:10:e5:16:f6:c5:66:d8: + 92:77:9c:26:25:4d:65:b4:ce:89:b5:c7:e7:2d:69:e3:63:9e: + 02:20:2a:ee:38:1c:ab:ae:8a:45:52:43:8a:29:be:31:5c:e6: + 2c:81:a0:d3:f7:75:ab:51:19:c2:25:f4:cb:af:64:f7 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.pem new file mode 100644 index 00000000..295a26e1 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEqjCCApICAQAwADCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANv1 +V7gDsh76O//d9wclBcW6kNpWeR6eAggzThwbMZjcO7GFHQsBZCZGGVdyS37uhejc +RrIBdtDDWXhoh3Dz+GQxD+6GuwAEFyL1F3MfT0v1HHoO8fE74G5mD6+ZA2HRDeU9 +jf8BPyVWHBtNbCmJGSlSNOFejWCmwvsLARQxqFBuTyRjgos4BkLyWMqZRukrzO1P +z7IBhuFrB608t+AG4vGnPXZNM7xefhzO8bPOiepT0YS2ERPkFmOy97SnwTGdKykw +ZYM9oKukYhE4Z+yOaTFpJMBNXwDCI5TMnhtc6eJrf5sOFH92n2E9+YWMoahUOiTw +G6XV5HfSpySpwORUaTITQRsPAM+bmK9f1jB6ctfFVwpa8uW/h8pSgbHgZvkeD6s6 +rFLh9TQ24t0vrRmhnY7/AMFgbgJoBTBq0l0lEXS4FCGKDGqQOqSws+eHR/pHA4uY +v8d498SQl9fYsT/c7Uj3/JnMSRVN942yQUFCzwLf0/WzWCi2HTqPM8CPh5ryiJ30 +GAN2eb026/noyTOXm479Tg9o86Tw9qczE0j0CdcRnr6J337RGHQg58PZ7j+hnUmK +wgyclyvjE10ZFBgToMGSnzYp5UeRcOFZ3bnK6LOsGC75mIvz2OQgSQeO5VQASEnO +9uhygNyo91sK4BtVroloit8ZCa82LlsHSCj/mMzPAgMBAAGgZTBjBgkqhkiG9w0B +CQ4xVjBUMFIGA1UdEQRLMEmCC2Fuc2libGUuY29thwR/AAABhxAAAAAAAAAAAAAA +AAAAAAABhxAgAQ2IrBD+AQAAAAAAAAAAhxAgARI0VnirzZh2VDIQ/ty6MA0GCSqG +SIb3DQEBCwUAA4ICAQBFRuANzVRcze+iur0YevjtYIXDa03GoWWkgnLuE8u8epTM +2248duG3TmvVvxWPN4iFrvFcZIvNsevBo+Z7kXJ24m3YldtXvwfAYmCZ062apSoh +yzgo3Q0KfDehwLcoJPe5bh+jbbgJVGGvJug/QFyHSVl+iGyFUXE7pwafl9LuNDi3 +yfOYZLIQ34mBH4Rsvymj9xSTYliWDEEU/o7RrrZeEqkOxNeLh64LbnifdrYUputz +yBURg2xs9hpAsytZJX90iJW8aYPM1aQ7eetqTViIRoqUAmIQobnKlNnpOliBHl+p +RY+AtTnsfAetKUP7OsAZkHRTGAXx0JHJQ1ITY8w5Dcw/v1bDCbAfkDubBP3X+us9 +RQk2h6m74hWFFNu9xOfkNejPf7h4gywfDjo/wGZFSWKyi6avB9V53znZgRUwc009 +p5MM9e37MH8pyBqfnbSwOj4hUoyecRCIAFdywjMb9akP2u15XP3MOtJOEvecyCxN +TZBxupTg65zB47GeSAufnc8FaTZkE8xPuCtbvqOVOkWYqzlqNdCfK8f3AZdlpwLh +38wdUm5G7LIu6aQNiY66aQs9qVpoGvqdmxHRkuSwqwZxGgzcY1yJaWGXQ6R4jgC3 +VKlMTUVs1WYV6jrYLHcVt6Rn/2FVTOns3Jn6cTPOdKViYoqF+yW8yCEAqAskZw== +-----END CERTIFICATE REQUEST----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.txt new file mode 100644 index 00000000..7a54ee3f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/csr_2.txt @@ -0,0 +1,78 @@ +Certificate Request: + Data: + Version: 1 (0x0) + Subject: + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (4096 bit) + Modulus: + 00:db:f5:57:b8:03:b2:1e:fa:3b:ff:dd:f7:07:25: + 05:c5:ba:90:da:56:79:1e:9e:02:08:33:4e:1c:1b: + 31:98:dc:3b:b1:85:1d:0b:01:64:26:46:19:57:72: + 4b:7e:ee:85:e8:dc:46:b2:01:76:d0:c3:59:78:68: + 87:70:f3:f8:64:31:0f:ee:86:bb:00:04:17:22:f5: + 17:73:1f:4f:4b:f5:1c:7a:0e:f1:f1:3b:e0:6e:66: + 0f:af:99:03:61:d1:0d:e5:3d:8d:ff:01:3f:25:56: + 1c:1b:4d:6c:29:89:19:29:52:34:e1:5e:8d:60:a6: + c2:fb:0b:01:14:31:a8:50:6e:4f:24:63:82:8b:38: + 06:42:f2:58:ca:99:46:e9:2b:cc:ed:4f:cf:b2:01: + 86:e1:6b:07:ad:3c:b7:e0:06:e2:f1:a7:3d:76:4d: + 33:bc:5e:7e:1c:ce:f1:b3:ce:89:ea:53:d1:84:b6: + 11:13:e4:16:63:b2:f7:b4:a7:c1:31:9d:2b:29:30: + 65:83:3d:a0:ab:a4:62:11:38:67:ec:8e:69:31:69: + 24:c0:4d:5f:00:c2:23:94:cc:9e:1b:5c:e9:e2:6b: + 7f:9b:0e:14:7f:76:9f:61:3d:f9:85:8c:a1:a8:54: + 3a:24:f0:1b:a5:d5:e4:77:d2:a7:24:a9:c0:e4:54: + 69:32:13:41:1b:0f:00:cf:9b:98:af:5f:d6:30:7a: + 72:d7:c5:57:0a:5a:f2:e5:bf:87:ca:52:81:b1:e0: + 66:f9:1e:0f:ab:3a:ac:52:e1:f5:34:36:e2:dd:2f: + ad:19:a1:9d:8e:ff:00:c1:60:6e:02:68:05:30:6a: + d2:5d:25:11:74:b8:14:21:8a:0c:6a:90:3a:a4:b0: + b3:e7:87:47:fa:47:03:8b:98:bf:c7:78:f7:c4:90: + 97:d7:d8:b1:3f:dc:ed:48:f7:fc:99:cc:49:15:4d: + f7:8d:b2:41:41:42:cf:02:df:d3:f5:b3:58:28:b6: + 1d:3a:8f:33:c0:8f:87:9a:f2:88:9d:f4:18:03:76: + 79:bd:36:eb:f9:e8:c9:33:97:9b:8e:fd:4e:0f:68: + f3:a4:f0:f6:a7:33:13:48:f4:09:d7:11:9e:be:89: + df:7e:d1:18:74:20:e7:c3:d9:ee:3f:a1:9d:49:8a: + c2:0c:9c:97:2b:e3:13:5d:19:14:18:13:a0:c1:92: + 9f:36:29:e5:47:91:70:e1:59:dd:b9:ca:e8:b3:ac: + 18:2e:f9:98:8b:f3:d8:e4:20:49:07:8e:e5:54:00: + 48:49:ce:f6:e8:72:80:dc:a8:f7:5b:0a:e0:1b:55: + ae:89:68:8a:df:19:09:af:36:2e:5b:07:48:28:ff: + 98:cc:cf + Exponent: 65537 (0x10001) + Attributes: + Requested Extensions: + X509v3 Subject Alternative Name: + DNS:ansible.com, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1, IP Address:2001:D88:AC10:FE01:0:0:0:0, IP Address:2001:1234:5678:ABCD:9876:5432:10FE:DCBA + Signature Algorithm: sha256WithRSAEncryption + 45:46:e0:0d:cd:54:5c:cd:ef:a2:ba:bd:18:7a:f8:ed:60:85: + c3:6b:4d:c6:a1:65:a4:82:72:ee:13:cb:bc:7a:94:cc:db:6e: + 3c:76:e1:b7:4e:6b:d5:bf:15:8f:37:88:85:ae:f1:5c:64:8b: + cd:b1:eb:c1:a3:e6:7b:91:72:76:e2:6d:d8:95:db:57:bf:07: + c0:62:60:99:d3:ad:9a:a5:2a:21:cb:38:28:dd:0d:0a:7c:37: + a1:c0:b7:28:24:f7:b9:6e:1f:a3:6d:b8:09:54:61:af:26:e8: + 3f:40:5c:87:49:59:7e:88:6c:85:51:71:3b:a7:06:9f:97:d2: + ee:34:38:b7:c9:f3:98:64:b2:10:df:89:81:1f:84:6c:bf:29: + a3:f7:14:93:62:58:96:0c:41:14:fe:8e:d1:ae:b6:5e:12:a9: + 0e:c4:d7:8b:87:ae:0b:6e:78:9f:76:b6:14:a6:eb:73:c8:15: + 11:83:6c:6c:f6:1a:40:b3:2b:59:25:7f:74:88:95:bc:69:83: + cc:d5:a4:3b:79:eb:6a:4d:58:88:46:8a:94:02:62:10:a1:b9: + ca:94:d9:e9:3a:58:81:1e:5f:a9:45:8f:80:b5:39:ec:7c:07: + ad:29:43:fb:3a:c0:19:90:74:53:18:05:f1:d0:91:c9:43:52: + 13:63:cc:39:0d:cc:3f:bf:56:c3:09:b0:1f:90:3b:9b:04:fd: + d7:fa:eb:3d:45:09:36:87:a9:bb:e2:15:85:14:db:bd:c4:e7: + e4:35:e8:cf:7f:b8:78:83:2c:1f:0e:3a:3f:c0:66:45:49:62: + b2:8b:a6:af:07:d5:79:df:39:d9:81:15:30:73:4d:3d:a7:93: + 0c:f5:ed:fb:30:7f:29:c8:1a:9f:9d:b4:b0:3a:3e:21:52:8c: + 9e:71:10:88:00:57:72:c2:33:1b:f5:a9:0f:da:ed:79:5c:fd: + cc:3a:d2:4e:12:f7:9c:c8:2c:4d:4d:90:71:ba:94:e0:eb:9c: + c1:e3:b1:9e:48:0b:9f:9d:cf:05:69:36:64:13:cc:4f:b8:2b: + 5b:be:a3:95:3a:45:98:ab:39:6a:35:d0:9f:2b:c7:f7:01:97: + 65:a7:02:e1:df:cc:1d:52:6e:46:ec:b2:2e:e9:a4:0d:89:8e: + ba:69:0b:3d:a9:5a:68:1a:fa:9d:9b:11:d1:92:e4:b0:ab:06: + 71:1a:0c:dc:63:5c:89:69:61:97:43:a4:78:8e:00:b7:54:a9: + 4c:4d:45:6c:d5:66:15:ea:3a:d8:2c:77:15:b7:a4:67:ff:61: + 55:4c:e9:ec:dc:99:fa:71:33:ce:74:a5:62:62:8a:85:fb:25: + bc:c8:21:00:a8:0b:24:67 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.pem b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.pem new file mode 100644 index 00000000..97209eda --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDWajU0PyhYKeulfy/luNtkAve7DkwQ01bXJ97zbxB66oAoGCCqGSM49 +AwEHoUQDQgAEAJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3lWszrS68G8QSzhXR6A +mQ3IzZDimnTTXO7XhVylDT8SLzE44/Epmw== +-----END EC PRIVATE KEY----- diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.txt new file mode 100644 index 00000000..e25cfd45 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/privatekey_1.txt @@ -0,0 +1,14 @@ +read EC key +Private-Key: (256 bit) +priv: + 35:9a:8d:4d:0f:ca:16:0a:7a:e9:5f:cb:f9:6e:36: + d9:00:bd:ee:c3:93:04:34:d5:b5:c9:f7:bc:db:c4: + 1e:ba +pub: + 04:00:9c:f4:c8:00:17:03:01:26:3a:14:d1:92:35: + f1:c2:07:9d:6d:63:ba:82:86:d8:33:79:56:b3:3a: + d2:eb:c1:bc:41:2c:e1:5d:1e:80:99:0d:c8:cd:90: + e2:9a:74:d3:5c:ee:d7:85:5c:a5:0d:3f:12:2f:31: + 38:e3:f1:29:9b +ASN1 OID: prime256v1 +NIST CURVE: P-256 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_acme.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_acme.py new file mode 100644 index 00000000..6fe90e6d --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_acme.py @@ -0,0 +1,216 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import base64 +import datetime +import os.path +import pytest + +from mock import MagicMock + + +from ansible_collections.community.crypto.plugins.module_utils.acme import ( + HAS_CURRENT_CRYPTOGRAPHY, + nopad_b64, + write_file, + read_file, + pem_to_der, + _parse_key_openssl, + # _sign_request_openssl, + _parse_key_cryptography, + # _sign_request_cryptography, + _normalize_ip, + openssl_get_csr_identifiers, + cryptography_get_csr_identifiers, + cryptography_get_cert_days, +) + + +def load_fixture(name): + with open(os.path.join(os.path.dirname(__file__), 'fixtures', name)) as f: + return f.read() + + +################################################ + +NOPAD_B64 = [ + ("", ""), + ("\n", "Cg"), + ("123", "MTIz"), + ("Lorem?ipsum", "TG9yZW0_aXBzdW0"), +] + + +@pytest.mark.parametrize("value, result", NOPAD_B64) +def test_nopad_b64(value, result): + assert nopad_b64(value.encode('utf-8')) == result + + +################################################ + +TEST_TEXT = r"""1234 +5678""" + + +def test_read_file(tmpdir): + fn = tmpdir / 'test.txt' + fn.write(TEST_TEXT) + assert read_file(str(fn), 't') == TEST_TEXT + assert read_file(str(fn), 'b') == TEST_TEXT.encode('utf-8') + + +def test_write_file(tmpdir): + fn = tmpdir / 'test.txt' + module = MagicMock() + write_file(module, str(fn), TEST_TEXT.encode('utf-8')) + assert fn.read() == TEST_TEXT + + +################################################ + +TEST_PEM_DERS = [ + ( + load_fixture('privatekey_1.pem'), + base64.b64decode('MHcCAQEEIDWajU0PyhYKeulfy/luNtkAve7DkwQ01bXJ97zbxB66oAo' + 'GCCqGSM49AwEHoUQDQgAEAJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3' + 'lWszrS68G8QSzhXR6AmQ3IzZDimnTTXO7XhVylDT8SLzE44/Epmw==') + ) +] + + +@pytest.mark.parametrize("pem, der", TEST_PEM_DERS) +def test_pem_to_der(pem, der, tmpdir): + fn = tmpdir / 'test.pem' + fn.write(pem) + assert pem_to_der(str(fn)) == der + + +################################################ + +TEST_KEYS = [ + ( + load_fixture('privatekey_1.pem'), + { + 'alg': 'ES256', + 'hash': 'sha256', + 'jwk': { + 'crv': 'P-256', + 'kty': 'EC', + 'x': 'AJz0yAAXAwEmOhTRkjXxwgedbWO6gobYM3lWszrS68E', + 'y': 'vEEs4V0egJkNyM2Q4pp001zu14VcpQ0_Ei8xOOPxKZs', + }, + 'point_size': 32, + 'type': 'ec', + }, + load_fixture('privatekey_1.txt'), + ) +] + + +@pytest.mark.parametrize("pem, result, openssl_output", TEST_KEYS) +def test_eckeyparse_openssl(pem, result, openssl_output, tmpdir): + fn = tmpdir / 'test.key' + fn.write(pem) + module = MagicMock() + module.run_command = MagicMock(return_value=(0, openssl_output, 0)) + error, key = _parse_key_openssl('openssl', module, key_file=str(fn)) + assert error is None + key.pop('key_file') + assert key == result + + +if HAS_CURRENT_CRYPTOGRAPHY: + @pytest.mark.parametrize("pem, result, dummy", TEST_KEYS) + def test_eckeyparse_cryptography(pem, result, dummy): + module = MagicMock() + error, key = _parse_key_cryptography(module, key_content=pem) + assert error is None + key.pop('key_obj') + assert key == result + + +################################################ + +TEST_IPS = [ + ("0:0:0:0:0:0:0:1", "::1"), + ("1::0:2", "1::2"), + ("0000:0001:0000:0000:0000:0000:0000:0001", "0:1::1"), + ("0000:0001:0000:0000:0001:0000:0000:0001", "0:1::1:0:0:1"), + ("0000:0001:0000:0001:0000:0001:0000:0001", "0:1:0:1:0:1:0:1"), + ("0.0.0.0", "0.0.0.0"), + ("000.001.000.000", "0.1.0.0"), + ("2001:d88:ac10:fe01:0:0:0:0", "2001:d88:ac10:fe01::"), + ("0000:0000:0000:0000:0000:0000:0000:0000", "::"), +] + + +@pytest.mark.parametrize("ip, result", TEST_IPS) +def test_normalize_ip(ip, result): + assert _normalize_ip(ip) == result + + +################################################ + +TEST_CSRS = [ + ( + load_fixture('csr_1.pem'), + set([ + ('dns', 'ansible.com'), + ('dns', 'example.com'), + ('dns', 'example.org') + ]), + load_fixture('csr_1.txt'), + ), + ( + load_fixture('csr_2.pem'), + set([ + ('dns', 'ansible.com'), + ('ip', '127.0.0.1'), + ('ip', '::1'), + ('ip', '2001:d88:ac10:fe01::'), + ('ip', '2001:1234:5678:abcd:9876:5432:10fe:dcba') + ]), + load_fixture('csr_2.txt'), + ), +] + + +@pytest.mark.parametrize("csr, result, openssl_output", TEST_CSRS) +def test_csridentifiers_openssl(csr, result, openssl_output, tmpdir): + fn = tmpdir / 'test.csr' + fn.write(csr) + module = MagicMock() + module.run_command = MagicMock(return_value=(0, openssl_output, 0)) + identifiers = openssl_get_csr_identifiers('openssl', module, str(fn)) + assert identifiers == result + + +if HAS_CURRENT_CRYPTOGRAPHY: + @pytest.mark.parametrize("csr, result, openssl_output", TEST_CSRS) + def test_csridentifiers_cryptography(csr, result, openssl_output, tmpdir): + fn = tmpdir / 'test.csr' + fn.write(csr) + module = MagicMock() + identifiers = cryptography_get_csr_identifiers(module, str(fn)) + assert identifiers == result + + +################################################ + +TEST_CERT = load_fixture("cert_1.pem") + +TEST_CERT_DAYS = [ + (datetime.datetime(2018, 11, 15, 1, 2, 3), 11), + (datetime.datetime(2018, 11, 25, 15, 20, 0), 1), + (datetime.datetime(2018, 11, 25, 15, 30, 0), 0), +] + + +if HAS_CURRENT_CRYPTOGRAPHY: + @pytest.mark.parametrize("now, expected_days", TEST_CERT_DAYS) + def test_certdays_cryptography(now, expected_days, tmpdir): + fn = tmpdir / 'test-cert.pem' + fn.write(TEST_CERT) + module = MagicMock() + days = cryptography_get_cert_days(module, str(fn), now=now) + assert days == expected_days diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/conftest.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/conftest.py new file mode 100644 index 00000000..89aecf85 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/conftest.py @@ -0,0 +1,72 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json +import sys +from io import BytesIO + +import pytest + +import ansible.module_utils.basic +from ansible.module_utils.six import PY3, string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def stdin(mocker, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ['ansible_unittest'] + + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the stdin pytest fixture') + + fake_stdin = BytesIO(to_bytes(args, errors='surrogate_or_strict')) + if PY3: + mocker.patch('ansible.module_utils.basic.sys.stdin', mocker.MagicMock()) + mocker.patch('ansible.module_utils.basic.sys.stdin.buffer', fake_stdin) + else: + mocker.patch('ansible.module_utils.basic.sys.stdin', fake_stdin) + + yield fake_stdin + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv + + +@pytest.fixture +def am(stdin, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ['ansible_unittest'] + + argspec = {} + if hasattr(request, 'param'): + if isinstance(request.param, dict): + argspec = request.param + + am = ansible.module_utils.basic.AnsibleModule( + argument_spec=argspec, + ) + am._name = 'ansible_unittest' + + yield am + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_asn1.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_asn1.py new file mode 100644 index 00000000..762ebfcc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_asn1.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# (c) 2020, Jordan Borean <jborean93@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import base64 +import re +import subprocess + +import pytest + +from ansible_collections.community.crypto.plugins.module_utils.crypto._asn1 import ( + serialize_asn1_string_as_der, + pack_asn1, +) + + +TEST_CASES = [ + ('UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + + ('EXPLICIT:10,UTF8:Hello World', b'\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('EXPLICIT:12U,UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('EXPLICIT:10A,UTF8:Hello World', b'\x6a\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('EXPLICIT:10P,UTF8:Hello World', b'\xea\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('EXPLICIT:10C,UTF8:Hello World', b'\xaa\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('EXPLICIT:1024P,UTF8:Hello World', b'\xff\x88\x00\x0d\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + + ('IMPLICIT:10,UTF8:Hello World', b'\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('IMPLICIT:12U,UTF8:Hello World', b'\x0c\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('IMPLICIT:10A,UTF8:Hello World', b'\x4a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('IMPLICIT:10P,UTF8:Hello World', b'\xca\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('IMPLICIT:10C,UTF8:Hello World', b'\x8a\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + ('IMPLICIT:1024P,UTF8:Hello World', b'\xdf\x88\x00\x0b\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64'), + + # Tests large data lengths, special logic for the length octet encoding. + ('UTF8:' + ('A' * 600), b'\x0c\x82\x02\x58' + (b'\x41' * 600)), + + # This isn't valid with openssl asn1parse but has been validated against an ASN.1 parser. OpenSSL seems to read the + # data u"café" encoded as UTF-8 bytes b"caf\xc3\xa9", decodes that internally with latin-1 (or similar variant) as + # u"café" then encodes that to UTF-8 b"caf\xc3\x83\xc2\xa9" for the UTF8String. Ultimately openssl is wrong here + # so we keep our assertion happening. + (u'UTF8:café', b'\x0c\x05\x63\x61\x66\xc3\xa9'), +] + + +@pytest.mark.parametrize('value, expected', TEST_CASES) +def test_serialize_asn1_string_as_der(value, expected): + actual = serialize_asn1_string_as_der(value) + print("%s | %s" % (value, base64.b16encode(actual).decode())) + assert actual == expected + + +@pytest.mark.parametrize('value', [ + 'invalid', + 'EXPLICIT,UTF:value', +]) +def test_serialize_asn1_string_as_der_invalid_format(value): + expected = "The ASN.1 serialized string must be in the format [modifier,]type[:value]" + with pytest.raises(ValueError, match=re.escape(expected)): + serialize_asn1_string_as_der(value) + + +def test_serialize_asn1_string_as_der_invalid_type(): + expected = "The ASN.1 serialized string is not a known type \"OID\", only UTF8 types are supported" + with pytest.raises(ValueError, match=re.escape(expected)): + serialize_asn1_string_as_der("OID:1.2.3.4") + + +def test_pack_asn_invalid_class(): + with pytest.raises(ValueError, match="tag_class must be between 0 and 3 not 4"): + pack_asn1(4, True, 0, b"") + + +@pytest.mark.skip() # This is to just to build the test case assertions and shouldn't run normally. +@pytest.mark.parametrize('value, expected', TEST_CASES) +def test_test_cases(value, expected, tmp_path): + test_file = tmp_path / 'test.der' + subprocess.run(['openssl', 'asn1parse', '-genstr', value, '-noout', '-out', test_file]) + + with open(test_file, mode='rb') as fd: + b_data = fd.read() + + hex_str = base64.b16encode(b_data).decode().lower() + print("%s | \\x%s" % (value, "\\x".join([hex_str[i:i + 2] for i in range(0, len(hex_str), 2)]))) + + # This is a know edge case where openssl asn1parse does not work properly. + if value != u'UTF8:café': + assert b_data == expected diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py new file mode 100644 index 00000000..6a5545b0 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_cryptography_support.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# (c) 2020, Jordan Borean <jborean93@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import pytest + +from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( + OpenSSLObjectError, +) + +from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( + cryptography_get_name, +) + + +def test_cryptography_get_name_invalid_prefix(): + with pytest.raises(OpenSSLObjectError, match="Cannot parse Subject Alternative Name"): + cryptography_get_name('fake:value') + + +def test_cryptography_get_name_other_name_no_oid(): + with pytest.raises(OpenSSLObjectError, match="Cannot parse Subject Alternative Name otherName"): + cryptography_get_name('otherName:value') + + +def test_cryptography_get_name_other_name_utfstring(): + actual = cryptography_get_name('otherName:1.3.6.1.4.1.311.20.2.3;UTF8:Hello World') + assert actual.type_id.dotted_string == '1.3.6.1.4.1.311.20.2.3' + assert actual.value == b'\x0c\x0bHello World' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/conftest.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/conftest.py new file mode 100644 index 00000000..5ecd9163 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/conftest.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import json + +import pytest + +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the patch_ansible_module pytest fixture') + + mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args)) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/test_luks_device.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/test_luks_device.py new file mode 100644 index 00000000..d798b447 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/test_luks_device.py @@ -0,0 +1,315 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import pytest +from ansible_collections.community.crypto.plugins.modules import luks_device +from ansible_collections.community.crypto.tests.unit.compat.mock import patch +from ansible.module_utils import basic + + +class DummyModule(object): + # module to mock AnsibleModule class + def __init__(self): + self.params = dict() + + def fail_json(self, msg=""): + raise ValueError(msg) + + def get_bin_path(self, command, dummy): + return command + + +# ===== Handler & CryptHandler methods tests ===== + +def test_generate_luks_name(monkeypatch): + module = DummyModule() + monkeypatch.setattr(luks_device.Handler, "_run_command", + lambda x, y: [0, "UUID", ""]) + crypt = luks_device.CryptHandler(module) + assert crypt.generate_luks_name("/dev/dummy") == "luks-UUID" + + +def test_get_container_name_by_device(monkeypatch): + module = DummyModule() + monkeypatch.setattr(luks_device.Handler, "_run_command", + lambda x, y: [0, "crypt container_name", ""]) + crypt = luks_device.CryptHandler(module) + assert crypt.get_container_name_by_device("/dev/dummy") == "container_name" + + +def test_get_container_device_by_name(monkeypatch): + module = DummyModule() + monkeypatch.setattr(luks_device.Handler, "_run_command", + lambda x, y: [0, "device: /dev/luksdevice", ""]) + crypt = luks_device.CryptHandler(module) + assert crypt.get_container_device_by_name("dummy") == "/dev/luksdevice" + + +def test_run_luks_remove(monkeypatch): + def run_command_check(self, command): + # check that wipefs command is actually called + assert command[0] == "wipefs" + return [0, "", ""] + + module = DummyModule() + monkeypatch.setattr(luks_device.CryptHandler, + "get_container_name_by_device", + lambda x, y: None) + monkeypatch.setattr(luks_device.Handler, + "_run_command", + run_command_check) + crypt = luks_device.CryptHandler(module) + crypt.run_luks_remove("dummy") + + +# ===== ConditionsHandler methods data and tests ===== + +# device, key, passphrase, state, is_luks, label, cipher, hash, expected +LUKS_CREATE_DATA = ( + ("dummy", "key", None, "present", False, None, "dummy", "dummy", True), + (None, "key", None, "present", False, None, "dummy", "dummy", False), + (None, "key", None, "present", False, "labelName", "dummy", "dummy", True), + ("dummy", None, None, "present", False, None, "dummy", "dummy", False), + ("dummy", "key", None, "absent", False, None, "dummy", "dummy", False), + ("dummy", "key", None, "opened", True, None, "dummy", "dummy", False), + ("dummy", "key", None, "closed", True, None, "dummy", "dummy", False), + ("dummy", "key", None, "present", True, None, "dummy", "dummy", False), + ("dummy", None, "foo", "present", False, None, "dummy", "dummy", True), + (None, None, "bar", "present", False, None, "dummy", "dummy", False), + (None, None, "baz", "present", False, "labelName", "dummy", "dummy", True), + ("dummy", None, None, "present", False, None, "dummy", "dummy", False), + ("dummy", None, "quz", "absent", False, None, "dummy", "dummy", False), + ("dummy", None, "qux", "opened", True, None, "dummy", "dummy", False), + ("dummy", None, "quux", "closed", True, None, "dummy", "dummy", False), + ("dummy", None, "corge", "present", True, None, "dummy", "dummy", False), + ("dummy", "key", None, "present", False, None, None, None, True), + ("dummy", "key", None, "present", False, None, None, "dummy", True), + ("dummy", "key", None, "present", False, None, "dummy", None, True)) + +# device, state, is_luks, expected +LUKS_REMOVE_DATA = ( + ("dummy", "absent", True, True), + (None, "absent", True, False), + ("dummy", "present", True, False), + ("dummy", "absent", False, False)) + +# device, key, passphrase, state, name, name_by_dev, expected +LUKS_OPEN_DATA = ( + ("dummy", "key", None, "present", "name", None, False), + ("dummy", "key", None, "absent", "name", None, False), + ("dummy", "key", None, "closed", "name", None, False), + ("dummy", "key", None, "opened", "name", None, True), + (None, "key", None, "opened", "name", None, False), + ("dummy", None, None, "opened", "name", None, False), + ("dummy", "key", None, "opened", "name", "name", False), + ("dummy", "key", None, "opened", "beer", "name", "exception"), + ("dummy", None, "foo", "present", "name", None, False), + ("dummy", None, "bar", "absent", "name", None, False), + ("dummy", None, "baz", "closed", "name", None, False), + ("dummy", None, "qux", "opened", "name", None, True), + (None, None, "quux", "opened", "name", None, False), + ("dummy", None, None, "opened", "name", None, False), + ("dummy", None, "quuz", "opened", "name", "name", False), + ("dummy", None, "corge", "opened", "beer", "name", "exception")) + +# device, dev_by_name, name, name_by_dev, state, label, expected +LUKS_CLOSE_DATA = ( + ("dummy", "dummy", "name", "name", "present", None, False), + ("dummy", "dummy", "name", "name", "absent", None, False), + ("dummy", "dummy", "name", "name", "opened", None, False), + ("dummy", "dummy", "name", "name", "closed", None, True), + (None, "dummy", "name", "name", "closed", None, True), + ("dummy", "dummy", None, "name", "closed", None, True), + (None, "dummy", None, "name", "closed", None, False)) + +# device, key, passphrase, new_key, new_passphrase, state, label, expected +LUKS_ADD_KEY_DATA = ( + ("dummy", "key", None, "new_key", None, "present", None, True), + (None, "key", None, "new_key", None, "present", "labelName", True), + (None, "key", None, "new_key", None, "present", None, False), + ("dummy", None, None, "new_key", None, "present", None, False), + ("dummy", "key", None, None, None, "present", None, False), + ("dummy", "key", None, "new_key", None, "absent", None, "exception"), + ("dummy", None, "pass", "new_key", None, "present", None, True), + (None, None, "pass", "new_key", None, "present", "labelName", True), + ("dummy", "key", None, None, "new_pass", "present", None, True), + (None, "key", None, None, "new_pass", "present", "labelName", True), + (None, "key", None, None, "new_pass", "present", None, False), + ("dummy", None, None, None, "new_pass", "present", None, False), + ("dummy", "key", None, None, None, "present", None, False), + ("dummy", "key", None, None, "new_pass", "absent", None, "exception"), + ("dummy", None, "pass", None, "new_pass", "present", None, True), + (None, None, "pass", None, "new_pass", "present", "labelName", True)) + +# device, remove_key, remove_passphrase, state, label, expected +LUKS_REMOVE_KEY_DATA = ( + ("dummy", "key", None, "present", None, True), + (None, "key", None, "present", None, False), + (None, "key", None, "present", "labelName", True), + ("dummy", None, None, "present", None, False), + ("dummy", "key", None, "absent", None, "exception"), + ("dummy", None, "foo", "present", None, True), + (None, None, "foo", "present", None, False), + (None, None, "foo", "present", "labelName", True), + ("dummy", None, None, "present", None, False), + ("dummy", None, "foo", "absent", None, "exception")) + + +@pytest.mark.parametrize("device, keyfile, passphrase, state, is_luks, " + + "label, cipher, hash_, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8]) + for d in LUKS_CREATE_DATA)) +def test_luks_create(device, keyfile, passphrase, state, is_luks, label, cipher, hash_, + expected, monkeypatch): + module = DummyModule() + + module.params["device"] = device + module.params["keyfile"] = keyfile + module.params["passphrase"] = passphrase + module.params["state"] = state + module.params["label"] = label + module.params["cipher"] = cipher + module.params["hash"] = hash_ + + monkeypatch.setattr(luks_device.CryptHandler, "is_luks", + lambda x, y: is_luks) + crypt = luks_device.CryptHandler(module) + if device is None: + monkeypatch.setattr(luks_device.Handler, "get_device_by_label", + lambda x, y: [0, "/dev/dummy", ""]) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_create() == expected + except ValueError: + assert expected == "exception" + + +@pytest.mark.parametrize("device, state, is_luks, expected", + ((d[0], d[1], d[2], d[3]) + for d in LUKS_REMOVE_DATA)) +def test_luks_remove(device, state, is_luks, expected, monkeypatch): + module = DummyModule() + + module.params["device"] = device + module.params["state"] = state + + monkeypatch.setattr(luks_device.CryptHandler, "is_luks", + lambda x, y: is_luks) + crypt = luks_device.CryptHandler(module) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_remove() == expected + except ValueError: + assert expected == "exception" + + +@pytest.mark.parametrize("device, keyfile, passphrase, state, name, " + "name_by_dev, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) + for d in LUKS_OPEN_DATA)) +def test_luks_open(device, keyfile, passphrase, state, name, name_by_dev, + expected, monkeypatch): + module = DummyModule() + module.params["device"] = device + module.params["keyfile"] = keyfile + module.params["passphrase"] = passphrase + module.params["state"] = state + module.params["name"] = name + + monkeypatch.setattr(luks_device.CryptHandler, + "get_container_name_by_device", + lambda x, y: name_by_dev) + monkeypatch.setattr(luks_device.CryptHandler, + "get_container_device_by_name", + lambda x, y: device) + monkeypatch.setattr(luks_device.Handler, "_run_command", + lambda x, y: [0, device, ""]) + crypt = luks_device.CryptHandler(module) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_open() == expected + except ValueError: + assert expected == "exception" + + +@pytest.mark.parametrize("device, dev_by_name, name, name_by_dev, " + "state, label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) + for d in LUKS_CLOSE_DATA)) +def test_luks_close(device, dev_by_name, name, name_by_dev, state, + label, expected, monkeypatch): + module = DummyModule() + module.params["device"] = device + module.params["name"] = name + module.params["state"] = state + module.params["label"] = label + + monkeypatch.setattr(luks_device.CryptHandler, + "get_container_name_by_device", + lambda x, y: name_by_dev) + monkeypatch.setattr(luks_device.CryptHandler, + "get_container_device_by_name", + lambda x, y: dev_by_name) + crypt = luks_device.CryptHandler(module) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_close() == expected + except ValueError: + assert expected == "exception" + + +@pytest.mark.parametrize("device, keyfile, passphrase, new_keyfile, " + + "new_passphrase, state, label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]) + for d in LUKS_ADD_KEY_DATA)) +def test_luks_add_key(device, keyfile, passphrase, new_keyfile, new_passphrase, + state, label, expected, monkeypatch): + module = DummyModule() + module.params["device"] = device + module.params["keyfile"] = keyfile + module.params["passphrase"] = passphrase + module.params["new_keyfile"] = new_keyfile + module.params["new_passphrase"] = new_passphrase + module.params["state"] = state + module.params["label"] = label + + monkeypatch.setattr(luks_device.Handler, "get_device_by_label", + lambda x, y: [0, "/dev/dummy", ""]) + monkeypatch.setattr(luks_device.CryptHandler, "luks_test_key", + lambda x, y, z, w: False) + + crypt = luks_device.CryptHandler(module) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_add_key() == expected + except ValueError: + assert expected == "exception" + + +@pytest.mark.parametrize("device, remove_keyfile, remove_passphrase, state, " + + "label, expected", + ((d[0], d[1], d[2], d[3], d[4], d[5]) + for d in LUKS_REMOVE_KEY_DATA)) +def test_luks_remove_key(device, remove_keyfile, remove_passphrase, state, + label, expected, monkeypatch): + + module = DummyModule() + module.params["device"] = device + module.params["remove_keyfile"] = remove_keyfile + module.params["remove_passphrase"] = remove_passphrase + module.params["state"] = state + module.params["label"] = label + + monkeypatch.setattr(luks_device.Handler, "get_device_by_label", + lambda x, y: [0, "/dev/dummy", ""]) + monkeypatch.setattr(luks_device.Handler, "_run_command", + lambda x, y: [0, device, ""]) + monkeypatch.setattr(luks_device.CryptHandler, "luks_test_key", + lambda x, y, z, w: True) + + crypt = luks_device.CryptHandler(module) + try: + conditions = luks_device.ConditionsHandler(module, crypt) + assert conditions.luks_remove_key() == expected + except ValueError: + assert expected == "exception" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/utils.py b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/utils.py new file mode 100644 index 00000000..d2d520d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/plugins/modules/utils.py @@ -0,0 +1,50 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import json + +from ansible_collections.community.crypto.tests.unit.compat import unittest +from ansible_collections.community.crypto.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + + def setUp(self): + self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) + self.mock_module.start() + self.mock_sleep = patch('time.sleep') + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/unit/requirements.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/requirements.txt new file mode 100644 index 00000000..054a9cf5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/unit/requirements.txt @@ -0,0 +1,6 @@ +cryptography +ipaddress +pyopenssl + +unittest2 ; python_version < '2.7' +importlib ; python_version < '2.7' diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/constraints.txt b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/constraints.txt new file mode 100644 index 00000000..ae6000ae --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/constraints.txt @@ -0,0 +1,52 @@ +coverage >= 4.2, < 5.0.0, != 4.3.2 ; python_version <= '3.7' # features in 4.2+ required, avoid known bug in 4.3.2 on python 2.6, coverage 5.0+ incompatible +coverage >= 4.5.4, < 5.0.0 ; python_version > '3.7' # coverage had a bug in < 4.5.4 that would cause unit tests to hang in Python 3.8, coverage 5.0+ incompatible +cryptography < 2.2 ; python_version < '2.7' # cryptography 2.2 drops support for python 2.6 +deepdiff < 4.0.0 ; python_version < '3' # deepdiff 4.0.0 and later require python 3 +jinja2 < 2.11 ; python_version < '2.7' # jinja2 2.11 and later require python 2.7 or later +urllib3 < 1.24 ; python_version < '2.7' # urllib3 1.24 and later require python 2.7 or later +pywinrm >= 0.3.0 # message encryption support +sphinx < 1.6 ; python_version < '2.7' # sphinx 1.6 and later require python 2.7 or later +sphinx < 1.8 ; python_version >= '2.7' # sphinx 1.8 and later are currently incompatible with rstcheck 3.3 +pygments >= 2.4.0 # Pygments 2.4.0 includes bugfixes for YAML and YAML+Jinja lexers +wheel < 0.30.0 ; python_version < '2.7' # wheel 0.30.0 and later require python 2.7 or later +yamllint != 1.8.0, < 1.14.0 ; python_version < '2.7' # yamllint 1.8.0 and 1.14.0+ require python 2.7+ +pycrypto >= 2.6 # Need features found in 2.6 and greater +ncclient >= 0.5.2 # Need features added in 0.5.2 and greater +idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna < 2.6, but cryptography will cause the latest version to be installed instead +paramiko < 2.4.0 ; python_version < '2.7' # paramiko 2.4.0 drops support for python 2.6 +pytest < 3.3.0 ; python_version < '2.7' # pytest 3.3.0 drops support for python 2.6 +pytest < 5.0.0 ; python_version == '2.7' # pytest 5.0.0 and later will no longer support python 2.7 +pytest-forked < 1.0.2 ; python_version < '2.7' # pytest-forked 1.0.2 and later require python 2.7 or later +pytest-forked >= 1.0.2 ; python_version >= '2.7' # pytest-forked before 1.0.2 does not work with pytest 4.2.0+ (which requires python 2.7+) +ntlm-auth >= 1.3.0 # message encryption support using cryptography +requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6 +requests-ntlm >= 1.1.0 # message encryption support +requests-credssp >= 0.1.0 # message encryption support +voluptuous >= 0.11.0 # Schema recursion via Self +openshift >= 0.6.2, < 0.9.0 # merge_type support +virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later +pathspec < 0.6.0 ; python_version < '2.7' # pathspec 0.6.0 and later require python 2.7 or later +pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later +pyfmg == 0.6.1 # newer versions do not pass current unit tests +pyyaml < 5.1 ; python_version < '2.7' # pyyaml 5.1 and later require python 2.7 or later +pycparser < 2.19 ; python_version < '2.7' # pycparser 2.19 and later require python 2.7 or later +mock >= 2.0.0 # needed for features backported from Python 3.6 unittest.mock (assert_called, assert_called_once...) +pytest-mock >= 1.4.0 # needed for mock_use_standalone_module pytest option +xmltodict < 0.12.0 ; python_version < '2.7' # xmltodict 0.12.0 and later require python 2.7 or later +lxml < 4.3.0 ; python_version < '2.7' # lxml 4.3.0 and later require python 2.7 or later +pyvmomi < 6.0.0 ; python_version < '2.7' # pyvmomi 6.0.0 and later require python 2.7 or later +pyone == 1.1.9 # newer versions do not pass current integration tests +boto3 < 1.11 ; python_version < '2.7' # boto3 1.11 drops Python 2.6 support +botocore >= 1.10.0, < 1.14 ; python_version < '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca; botocore 1.14 drops Python 2.6 support +botocore >= 1.10.0 ; python_version >= '2.7' # adds support for the following AWS services: secretsmanager, fms, and acm-pca +setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later +cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install: + +# freeze pylint and its requirements for consistent test results +astroid == 2.2.5 +isort == 4.3.15 +lazy-object-proxy == 1.3.1 +mccabe == 0.6.1 +pylint == 2.3.1 +typed-ast == 1.4.0 # 1.4.0 is required to compile on Python 3.8 +wrapt == 1.11.1 diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/check_matrix.py b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/check_matrix.py new file mode 100755 index 00000000..9981dd7f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/check_matrix.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +"""Verify the currently executing Shippable test matrix matches the one defined in the "shippable.yml" file.""" +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import datetime +import json +import os +import re +import sys +import time + +try: + from typing import NoReturn +except ImportError: + NoReturn = None + +try: + # noinspection PyCompatibility + from urllib2 import urlopen # pylint: disable=ansible-bad-import-from +except ImportError: + # noinspection PyCompatibility + from urllib.request import urlopen + + +def main(): # type: () -> None + """Main entry point.""" + repo_full_name = os.environ['REPO_FULL_NAME'] + required_repo_full_name = 'ansible-collections/community.crypto' + + if repo_full_name != required_repo_full_name: + sys.stderr.write('Skipping matrix check on repo "%s" which is not "%s".\n' % (repo_full_name, required_repo_full_name)) + return + + with open('shippable.yml', 'rb') as yaml_file: + yaml = yaml_file.read().decode('utf-8').splitlines() + + defined_matrix = [match.group(1) for match in [re.search(r'^ *- env: T=(.*)$', line) for line in yaml] if match and match.group(1) != 'none'] + + if not defined_matrix: + fail('No matrix entries found in the "shippable.yml" file.', + 'Did you modify the "shippable.yml" file?') + + run_id = os.environ['SHIPPABLE_BUILD_ID'] + sleep = 1 + jobs = [] + + for attempts_remaining in range(4, -1, -1): + try: + jobs = json.loads(urlopen('https://api.shippable.com/jobs?runIds=%s' % run_id).read()) + + if not isinstance(jobs, list): + raise Exception('Shippable run %s data is not a list.' % run_id) + + break + except Exception as ex: + if not attempts_remaining: + fail('Unable to retrieve Shippable run %s matrix.' % run_id, + str(ex)) + + sys.stderr.write('Unable to retrieve Shippable run %s matrix: %s\n' % (run_id, ex)) + sys.stderr.write('Trying again in %d seconds...\n' % sleep) + time.sleep(sleep) + sleep *= 2 + + if len(jobs) != len(defined_matrix): + if len(jobs) == 1: + hint = '\n\nMake sure you do not use the "Rebuild with SSH" option.' + else: + hint = '' + + fail('Shippable run %s has %d jobs instead of the expected %d jobs.' % (run_id, len(jobs), len(defined_matrix)), + 'Try re-running the entire matrix.%s' % hint) + + actual_matrix = dict((job.get('jobNumber'), dict(tuple(line.split('=', 1)) for line in job.get('env', [])).get('T', '')) for job in jobs) + errors = [(job_number, test, actual_matrix.get(job_number)) for job_number, test in enumerate(defined_matrix, 1) if actual_matrix.get(job_number) != test] + + if len(errors): + error_summary = '\n'.join('Job %s expected "%s" but found "%s" instead.' % (job_number, expected, actual) for job_number, expected, actual in errors) + + fail('Shippable run %s has a job matrix mismatch.' % run_id, + 'Try re-running the entire matrix.\n\n%s' % error_summary) + + +def fail(message, output): # type: (str, str) -> NoReturn + # Include a leading newline to improve readability on Shippable "Tests" tab. + # Without this, the first line becomes indented. + output = '\n' + output.strip() + + timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat() + + # hack to avoid requiring junit-xml, which isn't pre-installed on Shippable outside our test containers + xml = ''' +<?xml version="1.0" encoding="utf-8"?> +<testsuites disabled="0" errors="1" failures="0" tests="1" time="0.0"> +\t<testsuite disabled="0" errors="1" failures="0" file="None" log="None" name="ansible-test" skipped="0" tests="1" time="0" timestamp="%s" url="None"> +\t\t<testcase classname="timeout" name="timeout"> +\t\t\t<error message="%s" type="error">%s</error> +\t\t</testcase> +\t</testsuite> +</testsuites> +''' % (timestamp, message, output) + + path = 'shippable/testresults/check-matrix.xml' + dir_path = os.path.dirname(path) + + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + with open(path, 'w') as junit_fd: + junit_fd.write(xml.lstrip()) + + sys.stderr.write(message + '\n') + sys.stderr.write(output + '\n') + + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/cloud.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/cloud.sh new file mode 100755 index 00000000..811e558f --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/cloud.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +cloud="${args[0]}" +python="${args[1]}" +group="${args[2]}" + +target="shippable/${cloud}/group${group}/" + +stage="${S:-prod}" + +# shellcheck disable=SC2086 +export ANSIBLE_ACME_CONTAINER=quay.io/ansible/acme-test-container:2.0.0 # use new container until +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote-terminate always --remote-stage "${stage}" \ + --docker --python "${python}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/freebsd.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/freebsd.sh new file mode 100755 index 00000000..cd3014cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/freebsd.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/linux.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/linux.sh new file mode 100755 index 00000000..9cc2f966 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/linux.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +image="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --docker "${image}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/macos.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/macos.sh new file mode 100755 index 00000000..cd3014cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/macos.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/osx.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/osx.sh new file mode 100755 index 00000000..cd3014cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/osx.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/remote.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/remote.sh new file mode 100755 index 00000000..cd3014cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/remote.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/rhel.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/rhel.sh new file mode 100755 index 00000000..cd3014cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/rhel.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +platform="${args[0]}" +version="${args[1]}" + +if [ "${#args[@]}" -gt 2 ]; then + target="shippable/posix/group${args[2]}/" +else + target="shippable/posix/" +fi + +stage="${S:-prod}" +provider="${P:-default}" + +# shellcheck disable=SC2086 +ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \ + --remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/sanity.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/sanity.sh new file mode 100755 index 00000000..c216220e --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/sanity.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +group="${args[1]}" + +if [ "${BASE_BRANCH:-}" ]; then + base_branch="origin/${BASE_BRANCH}" +else + base_branch="" +fi + +if [ "${group}" == "extra" ]; then + # ansible-galaxy -vvv collection install community.internal_test_tools + git clone --single-branch --depth 1 https://github.com/ansible-collections/community.internal_test_tools.git ../internal_test_tools + + ../internal_test_tools/tools/run.py --color + exit +fi + +# shellcheck disable=SC2086 +ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ + --docker --base-branch "${base_branch}" \ + --allow-disabled diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/shippable.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/shippable.sh new file mode 100755 index 00000000..40696f54 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/shippable.sh @@ -0,0 +1,222 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +declare -a args +IFS='/:' read -ra args <<< "$1" + +ansible_version="${args[0]}" +script="${args[1]}" + +function join { + local IFS="$1"; + shift; + echo "$*"; +} + +# Ensure we can write other collections to this dir +sudo chown "$(whoami)" "${PWD}/../../" + +test="$(join / "${args[@]:1}")" +docker images ansible/ansible +docker images quay.io/ansible/* +docker ps + +for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do + docker rm -f "${container}" || true # ignore errors +done + +docker ps + +if [ -d /home/shippable/cache/ ]; then + ls -la /home/shippable/cache/ +fi + +command -v python +python -V + +function retry +{ + # shellcheck disable=SC2034 + for repetition in 1 2 3; do + set +e + "$@" + result=$? + set -e + if [ ${result} == 0 ]; then + return ${result} + fi + echo "@* -> ${result}" + done + echo "Command '@*' failed 3 times!" + exit -1 +} + +command -v pip +pip --version +pip list --disable-pip-version-check +if [ "${ansible_version}" == "devel" ]; then + retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +else + retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check +fi + +# START: HACK +if [ "${script}" == "osx" ] && [ "${ansible_version}" == "2.9" ]; then + # Make sure that the latest versions of pyOpenSSL and cryptography will be installed on macOS before + # ansible-playbook is started. This is no longer necessary for devel (https://github.com/ansible/ansible/issues/68701 + # is fixed), but 2.9 still needs this since the new collection loader probably won't get backported to stable-2.9. + sed -i -e 's/cryptography.*/cryptography >= 2.9.2/g' /root/venv/lib/python2.7/site-packages/ansible_test/_data/requirements/integration.txt + echo 'pyOpenSSL >= 19.1.0' >> /root/venv/lib/python2.7/site-packages/ansible_test/_data/requirements/integration.txt +fi +# END: HACK + + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then + export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" + SHIPPABLE_RESULT_DIR="$(pwd)/shippable" + TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/crypto" + mkdir -p "${TEST_DIR}" + cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" + cd "${TEST_DIR}" +else + # AZP + export ANSIBLE_COLLECTIONS_PATHS="$PWD/../../../" +fi + +# START: HACK install integration test dependencies +if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] && [ "${ansible_version}" != "2.9" ]; then + retry ansible-galaxy -vvv collection install community.general +fi +# END: HACK + + +export PYTHONIOENCODING='utf-8' + +if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then + COVERAGE=yes + COMPLETE=yes +fi + +if [ -n "${COVERAGE:-}" ]; then + # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value + export COVERAGE="--coverage" +elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then + # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message + export COVERAGE="--coverage" +else + # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled) + export COVERAGE="--coverage-check" +fi + +if [ -n "${COMPLETE:-}" ]; then + # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value + export CHANGED="" +elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then + # disable change detection triggered by having 'ci_complete' in the latest commit message + export CHANGED="" +else + # enable change detection (default behavior) + export CHANGED="--changed" +fi + +if [ "${IS_PULL_REQUEST:-}" == "true" ]; then + # run unstable tests which are targeted by focused changes on PRs + export UNSTABLE="--allow-unstable-changed" +else + # do not run unstable tests outside PRs + export UNSTABLE="" +fi + +# remove empty core/extras module directories from PRs created prior to the repo-merge +find plugins -type d -empty -print -delete + +function cleanup +{ + # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then + stub="--stub" + # trigger coverage reporting for stubs even if no other coverage data exists + mkdir -p tests/output/coverage/ + else + stub="" + fi + + if [ -d tests/output/coverage/ ]; then + if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then + process_coverage='yes' # process existing coverage files + elif [ "${stub}" ]; then + process_coverage='yes' # process coverage when stubs are enabled + else + process_coverage='' + fi + + if [ "${process_coverage}" ]; then + # use python 3.7 for coverage to avoid running out of memory during coverage xml processing + # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job + virtualenv --python /usr/bin/python3.7 ~/ansible-venv + set +ux + . ~/ansible-venv/bin/activate + set -ux + + # shellcheck disable=SC2086 + ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"} + cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/" + + if [ "${ansible_version}" != "2.9" ]; then + # analyze and capture code coverage aggregated by integration test target + ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json" + fi + + # upload coverage report to codecov.io only when using complete on-demand coverage + if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then + for file in tests/output/reports/coverage=*.xml; do + flags="${file##*/coverage=}" + flags="${flags%-powershell.xml}" + flags="${flags%.xml}" + # remove numbered component from stub files when converting to tags + flags="${flags//stub-[0-9]*/stub}" + flags="${flags//=/,}" + flags="${flags//[^a-zA-Z0-9_,]/_}" + + bash <(curl -s https://codecov.io/bash) \ + -f "${file}" \ + -F "${flags}" \ + -n "${test}" \ + -t 31525df8-da26-4e61-b31f-05e3df48b091 \ + -X coveragepy \ + -X gcov \ + -X fix \ + -X search \ + -X xcode \ + || echo "Failed to upload code coverage report to codecov.io: ${file}" + done + fi + fi + fi + + if [ -d tests/output/junit/ ]; then + cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/data/ ]; then + cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi + + if [ -d tests/output/bot/ ]; then + cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/" + fi +} + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=60 +else + timeout=50 +fi + +ansible-test env --dump --show --timeout "${timeout}" --color -v + +if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi +"tests/utils/shippable/${script}.sh" "${test}" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.py b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.py new file mode 100755 index 00000000..fb538271 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3.7 +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import time + +start = time.time() + +sys.stdin.reconfigure(errors='surrogateescape') +sys.stdout.reconfigure(errors='surrogateescape') + +for line in sys.stdin: + seconds = time.time() - start + sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line)) + sys.stdout.flush() diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.sh new file mode 100755 index 00000000..77e25783 --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/timing.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -o pipefail -eu + +"$@" 2>&1 | "$(dirname "$0")/timing.py" diff --git a/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/units.sh b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/units.sh new file mode 100755 index 00000000..4bf9e05c --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/crypto/tests/utils/shippable/units.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -o pipefail -eux + +if [[ "${COVERAGE:-}" == "--coverage" ]]; then + timeout=90 +else + timeout=30 +fi + +ansible-test env --timeout "${timeout}" --color -v + +# shellcheck disable=SC2086 +ansible-test units --color -v --docker default ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \ |